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