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