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