]> code.citadel.org Git - citadel.git/blob - citadel/msgbase.c
* quick fix
[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>, mailtype is %d\n", recipient, mailtype);
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         if (strlen(recipient) > 0) {
1781                 strcpy(actual_rm, SENTITEMS);
1782         }
1783
1784         /* If the user is a twit, move to the twit room for posting */
1785         lprintf(9, "Handling twit stuff\n");
1786         if (TWITDETECT) {
1787                 if (CC->usersupp.axlevel == 2) {
1788                         strcpy(hold_rm, actual_rm);
1789                         strcpy(actual_rm, config.c_twitroom);
1790                 }
1791         }
1792
1793         /* ...or if this message is destined for Aide> then go there. */
1794         if (strlen(force_room) > 0) {
1795                 strcpy(actual_rm, force_room);
1796         }
1797
1798         lprintf(9, "Possibly relocating\n");
1799         if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1800                 getroom(&CC->quickroom, actual_rm);
1801         }
1802
1803         /*
1804          * If this message has no O (room) field, generate one.
1805          */
1806         if (msg->cm_fields['O'] == NULL) {
1807                 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1808         }
1809
1810         /* Perform "before save" hooks (aborting if any return nonzero) */
1811         lprintf(9, "Performing before-save hooks\n");
1812         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1813
1814         /* If this message has an Extended ID, perform replication checks */
1815         lprintf(9, "Performing replication checks\n");
1816         if (ReplicationChecks(msg) > 0) return(-1);
1817
1818         /* Network mail - send a copy to the network program. */
1819         if ((strlen(recipient) > 0) && (mailtype == MES_IGNET)) {
1820                 lprintf(9, "Sending network spool\n");
1821                 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1822                         (long) getpid(), CC->cs_pid, ++seqnum);
1823                 lprintf(9, "Saving a copy to %s\n", aaa);
1824                 network_fp = fopen(aaa, "ab+");
1825                 if (network_fp == NULL)
1826                         lprintf(2, "ERROR: %s\n", strerror(errno));
1827         }
1828
1829         /* Save it to disk */
1830         lprintf(9, "Saving to disk\n");
1831         newmsgid = send_message(msg, network_fp);
1832         if (network_fp != NULL) {
1833                 fclose(network_fp);
1834                 /* FIXME start a network run here */
1835         }
1836
1837         if (newmsgid <= 0L) return(-1);
1838
1839         /* Write a supplemental message info record.  This doesn't have to
1840          * be a critical section because nobody else knows about this message
1841          * yet.
1842          */
1843         lprintf(9, "Creating MetaData record\n");
1844         memset(&smi, 0, sizeof(struct MetaData));
1845         smi.meta_msgnum = newmsgid;
1846         smi.meta_refcount = 0;
1847         safestrncpy(smi.meta_content_type, content_type, 64);
1848         PutMetaData(&smi);
1849
1850         /* Now figure out where to store the pointers */
1851         lprintf(9, "Storing pointers\n");
1852
1853         /* If this is being done by the networker delivering a private
1854          * message, we want to BYPASS saving the sender's copy (because there
1855          * is no local sender; it would otherwise go to the Trashcan).
1856          */
1857         if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1858                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1859                         lprintf(3, "ERROR saving message pointer!\n");
1860                         CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1861                 }
1862         }
1863
1864         /* For internet mail, drop a copy in the outbound queue room */
1865         if (mailtype == MES_INTERNET) {
1866                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1867         }
1868
1869         /* Bump this user's messages posted counter. */
1870         lprintf(9, "Updating user\n");
1871         lgetuser(&CC->usersupp, CC->curr_user);
1872         CC->usersupp.posted = CC->usersupp.posted + 1;
1873         lputuser(&CC->usersupp);
1874
1875         /* If this is private, local mail, make a copy in the
1876          * recipient's mailbox and bump the reference count.
1877          */
1878         if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1879                 if (getuser(&userbuf, recipient) == 0) {
1880                         lprintf(9, "Delivering private mail\n");
1881                         MailboxName(actual_rm, &userbuf, MAILROOM);
1882                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1883                 }
1884                 else {
1885                         lprintf(9, "No user <%s>, saving in %s> instead\n",
1886                                 recipient, AIDEROOM);
1887                         CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1888                 }
1889         }
1890
1891         /* Perform "after save" hooks */
1892         lprintf(9, "Performing after-save hooks\n");
1893         PerformMessageHooks(msg, EVT_AFTERSAVE);
1894
1895         /* */
1896         lprintf(9, "Returning to original room\n");
1897         if (strcasecmp(hold_rm, CC->quickroom.QRname))
1898                 getroom(&CC->quickroom, hold_rm);
1899
1900         /* For internet mail, generate delivery instructions 
1901          * (Yes, this is recursive!   Deal with it!)
1902          */
1903         if (mailtype == MES_INTERNET) {
1904                 lprintf(9, "Generating delivery instructions\n");
1905                 instr = mallok(2048);
1906                 sprintf(instr,
1907                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1908                         "bounceto|%s@%s\n"
1909                         "remote|%s|0||\n",
1910                         SPOOLMIME, newmsgid, (long)time(NULL),
1911                         msg->cm_fields['A'], msg->cm_fields['N'],
1912                         recipient );
1913
1914                 imsg = mallok(sizeof(struct CtdlMessage));
1915                 memset(imsg, 0, sizeof(struct CtdlMessage));
1916                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1917                 imsg->cm_anon_type = MES_NORMAL;
1918                 imsg->cm_format_type = FMT_RFC822;
1919                 imsg->cm_fields['A'] = strdoop("Citadel");
1920                 imsg->cm_fields['M'] = instr;
1921                 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1922                 CtdlFreeMessage(imsg);
1923         }
1924
1925         return(newmsgid);
1926 }
1927
1928
1929
1930 /*
1931  * Convenience function for generating small administrative messages.
1932  */
1933 void quickie_message(char *from, char *to, char *room, char *text)
1934 {
1935         struct CtdlMessage *msg;
1936
1937         msg = mallok(sizeof(struct CtdlMessage));
1938         memset(msg, 0, sizeof(struct CtdlMessage));
1939         msg->cm_magic = CTDLMESSAGE_MAGIC;
1940         msg->cm_anon_type = MES_NORMAL;
1941         msg->cm_format_type = 0;
1942         msg->cm_fields['A'] = strdoop(from);
1943         msg->cm_fields['O'] = strdoop(room);
1944         msg->cm_fields['N'] = strdoop(NODENAME);
1945         if (to != NULL)
1946                 msg->cm_fields['R'] = strdoop(to);
1947         msg->cm_fields['M'] = strdoop(text);
1948
1949         CtdlSaveMsg(msg, "", room, MES_LOCAL);
1950         CtdlFreeMessage(msg);
1951         syslog(LOG_NOTICE, text);
1952 }
1953
1954
1955
1956 /*
1957  * Back end function used by make_message() and similar functions
1958  */
1959 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
1960                         size_t maxlen,          /* maximum message length */
1961                         char *exist             /* if non-null, append to it;
1962                                                    exist is ALWAYS freed  */
1963                         ) {
1964         char buf[SIZ];
1965         int linelen;
1966         size_t message_len = 0;
1967         size_t buffer_len = 0;
1968         char *ptr;
1969         char *m;
1970
1971         if (exist == NULL) {
1972                 m = mallok(4096);
1973         }
1974         else {
1975                 m = reallok(exist, strlen(exist) + 4096);
1976                 if (m == NULL) phree(exist);
1977         }
1978         if (m == NULL) {
1979                 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1980                 return(NULL);
1981         } else {
1982                 buffer_len = 4096;
1983                 m[0] = 0;
1984                 message_len = 0;
1985         }
1986         /* read in the lines of message text one by one */
1987         while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1988
1989                 /* strip trailing newline type stuff */
1990                 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1991                 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1992
1993                 linelen = strlen(buf);
1994
1995                 /* augment the buffer if we have to */
1996                 if ((message_len + linelen + 2) > buffer_len) {
1997                         lprintf(9, "realloking\n");
1998                         ptr = reallok(m, (buffer_len * 2) );
1999                         if (ptr == NULL) {      /* flush if can't allocate */
2000                                 while ( (client_gets(buf)>0) &&
2001                                         strcmp(buf, terminator)) ;;
2002                                 return(m);
2003                         } else {
2004                                 buffer_len = (buffer_len * 2);
2005                                 m = ptr;
2006                                 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2007                         }
2008                 }
2009
2010                 /* Add the new line to the buffer.  We avoid using strcat()
2011                  * because that would involve traversing the entire message
2012                  * after each line, and this function needs to run fast.
2013                  */
2014                 strcpy(&m[message_len], buf);
2015                 m[message_len + linelen] = '\n';
2016                 m[message_len + linelen + 1] = 0;
2017                 message_len = message_len + linelen + 1;
2018
2019                 /* if we've hit the max msg length, flush the rest */
2020                 if (message_len >= maxlen) {
2021                         while ( (client_gets(buf)>0)
2022                                 && strcmp(buf, terminator)) ;;
2023                         return(m);
2024                 }
2025         }
2026         return(m);
2027 }
2028
2029
2030
2031
2032 /*
2033  * Build a binary message to be saved on disk.
2034  */
2035
2036 static struct CtdlMessage *make_message(
2037         struct usersupp *author,        /* author's usersupp structure */
2038         char *recipient,                /* NULL if it's not mail */
2039         char *room,                     /* room where it's going */
2040         int type,                       /* see MES_ types in header file */
2041         int net_type,                   /* see MES_ types in header file */
2042         int format_type,                /* local or remote (see citadel.h) */
2043         char *fake_name)                /* who we're masquerading as */
2044 {
2045
2046         int a;
2047         char dest_node[32];
2048         char buf[SIZ];
2049         struct CtdlMessage *msg;
2050
2051         msg = mallok(sizeof(struct CtdlMessage));
2052         memset(msg, 0, sizeof(struct CtdlMessage));
2053         msg->cm_magic = CTDLMESSAGE_MAGIC;
2054         msg->cm_anon_type = type;
2055         msg->cm_format_type = format_type;
2056
2057         /* Don't confuse the poor folks if it's not routed mail. */
2058         strcpy(dest_node, "");
2059
2060         /* If net_type is MES_IGNET, split out the destination node. */
2061         if (net_type == MES_IGNET) {
2062                 strcpy(dest_node, NODENAME);
2063                 for (a = 0; a < strlen(recipient); ++a) {
2064                         if (recipient[a] == '@') {
2065                                 recipient[a] = 0;
2066                                 strcpy(dest_node, &recipient[a + 1]);
2067                         }
2068                 }
2069         }
2070
2071         /* if net_type is MES_INTERNET, set the dest node to 'internet' */
2072         if (net_type == MES_INTERNET) {
2073                 strcpy(dest_node, "internet");
2074         }
2075
2076         while (isspace(recipient[strlen(recipient) - 1]))
2077                 recipient[strlen(recipient) - 1] = 0;
2078
2079         sprintf(buf, "cit%ld", author->usernum);                /* Path */
2080         msg->cm_fields['P'] = strdoop(buf);
2081
2082         sprintf(buf, "%ld", (long)time(NULL));                  /* timestamp */
2083         msg->cm_fields['T'] = strdoop(buf);
2084
2085         if (fake_name[0])                                       /* author */
2086                 msg->cm_fields['A'] = strdoop(fake_name);
2087         else
2088                 msg->cm_fields['A'] = strdoop(author->fullname);
2089
2090         if (CC->quickroom.QRflags & QR_MAILBOX)                 /* room */
2091                 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2092         else
2093                 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2094
2095         msg->cm_fields['N'] = strdoop(NODENAME);                /* nodename */
2096         msg->cm_fields['H'] = strdoop(HUMANNODE);               /* hnodename */
2097
2098         if (recipient[0] != 0)
2099                 msg->cm_fields['R'] = strdoop(recipient);
2100         if (dest_node[0] != 0)
2101                 msg->cm_fields['D'] = strdoop(dest_node);
2102
2103
2104         msg->cm_fields['M'] = CtdlReadMessageBody("000",
2105                                                 config.c_maxmsglen, NULL);
2106
2107
2108         return(msg);
2109 }
2110
2111
2112 /*
2113  * Check to see whether we have permission to post a message in the current
2114  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2115  * returns 0 on success.
2116  */
2117 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) {
2118
2119         if (!(CC->logged_in)) {
2120                 sprintf(errmsgbuf, "Not logged in.");
2121                 return (ERROR + NOT_LOGGED_IN);
2122         }
2123
2124         if ((CC->usersupp.axlevel < 2)
2125             && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2126                 sprintf(errmsgbuf, "Need to be validated to enter "
2127                                 "(except in %s> to sysop)", MAILROOM);
2128                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2129         }
2130
2131         if ((CC->usersupp.axlevel < 4)
2132            && (CC->quickroom.QRflags & QR_NETWORK)) {
2133                 sprintf(errmsgbuf, "Need net privileges to enter here.");
2134                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2135         }
2136
2137         if ((CC->usersupp.axlevel < 6)
2138            && (CC->quickroom.QRflags & QR_READONLY)) {
2139                 sprintf(errmsgbuf, "Sorry, this is a read-only room.");
2140                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2141         }
2142
2143         strcpy(errmsgbuf, "Ok");
2144         return(0);
2145 }
2146
2147
2148 #ifdef NOT_YET_FINISHED /* FIXME */
2149 /*
2150  * Validate recipients, count delivery types and errors, and handle aliasing
2151  */
2152 struct recptypes *validate_recipients(char *recipients) {
2153         struct recptypes *ret;
2154         char this_recp[SIZ];
2155         int num_recps;
2156         int i;
2157         int mailtype;
2158         struct usersupp tempUS;
2159
2160         /* Initialize */
2161         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2162         if (ret == NULL) return(NULL);
2163         memset(ret, 0, sizeof(struct recptypes));
2164
2165         ret->num_local = 0;
2166         ret->num_internet = 0;
2167         ret->num_ignet = 0;
2168         ret->num_error = 0;
2169
2170         if (recipients == NULL) return(ret);
2171         if (strlen(recipients) == NULL) return(ret);
2172
2173         /* Allow either , or ; separators by changing ; to , */
2174         for (i=0; i<strlen(recipients); ++i) {
2175                 if (recipients[i] == ';') {
2176                         recipients[i] = ',';
2177                 }
2178         }
2179
2180         /* Count 'em up */
2181         num_recps = num_tokens(recipients, ',');
2182         
2183         for (i=0; i<num_recps; ++i) {
2184                 extract_token(this_recp, recipients, i, ',');
2185                 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2186                 mailtype = alias(this_recp);
2187                 switch(mailtype) {
2188                         case MES_LOCAL:
2189                                 if (getuser(&tempUS, buf) == 0) {
2190                                         ++ret->num_local;
2191                                 }
2192                                 else {
2193                                         ++ret->num_error;
2194                                 }
2195                                 break;
2196                         case MES_INTERNET:
2197                                 ++ret->num_internet;
2198                                 break;
2199                         case MES_IGNET:
2200                                 ++ret->num_ignet;
2201                                 break;
2202                         case MES_ERROR:
2203                                 ++ret->num_error;
2204                                 break;
2205                 }
2206
2207         }
2208
2209         return(ret);
2210 }
2211 #endif /* FIXME */
2212
2213
2214
2215 /*
2216  * message entry  -  mode 0 (normal)
2217  */
2218 void cmd_ent0(char *entargs)
2219 {
2220         int post = 0;
2221         char recipient[SIZ];
2222         int anon_flag = 0;
2223         int format_type = 0;
2224         char newusername[SIZ];
2225         struct CtdlMessage *msg;
2226         int a, b;
2227         int e = 0;
2228         int mtsflag = 0;
2229         struct usersupp tempUS;
2230         char buf[SIZ];
2231         int err = 0;
2232
2233         post = extract_int(entargs, 0);
2234         extract(recipient, entargs, 1);
2235         anon_flag = extract_int(entargs, 2);
2236         format_type = extract_int(entargs, 3);
2237
2238         /* first check to make sure the request is valid. */
2239
2240         err = CtdlDoIHavePermissionToPostInThisRoom(buf);
2241         if (err) {
2242                 cprintf("%d %s\n", err, buf);
2243                 return;
2244         }
2245
2246         /* Check some other permission type things. */
2247
2248         if (post == 2) {
2249                 if (CC->usersupp.axlevel < 6) {
2250                         cprintf("%d You don't have permission to masquerade.\n",
2251                                 ERROR + HIGHER_ACCESS_REQUIRED);
2252                         return;
2253                 }
2254                 extract(newusername, entargs, 4);
2255                 memset(CC->fake_postname, 0, 32);
2256                 strcpy(CC->fake_postname, newusername);
2257                 cprintf("%d Ok\n", OK);
2258                 return;
2259         }
2260         CC->cs_flags |= CS_POSTING;
2261
2262         buf[0] = 0;
2263         if (CC->quickroom.QRflags & QR_MAILBOX) {
2264                 if (CC->usersupp.axlevel >= 2) {
2265                         strcpy(buf, recipient);
2266                 }
2267                 else {
2268                         strcpy(buf, "sysop");
2269                 }
2270                 e = alias(buf); /* alias and mail type */
2271                 if ((buf[0] == 0) || (e == MES_ERROR)) {
2272                         cprintf("%d Unknown address - cannot send message.\n",
2273                                 ERROR + NO_SUCH_USER);
2274                         return;
2275                 }
2276                 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2277                         cprintf("%d Net privileges required for network mail.\n",
2278                                 ERROR + HIGHER_ACCESS_REQUIRED);
2279                         return;
2280                 }
2281                 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2282                     && ((CC->usersupp.flags & US_INTERNET) == 0)
2283                     && (!CC->internal_pgm)) {
2284                         cprintf("%d You don't have access to Internet mail.\n",
2285                                 ERROR + HIGHER_ACCESS_REQUIRED);
2286                         return;
2287                 }
2288                 if (!strcasecmp(buf, "sysop")) {
2289                         mtsflag = 1;
2290                 }
2291                 else if (e == MES_LOCAL) {      /* don't search local file */
2292                         if (!strcasecmp(buf, CC->usersupp.fullname)) {
2293                                 cprintf("%d Can't send mail to yourself!\n",
2294                                         ERROR + NO_SUCH_USER);
2295                                 return;
2296                         }
2297                         /* Check to make sure the user exists; also get the correct
2298                          * upper/lower casing of the name.
2299                          */
2300                         a = getuser(&tempUS, buf);
2301                         if (a != 0) {
2302                                 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2303                                 return;
2304                         }
2305                         strcpy(buf, tempUS.fullname);
2306                 }
2307         }
2308
2309         b = MES_NORMAL;
2310         if (CC->quickroom.QRflags & QR_ANONONLY)
2311                 b = MES_ANON;
2312         if (CC->quickroom.QRflags & QR_ANONOPT) {
2313                 if (anon_flag == 1)
2314                         b = MES_AN2;
2315         }
2316         if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2317                 buf[0] = 0;
2318
2319         /* If we're only checking the validity of the request, return
2320          * success without creating the message.
2321          */
2322         if (post == 0) {
2323                 cprintf("%d %s\n", OK, buf);
2324                 return;
2325         }
2326
2327         cprintf("%d send message\n", SEND_LISTING);
2328
2329         /* Read in the message from the client. */
2330         if (CC->fake_postname[0]) {
2331                 msg = make_message(&CC->usersupp, buf,
2332                         CC->quickroom.QRname, b, e, format_type,
2333                         CC->fake_postname);
2334         }
2335         else if (CC->fake_username[0]) {
2336                 msg = make_message(&CC->usersupp, buf,
2337                         CC->quickroom.QRname, b, e, format_type,
2338                         CC->fake_username);
2339         }
2340         else {
2341                 msg = make_message(&CC->usersupp, buf,
2342                         CC->quickroom.QRname, b, e, format_type, "");
2343         }
2344
2345         if (msg != NULL) {
2346                 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2347                 CtdlFreeMessage(msg);
2348         }
2349         CC->fake_postname[0] = '\0';
2350         return;
2351 }
2352
2353
2354
2355 /*
2356  * API function to delete messages which match a set of criteria
2357  * (returns the actual number of messages deleted)
2358  */
2359 int CtdlDeleteMessages(char *room_name,         /* which room */
2360                        long dmsgnum,            /* or "0" for any */
2361                        char *content_type       /* or "" for any */
2362 )
2363 {
2364
2365         struct quickroom qrbuf;
2366         struct cdbdata *cdbfr;
2367         long *msglist = NULL;
2368         int num_msgs = 0;
2369         int i;
2370         int num_deleted = 0;
2371         int delete_this;
2372         struct MetaData smi;
2373
2374         lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2375                 room_name, dmsgnum, content_type);
2376
2377         /* get room record, obtaining a lock... */
2378         if (lgetroom(&qrbuf, room_name) != 0) {
2379                 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2380                         room_name);
2381                 return (0);     /* room not found */
2382         }
2383         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2384
2385         if (cdbfr != NULL) {
2386                 msglist = mallok(cdbfr->len);
2387                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2388                 num_msgs = cdbfr->len / sizeof(long);
2389                 cdb_free(cdbfr);
2390         }
2391         if (num_msgs > 0) {
2392                 for (i = 0; i < num_msgs; ++i) {
2393                         delete_this = 0x00;
2394
2395                         /* Set/clear a bit for each criterion */
2396
2397                         if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2398                                 delete_this |= 0x01;
2399                         }
2400                         if (strlen(content_type) == 0) {
2401                                 delete_this |= 0x02;
2402                         } else {
2403                                 GetMetaData(&smi, msglist[i]);
2404                                 if (!strcasecmp(smi.meta_content_type,
2405                                                 content_type)) {
2406                                         delete_this |= 0x02;
2407                                 }
2408                         }
2409
2410                         /* Delete message only if all bits are set */
2411                         if (delete_this == 0x03) {
2412                                 AdjRefCount(msglist[i], -1);
2413                                 msglist[i] = 0L;
2414                                 ++num_deleted;
2415                         }
2416                 }
2417
2418                 num_msgs = sort_msglist(msglist, num_msgs);
2419                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2420                           msglist, (num_msgs * sizeof(long)));
2421
2422                 qrbuf.QRhighest = msglist[num_msgs - 1];
2423                 phree(msglist);
2424         }
2425         lputroom(&qrbuf);
2426         lprintf(9, "%d message(s) deleted.\n", num_deleted);
2427         return (num_deleted);
2428 }
2429
2430
2431
2432 /*
2433  * Check whether the current user has permission to delete messages from
2434  * the current room (returns 1 for yes, 0 for no)
2435  */
2436 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2437         getuser(&CC->usersupp, CC->curr_user);
2438         if ((CC->usersupp.axlevel < 6)
2439             && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2440             && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2441             && (!(CC->internal_pgm))) {
2442                 return(0);
2443         }
2444         return(1);
2445 }
2446
2447
2448
2449 /*
2450  * Delete message from current room
2451  */
2452 void cmd_dele(char *delstr)
2453 {
2454         long delnum;
2455         int num_deleted;
2456
2457         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2458                 cprintf("%d Higher access required.\n",
2459                         ERROR + HIGHER_ACCESS_REQUIRED);
2460                 return;
2461         }
2462         delnum = extract_long(delstr, 0);
2463
2464         num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2465
2466         if (num_deleted) {
2467                 cprintf("%d %d message%s deleted.\n", OK,
2468                         num_deleted, ((num_deleted != 1) ? "s" : ""));
2469         } else {
2470                 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2471         }
2472 }
2473
2474
2475 /*
2476  * Back end API function for moves and deletes
2477  */
2478 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2479         int err;
2480
2481         err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2482                 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2483         if (err != 0) return(err);
2484
2485         return(0);
2486 }
2487
2488
2489
2490 /*
2491  * move or copy a message to another room
2492  */
2493 void cmd_move(char *args)
2494 {
2495         long num;
2496         char targ[SIZ];
2497         struct quickroom qtemp;
2498         int err;
2499         int is_copy = 0;
2500
2501         num = extract_long(args, 0);
2502         extract(targ, args, 1);
2503         targ[ROOMNAMELEN - 1] = 0;
2504         is_copy = extract_int(args, 2);
2505
2506         getuser(&CC->usersupp, CC->curr_user);
2507         if ((CC->usersupp.axlevel < 6)
2508             && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2509                 cprintf("%d Higher access required.\n",
2510                         ERROR + HIGHER_ACCESS_REQUIRED);
2511                 return;
2512         }
2513
2514         if (getroom(&qtemp, targ) != 0) {
2515                 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2516                 return;
2517         }
2518
2519         err = CtdlCopyMsgToRoom(num, targ);
2520         if (err != 0) {
2521                 cprintf("%d Cannot store message in %s: error %d\n",
2522                         err, targ, err);
2523                 return;
2524         }
2525
2526         /* Now delete the message from the source room,
2527          * if this is a 'move' rather than a 'copy' operation.
2528          */
2529         if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2530
2531         cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2532 }
2533
2534
2535
2536 /*
2537  * GetMetaData()  -  Get the supplementary record for a message
2538  */
2539 void GetMetaData(struct MetaData *smibuf, long msgnum)
2540 {
2541
2542         struct cdbdata *cdbsmi;
2543         long TheIndex;
2544
2545         memset(smibuf, 0, sizeof(struct MetaData));
2546         smibuf->meta_msgnum = msgnum;
2547         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
2548
2549         /* Use the negative of the message number for its supp record index */
2550         TheIndex = (0L - msgnum);
2551
2552         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2553         if (cdbsmi == NULL) {
2554                 return;         /* record not found; go with defaults */
2555         }
2556         memcpy(smibuf, cdbsmi->ptr,
2557                ((cdbsmi->len > sizeof(struct MetaData)) ?
2558                 sizeof(struct MetaData) : cdbsmi->len));
2559         cdb_free(cdbsmi);
2560         return;
2561 }
2562
2563
2564 /*
2565  * PutMetaData()  -  (re)write supplementary record for a message
2566  */
2567 void PutMetaData(struct MetaData *smibuf)
2568 {
2569         long TheIndex;
2570
2571         /* Use the negative of the message number for its supp record index */
2572         TheIndex = (0L - smibuf->meta_msgnum);
2573
2574         lprintf(9, "PuttMetaData(%ld) - ref count is %d\n",
2575                 smibuf->meta_msgnum, smibuf->meta_refcount);
2576
2577         cdb_store(CDB_MSGMAIN,
2578                   &TheIndex, sizeof(long),
2579                   smibuf, sizeof(struct MetaData));
2580
2581 }
2582
2583 /*
2584  * AdjRefCount  -  change the reference count for a message;
2585  *                 delete the message if it reaches zero
2586  */
2587 void AdjRefCount(long msgnum, int incr)
2588 {
2589
2590         struct MetaData smi;
2591         long delnum;
2592
2593         /* This is a *tight* critical section; please keep it that way, as
2594          * it may get called while nested in other critical sections.  
2595          * Complicating this any further will surely cause deadlock!
2596          */
2597         begin_critical_section(S_SUPPMSGMAIN);
2598         GetMetaData(&smi, msgnum);
2599         lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2600                 msgnum, smi.meta_refcount);
2601         smi.meta_refcount += incr;
2602         PutMetaData(&smi);
2603         end_critical_section(S_SUPPMSGMAIN);
2604         lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2605                 msgnum, smi.meta_refcount);
2606
2607         /* If the reference count is now zero, delete the message
2608          * (and its supplementary record as well).
2609          */
2610         if (smi.meta_refcount == 0) {
2611                 lprintf(9, "Deleting message <%ld>\n", msgnum);
2612                 delnum = msgnum;
2613                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2614                 delnum = (0L - msgnum);
2615                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2616         }
2617 }
2618
2619 /*
2620  * Write a generic object to this room
2621  *
2622  * Note: this could be much more efficient.  Right now we use two temporary
2623  * files, and still pull the message into memory as with all others.
2624  */
2625 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
2626                         char *content_type,     /* MIME type of this object */
2627                         char *tempfilename,     /* Where to fetch it from */
2628                         struct usersupp *is_mailbox,    /* Mailbox room? */
2629                         int is_binary,          /* Is encoding necessary? */
2630                         int is_unique,          /* Del others of this type? */
2631                         unsigned int flags      /* Internal save flags */
2632                         )
2633 {
2634
2635         FILE *fp, *tempfp;
2636         char filename[PATH_MAX];
2637         char cmdbuf[SIZ];
2638         char ch;
2639         struct quickroom qrbuf;
2640         char roomname[ROOMNAMELEN];
2641         struct CtdlMessage *msg;
2642         size_t len;
2643
2644         if (is_mailbox != NULL)
2645                 MailboxName(roomname, is_mailbox, req_room);
2646         else
2647                 safestrncpy(roomname, req_room, sizeof(roomname));
2648         lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2649
2650         strcpy(filename, tmpnam(NULL));
2651         fp = fopen(filename, "w");
2652         if (fp == NULL)
2653                 return;
2654
2655         tempfp = fopen(tempfilename, "r");
2656         if (tempfp == NULL) {
2657                 fclose(fp);
2658                 unlink(filename);
2659                 return;
2660         }
2661
2662         fprintf(fp, "Content-type: %s\n", content_type);
2663         lprintf(9, "Content-type: %s\n", content_type);
2664
2665         if (is_binary == 0) {
2666                 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2667                 while (ch = getc(tempfp), ch > 0)
2668                         putc(ch, fp);
2669                 fclose(tempfp);
2670                 putc(0, fp);
2671                 fclose(fp);
2672         } else {
2673                 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2674                 fclose(tempfp);
2675                 fclose(fp);
2676                 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2677                         tempfilename, filename);
2678                 system(cmdbuf);
2679         }
2680
2681         lprintf(9, "Allocating\n");
2682         msg = mallok(sizeof(struct CtdlMessage));
2683         memset(msg, 0, sizeof(struct CtdlMessage));
2684         msg->cm_magic = CTDLMESSAGE_MAGIC;
2685         msg->cm_anon_type = MES_NORMAL;
2686         msg->cm_format_type = 4;
2687         msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2688         msg->cm_fields['O'] = strdoop(req_room);
2689         msg->cm_fields['N'] = strdoop(config.c_nodename);
2690         msg->cm_fields['H'] = strdoop(config.c_humannode);
2691         msg->cm_flags = flags;
2692         
2693         lprintf(9, "Loading\n");
2694         fp = fopen(filename, "rb");
2695         fseek(fp, 0L, SEEK_END);
2696         len = ftell(fp);
2697         rewind(fp);
2698         msg->cm_fields['M'] = mallok(len);
2699         fread(msg->cm_fields['M'], len, 1, fp);
2700         fclose(fp);
2701         unlink(filename);
2702
2703         /* Create the requested room if we have to. */
2704         if (getroom(&qrbuf, roomname) != 0) {
2705                 create_room(roomname, 
2706                         ( (is_mailbox != NULL) ? 5 : 3 ),
2707                         "", 0, 1);
2708         }
2709         /* If the caller specified this object as unique, delete all
2710          * other objects of this type that are currently in the room.
2711          */
2712         if (is_unique) {
2713                 lprintf(9, "Deleted %d other msgs of this type\n",
2714                         CtdlDeleteMessages(roomname, 0L, content_type));
2715         }
2716         /* Now write the data */
2717         CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2718         CtdlFreeMessage(msg);
2719 }
2720
2721
2722
2723
2724
2725
2726 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2727         config_msgnum = msgnum;
2728 }
2729
2730
2731 char *CtdlGetSysConfig(char *sysconfname) {
2732         char hold_rm[ROOMNAMELEN];
2733         long msgnum;
2734         char *conf;
2735         struct CtdlMessage *msg;
2736         char buf[SIZ];
2737         
2738         strcpy(hold_rm, CC->quickroom.QRname);
2739         if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2740                 getroom(&CC->quickroom, hold_rm);
2741                 return NULL;
2742         }
2743
2744
2745         /* We want the last (and probably only) config in this room */
2746         begin_critical_section(S_CONFIG);
2747         config_msgnum = (-1L);
2748         CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2749                 CtdlGetSysConfigBackend, NULL);
2750         msgnum = config_msgnum;
2751         end_critical_section(S_CONFIG);
2752
2753         if (msgnum < 0L) {
2754                 conf = NULL;
2755         }
2756         else {
2757                 msg = CtdlFetchMessage(msgnum);
2758                 if (msg != NULL) {
2759                         conf = strdoop(msg->cm_fields['M']);
2760                         CtdlFreeMessage(msg);
2761                 }
2762                 else {
2763                         conf = NULL;
2764                 }
2765         }
2766
2767         getroom(&CC->quickroom, hold_rm);
2768
2769         if (conf != NULL) do {
2770                 extract_token(buf, conf, 0, '\n');
2771                 strcpy(conf, &conf[strlen(buf)+1]);
2772         } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2773
2774         return(conf);
2775 }
2776
2777 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2778         char temp[PATH_MAX];
2779         FILE *fp;
2780
2781         strcpy(temp, tmpnam(NULL));
2782
2783         fp = fopen(temp, "w");
2784         if (fp == NULL) return;
2785         fprintf(fp, "%s", sysconfdata);
2786         fclose(fp);
2787
2788         /* this handy API function does all the work for us */
2789         CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
2790         unlink(temp);
2791 }