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