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