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