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