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