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