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