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