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