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