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