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