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