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