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