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