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