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