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