* Add specific error codes for every command on the wire protocol, so that
[citadel.git] / citadel / msgbase.c
1 /*
2  * $Id$
3  *
4  * Implements the message store.
5  *
6  */
7
8 #ifdef DLL_EXPORT
9 #define IN_LIBCIT
10 #endif
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
20 # include <time.h>
21 #else
22 # if HAVE_SYS_TIME_H
23 #  include <sys/time.h>
24 # else
25 #  include <time.h>
26 # endif
27 #endif
28
29
30 #include <ctype.h>
31 #include <string.h>
32 #include <limits.h>
33 #include <errno.h>
34 #include <stdarg.h>
35 #include <sys/stat.h>
36 #include "citadel.h"
37 #include "server.h"
38 #include "serv_extensions.h"
39 #include "database.h"
40 #include "msgbase.h"
41 #include "support.h"
42 #include "sysdep_decls.h"
43 #include "citserver.h"
44 #include "room_ops.h"
45 #include "user_ops.h"
46 #include "file_ops.h"
47 #include "control.h"
48 #include "tools.h"
49 #include "mime_parser.h"
50 #include "html.h"
51 #include "genstamp.h"
52 #include "internet_addressing.h"
53
54 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
55 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
56 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
57
58 extern struct config config;
59 long config_msgnum;
60
61
62 /* 
63  * This really belongs in serv_network.c, but I don't know how to export
64  * symbols between modules.
65  */
66 struct FilterList *filterlist = NULL;
67
68
69 /*
70  * These are the four-character field headers we use when outputting
71  * messages in Citadel format (as opposed to RFC822 format).
72  */
73 char *msgkeys[] = {
74         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
75         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
76         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
77         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
78         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
79         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
80         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
81         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
82         NULL, 
83         "from",
84         NULL, NULL, NULL,
85         "exti",
86         "rfca",
87         NULL, 
88         "hnod",
89         "msgn",
90         NULL, NULL, NULL,
91         "text",
92         "node",
93         "room",
94         "path",
95         NULL,
96         "rcpt",
97         "spec",
98         "time",
99         "subj",
100         NULL,
101         NULL,
102         NULL,
103         NULL,
104         NULL
105 };
106
107 /*
108  * This function is self explanatory.
109  * (What can I say, I'm in a weird mood today...)
110  */
111 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
112 {
113         int i;
114
115         for (i = 0; i < strlen(name); ++i) {
116                 if (name[i] == '@') {
117                         while (isspace(name[i - 1]) && i > 0) {
118                                 strcpy(&name[i - 1], &name[i]);
119                                 --i;
120                         }
121                         while (isspace(name[i + 1])) {
122                                 strcpy(&name[i + 1], &name[i + 2]);
123                         }
124                 }
125         }
126 }
127
128
129 /*
130  * Aliasing for network mail.
131  * (Error messages have been commented out, because this is a server.)
132  */
133 int alias(char *name)
134 {                               /* process alias and routing info for mail */
135         FILE *fp;
136         int a, i;
137         char aaa[SIZ], bbb[SIZ];
138         char *ignetcfg = NULL;
139         char *ignetmap = NULL;
140         int at = 0;
141         char node[SIZ];
142         char testnode[SIZ];
143         char buf[SIZ];
144
145         striplt(name);
146         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
147
148         fp = fopen("network/mail.aliases", "r");
149         if (fp == NULL) {
150                 fp = fopen("/dev/null", "r");
151         }
152         if (fp == NULL) {
153                 return (MES_ERROR);
154         }
155         strcpy(aaa, "");
156         strcpy(bbb, "");
157         while (fgets(aaa, sizeof aaa, fp) != NULL) {
158                 while (isspace(name[0]))
159                         strcpy(name, &name[1]);
160                 aaa[strlen(aaa) - 1] = 0;
161                 strcpy(bbb, "");
162                 for (a = 0; a < strlen(aaa); ++a) {
163                         if (aaa[a] == ',') {
164                                 strcpy(bbb, &aaa[a + 1]);
165                                 aaa[a] = 0;
166                         }
167                 }
168                 if (!strcasecmp(name, aaa))
169                         strcpy(name, bbb);
170         }
171         fclose(fp);
172
173         /* Hit the Global Address Book */
174         if (CtdlDirectoryLookup(aaa, name) == 0) {
175                 strcpy(name, aaa);
176         }
177
178         lprintf(7, "Mail is being forwarded to %s\n", name);
179
180         /* Change "user @ xxx" to "user" if xxx is an alias for this host */
181         for (a=0; a<strlen(name); ++a) {
182                 if (name[a] == '@') {
183                         if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
184                                 name[a] = 0;
185                                 lprintf(7, "Changed to <%s>\n", name);
186                         }
187                 }
188         }
189
190         /* determine local or remote type, see citadel.h */
191         at = haschar(name, '@');
192         if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
193         if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
194         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
195
196         /* figure out the delivery mode */
197         extract_token(node, name, 1, '@');
198
199         /* If there are one or more dots in the nodename, we assume that it
200          * is an FQDN and will attempt SMTP delivery to the Internet.
201          */
202         if (haschar(node, '.') > 0) {
203                 return(MES_INTERNET);
204         }
205
206         /* Otherwise we look in the IGnet maps for a valid Citadel node.
207          * Try directly-connected nodes first...
208          */
209         ignetcfg = CtdlGetSysConfig(IGNETCFG);
210         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
211                 extract_token(buf, ignetcfg, i, '\n');
212                 extract_token(testnode, buf, 0, '|');
213                 if (!strcasecmp(node, testnode)) {
214                         phree(ignetcfg);
215                         return(MES_IGNET);
216                 }
217         }
218         phree(ignetcfg);
219
220         /*
221          * Then try nodes that are two or more hops away.
222          */
223         ignetmap = CtdlGetSysConfig(IGNETMAP);
224         for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
225                 extract_token(buf, ignetmap, i, '\n');
226                 extract_token(testnode, buf, 0, '|');
227                 if (!strcasecmp(node, testnode)) {
228                         phree(ignetmap);
229                         return(MES_IGNET);
230                 }
231         }
232         phree(ignetmap);
233
234         /* If we get to this point it's an invalid node name */
235         return (MES_ERROR);
236 }
237
238
239 void get_mm(void)
240 {
241         FILE *fp;
242
243         fp = fopen("citadel.control", "r");
244         fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
245         fclose(fp);
246 }
247
248
249
250 void simple_listing(long msgnum, void *userdata)
251 {
252         cprintf("%ld\n", msgnum);
253 }
254
255
256
257 /* Determine if a given message matches the fields in a message template.
258  * Return 0 for a successful match.
259  */
260 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
261         int i;
262
263         /* If there aren't any fields in the template, all messages will
264          * match.
265          */
266         if (template == NULL) return(0);
267
268         /* Null messages are bogus. */
269         if (msg == NULL) return(1);
270
271         for (i='A'; i<='Z'; ++i) {
272                 if (template->cm_fields[i] != NULL) {
273                         if (msg->cm_fields[i] == NULL) {
274                                 return 1;
275                         }
276                         if (strcasecmp(msg->cm_fields[i],
277                                 template->cm_fields[i])) return 1;
278                 }
279         }
280
281         /* All compares succeeded: we have a match! */
282         return 0;
283 }
284
285
286
287 /*
288  * Retrieve the "seen" message list for the current room.
289  */
290 void CtdlGetSeen(char *buf, int which_set) {
291         struct visit vbuf;
292
293         /* Learn about the user and room in question */
294         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
295
296         if (which_set == ctdlsetseen_seen)
297                 safestrncpy(buf, vbuf.v_seen, SIZ);
298         if (which_set == ctdlsetseen_answered)
299                 safestrncpy(buf, vbuf.v_answered, SIZ);
300 }
301
302
303
304 /*
305  * Manipulate the "seen msgs" string (or other message set strings)
306  */
307 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
308         char newseen[SIZ];
309         struct cdbdata *cdbfr;
310         int i;
311         int is_seen = 0;
312         int was_seen = 1;
313         long lo = (-1L);
314         long hi = (-1L);
315         struct visit vbuf;
316         long *msglist;
317         int num_msgs = 0;
318         char vset[SIZ];
319
320         /* Learn about the user and room in question */
321         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
322
323         /* Load the message list */
324         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
325         if (cdbfr != NULL) {
326                 msglist = mallok(cdbfr->len);
327                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
328                 num_msgs = cdbfr->len / sizeof(long);
329                 cdb_free(cdbfr);
330         } else {
331                 return; /* No messages at all?  No further action. */
332         }
333
334         /* Decide which message set we're manipulating */
335         if (which_set == ctdlsetseen_seen) strcpy(vset, vbuf.v_seen);
336         if (which_set == ctdlsetseen_answered) strcpy(vset, vbuf.v_answered);
337
338         lprintf(9, "before optimize: %s\n", vset);
339         strcpy(newseen, "");
340
341         for (i=0; i<num_msgs; ++i) {
342                 is_seen = 0;
343
344                 if (msglist[i] == target_msgnum) {
345                         is_seen = target_setting;
346                 }
347                 else {
348                         if (is_msg_in_mset(vset, msglist[i])) {
349                                 is_seen = 1;
350                         }
351                 }
352
353                 if (is_seen == 1) {
354                         if (lo < 0L) lo = msglist[i];
355                         hi = msglist[i];
356                 }
357                 if (  ((is_seen == 0) && (was_seen == 1))
358                    || ((is_seen == 1) && (i == num_msgs-1)) ) {
359                         size_t tmp;
360
361                         if ( (strlen(newseen) + 20) > SIZ) {
362                                 strcpy(newseen, &newseen[20]);
363                                 newseen[0] = '*';
364                         }
365                         tmp = strlen(newseen);
366                         if (tmp > 0) {
367                                 strcat(newseen, ",");
368                                 tmp++;
369                         }
370                         if (lo == hi) {
371                                 snprintf(&newseen[tmp], sizeof newseen - tmp,
372                                          "%ld", lo);
373                         }
374                         else {
375                                 snprintf(&newseen[tmp], sizeof newseen - tmp,
376                                          "%ld:%ld", lo, hi);
377                         }
378                         lo = (-1L);
379                         hi = (-1L);
380                 }
381                 was_seen = is_seen;
382         }
383
384         /* Decide which message set we're manipulating */
385         if (which_set == ctdlsetseen_seen) strcpy(vbuf.v_seen, newseen);
386         if (which_set == ctdlsetseen_answered) strcpy(vbuf.v_answered, newseen);
387
388         lprintf(9, " after optimize: %s\n", newseen);
389         phree(msglist);
390         CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
391 }
392
393
394 /*
395  * API function to perform an operation for each qualifying message in the
396  * current room.  (Returns the number of messages processed.)
397  */
398 int CtdlForEachMessage(int mode, long ref,
399                         char *content_type,
400                         struct CtdlMessage *compare,
401                         void (*CallBack) (long, void *),
402                         void *userdata)
403 {
404
405         int a;
406         struct visit vbuf;
407         struct cdbdata *cdbfr;
408         long *msglist = NULL;
409         int num_msgs = 0;
410         int num_processed = 0;
411         long thismsg;
412         struct MetaData smi;
413         struct CtdlMessage *msg;
414         int is_seen;
415         long lastold = 0L;
416         int printed_lastold = 0;
417
418         /* Learn about the user and room in question */
419         get_mm();
420         getuser(&CC->user, CC->curr_user);
421         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
422
423         /* Load the message list */
424         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
425         if (cdbfr != NULL) {
426                 msglist = mallok(cdbfr->len);
427                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
428                 num_msgs = cdbfr->len / sizeof(long);
429                 cdb_free(cdbfr);
430         } else {
431                 return 0;       /* No messages at all?  No further action. */
432         }
433
434
435         /*
436          * Now begin the traversal.
437          */
438         if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
439
440                 /* If the caller is looking for a specific MIME type, filter
441                  * out all messages which are not of the type requested.
442                  */
443                 if (content_type != NULL) if (strlen(content_type) > 0) {
444
445                         /* This call to GetMetaData() sits inside this loop
446                          * so that we only do the extra database read per msg
447                          * if we need to.  Doing the extra read all the time
448                          * really kills the server.  If we ever need to use
449                          * metadata for another search criterion, we need to
450                          * move the read somewhere else -- but still be smart
451                          * enough to only do the read if the caller has
452                          * specified something that will need it.
453                          */
454                         GetMetaData(&smi, msglist[a]);
455
456                         if (strcasecmp(smi.meta_content_type, content_type)) {
457                                 msglist[a] = 0L;
458                         }
459                 }
460         }
461
462         num_msgs = sort_msglist(msglist, num_msgs);
463
464         /* If a template was supplied, filter out the messages which
465          * don't match.  (This could induce some delays!)
466          */
467         if (num_msgs > 0) {
468                 if (compare != NULL) {
469                         for (a = 0; a < num_msgs; ++a) {
470                                 msg = CtdlFetchMessage(msglist[a]);
471                                 if (msg != NULL) {
472                                         if (CtdlMsgCmp(msg, compare)) {
473                                                 msglist[a] = 0L;
474                                         }
475                                         CtdlFreeMessage(msg);
476                                 }
477                         }
478                 }
479         }
480
481         
482         /*
483          * Now iterate through the message list, according to the
484          * criteria supplied by the caller.
485          */
486         if (num_msgs > 0)
487                 for (a = 0; a < num_msgs; ++a) {
488                         thismsg = msglist[a];
489                         is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
490                         if (is_seen) lastold = thismsg;
491                         if ((thismsg > 0L)
492                             && (
493
494                                        (mode == MSGS_ALL)
495                                        || ((mode == MSGS_OLD) && (is_seen))
496                                        || ((mode == MSGS_NEW) && (!is_seen))
497                                        || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
498                                    || ((mode == MSGS_FIRST) && (a < ref))
499                                 || ((mode == MSGS_GT) && (thismsg > ref))
500                                 || ((mode == MSGS_EQ) && (thismsg == ref))
501                             )
502                             ) {
503                                 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
504                                         if (CallBack)
505                                                 CallBack(lastold, userdata);
506                                         printed_lastold = 1;
507                                         ++num_processed;
508                                 }
509                                 if (CallBack) CallBack(thismsg, userdata);
510                                 ++num_processed;
511                         }
512                 }
513         phree(msglist);         /* Clean up */
514         return num_processed;
515 }
516
517
518
519 /*
520  * cmd_msgs()  -  get list of message #'s in this room
521  *                implements the MSGS server command using CtdlForEachMessage()
522  */
523 void cmd_msgs(char *cmdbuf)
524 {
525         int mode = 0;
526         char which[SIZ];
527         char buf[SIZ];
528         char tfield[SIZ];
529         char tvalue[SIZ];
530         int cm_ref = 0;
531         int i;
532         int with_template = 0;
533         struct CtdlMessage *template = NULL;
534
535         extract(which, cmdbuf, 0);
536         cm_ref = extract_int(cmdbuf, 1);
537         with_template = extract_int(cmdbuf, 2);
538
539         mode = MSGS_ALL;
540         strcat(which, "   ");
541         if (!strncasecmp(which, "OLD", 3))
542                 mode = MSGS_OLD;
543         else if (!strncasecmp(which, "NEW", 3))
544                 mode = MSGS_NEW;
545         else if (!strncasecmp(which, "FIRST", 5))
546                 mode = MSGS_FIRST;
547         else if (!strncasecmp(which, "LAST", 4))
548                 mode = MSGS_LAST;
549         else if (!strncasecmp(which, "GT", 2))
550                 mode = MSGS_GT;
551
552         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
553                 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
554                 return;
555         }
556
557         if (with_template) {
558                 cprintf("%d Send template then receive message list\n",
559                         START_CHAT_MODE);
560                 template = (struct CtdlMessage *)
561                         mallok(sizeof(struct CtdlMessage));
562                 memset(template, 0, sizeof(struct CtdlMessage));
563                 while(client_gets(buf), strcmp(buf,"000")) {
564                         extract(tfield, buf, 0);
565                         extract(tvalue, buf, 1);
566                         for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
567                                 if (!strcasecmp(tfield, msgkeys[i])) {
568                                         template->cm_fields[i] =
569                                                 strdoop(tvalue);
570                                 }
571                         }
572                 }
573         }
574         else {
575                 cprintf("%d Message list...\n", LISTING_FOLLOWS);
576         }
577
578         CtdlForEachMessage(mode, cm_ref,
579                 NULL, template, simple_listing, NULL);
580         if (template != NULL) CtdlFreeMessage(template);
581         cprintf("000\n");
582 }
583
584
585
586
587 /* 
588  * help_subst()  -  support routine for help file viewer
589  */
590 void help_subst(char *strbuf, char *source, char *dest)
591 {
592         char workbuf[SIZ];
593         int p;
594
595         while (p = pattern2(strbuf, source), (p >= 0)) {
596                 strcpy(workbuf, &strbuf[p + strlen(source)]);
597                 strcpy(&strbuf[p], dest);
598                 strcat(strbuf, workbuf);
599         }
600 }
601
602
603 void do_help_subst(char *buffer)
604 {
605         char buf2[16];
606
607         help_subst(buffer, "^nodename", config.c_nodename);
608         help_subst(buffer, "^humannode", config.c_humannode);
609         help_subst(buffer, "^fqdn", config.c_fqdn);
610         help_subst(buffer, "^username", CC->user.fullname);
611         snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
612         help_subst(buffer, "^usernum", buf2);
613         help_subst(buffer, "^sysadm", config.c_sysadm);
614         help_subst(buffer, "^variantname", CITADEL);
615         snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
616         help_subst(buffer, "^maxsessions", buf2);
617         help_subst(buffer, "^bbsdir", BBSDIR);
618 }
619
620
621
622 /*
623  * memfmout()  -  Citadel text formatter and paginator.
624  *             Although the original purpose of this routine was to format
625  *             text to the reader's screen width, all we're really using it
626  *             for here is to format text out to 80 columns before sending it
627  *             to the client.  The client software may reformat it again.
628  */
629 void memfmout(
630         int width,              /* screen width to use */
631         char *mptr,             /* where are we going to get our text from? */
632         char subst,             /* nonzero if we should do substitutions */
633         char *nl)               /* string to terminate lines with */
634 {
635         int a, b, c;
636         int real = 0;
637         int old = 0;
638         cit_uint8_t ch;
639         char aaa[140];
640         char buffer[SIZ];
641
642         strcpy(aaa, "");
643         old = 255;
644         strcpy(buffer, "");
645         c = 1;                  /* c is the current pos */
646
647         do {
648                 if (subst) {
649                         while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
650                                 ch = *mptr++;
651                                 buffer[strlen(buffer) + 1] = 0;
652                                 buffer[strlen(buffer)] = ch;
653                         }
654
655                         if (buffer[0] == '^')
656                                 do_help_subst(buffer);
657
658                         buffer[strlen(buffer) + 1] = 0;
659                         a = buffer[0];
660                         strcpy(buffer, &buffer[1]);
661                 } else {
662                         ch = *mptr++;
663                 }
664
665                 old = real;
666                 real = ch;
667
668                 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
669                         ch = 32;
670                 if (((old == 13) || (old == 10)) && (isspace(real))) {
671                         cprintf("%s", nl);
672                         c = 1;
673                 }
674                 if (ch > 126)
675                         continue;
676
677                 if (ch > 32) {
678                         if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
679                                 cprintf("%s%s", nl, aaa);
680                                 c = strlen(aaa);
681                                 aaa[0] = 0;
682                         }
683                         b = strlen(aaa);
684                         aaa[b] = ch;
685                         aaa[b + 1] = 0;
686                 }
687                 if (ch == 32) {
688                         if ((strlen(aaa) + c) > (width - 5)) {
689                                 cprintf("%s", nl);
690                                 c = 1;
691                         }
692                         cprintf("%s ", aaa);
693                         ++c;
694                         c = c + strlen(aaa);
695                         strcpy(aaa, "");
696                 }
697                 if ((ch == 13) || (ch == 10)) {
698                         cprintf("%s%s", aaa, nl);
699                         c = 1;
700                         strcpy(aaa, "");
701                 }
702
703         } while (ch > 0);
704
705         cprintf("%s%s", aaa, nl);
706 }
707
708
709
710 /*
711  * Callback function for mime parser that simply lists the part
712  */
713 void list_this_part(char *name, char *filename, char *partnum, char *disp,
714                     void *content, char *cbtype, size_t length, char *encoding,
715                     void *cbuserdata)
716 {
717
718         cprintf("part=%s|%s|%s|%s|%s|%ld\n",
719                 name, filename, partnum, disp, cbtype, (long)length);
720 }
721
722 /* 
723  * Callback function for multipart prefix
724  */
725 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
726                     void *content, char *cbtype, size_t length, char *encoding,
727                     void *cbuserdata)
728 {
729         cprintf("pref=%s|%s\n", partnum, cbtype);
730 }
731
732 /* 
733  * Callback function for multipart sufffix
734  */
735 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
736                     void *content, char *cbtype, size_t length, char *encoding,
737                     void *cbuserdata)
738 {
739         cprintf("suff=%s|%s\n", partnum, cbtype);
740 }
741
742
743 /*
744  * Callback function for mime parser that opens a section for downloading
745  */
746 void mime_download(char *name, char *filename, char *partnum, char *disp,
747                    void *content, char *cbtype, size_t length, char *encoding,
748                    void *cbuserdata)
749 {
750
751         /* Silently go away if there's already a download open... */
752         if (CC->download_fp != NULL)
753                 return;
754
755         /* ...or if this is not the desired section */
756         if (strcasecmp(desired_section, partnum))
757                 return;
758
759         CC->download_fp = tmpfile();
760         if (CC->download_fp == NULL)
761                 return;
762
763         fwrite(content, length, 1, CC->download_fp);
764         fflush(CC->download_fp);
765         rewind(CC->download_fp);
766
767         OpenCmdResult(filename, cbtype);
768 }
769
770
771
772 /*
773  * Load a message from disk into memory.
774  * This is used by CtdlOutputMsg() and other fetch functions.
775  *
776  * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
777  *       using the CtdlMessageFree() function.
778  */
779 struct CtdlMessage *CtdlFetchMessage(long msgnum)
780 {
781         struct cdbdata *dmsgtext;
782         struct CtdlMessage *ret = NULL;
783         char *mptr;
784         cit_uint8_t ch;
785         cit_uint8_t field_header;
786         size_t field_length;
787
788         dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
789         if (dmsgtext == NULL) {
790                 return NULL;
791         }
792         mptr = dmsgtext->ptr;
793
794         /* Parse the three bytes that begin EVERY message on disk.
795          * The first is always 0xFF, the on-disk magic number.
796          * The second is the anonymous/public type byte.
797          * The third is the format type byte (vari, fixed, or MIME).
798          */
799         ch = *mptr++;
800         if (ch != 255) {
801                 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
802                 cdb_free(dmsgtext);
803                 return NULL;
804         }
805         ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
806         memset(ret, 0, sizeof(struct CtdlMessage));
807
808         ret->cm_magic = CTDLMESSAGE_MAGIC;
809         ret->cm_anon_type = *mptr++;    /* Anon type byte */
810         ret->cm_format_type = *mptr++;  /* Format type byte */
811
812         /*
813          * The rest is zero or more arbitrary fields.  Load them in.
814          * We're done when we encounter either a zero-length field or
815          * have just processed the 'M' (message text) field.
816          */
817         do {
818                 field_length = strlen(mptr);
819                 if (field_length == 0)
820                         break;
821                 field_header = *mptr++;
822                 ret->cm_fields[field_header] = mallok(field_length);
823                 strcpy(ret->cm_fields[field_header], mptr);
824
825                 while (*mptr++ != 0);   /* advance to next field */
826
827         } while ((field_length > 0) && (field_header != 'M'));
828
829         cdb_free(dmsgtext);
830
831         /* Always make sure there's something in the msg text field */
832         if (ret->cm_fields['M'] == NULL)
833                 ret->cm_fields['M'] = strdoop("<no text>\n");
834
835         /* Perform "before read" hooks (aborting if any return nonzero) */
836         if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
837                 CtdlFreeMessage(ret);
838                 return NULL;
839         }
840
841         return (ret);
842 }
843
844
845 /*
846  * Returns 1 if the supplied pointer points to a valid Citadel message.
847  * If the pointer is NULL or the magic number check fails, returns 0.
848  */
849 int is_valid_message(struct CtdlMessage *msg) {
850         if (msg == NULL)
851                 return 0;
852         if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
853                 lprintf(7, "is_valid_message() -- self-check failed\n");
854                 return 0;
855         }
856         return 1;
857 }
858
859
860 /*
861  * 'Destructor' for struct CtdlMessage
862  */
863 void CtdlFreeMessage(struct CtdlMessage *msg)
864 {
865         int i;
866
867         if (is_valid_message(msg) == 0) return;
868
869         for (i = 0; i < 256; ++i)
870                 if (msg->cm_fields[i] != NULL) {
871                         phree(msg->cm_fields[i]);
872                 }
873
874         msg->cm_magic = 0;      /* just in case */
875         phree(msg);
876 }
877
878
879 /*
880  * Pre callback function for multipart/alternative
881  *
882  * NOTE: this differs from the standard behavior for a reason.  Normally when
883  *       displaying multipart/alternative you want to show the _last_ usable
884  *       format in the message.  Here we show the _first_ one, because it's
885  *       usually text/plain.  Since this set of functions is designed for text
886  *       output to non-MIME-aware clients, this is the desired behavior.
887  *
888  */
889 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
890                 void *content, char *cbtype, size_t length, char *encoding,
891                 void *cbuserdata)
892 {
893                 lprintf(9, "fixed_output_pre() type=<%s>\n", cbtype);   
894                 if (!strcasecmp(cbtype, "multipart/alternative")) {
895                         ++ma->is_ma;
896                         ma->did_print = 0;
897                         return;
898                 }
899 }
900
901 /*
902  * Post callback function for multipart/alternative
903  */
904 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
905                 void *content, char *cbtype, size_t length, char *encoding,
906                 void *cbuserdata)
907 {
908                 lprintf(9, "fixed_output_post() type=<%s>\n", cbtype);  
909                 if (!strcasecmp(cbtype, "multipart/alternative")) {
910                         --ma->is_ma;
911                         ma->did_print = 0;
912                         return;
913                 }
914 }
915
916 /*
917  * Inline callback function for mime parser that wants to display text
918  */
919 void fixed_output(char *name, char *filename, char *partnum, char *disp,
920                 void *content, char *cbtype, size_t length, char *encoding,
921                 void *cbuserdata)
922         {
923                 char *ptr;
924                 char *wptr;
925                 size_t wlen;
926
927                 lprintf(9, "fixed_output() type=<%s>\n", cbtype);       
928
929                 /*
930                  * If we're in the middle of a multipart/alternative scope and
931                  * we've already printed another section, skip this one.
932                  */     
933                 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
934                         lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
935                         return;
936                 }
937                 ma->did_print = 1;
938         
939                 if ( (!strcasecmp(cbtype, "text/plain")) 
940                    || (strlen(cbtype)==0) ) {
941                         wptr = content;
942                         if (length > 0) {
943                                 client_write(wptr, length);
944                                 if (wptr[length-1] != '\n') {
945                                         cprintf("\n");
946                                 }
947                         }
948                 }
949                 else if (!strcasecmp(cbtype, "text/html")) {
950                         ptr = html_to_ascii(content, 80, 0);
951                         wlen = strlen(ptr);
952                         client_write(ptr, wlen);
953                         if (ptr[wlen-1] != '\n') {
954                                 cprintf("\n");
955                         }
956                         phree(ptr);
957                 }
958                 else if (strncasecmp(cbtype, "multipart/", 10)) {
959                         cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
960                                 partnum, filename, cbtype, (long)length);
961                 }
962         }
963
964 /*
965  * The client is elegant and sophisticated and wants to be choosy about
966  * MIME content types, so figure out which multipart/alternative part
967  * we're going to send.
968  */
969 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
970                 void *content, char *cbtype, size_t length, char *encoding,
971                 void *cbuserdata)
972 {
973         char buf[SIZ];
974         int i;
975
976         if (ma->is_ma > 0) {
977                 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
978                         extract(buf, CC->preferred_formats, i);
979                         if (!strcasecmp(buf, cbtype)) {
980                                 strcpy(ma->chosen_part, partnum);
981                         }
982                 }
983         }
984 }
985
986 /*
987  * Now that we've chosen our preferred part, output it.
988  */
989 void output_preferred(char *name, char *filename, char *partnum, char *disp,
990                 void *content, char *cbtype, size_t length, char *encoding,
991                 void *cbuserdata)
992 {
993         int i;
994         char buf[SIZ];
995         int add_newline = 0;
996         char *text_content;
997
998         /* This is not the MIME part you're looking for... */
999         if (strcasecmp(partnum, ma->chosen_part)) return;
1000
1001         /* If the content-type of this part is in our preferred formats
1002          * list, we can simply output it verbatim.
1003          */
1004         for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1005                 extract(buf, CC->preferred_formats, i);
1006                 if (!strcasecmp(buf, cbtype)) {
1007                         /* Yeah!  Go!  W00t!! */
1008
1009                         text_content = (char *)content;
1010                         if (text_content[length-1] != '\n') {
1011                                 ++add_newline;
1012                         }
1013
1014                         cprintf("Content-type: %s\n", cbtype);
1015                         cprintf("Content-length: %d\n",
1016                                 (int)(length + add_newline) );
1017                         if (strlen(encoding) > 0) {
1018                                 cprintf("Content-transfer-encoding: %s\n", encoding);
1019                         }
1020                         else {
1021                                 cprintf("Content-transfer-encoding: 7bit\n");
1022                         }
1023                         cprintf("\n");
1024                         client_write(content, length);
1025                         if (add_newline) cprintf("\n");
1026                         return;
1027                 }
1028         }
1029
1030         /* No translations required or possible: output as text/plain */
1031         cprintf("Content-type: text/plain\n\n");
1032         fixed_output(name, filename, partnum, disp, content, cbtype,
1033                         length, encoding, cbuserdata);
1034 }
1035
1036
1037 /*
1038  * Get a message off disk.  (returns om_* values found in msgbase.h)
1039  * 
1040  */
1041 int CtdlOutputMsg(long msg_num,         /* message number (local) to fetch */
1042                 int mode,               /* how would you like that message? */
1043                 int headers_only,       /* eschew the message body? */
1044                 int do_proto,           /* do Citadel protocol responses? */
1045                 int crlf                /* Use CRLF newlines instead of LF? */
1046 ) {
1047         struct CtdlMessage *TheMessage;
1048         int retcode;
1049
1050         lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n", 
1051                 msg_num, mode);
1052
1053         TheMessage = NULL;
1054
1055         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1056                 if (do_proto) cprintf("%d Not logged in.\n",
1057                         ERROR + NOT_LOGGED_IN);
1058                 return(om_not_logged_in);
1059         }
1060
1061         /* FIXME ... small security issue
1062          * We need to check to make sure the requested message is actually
1063          * in the current room, and set msg_ok to 1 only if it is.  This
1064          * functionality is currently missing because I'm in a hurry to replace
1065          * broken production code with nonbroken pre-beta code.  :(   -- ajc
1066          *
1067          if (!msg_ok) {
1068          if (do_proto) cprintf("%d Message %ld is not in this room.\n",
1069          ERROR + MESSAGE_NOT_FOUND, msg_num);
1070          return(om_no_such_msg);
1071          }
1072          */
1073
1074         /*
1075          * Fetch the message from disk.
1076          */
1077         TheMessage = CtdlFetchMessage(msg_num);
1078
1079         if (TheMessage == NULL) {
1080                 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1081                         ERROR + MESSAGE_NOT_FOUND, msg_num);
1082                 return(om_no_such_msg);
1083         }
1084         
1085         retcode = CtdlOutputPreLoadedMsg(
1086                         TheMessage, msg_num, mode,
1087                         headers_only, do_proto, crlf);
1088
1089         CtdlFreeMessage(TheMessage);
1090
1091         return(retcode);
1092 }
1093
1094
1095 /*
1096  * Get a message off disk.  (returns om_* values found in msgbase.h)
1097  * 
1098  */
1099 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
1100                 long msg_num,
1101                 int mode,               /* how would you like that message? */
1102                 int headers_only,       /* eschew the message body? */
1103                 int do_proto,           /* do Citadel protocol responses? */
1104                 int crlf                /* Use CRLF newlines instead of LF? */
1105 ) {
1106         int i, k;
1107         char buf[1024];
1108         cit_uint8_t ch;
1109         char allkeys[SIZ];
1110         char display_name[SIZ];
1111         char *mptr;
1112         char *nl;       /* newline string */
1113         int suppress_f = 0;
1114         int subject_found = 0;
1115
1116         /* buffers needed for RFC822 translation */
1117         char suser[SIZ];
1118         char luser[SIZ];
1119         char fuser[SIZ];
1120         char snode[SIZ];
1121         char lnode[SIZ];
1122         char mid[SIZ];
1123         char datestamp[SIZ];
1124         /*                                       */
1125
1126         snprintf(mid, sizeof mid, "%ld", msg_num);
1127         nl = (crlf ? "\r\n" : "\n");
1128
1129         if (!is_valid_message(TheMessage)) {
1130                 lprintf(1, "ERROR: invalid preloaded message for output\n");
1131                 return(om_no_such_msg);
1132         }
1133
1134         /* Are we downloading a MIME component? */
1135         if (mode == MT_DOWNLOAD) {
1136                 if (TheMessage->cm_format_type != FMT_RFC822) {
1137                         if (do_proto)
1138                                 cprintf("%d This is not a MIME message.\n",
1139                                 ERROR + ILLEGAL_VALUE);
1140                 } else if (CC->download_fp != NULL) {
1141                         if (do_proto) cprintf(
1142                                 "%d You already have a download open.\n",
1143                                 ERROR + RESOURCE_BUSY);
1144                 } else {
1145                         /* Parse the message text component */
1146                         mptr = TheMessage->cm_fields['M'];
1147                         mime_parser(mptr, NULL,
1148                                 *mime_download, NULL, NULL,
1149                                 NULL, 0);
1150                         /* If there's no file open by this time, the requested
1151                          * section wasn't found, so print an error
1152                          */
1153                         if (CC->download_fp == NULL) {
1154                                 if (do_proto) cprintf(
1155                                         "%d Section %s not found.\n",
1156                                         ERROR + FILE_NOT_FOUND,
1157                                         desired_section);
1158                         }
1159                 }
1160                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1161         }
1162
1163         /* now for the user-mode message reading loops */
1164         if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1165
1166         /* Does the caller want to skip the headers? */
1167         if (headers_only == HEADERS_NONE) goto START_TEXT;
1168
1169         /* Tell the client which format type we're using. */
1170         if ( (mode == MT_CITADEL) && (do_proto) ) {
1171                 cprintf("type=%d\n", TheMessage->cm_format_type);
1172         }
1173
1174         /* nhdr=yes means that we're only displaying headers, no body */
1175         if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1176            && (mode == MT_CITADEL)
1177            && (do_proto)
1178            ) {
1179                 cprintf("nhdr=yes\n");
1180         }
1181
1182         /* begin header processing loop for Citadel message format */
1183
1184         if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1185
1186                 strcpy(display_name, "<unknown>");
1187                 if (TheMessage->cm_fields['A']) {
1188                         strcpy(buf, TheMessage->cm_fields['A']);
1189                         PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1190                         if (TheMessage->cm_anon_type == MES_ANONONLY) {
1191                                 strcpy(display_name, "****");
1192                         }
1193                         else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1194                                 strcpy(display_name, "anonymous");
1195                         }
1196                         else {
1197                                 strcpy(display_name, buf);
1198                         }
1199                         if ((is_room_aide())
1200                             && ((TheMessage->cm_anon_type == MES_ANONONLY)
1201                              || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1202                                 size_t tmp = strlen(display_name);
1203                                 snprintf(&display_name[tmp],
1204                                          sizeof display_name - tmp,
1205                                          " [%s]", buf);
1206                         }
1207                 }
1208
1209                 /* Don't show Internet address for users on the
1210                  * local Citadel network.
1211                  */
1212                 suppress_f = 0;
1213                 if (TheMessage->cm_fields['N'] != NULL)
1214                    if (strlen(TheMessage->cm_fields['N']) > 0)
1215                       if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1216                         suppress_f = 1;
1217                 }
1218                 
1219                 /* Now spew the header fields in the order we like them. */
1220                 strcpy(allkeys, FORDER);
1221                 for (i=0; i<strlen(allkeys); ++i) {
1222                         k = (int) allkeys[i];
1223                         if (k != 'M') {
1224                                 if ( (TheMessage->cm_fields[k] != NULL)
1225                                    && (msgkeys[k] != NULL) ) {
1226                                         if (k == 'A') {
1227                                                 if (do_proto) cprintf("%s=%s\n",
1228                                                         msgkeys[k],
1229                                                         display_name);
1230                                         }
1231                                         else if ((k == 'F') && (suppress_f)) {
1232                                                 /* do nothing */
1233                                         }
1234                                         /* Masquerade display name if needed */
1235                                         else {
1236                                                 if (do_proto) cprintf("%s=%s\n",
1237                                                         msgkeys[k],
1238                                                         TheMessage->cm_fields[k]
1239                                         );
1240                                         }
1241                                 }
1242                         }
1243                 }
1244
1245         }
1246
1247         /* begin header processing loop for RFC822 transfer format */
1248
1249         strcpy(suser, "");
1250         strcpy(luser, "");
1251         strcpy(fuser, "");
1252         strcpy(snode, NODENAME);
1253         strcpy(lnode, HUMANNODE);
1254         if (mode == MT_RFC822) {
1255                 cprintf("X-UIDL: %ld%s", msg_num, nl);
1256                 for (i = 0; i < 256; ++i) {
1257                         if (TheMessage->cm_fields[i]) {
1258                                 mptr = TheMessage->cm_fields[i];
1259
1260                                 if (i == 'A') {
1261                                         strcpy(luser, mptr);
1262                                         strcpy(suser, mptr);
1263                                 }
1264 /****
1265  "Path:" removed for now because it confuses brain-dead Microsoft shitware
1266  into thinking that mail messages are newsgroup messages instead.  When we
1267  add NNTP support back into Citadel we'll have to add code to only output
1268  this field when appropriate.
1269                                 else if (i == 'P') {
1270                                         cprintf("Path: %s%s", mptr, nl);
1271                                 }
1272  ****/
1273                                 else if (i == 'U') {
1274                                         cprintf("Subject: %s%s", mptr, nl);
1275                                         subject_found = 1;
1276                                 }
1277                                 else if (i == 'I')
1278                                         safestrncpy(mid, mptr, sizeof mid);
1279                                 else if (i == 'H')
1280                                         safestrncpy(lnode, mptr, sizeof lnode);
1281                                 else if (i == 'F')
1282                                         safestrncpy(fuser, mptr, sizeof fuser);
1283                                 else if (i == 'O')
1284                                         cprintf("X-Citadel-Room: %s%s",
1285                                                 mptr, nl);
1286                                 else if (i == 'N')
1287                                         safestrncpy(snode, mptr, sizeof snode);
1288                                 else if (i == 'R')
1289                                         cprintf("To: %s%s", mptr, nl);
1290                                 else if (i == 'T') {
1291                                         datestring(datestamp, sizeof datestamp,
1292                                                 atol(mptr), DATESTRING_RFC822);
1293                                         cprintf("Date: %s%s", datestamp, nl);
1294                                 }
1295                         }
1296                 }
1297                 if (subject_found == 0) {
1298                         cprintf("Subject: (no subject)%s", nl);
1299                 }
1300         }
1301
1302         for (i=0; i<strlen(suser); ++i) {
1303                 suser[i] = tolower(suser[i]);
1304                 if (!isalnum(suser[i])) suser[i]='_';
1305         }
1306
1307         if (mode == MT_RFC822) {
1308                 if (!strcasecmp(snode, NODENAME)) {
1309                         strcpy(snode, FQDN);
1310                 }
1311
1312                 /* Construct a fun message id */
1313                 cprintf("Message-ID: <%s", mid);
1314                 if (strchr(mid, '@')==NULL) {
1315                         cprintf("@%s", snode);
1316                 }
1317                 cprintf(">%s", nl);
1318
1319                 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1320
1321                 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1322                         cprintf("From: x@x.org (----)%s", nl);
1323                 }
1324                 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1325                         cprintf("From: x@x.org (anonymous)%s", nl);
1326                 }
1327                 else if (strlen(fuser) > 0) {
1328                         cprintf("From: %s (%s)%s", fuser, luser, nl);
1329                 }
1330                 else {
1331                         cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1332                 }
1333
1334                 cprintf("Organization: %s%s", lnode, nl);
1335
1336                 /* Blank line signifying RFC822 end-of-headers */
1337                 if (TheMessage->cm_format_type != FMT_RFC822) {
1338                         cprintf("%s", nl);
1339                 }
1340         }
1341
1342         /* end header processing loop ... at this point, we're in the text */
1343 START_TEXT:
1344         mptr = TheMessage->cm_fields['M'];
1345
1346         /* Tell the client about the MIME parts in this message */
1347         if (TheMessage->cm_format_type == FMT_RFC822) {
1348                 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1349                         mime_parser(mptr, NULL,
1350                                 *list_this_part,
1351                                 *list_this_pref,
1352                                 *list_this_suff,
1353                                 NULL, 0);
1354                 }
1355                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
1356                         /* FIXME ... we have to put some code in here to avoid
1357                          * printing duplicate header information when both
1358                          * Citadel and RFC822 headers exist.  Preference should
1359                          * probably be given to the RFC822 headers.
1360                          */
1361                         int done_rfc822_hdrs = 0;
1362                         while (ch=*(mptr++), ch!=0) {
1363                                 if (ch==13) {
1364                                         /* do nothing */
1365                                 }
1366                                 else if (ch==10) {
1367                                         if (!done_rfc822_hdrs) {
1368                                                 if (headers_only != HEADERS_NONE) {
1369                                                         cprintf("%s", nl);
1370                                                 }
1371                                         }
1372                                         else {
1373                                                 if (headers_only != HEADERS_ONLY) {
1374                                                         cprintf("%s", nl);
1375                                                 }
1376                                         }
1377                                         if ((*(mptr) == 13) || (*(mptr) == 10)) {
1378                                                 done_rfc822_hdrs = 1;
1379                                         }
1380                                 }
1381                                 else {
1382                                         if (done_rfc822_hdrs) {
1383                                                 if (headers_only != HEADERS_NONE) {
1384                                                         cprintf("%c", ch);
1385                                                 }
1386                                         }
1387                                         else {
1388                                                 if (headers_only != HEADERS_ONLY) {
1389                                                         cprintf("%c", ch);
1390                                                 }
1391                                         }
1392                                         if ((*mptr == 13) || (*mptr == 10)) {
1393                                                 done_rfc822_hdrs = 1;
1394                                         }
1395                                 }
1396                         }
1397                         goto DONE;
1398                 }
1399         }
1400
1401         if (headers_only == HEADERS_ONLY) {
1402                 goto DONE;
1403         }
1404
1405         /* signify start of msg text */
1406         if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1407                 if (do_proto) cprintf("text\n");
1408         }
1409
1410         /* If the format type on disk is 1 (fixed-format), then we want
1411          * everything to be output completely literally ... regardless of
1412          * what message transfer format is in use.
1413          */
1414         if (TheMessage->cm_format_type == FMT_FIXED) {
1415                 if (mode == MT_MIME) {
1416                         cprintf("Content-type: text/plain\n\n");
1417                 }
1418                 strcpy(buf, "");
1419                 while (ch = *mptr++, ch > 0) {
1420                         if (ch == 13)
1421                                 ch = 10;
1422                         if ((ch == 10) || (strlen(buf) > 250)) {
1423                                 cprintf("%s%s", buf, nl);
1424                                 strcpy(buf, "");
1425                         } else {
1426                                 buf[strlen(buf) + 1] = 0;
1427                                 buf[strlen(buf)] = ch;
1428                         }
1429                 }
1430                 if (strlen(buf) > 0)
1431                         cprintf("%s%s", buf, nl);
1432         }
1433
1434         /* If the message on disk is format 0 (Citadel vari-format), we
1435          * output using the formatter at 80 columns.  This is the final output
1436          * form if the transfer format is RFC822, but if the transfer format
1437          * is Citadel proprietary, it'll still work, because the indentation
1438          * for new paragraphs is correct and the client will reformat the
1439          * message to the reader's screen width.
1440          */
1441         if (TheMessage->cm_format_type == FMT_CITADEL) {
1442                 if (mode == MT_MIME) {
1443                         cprintf("Content-type: text/x-citadel-variformat\n\n");
1444                 }
1445                 memfmout(80, mptr, 0, nl);
1446         }
1447
1448         /* If the message on disk is format 4 (MIME), we've gotta hand it
1449          * off to the MIME parser.  The client has already been told that
1450          * this message is format 1 (fixed format), so the callback function
1451          * we use will display those parts as-is.
1452          */
1453         if (TheMessage->cm_format_type == FMT_RFC822) {
1454                 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1455                 memset(ma, 0, sizeof(struct ma_info));
1456
1457                 if (mode == MT_MIME) {
1458                         strcpy(ma->chosen_part, "1");
1459                         mime_parser(mptr, NULL,
1460                                 *choose_preferred, *fixed_output_pre,
1461                                 *fixed_output_post, NULL, 0);
1462                         mime_parser(mptr, NULL,
1463                                 *output_preferred, NULL, NULL, NULL, 0);
1464                 }
1465                 else {
1466                         mime_parser(mptr, NULL,
1467                                 *fixed_output, *fixed_output_pre,
1468                                 *fixed_output_post, NULL, 0);
1469                 }
1470         }
1471
1472 DONE:   /* now we're done */
1473         if (do_proto) cprintf("000\n");
1474         return(om_ok);
1475 }
1476
1477
1478
1479 /*
1480  * display a message (mode 0 - Citadel proprietary)
1481  */
1482 void cmd_msg0(char *cmdbuf)
1483 {
1484         long msgid;
1485         int headers_only = HEADERS_ALL;
1486
1487         msgid = extract_long(cmdbuf, 0);
1488         headers_only = extract_int(cmdbuf, 1);
1489
1490         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1491         return;
1492 }
1493
1494
1495 /*
1496  * display a message (mode 2 - RFC822)
1497  */
1498 void cmd_msg2(char *cmdbuf)
1499 {
1500         long msgid;
1501         int headers_only = HEADERS_ALL;
1502
1503         msgid = extract_long(cmdbuf, 0);
1504         headers_only = extract_int(cmdbuf, 1);
1505
1506         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1507 }
1508
1509
1510
1511 /* 
1512  * display a message (mode 3 - IGnet raw format - internal programs only)
1513  */
1514 void cmd_msg3(char *cmdbuf)
1515 {
1516         long msgnum;
1517         struct CtdlMessage *msg;
1518         struct ser_ret smr;
1519
1520         if (CC->internal_pgm == 0) {
1521                 cprintf("%d This command is for internal programs only.\n",
1522                         ERROR + HIGHER_ACCESS_REQUIRED);
1523                 return;
1524         }
1525
1526         msgnum = extract_long(cmdbuf, 0);
1527         msg = CtdlFetchMessage(msgnum);
1528         if (msg == NULL) {
1529                 cprintf("%d Message %ld not found.\n", 
1530                         ERROR + MESSAGE_NOT_FOUND, msgnum);
1531                 return;
1532         }
1533
1534         serialize_message(&smr, msg);
1535         CtdlFreeMessage(msg);
1536
1537         if (smr.len == 0) {
1538                 cprintf("%d Unable to serialize message\n",
1539                         ERROR + INTERNAL_ERROR);
1540                 return;
1541         }
1542
1543         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1544         client_write(smr.ser, smr.len);
1545         phree(smr.ser);
1546 }
1547
1548
1549
1550 /* 
1551  * Display a message using MIME content types
1552  */
1553 void cmd_msg4(char *cmdbuf)
1554 {
1555         long msgid;
1556
1557         msgid = extract_long(cmdbuf, 0);
1558         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1559 }
1560
1561
1562
1563 /* 
1564  * Client tells us its preferred message format(s)
1565  */
1566 void cmd_msgp(char *cmdbuf)
1567 {
1568         safestrncpy(CC->preferred_formats, cmdbuf,
1569                         sizeof(CC->preferred_formats));
1570         cprintf("%d ok\n", CIT_OK);
1571 }
1572
1573
1574 /*
1575  * Open a component of a MIME message as a download file 
1576  */
1577 void cmd_opna(char *cmdbuf)
1578 {
1579         long msgid;
1580
1581         CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1582
1583         msgid = extract_long(cmdbuf, 0);
1584         extract(desired_section, cmdbuf, 1);
1585
1586         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1587 }                       
1588
1589
1590 /*
1591  * Save a message pointer into a specified room
1592  * (Returns 0 for success, nonzero for failure)
1593  * roomname may be NULL to use the current room
1594  */
1595 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1596         int i;
1597         char hold_rm[ROOMNAMELEN];
1598         struct cdbdata *cdbfr;
1599         int num_msgs;
1600         long *msglist;
1601         long highest_msg = 0L;
1602         struct CtdlMessage *msg = NULL;
1603
1604         lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1605                 roomname, msgid, flags);
1606
1607         strcpy(hold_rm, CC->room.QRname);
1608
1609         /* We may need to check to see if this message is real */
1610         if (  (flags & SM_VERIFY_GOODNESS)
1611            || (flags & SM_DO_REPL_CHECK)
1612            ) {
1613                 msg = CtdlFetchMessage(msgid);
1614                 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1615         }
1616
1617         /* Perform replication checks if necessary */
1618         if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1619
1620                 if (getroom(&CC->room,
1621                    ((roomname != NULL) ? roomname : CC->room.QRname) )
1622                    != 0) {
1623                         lprintf(9, "No such room <%s>\n", roomname);
1624                         if (msg != NULL) CtdlFreeMessage(msg);
1625                         return(ERROR + ROOM_NOT_FOUND);
1626                 }
1627
1628                 if (ReplicationChecks(msg) != 0) {
1629                         getroom(&CC->room, hold_rm);
1630                         if (msg != NULL) CtdlFreeMessage(msg);
1631                         lprintf(9, "Did replication, and newer exists\n");
1632                         return(0);
1633                 }
1634         }
1635
1636         /* Now the regular stuff */
1637         if (lgetroom(&CC->room,
1638            ((roomname != NULL) ? roomname : CC->room.QRname) )
1639            != 0) {
1640                 lprintf(9, "No such room <%s>\n", roomname);
1641                 if (msg != NULL) CtdlFreeMessage(msg);
1642                 return(ERROR + ROOM_NOT_FOUND);
1643         }
1644
1645         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1646         if (cdbfr == NULL) {
1647                 msglist = NULL;
1648                 num_msgs = 0;
1649         } else {
1650                 msglist = mallok(cdbfr->len);
1651                 if (msglist == NULL)
1652                         lprintf(3, "ERROR malloc msglist!\n");
1653                 num_msgs = cdbfr->len / sizeof(long);
1654                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1655                 cdb_free(cdbfr);
1656         }
1657
1658
1659         /* Make sure the message doesn't already exist in this room.  It
1660          * is absolutely taboo to have more than one reference to the same
1661          * message in a room.
1662          */
1663         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1664                 if (msglist[i] == msgid) {
1665                         lputroom(&CC->room);    /* unlock the room */
1666                         getroom(&CC->room, hold_rm);
1667                         if (msg != NULL) CtdlFreeMessage(msg);
1668                         return(ERROR + ALREADY_EXISTS);
1669                 }
1670         }
1671
1672         /* Now add the new message */
1673         ++num_msgs;
1674         msglist = reallok(msglist,
1675                           (num_msgs * sizeof(long)));
1676
1677         if (msglist == NULL) {
1678                 lprintf(3, "ERROR: can't realloc message list!\n");
1679         }
1680         msglist[num_msgs - 1] = msgid;
1681
1682         /* Sort the message list, so all the msgid's are in order */
1683         num_msgs = sort_msglist(msglist, num_msgs);
1684
1685         /* Determine the highest message number */
1686         highest_msg = msglist[num_msgs - 1];
1687
1688         /* Write it back to disk. */
1689         cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long),
1690                   msglist, num_msgs * sizeof(long));
1691
1692         /* Free up the memory we used. */
1693         phree(msglist);
1694
1695         /* Update the highest-message pointer and unlock the room. */
1696         CC->room.QRhighest = highest_msg;
1697         lputroom(&CC->room);
1698         getroom(&CC->room, hold_rm);
1699
1700         /* Bump the reference count for this message. */
1701         if ((flags & SM_DONT_BUMP_REF)==0) {
1702                 AdjRefCount(msgid, +1);
1703         }
1704
1705         /* Return success. */
1706         if (msg != NULL) CtdlFreeMessage(msg);
1707         return (0);
1708 }
1709
1710
1711
1712 /*
1713  * Message base operation to send a message to the master file
1714  * (returns new message number)
1715  *
1716  * This is the back end for CtdlSubmitMsg() and should not be directly
1717  * called by server-side modules.
1718  *
1719  */
1720 long send_message(struct CtdlMessage *msg,      /* pointer to buffer */
1721                 FILE *save_a_copy)              /* save a copy to disk? */
1722 {
1723         long newmsgid;
1724         long retval;
1725         char msgidbuf[SIZ];
1726         struct ser_ret smr;
1727
1728         /* Get a new message number */
1729         newmsgid = get_new_message_number();
1730         snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1731
1732         /* Generate an ID if we don't have one already */
1733         if (msg->cm_fields['I']==NULL) {
1734                 msg->cm_fields['I'] = strdoop(msgidbuf);
1735         }
1736         
1737         serialize_message(&smr, msg);
1738
1739         if (smr.len == 0) {
1740                 cprintf("%d Unable to serialize message\n",
1741                         ERROR + INTERNAL_ERROR);
1742                 return (-1L);
1743         }
1744
1745         /* Write our little bundle of joy into the message base */
1746         if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1747                       smr.ser, smr.len) < 0) {
1748                 lprintf(2, "Can't store message\n");
1749                 retval = 0L;
1750         } else {
1751                 retval = newmsgid;
1752         }
1753
1754         /* If the caller specified that a copy should be saved to a particular
1755          * file handle, do that now too.
1756          */
1757         if (save_a_copy != NULL) {
1758                 fwrite(smr.ser, smr.len, 1, save_a_copy);
1759         }
1760
1761         /* Free the memory we used for the serialized message */
1762         phree(smr.ser);
1763
1764         /* Return the *local* message ID to the caller
1765          * (even if we're storing an incoming network message)
1766          */
1767         return(retval);
1768 }
1769
1770
1771
1772 /*
1773  * Serialize a struct CtdlMessage into the format used on disk and network.
1774  * 
1775  * This function loads up a "struct ser_ret" (defined in server.h) which
1776  * contains the length of the serialized message and a pointer to the
1777  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
1778  */
1779 void serialize_message(struct ser_ret *ret,             /* return values */
1780                         struct CtdlMessage *msg)        /* unserialized msg */
1781 {
1782         size_t wlen;
1783         int i;
1784         static char *forder = FORDER;
1785
1786         if (is_valid_message(msg) == 0) return;         /* self check */
1787
1788         ret->len = 3;
1789         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1790                 ret->len = ret->len +
1791                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
1792
1793         lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1794         ret->ser = mallok(ret->len);
1795         if (ret->ser == NULL) {
1796                 ret->len = 0;
1797                 return;
1798         }
1799
1800         ret->ser[0] = 0xFF;
1801         ret->ser[1] = msg->cm_anon_type;
1802         ret->ser[2] = msg->cm_format_type;
1803         wlen = 3;
1804
1805         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1806                 ret->ser[wlen++] = (char)forder[i];
1807                 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1808                 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1809         }
1810         if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1811                 (long)ret->len, (long)wlen);
1812
1813         return;
1814 }
1815
1816
1817
1818 /*
1819  * Back end for the ReplicationChecks() function
1820  */
1821 void check_repl(long msgnum, void *userdata) {
1822         struct CtdlMessage *msg;
1823         time_t timestamp = (-1L);
1824
1825         lprintf(9, "check_repl() found message %ld\n", msgnum);
1826         msg = CtdlFetchMessage(msgnum);
1827         if (msg == NULL) return;
1828         if (msg->cm_fields['T'] != NULL) {
1829                 timestamp = atol(msg->cm_fields['T']);
1830         }
1831         CtdlFreeMessage(msg);
1832
1833         if (timestamp > msg_repl->highest) {
1834                 msg_repl->highest = timestamp;  /* newer! */
1835                 lprintf(9, "newer!\n");
1836                 return;
1837         }
1838         lprintf(9, "older!\n");
1839
1840         /* Existing isn't newer?  Then delete the old one(s). */
1841         CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1842 }
1843
1844
1845 /*
1846  * Check to see if any messages already exist which carry the same Extended ID
1847  * as this one.  
1848  *
1849  * If any are found:
1850  * -> With older timestamps: delete them and return 0.  Message will be saved.
1851  * -> With newer timestamps: return 1.  Message save will be aborted.
1852  */
1853 int ReplicationChecks(struct CtdlMessage *msg) {
1854         struct CtdlMessage *template;
1855         int abort_this = 0;
1856
1857         lprintf(9, "ReplicationChecks() started\n");
1858         /* No extended id?  Don't do anything. */
1859         if (msg->cm_fields['E'] == NULL) return 0;
1860         if (strlen(msg->cm_fields['E']) == 0) return 0;
1861         lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1862
1863         CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1864         strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1865         msg_repl->highest = atol(msg->cm_fields['T']);
1866
1867         template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1868         memset(template, 0, sizeof(struct CtdlMessage));
1869         template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1870
1871         CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1872
1873         /* If a newer message exists with the same Extended ID, abort
1874          * this save.
1875          */
1876         if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1877                 abort_this = 1;
1878                 }
1879
1880         CtdlFreeMessage(template);
1881         lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1882         return(abort_this);
1883 }
1884
1885
1886
1887
1888 /*
1889  * Save a message to disk and submit it into the delivery system.
1890  */
1891 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
1892                 struct recptypes *recps,        /* recipients (if mail) */
1893                 char *force                     /* force a particular room? */
1894 ) {
1895         char aaa[SIZ];
1896         char hold_rm[ROOMNAMELEN];
1897         char actual_rm[ROOMNAMELEN];
1898         char force_room[ROOMNAMELEN];
1899         char content_type[SIZ];                 /* We have to learn this */
1900         char recipient[SIZ];
1901         long newmsgid;
1902         char *mptr = NULL;
1903         struct ctdluser userbuf;
1904         int a, i;
1905         struct MetaData smi;
1906         FILE *network_fp = NULL;
1907         static int seqnum = 1;
1908         struct CtdlMessage *imsg = NULL;
1909         char *instr;
1910         struct ser_ret smr;
1911         char *hold_R, *hold_D;
1912
1913         lprintf(9, "CtdlSubmitMsg() called\n");
1914         if (is_valid_message(msg) == 0) return(-1);     /* self check */
1915
1916         /* If this message has no timestamp, we take the liberty of
1917          * giving it one, right now.
1918          */
1919         if (msg->cm_fields['T'] == NULL) {
1920                 lprintf(9, "Generating timestamp\n");
1921                 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1922                 msg->cm_fields['T'] = strdoop(aaa);
1923         }
1924
1925         /* If this message has no path, we generate one.
1926          */
1927         if (msg->cm_fields['P'] == NULL) {
1928                 lprintf(9, "Generating path\n");
1929                 if (msg->cm_fields['A'] != NULL) {
1930                         msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1931                         for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1932                                 if (isspace(msg->cm_fields['P'][a])) {
1933                                         msg->cm_fields['P'][a] = ' ';
1934                                 }
1935                         }
1936                 }
1937                 else {
1938                         msg->cm_fields['P'] = strdoop("unknown");
1939                 }
1940         }
1941
1942         if (force == NULL) {
1943                 strcpy(force_room, "");
1944         }
1945         else {
1946                 strcpy(force_room, force);
1947         }
1948
1949         /* Learn about what's inside, because it's what's inside that counts */
1950         lprintf(9, "Learning what's inside\n");
1951         if (msg->cm_fields['M'] == NULL) {
1952                 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1953         }
1954
1955         switch (msg->cm_format_type) {
1956         case 0:
1957                 strcpy(content_type, "text/x-citadel-variformat");
1958                 break;
1959         case 1:
1960                 strcpy(content_type, "text/plain");
1961                 break;
1962         case 4:
1963                 strcpy(content_type, "text/plain");
1964                 /* advance past header fields */
1965                 mptr = msg->cm_fields['M'];
1966                 a = strlen(mptr);
1967                 while ((--a) > 0) {
1968                         if (!strncasecmp(mptr, "Content-type: ", 14)) {
1969                                 safestrncpy(content_type, mptr,
1970                                             sizeof(content_type));
1971                                 strcpy(content_type, &content_type[14]);
1972                                 for (a = 0; a < strlen(content_type); ++a)
1973                                         if ((content_type[a] == ';')
1974                                             || (content_type[a] == ' ')
1975                                             || (content_type[a] == 13)
1976                                             || (content_type[a] == 10))
1977                                                 content_type[a] = 0;
1978                                 break;
1979                         }
1980                         ++mptr;
1981                 }
1982         }
1983
1984         /* Goto the correct room */
1985         lprintf(9, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
1986         strcpy(hold_rm, CC->room.QRname);
1987         strcpy(actual_rm, CC->room.QRname);
1988         if (recps != NULL) {
1989                 strcpy(actual_rm, SENTITEMS);
1990         }
1991
1992         /* If the user is a twit, move to the twit room for posting */
1993         lprintf(9, "Handling twit stuff: %s\n",
1994                         (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
1995         if (TWITDETECT) {
1996                 if (CC->user.axlevel == 2) {
1997                         strcpy(hold_rm, actual_rm);
1998                         strcpy(actual_rm, config.c_twitroom);
1999                 }
2000         }
2001
2002         /* ...or if this message is destined for Aide> then go there. */
2003         if (strlen(force_room) > 0) {
2004                 strcpy(actual_rm, force_room);
2005         }
2006
2007         lprintf(9, "Final selection: %s\n", actual_rm);
2008         if (strcasecmp(actual_rm, CC->room.QRname)) {
2009                 getroom(&CC->room, actual_rm);
2010         }
2011
2012         /*
2013          * If this message has no O (room) field, generate one.
2014          */
2015         if (msg->cm_fields['O'] == NULL) {
2016                 msg->cm_fields['O'] = strdoop(CC->room.QRname);
2017         }
2018
2019         /* Perform "before save" hooks (aborting if any return nonzero) */
2020         lprintf(9, "Performing before-save hooks\n");
2021         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
2022
2023         /* If this message has an Extended ID, perform replication checks */
2024         lprintf(9, "Performing replication checks\n");
2025         if (ReplicationChecks(msg) > 0) return(-1);
2026
2027         /* Save it to disk */
2028         lprintf(9, "Saving to disk\n");
2029         newmsgid = send_message(msg, NULL);
2030         if (newmsgid <= 0L) return(-1);
2031
2032         /* Write a supplemental message info record.  This doesn't have to
2033          * be a critical section because nobody else knows about this message
2034          * yet.
2035          */
2036         lprintf(9, "Creating MetaData record\n");
2037         memset(&smi, 0, sizeof(struct MetaData));
2038         smi.meta_msgnum = newmsgid;
2039         smi.meta_refcount = 0;
2040         safestrncpy(smi.meta_content_type, content_type, 64);
2041         PutMetaData(&smi);
2042
2043         /* Now figure out where to store the pointers */
2044         lprintf(9, "Storing pointers\n");
2045
2046         /* If this is being done by the networker delivering a private
2047          * message, we want to BYPASS saving the sender's copy (because there
2048          * is no local sender; it would otherwise go to the Trashcan).
2049          */
2050         if ((!CC->internal_pgm) || (recps == NULL)) {
2051                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2052                         lprintf(3, "ERROR saving message pointer!\n");
2053                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2054                 }
2055         }
2056
2057         /* For internet mail, drop a copy in the outbound queue room */
2058         if (recps != NULL)
2059          if (recps->num_internet > 0) {
2060                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2061         }
2062
2063         /* If other rooms are specified, drop them there too. */
2064         if (recps != NULL)
2065          if (recps->num_room > 0)
2066           for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2067                 extract(recipient, recps->recp_room, i);
2068                 lprintf(9, "Delivering to local room <%s>\n", recipient);
2069                 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2070         }
2071
2072         /* Bump this user's messages posted counter. */
2073         lprintf(9, "Updating user\n");
2074         lgetuser(&CC->user, CC->curr_user);
2075         CC->user.posted = CC->user.posted + 1;
2076         lputuser(&CC->user);
2077
2078         /* If this is private, local mail, make a copy in the
2079          * recipient's mailbox and bump the reference count.
2080          */
2081         if (recps != NULL)
2082          if (recps->num_local > 0)
2083           for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2084                 extract(recipient, recps->recp_local, i);
2085                 lprintf(9, "Delivering private local mail to <%s>\n",
2086                         recipient);
2087                 if (getuser(&userbuf, recipient) == 0) {
2088                         MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2089                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2090                         BumpNewMailCounter(userbuf.usernum);
2091                 }
2092                 else {
2093                         lprintf(9, "No user <%s>\n", recipient);
2094                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2095                 }
2096         }
2097
2098         /* Perform "after save" hooks */
2099         lprintf(9, "Performing after-save hooks\n");
2100         PerformMessageHooks(msg, EVT_AFTERSAVE);
2101
2102         /* For IGnet mail, we have to save a new copy into the spooler for
2103          * each recipient, with the R and D fields set to the recipient and
2104          * destination-node.  This has two ugly side effects: all other
2105          * recipients end up being unlisted in this recipient's copy of the
2106          * message, and it has to deliver multiple messages to the same
2107          * node.  We'll revisit this again in a year or so when everyone has
2108          * a network spool receiver that can handle the new style messages.
2109          */
2110         if (recps != NULL)
2111          if (recps->num_ignet > 0)
2112           for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2113                 extract(recipient, recps->recp_ignet, i);
2114
2115                 hold_R = msg->cm_fields['R'];
2116                 hold_D = msg->cm_fields['D'];
2117                 msg->cm_fields['R'] = mallok(SIZ);
2118                 msg->cm_fields['D'] = mallok(SIZ);
2119                 extract_token(msg->cm_fields['R'], recipient, 0, '@');
2120                 extract_token(msg->cm_fields['D'], recipient, 1, '@');
2121                 
2122                 serialize_message(&smr, msg);
2123                 if (smr.len > 0) {
2124                         snprintf(aaa, sizeof aaa,
2125                                 "./network/spoolin/netmail.%04lx.%04x.%04x",
2126                                 (long) getpid(), CC->cs_pid, ++seqnum);
2127                         network_fp = fopen(aaa, "wb+");
2128                         if (network_fp != NULL) {
2129                                 fwrite(smr.ser, smr.len, 1, network_fp);
2130                                 fclose(network_fp);
2131                         }
2132                         phree(smr.ser);
2133                 }
2134
2135                 phree(msg->cm_fields['R']);
2136                 phree(msg->cm_fields['D']);
2137                 msg->cm_fields['R'] = hold_R;
2138                 msg->cm_fields['D'] = hold_D;
2139         }
2140
2141         /* Go back to the room we started from */
2142         lprintf(9, "Returning to original room %s\n", hold_rm);
2143         if (strcasecmp(hold_rm, CC->room.QRname))
2144                 getroom(&CC->room, hold_rm);
2145
2146         /* For internet mail, generate delivery instructions.
2147          * Yes, this is recursive.  Deal with it.  Infinite recursion does
2148          * not happen because the delivery instructions message does not
2149          * contain a recipient.
2150          */
2151         if (recps != NULL)
2152          if (recps->num_internet > 0) {
2153                 lprintf(9, "Generating delivery instructions\n");
2154                 instr = mallok(SIZ * 2);
2155                 snprintf(instr, SIZ * 2,
2156                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2157                         "bounceto|%s@%s\n",
2158                         SPOOLMIME, newmsgid, (long)time(NULL),
2159                         msg->cm_fields['A'], msg->cm_fields['N']
2160                 );
2161
2162                 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2163                         size_t tmp = strlen(instr);
2164                         extract(recipient, recps->recp_internet, i);
2165                         snprintf(&instr[tmp], SIZ * 2 - tmp,
2166                                  "remote|%s|0||\n", recipient);
2167                 }
2168
2169                 imsg = mallok(sizeof(struct CtdlMessage));
2170                 memset(imsg, 0, sizeof(struct CtdlMessage));
2171                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2172                 imsg->cm_anon_type = MES_NORMAL;
2173                 imsg->cm_format_type = FMT_RFC822;
2174                 imsg->cm_fields['A'] = strdoop("Citadel");
2175                 imsg->cm_fields['M'] = instr;
2176                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2177                 CtdlFreeMessage(imsg);
2178         }
2179
2180         return(newmsgid);
2181 }
2182
2183
2184
2185 /*
2186  * Convenience function for generating small administrative messages.
2187  */
2188 void quickie_message(char *from, char *to, char *room, char *text, 
2189                         int format_type, char *subject)
2190 {
2191         struct CtdlMessage *msg;
2192         struct recptypes *recp = NULL;
2193
2194         msg = mallok(sizeof(struct CtdlMessage));
2195         memset(msg, 0, sizeof(struct CtdlMessage));
2196         msg->cm_magic = CTDLMESSAGE_MAGIC;
2197         msg->cm_anon_type = MES_NORMAL;
2198         msg->cm_format_type = format_type;
2199         msg->cm_fields['A'] = strdoop(from);
2200         if (room != NULL) msg->cm_fields['O'] = strdoop(room);
2201         msg->cm_fields['N'] = strdoop(NODENAME);
2202         if (to != NULL) {
2203                 msg->cm_fields['R'] = strdoop(to);
2204                 recp = validate_recipients(to);
2205         }
2206         if (subject != NULL) {
2207                 msg->cm_fields['U'] = strdoop(subject);
2208         }
2209         msg->cm_fields['M'] = strdoop(text);
2210
2211         CtdlSubmitMsg(msg, recp, room);
2212         CtdlFreeMessage(msg);
2213         if (recp != NULL) phree(recp);
2214 }
2215
2216
2217
2218 /*
2219  * Back end function used by CtdlMakeMessage() and similar functions
2220  */
2221 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
2222                         size_t maxlen,          /* maximum message length */
2223                         char *exist,            /* if non-null, append to it;
2224                                                    exist is ALWAYS freed  */
2225                         int crlf                /* CRLF newlines instead of LF */
2226                         ) {
2227         char buf[SIZ];
2228         int linelen;
2229         size_t message_len = 0;
2230         size_t buffer_len = 0;
2231         char *ptr;
2232         char *m;
2233         int flushing = 0;
2234         int finished = 0;
2235
2236         if (exist == NULL) {
2237                 m = mallok(4096);
2238                 m[0] = 0;
2239                 buffer_len = 4096;
2240                 message_len = 0;
2241         }
2242         else {
2243                 message_len = strlen(exist);
2244                 buffer_len = message_len + 4096;
2245                 m = reallok(exist, buffer_len);
2246                 if (m == NULL) {
2247                         phree(exist);
2248                         return m;
2249                 }
2250         }
2251
2252         /* flush the input if we have nowhere to store it */
2253         if (m == NULL) {
2254                 flushing = 1;
2255         }
2256
2257         /* read in the lines of message text one by one */
2258         do {
2259                 if (client_gets(buf) < 1) finished = 1;
2260                 if (!strcmp(buf, terminator)) finished = 1;
2261                 if (crlf) {
2262                         strcat(buf, "\r\n");
2263                 }
2264                 else {
2265                         strcat(buf, "\n");
2266                 }
2267
2268                 if ( (!flushing) && (!finished) ) {
2269                         /* Measure the line */
2270                         linelen = strlen(buf);
2271         
2272                         /* augment the buffer if we have to */
2273                         if ((message_len + linelen) >= buffer_len) {
2274                                 ptr = reallok(m, (buffer_len * 2) );
2275                                 if (ptr == NULL) {      /* flush if can't allocate */
2276                                         flushing = 1;
2277                                 } else {
2278                                         buffer_len = (buffer_len * 2);
2279                                         m = ptr;
2280                                         lprintf(9, "buffer_len is now %ld\n", (long)buffer_len);
2281                                 }
2282                         }
2283         
2284                         /* Add the new line to the buffer.  NOTE: this loop must avoid
2285                         * using functions like strcat() and strlen() because they
2286                         * traverse the entire buffer upon every call, and doing that
2287                         * for a multi-megabyte message slows it down beyond usability.
2288                         */
2289                         strcpy(&m[message_len], buf);
2290                         message_len += linelen;
2291                 }
2292
2293                 /* if we've hit the max msg length, flush the rest */
2294                 if (message_len >= maxlen) flushing = 1;
2295
2296         } while (!finished);
2297         return(m);
2298 }
2299
2300
2301
2302
2303 /*
2304  * Build a binary message to be saved on disk.
2305  * (NOTE: if you supply 'preformatted_text', the buffer you give it
2306  * will become part of the message.  This means you are no longer
2307  * responsible for managing that memory -- it will be freed along with
2308  * the rest of the fields when CtdlFreeMessage() is called.)
2309  */
2310
2311 struct CtdlMessage *CtdlMakeMessage(
2312         struct ctdluser *author,        /* author's user structure */
2313         char *recipient,                /* NULL if it's not mail */
2314         char *room,                     /* room where it's going */
2315         int type,                       /* see MES_ types in header file */
2316         int format_type,                /* variformat, plain text, MIME... */
2317         char *fake_name,                /* who we're masquerading as */
2318         char *subject,                  /* Subject (optional) */
2319         char *preformatted_text         /* ...or NULL to read text from client */
2320 ) {
2321         char dest_node[SIZ];
2322         char buf[SIZ];
2323         struct CtdlMessage *msg;
2324
2325         msg = mallok(sizeof(struct CtdlMessage));
2326         memset(msg, 0, sizeof(struct CtdlMessage));
2327         msg->cm_magic = CTDLMESSAGE_MAGIC;
2328         msg->cm_anon_type = type;
2329         msg->cm_format_type = format_type;
2330
2331         /* Don't confuse the poor folks if it's not routed mail. */
2332         strcpy(dest_node, "");
2333
2334         striplt(recipient);
2335
2336         snprintf(buf, sizeof buf, "cit%ld", author->usernum);   /* Path */
2337         msg->cm_fields['P'] = strdoop(buf);
2338
2339         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
2340         msg->cm_fields['T'] = strdoop(buf);
2341
2342         if (fake_name[0])                                       /* author */
2343                 msg->cm_fields['A'] = strdoop(fake_name);
2344         else
2345                 msg->cm_fields['A'] = strdoop(author->fullname);
2346
2347         if (CC->room.QRflags & QR_MAILBOX) {            /* room */
2348                 msg->cm_fields['O'] = strdoop(&CC->room.QRname[11]);
2349         }
2350         else {
2351                 msg->cm_fields['O'] = strdoop(CC->room.QRname);
2352         }
2353
2354         msg->cm_fields['N'] = strdoop(NODENAME);                /* nodename */
2355         msg->cm_fields['H'] = strdoop(HUMANNODE);               /* hnodename */
2356
2357         if (recipient[0] != 0) {
2358                 msg->cm_fields['R'] = strdoop(recipient);
2359         }
2360         if (dest_node[0] != 0) {
2361                 msg->cm_fields['D'] = strdoop(dest_node);
2362         }
2363
2364         if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2365                 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2366         }
2367
2368         if (subject != NULL) {
2369                 striplt(subject);
2370                 if (strlen(subject) > 0) {
2371                         msg->cm_fields['U'] = strdoop(subject);
2372                 }
2373         }
2374
2375         if (preformatted_text != NULL) {
2376                 msg->cm_fields['M'] = preformatted_text;
2377         }
2378         else {
2379                 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2380                                         config.c_maxmsglen, NULL, 0);
2381         }
2382
2383         return(msg);
2384 }
2385
2386
2387 /*
2388  * Check to see whether we have permission to post a message in the current
2389  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2390  * returns 0 on success.
2391  */
2392 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2393
2394         if (!(CC->logged_in)) {
2395                 snprintf(errmsgbuf, n, "Not logged in.");
2396                 return (ERROR + NOT_LOGGED_IN);
2397         }
2398
2399         if ((CC->user.axlevel < 2)
2400             && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2401                 snprintf(errmsgbuf, n, "Need to be validated to enter "
2402                                 "(except in %s> to sysop)", MAILROOM);
2403                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2404         }
2405
2406         if ((CC->user.axlevel < 4)
2407            && (CC->room.QRflags & QR_NETWORK)) {
2408                 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2409                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2410         }
2411
2412         if ((CC->user.axlevel < 6)
2413            && (CC->room.QRflags & QR_READONLY)) {
2414                 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2415                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2416         }
2417
2418         strcpy(errmsgbuf, "Ok");
2419         return(0);
2420 }
2421
2422
2423 /*
2424  * Check to see if the specified user has Internet mail permission
2425  * (returns nonzero if permission is granted)
2426  */
2427 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2428
2429         /* Globally enabled? */
2430         if (config.c_restrict == 0) return(1);
2431
2432         /* User flagged ok? */
2433         if (who->flags & US_INTERNET) return(2);
2434
2435         /* Aide level access? */
2436         if (who->axlevel >= 6) return(3);
2437
2438         /* No mail for you! */
2439         return(0);
2440 }
2441
2442
2443
2444 /*
2445  * Validate recipients, count delivery types and errors, and handle aliasing
2446  * FIXME check for dupes!!!!!
2447  */
2448 struct recptypes *validate_recipients(char *recipients) {
2449         struct recptypes *ret;
2450         char this_recp[SIZ];
2451         char this_recp_cooked[SIZ];
2452         char append[SIZ];
2453         int num_recps;
2454         int i, j;
2455         int mailtype;
2456         int invalid;
2457         struct ctdluser tempUS;
2458         struct ctdlroom tempQR;
2459
2460         /* Initialize */
2461         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2462         if (ret == NULL) return(NULL);
2463         memset(ret, 0, sizeof(struct recptypes));
2464
2465         ret->num_local = 0;
2466         ret->num_internet = 0;
2467         ret->num_ignet = 0;
2468         ret->num_error = 0;
2469         ret->num_room = 0;
2470
2471         if (recipients == NULL) {
2472                 num_recps = 0;
2473         }
2474         else if (strlen(recipients) == 0) {
2475                 num_recps = 0;
2476         }
2477         else {
2478                 /* Change all valid separator characters to commas */
2479                 for (i=0; i<strlen(recipients); ++i) {
2480                         if ((recipients[i] == ';') || (recipients[i] == '|')) {
2481                                 recipients[i] = ',';
2482                         }
2483                 }
2484
2485                 /* Count 'em up */
2486                 num_recps = num_tokens(recipients, ',');
2487         }
2488
2489         if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2490                 extract_token(this_recp, recipients, i, ',');
2491                 striplt(this_recp);
2492                 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2493                 mailtype = alias(this_recp);
2494                 mailtype = alias(this_recp);
2495                 mailtype = alias(this_recp);
2496                 for (j=0; j<=strlen(this_recp); ++j) {
2497                         if (this_recp[j]=='_') {
2498                                 this_recp_cooked[j] = ' ';
2499                         }
2500                         else {
2501                                 this_recp_cooked[j] = this_recp[j];
2502                         }
2503                 }
2504                 invalid = 0;
2505                 switch(mailtype) {
2506                         case MES_LOCAL:
2507                                 if (!strcasecmp(this_recp, "sysop")) {
2508                                         ++ret->num_room;
2509                                         strcpy(this_recp, config.c_aideroom);
2510                                         if (strlen(ret->recp_room) > 0) {
2511                                                 strcat(ret->recp_room, "|");
2512                                         }
2513                                         strcat(ret->recp_room, this_recp);
2514                                 }
2515                                 else if (getuser(&tempUS, this_recp) == 0) {
2516                                         ++ret->num_local;
2517                                         strcpy(this_recp, tempUS.fullname);
2518                                         if (strlen(ret->recp_local) > 0) {
2519                                                 strcat(ret->recp_local, "|");
2520                                         }
2521                                         strcat(ret->recp_local, this_recp);
2522                                 }
2523                                 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2524                                         ++ret->num_local;
2525                                         strcpy(this_recp, tempUS.fullname);
2526                                         if (strlen(ret->recp_local) > 0) {
2527                                                 strcat(ret->recp_local, "|");
2528                                         }
2529                                         strcat(ret->recp_local, this_recp);
2530                                 }
2531                                 else if ( (!strncasecmp(this_recp, "room_", 5))
2532                                       && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2533                                         ++ret->num_room;
2534                                         if (strlen(ret->recp_room) > 0) {
2535                                                 strcat(ret->recp_room, "|");
2536                                         }
2537                                         strcat(ret->recp_room, &this_recp_cooked[5]);
2538                                 }
2539                                 else {
2540                                         ++ret->num_error;
2541                                         invalid = 1;
2542                                 }
2543                                 break;
2544                         case MES_INTERNET:
2545                                 /* Yes, you're reading this correctly: if the target
2546                                  * domain points back to the local system or an attached
2547                                  * Citadel directory, the address is invalid.  That's
2548                                  * because if the address were valid, we would have
2549                                  * already translated it to a local address by now.
2550                                  */
2551                                 if (IsDirectory(this_recp)) {
2552                                         ++ret->num_error;
2553                                         invalid = 1;
2554                                 }
2555                                 else {
2556                                         ++ret->num_internet;
2557                                         if (strlen(ret->recp_internet) > 0) {
2558                                                 strcat(ret->recp_internet, "|");
2559                                         }
2560                                         strcat(ret->recp_internet, this_recp);
2561                                 }
2562                                 break;
2563                         case MES_IGNET:
2564                                 ++ret->num_ignet;
2565                                 if (strlen(ret->recp_ignet) > 0) {
2566                                         strcat(ret->recp_ignet, "|");
2567                                 }
2568                                 strcat(ret->recp_ignet, this_recp);
2569                                 break;
2570                         case MES_ERROR:
2571                                 ++ret->num_error;
2572                                 invalid = 1;
2573                                 break;
2574                 }
2575                 if (invalid) {
2576                         if (strlen(ret->errormsg) == 0) {
2577                                 snprintf(append, sizeof append,
2578                                          "Invalid recipient: %s",
2579                                          this_recp);
2580                         }
2581                         else {
2582                                 snprintf(append, sizeof append,
2583                                          ", %s", this_recp);
2584                         }
2585                         if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2586                                 strcat(ret->errormsg, append);
2587                         }
2588                 }
2589                 else {
2590                         if (strlen(ret->display_recp) == 0) {
2591                                 strcpy(append, this_recp);
2592                         }
2593                         else {
2594                                 snprintf(append, sizeof append, ", %s",
2595                                          this_recp);
2596                         }
2597                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2598                                 strcat(ret->display_recp, append);
2599                         }
2600                 }
2601         }
2602
2603         if ((ret->num_local + ret->num_internet + ret->num_ignet +
2604            ret->num_room + ret->num_error) == 0) {
2605                 ++ret->num_error;
2606                 strcpy(ret->errormsg, "No recipients specified.");
2607         }
2608
2609         lprintf(9, "validate_recipients()\n");
2610         lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2611         lprintf(9, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
2612         lprintf(9, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2613         lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2614         lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2615
2616         return(ret);
2617 }
2618
2619
2620
2621 /*
2622  * message entry  -  mode 0 (normal)
2623  */
2624 void cmd_ent0(char *entargs)
2625 {
2626         int post = 0;
2627         char recp[SIZ];
2628         char masquerade_as[SIZ];
2629         int anon_flag = 0;
2630         int format_type = 0;
2631         char newusername[SIZ];
2632         struct CtdlMessage *msg;
2633         int anonymous = 0;
2634         char errmsg[SIZ];
2635         int err = 0;
2636         struct recptypes *valid = NULL;
2637         char subject[SIZ];
2638
2639         post = extract_int(entargs, 0);
2640         extract(recp, entargs, 1);
2641         anon_flag = extract_int(entargs, 2);
2642         format_type = extract_int(entargs, 3);
2643         extract(subject, entargs, 4);
2644
2645         /* first check to make sure the request is valid. */
2646
2647         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2648         if (err) {
2649                 cprintf("%d %s\n", err, errmsg);
2650                 return;
2651         }
2652
2653         /* Check some other permission type things. */
2654
2655         if (post == 2) {
2656                 if (CC->user.axlevel < 6) {
2657                         cprintf("%d You don't have permission to masquerade.\n",
2658                                 ERROR + HIGHER_ACCESS_REQUIRED);
2659                         return;
2660                 }
2661                 extract(newusername, entargs, 5);
2662                 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2663                 safestrncpy(CC->fake_postname, newusername,
2664                         sizeof(CC->fake_postname) );
2665                 cprintf("%d ok\n", CIT_OK);
2666                 return;
2667         }
2668         CC->cs_flags |= CS_POSTING;
2669
2670         /* In the Mail> room we have to behave a little differently --
2671          * make sure the user has specified at least one recipient.  Then
2672          * validate the recipient(s).
2673          */
2674         if ( (CC->room.QRflags & QR_MAILBOX)
2675            && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2676
2677                 if (CC->user.axlevel < 2) {
2678                         strcpy(recp, "sysop");
2679                 }
2680
2681                 valid = validate_recipients(recp);
2682                 if (valid->num_error > 0) {
2683                         cprintf("%d %s\n",
2684                                 ERROR + NO_SUCH_USER, valid->errormsg);
2685                         phree(valid);
2686                         return;
2687                 }
2688                 if (valid->num_internet > 0) {
2689                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2690                                 cprintf("%d You do not have permission "
2691                                         "to send Internet mail.\n",
2692                                         ERROR + HIGHER_ACCESS_REQUIRED);
2693                                 phree(valid);
2694                                 return;
2695                         }
2696                 }
2697
2698                 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2699                    && (CC->user.axlevel < 4) ) {
2700                         cprintf("%d Higher access required for network mail.\n",
2701                                 ERROR + HIGHER_ACCESS_REQUIRED);
2702                         phree(valid);
2703                         return;
2704                 }
2705         
2706                 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2707                     && ((CC->user.flags & US_INTERNET) == 0)
2708                     && (!CC->internal_pgm)) {
2709                         cprintf("%d You don't have access to Internet mail.\n",
2710                                 ERROR + HIGHER_ACCESS_REQUIRED);
2711                         phree(valid);
2712                         return;
2713                 }
2714
2715         }
2716
2717         /* Is this a room which has anonymous-only or anonymous-option? */
2718         anonymous = MES_NORMAL;
2719         if (CC->room.QRflags & QR_ANONONLY) {
2720                 anonymous = MES_ANONONLY;
2721         }
2722         if (CC->room.QRflags & QR_ANONOPT) {
2723                 if (anon_flag == 1) {   /* only if the user requested it */
2724                         anonymous = MES_ANONOPT;
2725                 }
2726         }
2727
2728         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2729                 recp[0] = 0;
2730         }
2731
2732         /* If we're only checking the validity of the request, return
2733          * success without creating the message.
2734          */
2735         if (post == 0) {
2736                 cprintf("%d %s\n", CIT_OK,
2737                         ((valid != NULL) ? valid->display_recp : "") );
2738                 phree(valid);
2739                 return;
2740         }
2741
2742         /* Handle author masquerading */
2743         if (CC->fake_postname[0]) {
2744                 strcpy(masquerade_as, CC->fake_postname);
2745         }
2746         else if (CC->fake_username[0]) {
2747                 strcpy(masquerade_as, CC->fake_username);
2748         }
2749         else {
2750                 strcpy(masquerade_as, "");
2751         }
2752
2753         /* Read in the message from the client. */
2754         cprintf("%d send message\n", SEND_LISTING);
2755         msg = CtdlMakeMessage(&CC->user, recp,
2756                 CC->room.QRname, anonymous, format_type,
2757                 masquerade_as, subject, NULL);
2758
2759         if (msg != NULL) {
2760                 CtdlSubmitMsg(msg, valid, "");
2761                 CtdlFreeMessage(msg);
2762         }
2763         CC->fake_postname[0] = '\0';
2764         phree(valid);
2765         return;
2766 }
2767
2768
2769
2770 /*
2771  * API function to delete messages which match a set of criteria
2772  * (returns the actual number of messages deleted)
2773  */
2774 int CtdlDeleteMessages(char *room_name,         /* which room */
2775                        long dmsgnum,            /* or "0" for any */
2776                        char *content_type       /* or "" for any */
2777 )
2778 {
2779
2780         struct ctdlroom qrbuf;
2781         struct cdbdata *cdbfr;
2782         long *msglist = NULL;
2783         long *dellist = NULL;
2784         int num_msgs = 0;
2785         int i;
2786         int num_deleted = 0;
2787         int delete_this;
2788         struct MetaData smi;
2789
2790         lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2791                 room_name, dmsgnum, content_type);
2792
2793         /* get room record, obtaining a lock... */
2794         if (lgetroom(&qrbuf, room_name) != 0) {
2795                 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2796                         room_name);
2797                 return (0);     /* room not found */
2798         }
2799         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2800
2801         if (cdbfr != NULL) {
2802                 msglist = mallok(cdbfr->len);
2803                 dellist = mallok(cdbfr->len);
2804                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2805                 num_msgs = cdbfr->len / sizeof(long);
2806                 cdb_free(cdbfr);
2807         }
2808         if (num_msgs > 0) {
2809                 for (i = 0; i < num_msgs; ++i) {
2810                         delete_this = 0x00;
2811
2812                         /* Set/clear a bit for each criterion */
2813
2814                         if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2815                                 delete_this |= 0x01;
2816                         }
2817                         if (strlen(content_type) == 0) {
2818                                 delete_this |= 0x02;
2819                         } else {
2820                                 GetMetaData(&smi, msglist[i]);
2821                                 if (!strcasecmp(smi.meta_content_type,
2822                                                 content_type)) {
2823                                         delete_this |= 0x02;
2824                                 }
2825                         }
2826
2827                         /* Delete message only if all bits are set */
2828                         if (delete_this == 0x03) {
2829                                 dellist[num_deleted++] = msglist[i];
2830                                 msglist[i] = 0L;
2831                         }
2832                 }
2833
2834                 num_msgs = sort_msglist(msglist, num_msgs);
2835                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2836                           msglist, (num_msgs * sizeof(long)));
2837
2838                 qrbuf.QRhighest = msglist[num_msgs - 1];
2839         }
2840         lputroom(&qrbuf);
2841
2842         /* Go through the messages we pulled out of the index, and decrement
2843          * their reference counts by 1.  If this is the only room the message
2844          * was in, the reference count will reach zero and the message will
2845          * automatically be deleted from the database.  We do this in a
2846          * separate pass because there might be plug-in hooks getting called,
2847          * and we don't want that happening during an S_ROOMS critical
2848          * section.
2849          */
2850         if (num_deleted) for (i=0; i<num_deleted; ++i) {
2851                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2852                 AdjRefCount(dellist[i], -1);
2853         }
2854
2855         /* Now free the memory we used, and go away. */
2856         if (msglist != NULL) phree(msglist);
2857         if (dellist != NULL) phree(dellist);
2858         lprintf(9, "%d message(s) deleted.\n", num_deleted);
2859         return (num_deleted);
2860 }
2861
2862
2863
2864 /*
2865  * Check whether the current user has permission to delete messages from
2866  * the current room (returns 1 for yes, 0 for no)
2867  */
2868 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2869         getuser(&CC->user, CC->curr_user);
2870         if ((CC->user.axlevel < 6)
2871             && (CC->user.usernum != CC->room.QRroomaide)
2872             && ((CC->room.QRflags & QR_MAILBOX) == 0)
2873             && (!(CC->internal_pgm))) {
2874                 return(0);
2875         }
2876         return(1);
2877 }
2878
2879
2880
2881 /*
2882  * Delete message from current room
2883  */
2884 void cmd_dele(char *delstr)
2885 {
2886         long delnum;
2887         int num_deleted;
2888
2889         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2890                 cprintf("%d Higher access required.\n",
2891                         ERROR + HIGHER_ACCESS_REQUIRED);
2892                 return;
2893         }
2894         delnum = extract_long(delstr, 0);
2895
2896         num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2897
2898         if (num_deleted) {
2899                 cprintf("%d %d message%s deleted.\n", CIT_OK,
2900                         num_deleted, ((num_deleted != 1) ? "s" : ""));
2901         } else {
2902                 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
2903         }
2904 }
2905
2906
2907 /*
2908  * Back end API function for moves and deletes
2909  */
2910 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2911         int err;
2912
2913         err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2914                 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2915         if (err != 0) return(err);
2916
2917         return(0);
2918 }
2919
2920
2921
2922 /*
2923  * move or copy a message to another room
2924  */
2925 void cmd_move(char *args)
2926 {
2927         long num;
2928         char targ[SIZ];
2929         struct ctdlroom qtemp;
2930         int err;
2931         int is_copy = 0;
2932
2933         num = extract_long(args, 0);
2934         extract(targ, args, 1);
2935         targ[ROOMNAMELEN - 1] = 0;
2936         is_copy = extract_int(args, 2);
2937
2938         if (getroom(&qtemp, targ) != 0) {
2939                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
2940                 return;
2941         }
2942
2943         getuser(&CC->user, CC->curr_user);
2944         /* Aides can move/copy */
2945         if ((CC->user.axlevel < 6)
2946             /* Roomaides can move/copy */
2947             && (CC->user.usernum != CC->room.QRroomaide)
2948             /* Permit move/copy to/from personal rooms */
2949             && (!((CC->room.QRflags & QR_MAILBOX)
2950                             && (qtemp.QRflags & QR_MAILBOX)))
2951             /* Permit only copy from public to personal room */
2952             && (!(is_copy && !(CC->room.QRflags & QR_MAILBOX)
2953                             && (qtemp.QRflags & QR_MAILBOX)))) {
2954                 cprintf("%d Higher access required.\n",
2955                         ERROR + HIGHER_ACCESS_REQUIRED);
2956                 return;
2957         }
2958
2959         err = CtdlCopyMsgToRoom(num, targ);
2960         if (err != 0) {
2961                 cprintf("%d Cannot store message in %s: error %d\n",
2962                         err, targ, err);
2963                 return;
2964         }
2965
2966         /* Now delete the message from the source room,
2967          * if this is a 'move' rather than a 'copy' operation.
2968          */
2969         if (is_copy == 0) {
2970                 CtdlDeleteMessages(CC->room.QRname, num, "");
2971         }
2972
2973         cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
2974 }
2975
2976
2977
2978 /*
2979  * GetMetaData()  -  Get the supplementary record for a message
2980  */
2981 void GetMetaData(struct MetaData *smibuf, long msgnum)
2982 {
2983
2984         struct cdbdata *cdbsmi;
2985         long TheIndex;
2986
2987         memset(smibuf, 0, sizeof(struct MetaData));
2988         smibuf->meta_msgnum = msgnum;
2989         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
2990
2991         /* Use the negative of the message number for its supp record index */
2992         TheIndex = (0L - msgnum);
2993
2994         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2995         if (cdbsmi == NULL) {
2996                 return;         /* record not found; go with defaults */
2997         }
2998         memcpy(smibuf, cdbsmi->ptr,
2999                ((cdbsmi->len > sizeof(struct MetaData)) ?
3000                 sizeof(struct MetaData) : cdbsmi->len));
3001         cdb_free(cdbsmi);
3002         return;
3003 }
3004
3005
3006 /*
3007  * PutMetaData()  -  (re)write supplementary record for a message
3008  */
3009 void PutMetaData(struct MetaData *smibuf)
3010 {
3011         long TheIndex;
3012
3013         /* Use the negative of the message number for the metadata db index */
3014         TheIndex = (0L - smibuf->meta_msgnum);
3015
3016         lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
3017                 smibuf->meta_msgnum, smibuf->meta_refcount);
3018
3019         cdb_store(CDB_MSGMAIN,
3020                   &TheIndex, sizeof(long),
3021                   smibuf, sizeof(struct MetaData));
3022
3023 }
3024
3025 /*
3026  * AdjRefCount  -  change the reference count for a message;
3027  *                 delete the message if it reaches zero
3028  */
3029 void AdjRefCount(long msgnum, int incr)
3030 {
3031
3032         struct MetaData smi;
3033         long delnum;
3034
3035         /* This is a *tight* critical section; please keep it that way, as
3036          * it may get called while nested in other critical sections.  
3037          * Complicating this any further will surely cause deadlock!
3038          */
3039         begin_critical_section(S_SUPPMSGMAIN);
3040         GetMetaData(&smi, msgnum);
3041         lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
3042                 msgnum, smi.meta_refcount);
3043         smi.meta_refcount += incr;
3044         PutMetaData(&smi);
3045         end_critical_section(S_SUPPMSGMAIN);
3046         lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
3047                 msgnum, smi.meta_refcount);
3048
3049         /* If the reference count is now zero, delete the message
3050          * (and its supplementary record as well).
3051          */
3052         if (smi.meta_refcount == 0) {
3053                 lprintf(9, "Deleting message <%ld>\n", msgnum);
3054                 delnum = msgnum;
3055                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3056
3057                 /* We have to delete the metadata record too! */
3058                 delnum = (0L - msgnum);
3059                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3060         }
3061 }
3062
3063 /*
3064  * Write a generic object to this room
3065  *
3066  * Note: this could be much more efficient.  Right now we use two temporary
3067  * files, and still pull the message into memory as with all others.
3068  */
3069 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
3070                         char *content_type,     /* MIME type of this object */
3071                         char *tempfilename,     /* Where to fetch it from */
3072                         struct ctdluser *is_mailbox,    /* Mailbox room? */
3073                         int is_binary,          /* Is encoding necessary? */
3074                         int is_unique,          /* Del others of this type? */
3075                         unsigned int flags      /* Internal save flags */
3076                         )
3077 {
3078
3079         FILE *fp;
3080         struct ctdlroom qrbuf;
3081         char roomname[ROOMNAMELEN];
3082         struct CtdlMessage *msg;
3083
3084         char *raw_message = NULL;
3085         char *encoded_message = NULL;
3086         off_t raw_length = 0;
3087
3088         if (is_mailbox != NULL)
3089                 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3090         else
3091                 safestrncpy(roomname, req_room, sizeof(roomname));
3092         lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3093
3094
3095         fp = fopen(tempfilename, "rb");
3096         if (fp == NULL) {
3097                 lprintf(5, "Cannot open %s: %s\n",
3098                         tempfilename, strerror(errno));
3099                 return;
3100         }
3101         fseek(fp, 0L, SEEK_END);
3102         raw_length = ftell(fp);
3103         rewind(fp);
3104         lprintf(9, "Raw length is %ld\n", (long)raw_length);
3105
3106         raw_message = mallok((size_t)raw_length + 2);
3107         fread(raw_message, (size_t)raw_length, 1, fp);
3108         fclose(fp);
3109
3110         if (is_binary) {
3111                 encoded_message = mallok((size_t)
3112                         (((raw_length * 134) / 100) + 4096 ) );
3113         }
3114         else {
3115                 encoded_message = mallok((size_t)(raw_length + 4096));
3116         }
3117
3118         sprintf(encoded_message, "Content-type: %s\n", content_type);
3119
3120         if (is_binary) {
3121                 sprintf(&encoded_message[strlen(encoded_message)],
3122                         "Content-transfer-encoding: base64\n\n"
3123                 );
3124         }
3125         else {
3126                 sprintf(&encoded_message[strlen(encoded_message)],
3127                         "Content-transfer-encoding: 7bit\n\n"
3128                 );
3129         }
3130
3131         if (is_binary) {
3132                 CtdlEncodeBase64(
3133                         &encoded_message[strlen(encoded_message)],
3134                         raw_message,
3135                         (int)raw_length
3136                 );
3137         }
3138         else {
3139                 raw_message[raw_length] = 0;
3140                 memcpy(
3141                         &encoded_message[strlen(encoded_message)],
3142                         raw_message,
3143                         (int)(raw_length+1)
3144                 );
3145         }
3146
3147         phree(raw_message);
3148
3149         lprintf(9, "Allocating\n");
3150         msg = mallok(sizeof(struct CtdlMessage));
3151         memset(msg, 0, sizeof(struct CtdlMessage));
3152         msg->cm_magic = CTDLMESSAGE_MAGIC;
3153         msg->cm_anon_type = MES_NORMAL;
3154         msg->cm_format_type = 4;
3155         msg->cm_fields['A'] = strdoop(CC->user.fullname);
3156         msg->cm_fields['O'] = strdoop(req_room);
3157         msg->cm_fields['N'] = strdoop(config.c_nodename);
3158         msg->cm_fields['H'] = strdoop(config.c_humannode);
3159         msg->cm_flags = flags;
3160         
3161         msg->cm_fields['M'] = encoded_message;
3162
3163         /* Create the requested room if we have to. */
3164         if (getroom(&qrbuf, roomname) != 0) {
3165                 create_room(roomname, 
3166                         ( (is_mailbox != NULL) ? 5 : 3 ),
3167                         "", 0, 1, 0);
3168         }
3169         /* If the caller specified this object as unique, delete all
3170          * other objects of this type that are currently in the room.
3171          */
3172         if (is_unique) {
3173                 lprintf(9, "Deleted %d other msgs of this type\n",
3174                         CtdlDeleteMessages(roomname, 0L, content_type));
3175         }
3176         /* Now write the data */
3177         CtdlSubmitMsg(msg, NULL, roomname);
3178         CtdlFreeMessage(msg);
3179 }
3180
3181
3182
3183
3184
3185
3186 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3187         config_msgnum = msgnum;
3188 }
3189
3190
3191 char *CtdlGetSysConfig(char *sysconfname) {
3192         char hold_rm[ROOMNAMELEN];
3193         long msgnum;
3194         char *conf;
3195         struct CtdlMessage *msg;
3196         char buf[SIZ];
3197         
3198         strcpy(hold_rm, CC->room.QRname);
3199         if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3200                 getroom(&CC->room, hold_rm);
3201                 return NULL;
3202         }
3203
3204
3205         /* We want the last (and probably only) config in this room */
3206         begin_critical_section(S_CONFIG);
3207         config_msgnum = (-1L);
3208         CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3209                 CtdlGetSysConfigBackend, NULL);
3210         msgnum = config_msgnum;
3211         end_critical_section(S_CONFIG);
3212
3213         if (msgnum < 0L) {
3214                 conf = NULL;
3215         }
3216         else {
3217                 msg = CtdlFetchMessage(msgnum);
3218                 if (msg != NULL) {
3219                         conf = strdoop(msg->cm_fields['M']);
3220                         CtdlFreeMessage(msg);
3221                 }
3222                 else {
3223                         conf = NULL;
3224                 }
3225         }
3226
3227         getroom(&CC->room, hold_rm);
3228
3229         if (conf != NULL) do {
3230                 extract_token(buf, conf, 0, '\n');
3231                 strcpy(conf, &conf[strlen(buf)+1]);
3232         } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3233
3234         return(conf);
3235 }
3236
3237 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3238         char temp[PATH_MAX];
3239         FILE *fp;
3240
3241         strcpy(temp, tmpnam(NULL));
3242
3243         fp = fopen(temp, "w");
3244         if (fp == NULL) return;
3245         fprintf(fp, "%s", sysconfdata);
3246         fclose(fp);
3247
3248         /* this handy API function does all the work for us */
3249         CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3250         unlink(temp);
3251 }
3252
3253
3254 /*
3255  * Determine whether a given Internet address belongs to the current user
3256  */
3257 int CtdlIsMe(char *addr) {
3258         struct recptypes *recp;
3259         int i;
3260
3261         recp = validate_recipients(addr);
3262         if (recp == NULL) return(0);
3263
3264         if (recp->num_local == 0) {
3265                 phree(recp);
3266                 return(0);
3267         }
3268
3269         for (i=0; i<recp->num_local; ++i) {
3270                 extract(addr, recp->recp_local, i);
3271                 if (!strcasecmp(addr, CC->user.fullname)) {
3272                         phree(recp);
3273                         return(1);
3274                 }
3275         }
3276
3277         phree(recp);
3278         return(0);
3279 }
3280
3281
3282 /*
3283  * Citadel protocol command to do the same
3284  */
3285 void cmd_isme(char *argbuf) {
3286         char addr[SIZ];
3287
3288         if (CtdlAccessCheck(ac_logged_in)) return;
3289         extract(addr, argbuf, 0);
3290
3291         if (CtdlIsMe(addr)) {
3292                 cprintf("%d %s\n", CIT_OK, addr);
3293         }
3294         else {
3295                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
3296         }
3297
3298 }