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