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