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