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