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