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