]> code.citadel.org Git - citadel.git/blob - citadel/msgbase.c
* Don't give away anonymous poster information in IMAP or RFC822
[citadel.git] / citadel / msgbase.c
1 /*
2  * $Id$
3  *
4  * Implements the message store.
5  *
6  */
7
8 #ifdef DLL_EXPORT
9 #define IN_LIBCIT
10 #endif
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
20 # include <time.h>
21 #else
22 # if HAVE_SYS_TIME_H
23 #  include <sys/time.h>
24 # else
25 #  include <time.h>
26 # endif
27 #endif
28
29
30 #include <ctype.h>
31 #include <string.h>
32 #include <syslog.h>
33 #include <limits.h>
34 #include <errno.h>
35 #include <stdarg.h>
36 #include <sys/stat.h>
37 #include "citadel.h"
38 #include "server.h"
39 #include "serv_extensions.h"
40 #include "database.h"
41 #include "msgbase.h"
42 #include "support.h"
43 #include "sysdep_decls.h"
44 #include "citserver.h"
45 #include "room_ops.h"
46 #include "user_ops.h"
47 #include "file_ops.h"
48 #include "control.h"
49 #include "tools.h"
50 #include "mime_parser.h"
51 #include "html.h"
52 #include "genstamp.h"
53 #include "internet_addressing.h"
54
55 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
56 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
57 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
58
59 extern struct config config;
60 long config_msgnum;
61
62
63 /* 
64  * This really belongs in serv_network.c, but I don't know how to export
65  * symbols between modules.
66  */
67 struct FilterList *filterlist = NULL;
68
69
70 /*
71  * These are the four-character field headers we use when outputting
72  * messages in Citadel format (as opposed to RFC822 format).
73  */
74 char *msgkeys[] = {
75         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
76         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
77         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
78         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
79         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
80         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
81         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
82         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
83         NULL, 
84         "from",
85         NULL, NULL, NULL,
86         "exti",
87         "rfca",
88         NULL, 
89         "hnod",
90         "msgn",
91         NULL, NULL, NULL,
92         "text",
93         "node",
94         "room",
95         "path",
96         NULL,
97         "rcpt",
98         "spec",
99         "time",
100         "subj",
101         NULL,
102         NULL,
103         NULL,
104         NULL,
105         NULL
106 };
107
108 /*
109  * This function is self explanatory.
110  * (What can I say, I'm in a weird mood today...)
111  */
112 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
113 {
114         int i;
115
116         for (i = 0; i < strlen(name); ++i) {
117                 if (name[i] == '@') {
118                         while (isspace(name[i - 1]) && i > 0) {
119                                 strcpy(&name[i - 1], &name[i]);
120                                 --i;
121                         }
122                         while (isspace(name[i + 1])) {
123                                 strcpy(&name[i + 1], &name[i + 2]);
124                         }
125                 }
126         }
127 }
128
129
130 /*
131  * Aliasing for network mail.
132  * (Error messages have been commented out, because this is a server.)
133  */
134 int alias(char *name)
135 {                               /* process alias and routing info for mail */
136         FILE *fp;
137         int a, i;
138         char aaa[SIZ], bbb[SIZ];
139         char *ignetcfg = NULL;
140         char *ignetmap = NULL;
141         int at = 0;
142         char node[SIZ];
143         char testnode[SIZ];
144         char buf[SIZ];
145
146         striplt(name);
147         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
148
149         fp = fopen("network/mail.aliases", "r");
150         if (fp == NULL) {
151                 fp = fopen("/dev/null", "r");
152         }
153         if (fp == NULL) {
154                 return (MES_ERROR);
155         }
156         strcpy(aaa, "");
157         strcpy(bbb, "");
158         while (fgets(aaa, sizeof aaa, fp) != NULL) {
159                 while (isspace(name[0]))
160                         strcpy(name, &name[1]);
161                 aaa[strlen(aaa) - 1] = 0;
162                 strcpy(bbb, "");
163                 for (a = 0; a < strlen(aaa); ++a) {
164                         if (aaa[a] == ',') {
165                                 strcpy(bbb, &aaa[a + 1]);
166                                 aaa[a] = 0;
167                         }
168                 }
169                 if (!strcasecmp(name, aaa))
170                         strcpy(name, bbb);
171         }
172         fclose(fp);
173
174         /* Hit the Global Address Book */
175         if (CtdlDirectoryLookup(aaa, name) == 0) {
176                 strcpy(name, aaa);
177         }
178
179         lprintf(7, "Mail is being forwarded to %s\n", name);
180
181         /* Change "user @ xxx" to "user" if xxx is an alias for this host */
182         for (a=0; a<strlen(name); ++a) {
183                 if (name[a] == '@') {
184                         if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
185                                 name[a] = 0;
186                                 lprintf(7, "Changed to <%s>\n", name);
187                         }
188                 }
189         }
190
191         /* determine local or remote type, see citadel.h */
192         at = haschar(name, '@');
193         if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
194         if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
195         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
196
197         /* figure out the delivery mode */
198         extract_token(node, name, 1, '@');
199
200         /* If there are one or more dots in the nodename, we assume that it
201          * is an FQDN and will attempt SMTP delivery to the Internet.
202          */
203         if (haschar(node, '.') > 0) {
204                 return(MES_INTERNET);
205         }
206
207         /* Otherwise we look in the IGnet maps for a valid Citadel node.
208          * Try directly-connected nodes first...
209          */
210         ignetcfg = CtdlGetSysConfig(IGNETCFG);
211         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
212                 extract_token(buf, ignetcfg, i, '\n');
213                 extract_token(testnode, buf, 0, '|');
214                 if (!strcasecmp(node, testnode)) {
215                         phree(ignetcfg);
216                         return(MES_IGNET);
217                 }
218         }
219         phree(ignetcfg);
220
221         /*
222          * Then try nodes that are two or more hops away.
223          */
224         ignetmap = CtdlGetSysConfig(IGNETMAP);
225         for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
226                 extract_token(buf, ignetmap, i, '\n');
227                 extract_token(testnode, buf, 0, '|');
228                 if (!strcasecmp(node, testnode)) {
229                         phree(ignetmap);
230                         return(MES_IGNET);
231                 }
232         }
233         phree(ignetmap);
234
235         /* If we get to this point it's an invalid node name */
236         return (MES_ERROR);
237 }
238
239
240 void get_mm(void)
241 {
242         FILE *fp;
243
244         fp = fopen("citadel.control", "r");
245         fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
246         fclose(fp);
247 }
248
249
250
251 void simple_listing(long msgnum, void *userdata)
252 {
253         cprintf("%ld\n", msgnum);
254 }
255
256
257
258 /* Determine if a given message matches the fields in a message template.
259  * Return 0 for a successful match.
260  */
261 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
262         int i;
263
264         /* If there aren't any fields in the template, all messages will
265          * match.
266          */
267         if (template == NULL) return(0);
268
269         /* Null messages are bogus. */
270         if (msg == NULL) return(1);
271
272         for (i='A'; i<='Z'; ++i) {
273                 if (template->cm_fields[i] != NULL) {
274                         if (msg->cm_fields[i] == NULL) {
275                                 return 1;
276                         }
277                         if (strcasecmp(msg->cm_fields[i],
278                                 template->cm_fields[i])) return 1;
279                 }
280         }
281
282         /* All compares succeeded: we have a match! */
283         return 0;
284 }
285
286
287
288 /*
289  * Retrieve the "seen" message list for the current room.
290  */
291 void CtdlGetSeen(char *buf, int which_set) {
292         struct visit vbuf;
293
294         /* Learn about the user and room in question */
295         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
296
297         if (which_set == ctdlsetseen_seen)
298                 safestrncpy(buf, vbuf.v_seen, SIZ);
299         if (which_set == ctdlsetseen_answered)
300                 safestrncpy(buf, vbuf.v_answered, SIZ);
301 }
302
303
304
305 /*
306  * Manipulate the "seen msgs" string (or other message set strings)
307  */
308 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
309         char newseen[SIZ];
310         struct cdbdata *cdbfr;
311         int i;
312         int is_seen = 0;
313         int was_seen = 1;
314         long lo = (-1L);
315         long hi = (-1L);
316         struct visit vbuf;
317         long *msglist;
318         int num_msgs = 0;
319         char vset[SIZ];
320
321         /* Learn about the user and room in question */
322         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
323
324         /* Load the message list */
325         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
326         if (cdbfr != NULL) {
327                 msglist = mallok(cdbfr->len);
328                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
329                 num_msgs = cdbfr->len / sizeof(long);
330                 cdb_free(cdbfr);
331         } else {
332                 return; /* No messages at all?  No further action. */
333         }
334
335         /* Decide which message set we're manipulating */
336         if (which_set == ctdlsetseen_seen) strcpy(vset, vbuf.v_seen);
337         if (which_set == ctdlsetseen_answered) strcpy(vset, vbuf.v_answered);
338
339         lprintf(9, "before optimize: %s\n", vset);
340         strcpy(newseen, "");
341
342         for (i=0; i<num_msgs; ++i) {
343                 is_seen = 0;
344
345                 if (msglist[i] == target_msgnum) {
346                         is_seen = target_setting;
347                 }
348                 else {
349                         if (is_msg_in_mset(vset, msglist[i])) {
350                                 is_seen = 1;
351                         }
352                 }
353
354                 if (is_seen == 1) {
355                         if (lo < 0L) lo = msglist[i];
356                         hi = msglist[i];
357                 }
358                 if (  ((is_seen == 0) && (was_seen == 1))
359                    || ((is_seen == 1) && (i == num_msgs-1)) ) {
360                         size_t tmp;
361
362                         if ( (strlen(newseen) + 20) > SIZ) {
363                                 strcpy(newseen, &newseen[20]);
364                                 newseen[0] = '*';
365                         }
366                         tmp = strlen(newseen);
367                         if (tmp > 0) {
368                                 strcat(newseen, ",");
369                                 tmp++;
370                         }
371                         if (lo == hi) {
372                                 snprintf(&newseen[tmp], sizeof newseen - tmp,
373                                          "%ld", lo);
374                         }
375                         else {
376                                 snprintf(&newseen[tmp], sizeof newseen - tmp,
377                                          "%ld:%ld", lo, hi);
378                         }
379                         lo = (-1L);
380                         hi = (-1L);
381                 }
382                 was_seen = is_seen;
383         }
384
385         /* Decide which message set we're manipulating */
386         if (which_set == ctdlsetseen_seen) strcpy(vbuf.v_seen, newseen);
387         if (which_set == ctdlsetseen_answered) strcpy(vbuf.v_answered, newseen);
388
389         lprintf(9, " after optimize: %s\n", newseen);
390         phree(msglist);
391         CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
392 }
393
394
395 /*
396  * API function to perform an operation for each qualifying message in the
397  * current room.  (Returns the number of messages processed.)
398  */
399 int CtdlForEachMessage(int mode, long ref,
400                         char *content_type,
401                         struct CtdlMessage *compare,
402                         void (*CallBack) (long, void *),
403                         void *userdata)
404 {
405
406         int a;
407         struct visit vbuf;
408         struct cdbdata *cdbfr;
409         long *msglist = NULL;
410         int num_msgs = 0;
411         int num_processed = 0;
412         long thismsg;
413         struct MetaData smi;
414         struct CtdlMessage *msg;
415         int is_seen;
416         long lastold = 0L;
417         int printed_lastold = 0;
418
419         /* Learn about the user and room in question */
420         get_mm();
421         getuser(&CC->user, CC->curr_user);
422         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
423
424         /* Load the message list */
425         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
426         if (cdbfr != NULL) {
427                 msglist = mallok(cdbfr->len);
428                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
429                 num_msgs = cdbfr->len / sizeof(long);
430                 cdb_free(cdbfr);
431         } else {
432                 return 0;       /* No messages at all?  No further action. */
433         }
434
435
436         /*
437          * Now begin the traversal.
438          */
439         if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
440
441                 /* If the caller is looking for a specific MIME type, filter
442                  * out all messages which are not of the type requested.
443                  */
444                 if (content_type != NULL) if (strlen(content_type) > 0) {
445
446                         /* This call to GetMetaData() sits inside this loop
447                          * so that we only do the extra database read per msg
448                          * if we need to.  Doing the extra read all the time
449                          * really kills the server.  If we ever need to use
450                          * metadata for another search criterion, we need to
451                          * move the read somewhere else -- but still be smart
452                          * enough to only do the read if the caller has
453                          * specified something that will need it.
454                          */
455                         GetMetaData(&smi, msglist[a]);
456
457                         if (strcasecmp(smi.meta_content_type, content_type)) {
458                                 msglist[a] = 0L;
459                         }
460                 }
461         }
462
463         num_msgs = sort_msglist(msglist, num_msgs);
464
465         /* If a template was supplied, filter out the messages which
466          * don't match.  (This could induce some delays!)
467          */
468         if (num_msgs > 0) {
469                 if (compare != NULL) {
470                         for (a = 0; a < num_msgs; ++a) {
471                                 msg = CtdlFetchMessage(msglist[a]);
472                                 if (msg != NULL) {
473                                         if (CtdlMsgCmp(msg, compare)) {
474                                                 msglist[a] = 0L;
475                                         }
476                                         CtdlFreeMessage(msg);
477                                 }
478                         }
479                 }
480         }
481
482         
483         /*
484          * Now iterate through the message list, according to the
485          * criteria supplied by the caller.
486          */
487         if (num_msgs > 0)
488                 for (a = 0; a < num_msgs; ++a) {
489                         thismsg = msglist[a];
490                         is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
491                         if (is_seen) lastold = thismsg;
492                         if ((thismsg > 0L)
493                             && (
494
495                                        (mode == MSGS_ALL)
496                                        || ((mode == MSGS_OLD) && (is_seen))
497                                        || ((mode == MSGS_NEW) && (!is_seen))
498                                        || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
499                                    || ((mode == MSGS_FIRST) && (a < ref))
500                                 || ((mode == MSGS_GT) && (thismsg > ref))
501                                 || ((mode == MSGS_EQ) && (thismsg == ref))
502                             )
503                             ) {
504                                 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
505                                         if (CallBack)
506                                                 CallBack(lastold, userdata);
507                                         printed_lastold = 1;
508                                         ++num_processed;
509                                 }
510                                 if (CallBack) CallBack(thismsg, userdata);
511                                 ++num_processed;
512                         }
513                 }
514         phree(msglist);         /* Clean up */
515         return num_processed;
516 }
517
518
519
520 /*
521  * cmd_msgs()  -  get list of message #'s in this room
522  *                implements the MSGS server command using CtdlForEachMessage()
523  */
524 void cmd_msgs(char *cmdbuf)
525 {
526         int mode = 0;
527         char which[SIZ];
528         char buf[SIZ];
529         char tfield[SIZ];
530         char tvalue[SIZ];
531         int cm_ref = 0;
532         int i;
533         int with_template = 0;
534         struct CtdlMessage *template = NULL;
535
536         extract(which, cmdbuf, 0);
537         cm_ref = extract_int(cmdbuf, 1);
538         with_template = extract_int(cmdbuf, 2);
539
540         mode = MSGS_ALL;
541         strcat(which, "   ");
542         if (!strncasecmp(which, "OLD", 3))
543                 mode = MSGS_OLD;
544         else if (!strncasecmp(which, "NEW", 3))
545                 mode = MSGS_NEW;
546         else if (!strncasecmp(which, "FIRST", 5))
547                 mode = MSGS_FIRST;
548         else if (!strncasecmp(which, "LAST", 4))
549                 mode = MSGS_LAST;
550         else if (!strncasecmp(which, "GT", 2))
551                 mode = MSGS_GT;
552
553         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
554                 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
555                 return;
556         }
557
558         if (with_template) {
559                 cprintf("%d Send template then receive message list\n",
560                         START_CHAT_MODE);
561                 template = (struct CtdlMessage *)
562                         mallok(sizeof(struct CtdlMessage));
563                 memset(template, 0, sizeof(struct CtdlMessage));
564                 while(client_gets(buf), strcmp(buf,"000")) {
565                         extract(tfield, buf, 0);
566                         extract(tvalue, buf, 1);
567                         for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
568                                 if (!strcasecmp(tfield, msgkeys[i])) {
569                                         template->cm_fields[i] =
570                                                 strdoop(tvalue);
571                                 }
572                         }
573                 }
574         }
575         else {
576                 cprintf("%d Message list...\n", LISTING_FOLLOWS);
577         }
578
579         CtdlForEachMessage(mode, cm_ref,
580                 NULL, template, simple_listing, NULL);
581         if (template != NULL) CtdlFreeMessage(template);
582         cprintf("000\n");
583 }
584
585
586
587
588 /* 
589  * help_subst()  -  support routine for help file viewer
590  */
591 void help_subst(char *strbuf, char *source, char *dest)
592 {
593         char workbuf[SIZ];
594         int p;
595
596         while (p = pattern2(strbuf, source), (p >= 0)) {
597                 strcpy(workbuf, &strbuf[p + strlen(source)]);
598                 strcpy(&strbuf[p], dest);
599                 strcat(strbuf, workbuf);
600         }
601 }
602
603
604 void do_help_subst(char *buffer)
605 {
606         char buf2[16];
607
608         help_subst(buffer, "^nodename", config.c_nodename);
609         help_subst(buffer, "^humannode", config.c_humannode);
610         help_subst(buffer, "^fqdn", config.c_fqdn);
611         help_subst(buffer, "^username", CC->user.fullname);
612         snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
613         help_subst(buffer, "^usernum", buf2);
614         help_subst(buffer, "^sysadm", config.c_sysadm);
615         help_subst(buffer, "^variantname", CITADEL);
616         snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
617         help_subst(buffer, "^maxsessions", buf2);
618         help_subst(buffer, "^bbsdir", BBSDIR);
619 }
620
621
622
623 /*
624  * memfmout()  -  Citadel text formatter and paginator.
625  *             Although the original purpose of this routine was to format
626  *             text to the reader's screen width, all we're really using it
627  *             for here is to format text out to 80 columns before sending it
628  *             to the client.  The client software may reformat it again.
629  */
630 void memfmout(
631         int width,              /* screen width to use */
632         char *mptr,             /* where are we going to get our text from? */
633         char subst,             /* nonzero if we should do substitutions */
634         char *nl)               /* string to terminate lines with */
635 {
636         int a, b, c;
637         int real = 0;
638         int old = 0;
639         cit_uint8_t ch;
640         char aaa[140];
641         char buffer[SIZ];
642
643         strcpy(aaa, "");
644         old = 255;
645         strcpy(buffer, "");
646         c = 1;                  /* c is the current pos */
647
648         do {
649                 if (subst) {
650                         while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
651                                 ch = *mptr++;
652                                 buffer[strlen(buffer) + 1] = 0;
653                                 buffer[strlen(buffer)] = ch;
654                         }
655
656                         if (buffer[0] == '^')
657                                 do_help_subst(buffer);
658
659                         buffer[strlen(buffer) + 1] = 0;
660                         a = buffer[0];
661                         strcpy(buffer, &buffer[1]);
662                 } else {
663                         ch = *mptr++;
664                 }
665
666                 old = real;
667                 real = ch;
668
669                 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
670                         ch = 32;
671                 if (((old == 13) || (old == 10)) && (isspace(real))) {
672                         cprintf("%s", nl);
673                         c = 1;
674                 }
675                 if (ch > 126)
676                         continue;
677
678                 if (ch > 32) {
679                         if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
680                                 cprintf("%s%s", nl, aaa);
681                                 c = strlen(aaa);
682                                 aaa[0] = 0;
683                         }
684                         b = strlen(aaa);
685                         aaa[b] = ch;
686                         aaa[b + 1] = 0;
687                 }
688                 if (ch == 32) {
689                         if ((strlen(aaa) + c) > (width - 5)) {
690                                 cprintf("%s", nl);
691                                 c = 1;
692                         }
693                         cprintf("%s ", aaa);
694                         ++c;
695                         c = c + strlen(aaa);
696                         strcpy(aaa, "");
697                 }
698                 if ((ch == 13) || (ch == 10)) {
699                         cprintf("%s%s", aaa, nl);
700                         c = 1;
701                         strcpy(aaa, "");
702                 }
703
704         } while (ch > 0);
705
706         cprintf("%s%s", aaa, nl);
707 }
708
709
710
711 /*
712  * Callback function for mime parser that simply lists the part
713  */
714 void list_this_part(char *name, char *filename, char *partnum, char *disp,
715                     void *content, char *cbtype, size_t length, char *encoding,
716                     void *cbuserdata)
717 {
718
719         cprintf("part=%s|%s|%s|%s|%s|%ld\n",
720                 name, filename, partnum, disp, cbtype, (long)length);
721 }
722
723 /* 
724  * Callback function for multipart prefix
725  */
726 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
727                     void *content, char *cbtype, size_t length, char *encoding,
728                     void *cbuserdata)
729 {
730         cprintf("pref=%s|%s\n", partnum, cbtype);
731 }
732
733 /* 
734  * Callback function for multipart sufffix
735  */
736 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
737                     void *content, char *cbtype, size_t length, char *encoding,
738                     void *cbuserdata)
739 {
740         cprintf("suff=%s|%s\n", partnum, cbtype);
741 }
742
743
744 /*
745  * Callback function for mime parser that opens a section for downloading
746  */
747 void mime_download(char *name, char *filename, char *partnum, char *disp,
748                    void *content, char *cbtype, size_t length, char *encoding,
749                    void *cbuserdata)
750 {
751
752         /* Silently go away if there's already a download open... */
753         if (CC->download_fp != NULL)
754                 return;
755
756         /* ...or if this is not the desired section */
757         if (strcasecmp(desired_section, partnum))
758                 return;
759
760         CC->download_fp = tmpfile();
761         if (CC->download_fp == NULL)
762                 return;
763
764         fwrite(content, length, 1, CC->download_fp);
765         fflush(CC->download_fp);
766         rewind(CC->download_fp);
767
768         OpenCmdResult(filename, cbtype);
769 }
770
771
772
773 /*
774  * Load a message from disk into memory.
775  * This is used by CtdlOutputMsg() and other fetch functions.
776  *
777  * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
778  *       using the CtdlMessageFree() function.
779  */
780 struct CtdlMessage *CtdlFetchMessage(long msgnum)
781 {
782         struct cdbdata *dmsgtext;
783         struct CtdlMessage *ret = NULL;
784         char *mptr;
785         cit_uint8_t ch;
786         cit_uint8_t field_header;
787         size_t field_length;
788
789         dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
790         if (dmsgtext == NULL) {
791                 return NULL;
792         }
793         mptr = dmsgtext->ptr;
794
795         /* Parse the three bytes that begin EVERY message on disk.
796          * The first is always 0xFF, the on-disk magic number.
797          * The second is the anonymous/public type byte.
798          * The third is the format type byte (vari, fixed, or MIME).
799          */
800         ch = *mptr++;
801         if (ch != 255) {
802                 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
803                 cdb_free(dmsgtext);
804                 return NULL;
805         }
806         ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
807         memset(ret, 0, sizeof(struct CtdlMessage));
808
809         ret->cm_magic = CTDLMESSAGE_MAGIC;
810         ret->cm_anon_type = *mptr++;    /* Anon type byte */
811         ret->cm_format_type = *mptr++;  /* Format type byte */
812
813         /*
814          * The rest is zero or more arbitrary fields.  Load them in.
815          * We're done when we encounter either a zero-length field or
816          * have just processed the 'M' (message text) field.
817          */
818         do {
819                 field_length = strlen(mptr);
820                 if (field_length == 0)
821                         break;
822                 field_header = *mptr++;
823                 ret->cm_fields[field_header] = mallok(field_length);
824                 strcpy(ret->cm_fields[field_header], mptr);
825
826                 while (*mptr++ != 0);   /* advance to next field */
827
828         } while ((field_length > 0) && (field_header != 'M'));
829
830         cdb_free(dmsgtext);
831
832         /* Always make sure there's something in the msg text field */
833         if (ret->cm_fields['M'] == NULL)
834                 ret->cm_fields['M'] = strdoop("<no text>\n");
835
836         /* Perform "before read" hooks (aborting if any return nonzero) */
837         if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
838                 CtdlFreeMessage(ret);
839                 return NULL;
840         }
841
842         return (ret);
843 }
844
845
846 /*
847  * Returns 1 if the supplied pointer points to a valid Citadel message.
848  * If the pointer is NULL or the magic number check fails, returns 0.
849  */
850 int is_valid_message(struct CtdlMessage *msg) {
851         if (msg == NULL)
852                 return 0;
853         if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
854                 lprintf(3, "is_valid_message() -- self-check failed\n");
855                 return 0;
856         }
857         return 1;
858 }
859
860
861 /*
862  * 'Destructor' for struct CtdlMessage
863  */
864 void CtdlFreeMessage(struct CtdlMessage *msg)
865 {
866         int i;
867
868         if (is_valid_message(msg) == 0) return;
869
870         for (i = 0; i < 256; ++i)
871                 if (msg->cm_fields[i] != NULL) {
872                         phree(msg->cm_fields[i]);
873                 }
874
875         msg->cm_magic = 0;      /* just in case */
876         phree(msg);
877 }
878
879
880 /*
881  * Pre callback function for multipart/alternative
882  *
883  * NOTE: this differs from the standard behavior for a reason.  Normally when
884  *       displaying multipart/alternative you want to show the _last_ usable
885  *       format in the message.  Here we show the _first_ one, because it's
886  *       usually text/plain.  Since this set of functions is designed for text
887  *       output to non-MIME-aware clients, this is the desired behavior.
888  *
889  */
890 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
891                 void *content, char *cbtype, size_t length, char *encoding,
892                 void *cbuserdata)
893 {
894                 lprintf(9, "fixed_output_pre() type=<%s>\n", cbtype);   
895                 if (!strcasecmp(cbtype, "multipart/alternative")) {
896                         ++ma->is_ma;
897                         ma->did_print = 0;
898                         return;
899                 }
900 }
901
902 /*
903  * Post callback function for multipart/alternative
904  */
905 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
906                 void *content, char *cbtype, size_t length, char *encoding,
907                 void *cbuserdata)
908 {
909                 lprintf(9, "fixed_output_post() type=<%s>\n", cbtype);  
910                 if (!strcasecmp(cbtype, "multipart/alternative")) {
911                         --ma->is_ma;
912                         ma->did_print = 0;
913                         return;
914                 }
915 }
916
917 /*
918  * Inline callback function for mime parser that wants to display text
919  */
920 void fixed_output(char *name, char *filename, char *partnum, char *disp,
921                 void *content, char *cbtype, size_t length, char *encoding,
922                 void *cbuserdata)
923         {
924                 char *ptr;
925                 char *wptr;
926                 size_t wlen;
927
928                 lprintf(9, "fixed_output() type=<%s>\n", cbtype);       
929
930                 /*
931                  * If we're in the middle of a multipart/alternative scope and
932                  * we've already printed another section, skip this one.
933                  */     
934                 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
935                         lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
936                         return;
937                 }
938                 ma->did_print = 1;
939         
940                 if ( (!strcasecmp(cbtype, "text/plain")) 
941                    || (strlen(cbtype)==0) ) {
942                         wptr = content;
943                         if (length > 0) {
944                                 client_write(wptr, length);
945                                 if (wptr[length-1] != '\n') {
946                                         cprintf("\n");
947                                 }
948                         }
949                 }
950                 else if (!strcasecmp(cbtype, "text/html")) {
951                         ptr = html_to_ascii(content, 80, 0);
952                         wlen = strlen(ptr);
953                         client_write(ptr, wlen);
954                         if (ptr[wlen-1] != '\n') {
955                                 cprintf("\n");
956                         }
957                         phree(ptr);
958                 }
959                 else if (strncasecmp(cbtype, "multipart/", 10)) {
960                         cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
961                                 partnum, filename, cbtype, (long)length);
962                 }
963         }
964
965 /*
966  * The client is elegant and sophisticated and wants to be choosy about
967  * MIME content types, so figure out which multipart/alternative part
968  * we're going to send.
969  */
970 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
971                 void *content, char *cbtype, size_t length, char *encoding,
972                 void *cbuserdata)
973 {
974         char buf[SIZ];
975         int i;
976
977         if (ma->is_ma > 0) {
978                 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
979                         extract(buf, CC->preferred_formats, i);
980                         if (!strcasecmp(buf, cbtype)) {
981                                 strcpy(ma->chosen_part, partnum);
982                         }
983                 }
984         }
985 }
986
987 /*
988  * Now that we've chosen our preferred part, output it.
989  */
990 void output_preferred(char *name, char *filename, char *partnum, char *disp,
991                 void *content, char *cbtype, size_t length, char *encoding,
992                 void *cbuserdata)
993 {
994         int i;
995         char buf[SIZ];
996         int add_newline = 0;
997         char *text_content;
998
999         /* This is not the MIME part you're looking for... */
1000         if (strcasecmp(partnum, ma->chosen_part)) return;
1001
1002         /* If the content-type of this part is in our preferred formats
1003          * list, we can simply output it verbatim.
1004          */
1005         for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1006                 extract(buf, CC->preferred_formats, i);
1007                 if (!strcasecmp(buf, cbtype)) {
1008                         /* Yeah!  Go!  W00t!! */
1009
1010                         text_content = (char *)content;
1011                         if (text_content[length-1] != '\n') {
1012                                 ++add_newline;
1013                         }
1014
1015                         cprintf("Content-type: %s\n", cbtype);
1016                         cprintf("Content-length: %d\n",
1017                                 (int)(length + add_newline) );
1018                         if (strlen(encoding) > 0) {
1019                                 cprintf("Content-transfer-encoding: %s\n", encoding);
1020                         }
1021                         else {
1022                                 cprintf("Content-transfer-encoding: 7bit\n");
1023                         }
1024                         cprintf("\n");
1025                         client_write(content, length);
1026                         if (add_newline) cprintf("\n");
1027                         return;
1028                 }
1029         }
1030
1031         /* No translations required or possible: output as text/plain */
1032         cprintf("Content-type: text/plain\n\n");
1033         fixed_output(name, filename, partnum, disp, content, cbtype,
1034                         length, encoding, cbuserdata);
1035 }
1036
1037
1038 /*
1039  * Get a message off disk.  (returns om_* values found in msgbase.h)
1040  * 
1041  */
1042 int CtdlOutputMsg(long msg_num,         /* message number (local) to fetch */
1043                 int mode,               /* how would you like that message? */
1044                 int headers_only,       /* eschew the message body? */
1045                 int do_proto,           /* do Citadel protocol responses? */
1046                 int crlf                /* Use CRLF newlines instead of LF? */
1047 ) {
1048         struct CtdlMessage *TheMessage;
1049         int retcode;
1050
1051         lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n", 
1052                 msg_num, mode);
1053
1054         TheMessage = NULL;
1055
1056         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1057                 if (do_proto) cprintf("%d Not logged in.\n",
1058                         ERROR + NOT_LOGGED_IN);
1059                 return(om_not_logged_in);
1060         }
1061
1062         /* FIXME ... small security issue
1063          * We need to check to make sure the requested message is actually
1064          * in the current room, and set msg_ok to 1 only if it is.  This
1065          * functionality is currently missing because I'm in a hurry to replace
1066          * broken production code with nonbroken pre-beta code.  :(   -- ajc
1067          *
1068          if (!msg_ok) {
1069          if (do_proto) cprintf("%d Message %ld is not in this room.\n",
1070          ERROR, msg_num);
1071          return(om_no_such_msg);
1072          }
1073          */
1074
1075         /*
1076          * Fetch the message from disk.
1077          */
1078         TheMessage = CtdlFetchMessage(msg_num);
1079
1080         if (TheMessage == NULL) {
1081                 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1082                         ERROR, msg_num);
1083                 return(om_no_such_msg);
1084         }
1085         
1086         retcode = CtdlOutputPreLoadedMsg(
1087                         TheMessage, msg_num, mode,
1088                         headers_only, do_proto, crlf);
1089
1090         CtdlFreeMessage(TheMessage);
1091
1092         return(retcode);
1093 }
1094
1095
1096 /*
1097  * Get a message off disk.  (returns om_* values found in msgbase.h)
1098  * 
1099  */
1100 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
1101                 long msg_num,
1102                 int mode,               /* how would you like that message? */
1103                 int headers_only,       /* eschew the message body? */
1104                 int do_proto,           /* do Citadel protocol responses? */
1105                 int crlf                /* Use CRLF newlines instead of LF? */
1106 ) {
1107         int i, k;
1108         char buf[1024];
1109         cit_uint8_t ch;
1110         char allkeys[SIZ];
1111         char display_name[SIZ];
1112         char *mptr;
1113         char *nl;       /* newline string */
1114         int suppress_f = 0;
1115         int subject_found = 0;
1116
1117         /* buffers needed for RFC822 translation */
1118         char suser[SIZ];
1119         char luser[SIZ];
1120         char fuser[SIZ];
1121         char snode[SIZ];
1122         char lnode[SIZ];
1123         char mid[SIZ];
1124         char datestamp[SIZ];
1125         /*                                       */
1126
1127         snprintf(mid, sizeof mid, "%ld", msg_num);
1128         nl = (crlf ? "\r\n" : "\n");
1129
1130         if (!is_valid_message(TheMessage)) {
1131                 lprintf(1, "ERROR: invalid preloaded message for output\n");
1132                 return(om_no_such_msg);
1133         }
1134
1135         /* Are we downloading a MIME component? */
1136         if (mode == MT_DOWNLOAD) {
1137                 if (TheMessage->cm_format_type != FMT_RFC822) {
1138                         if (do_proto)
1139                                 cprintf("%d This is not a MIME message.\n",
1140                                 ERROR);
1141                 } else if (CC->download_fp != NULL) {
1142                         if (do_proto) cprintf(
1143                                 "%d You already have a download open.\n",
1144                                 ERROR);
1145                 } else {
1146                         /* Parse the message text component */
1147                         mptr = TheMessage->cm_fields['M'];
1148                         mime_parser(mptr, NULL,
1149                                 *mime_download, NULL, NULL,
1150                                 NULL, 0);
1151                         /* If there's no file open by this time, the requested
1152                          * section wasn't found, so print an error
1153                          */
1154                         if (CC->download_fp == NULL) {
1155                                 if (do_proto) cprintf(
1156                                         "%d Section %s not found.\n",
1157                                         ERROR + FILE_NOT_FOUND,
1158                                         desired_section);
1159                         }
1160                 }
1161                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1162         }
1163
1164         /* now for the user-mode message reading loops */
1165         if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1166
1167         /* Does the caller want to skip the headers? */
1168         if (headers_only == HEADERS_NONE) goto START_TEXT;
1169
1170         /* Tell the client which format type we're using. */
1171         if ( (mode == MT_CITADEL) && (do_proto) ) {
1172                 cprintf("type=%d\n", TheMessage->cm_format_type);
1173         }
1174
1175         /* nhdr=yes means that we're only displaying headers, no body */
1176         if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1177            && (mode == MT_CITADEL)
1178            && (do_proto)
1179            ) {
1180                 cprintf("nhdr=yes\n");
1181         }
1182
1183         /* begin header processing loop for Citadel message format */
1184
1185         if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1186
1187                 strcpy(display_name, "<unknown>");
1188                 if (TheMessage->cm_fields['A']) {
1189                         strcpy(buf, TheMessage->cm_fields['A']);
1190                         PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1191                         if (TheMessage->cm_anon_type == MES_ANONONLY) {
1192                                 strcpy(display_name, "****");
1193                         }
1194                         else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1195                                 strcpy(display_name, "anonymous");
1196                         }
1197                         else {
1198                                 strcpy(display_name, buf);
1199                         }
1200                         if ((is_room_aide())
1201                             && ((TheMessage->cm_anon_type == MES_ANONONLY)
1202                              || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1203                                 size_t tmp = strlen(display_name);
1204                                 snprintf(&display_name[tmp],
1205                                          sizeof display_name - tmp,
1206                                          " [%s]", buf);
1207                         }
1208                 }
1209
1210                 /* Don't show Internet address for users on the
1211                  * local Citadel network.
1212                  */
1213                 suppress_f = 0;
1214                 if (TheMessage->cm_fields['N'] != NULL)
1215                    if (strlen(TheMessage->cm_fields['N']) > 0)
1216                       if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1217                         suppress_f = 1;
1218                 }
1219                 
1220                 /* Now spew the header fields in the order we like them. */
1221                 strcpy(allkeys, FORDER);
1222                 for (i=0; i<strlen(allkeys); ++i) {
1223                         k = (int) allkeys[i];
1224                         if (k != 'M') {
1225                                 if ( (TheMessage->cm_fields[k] != NULL)
1226                                    && (msgkeys[k] != NULL) ) {
1227                                         if (k == 'A') {
1228                                                 if (do_proto) cprintf("%s=%s\n",
1229                                                         msgkeys[k],
1230                                                         display_name);
1231                                         }
1232                                         else if ((k == 'F') && (suppress_f)) {
1233                                                 /* do nothing */
1234                                         }
1235                                         /* Masquerade display name if needed */
1236                                         else {
1237                                                 if (do_proto) cprintf("%s=%s\n",
1238                                                         msgkeys[k],
1239                                                         TheMessage->cm_fields[k]
1240                                         );
1241                                         }
1242                                 }
1243                         }
1244                 }
1245
1246         }
1247
1248         /* begin header processing loop for RFC822 transfer format */
1249
1250         strcpy(suser, "");
1251         strcpy(luser, "");
1252         strcpy(fuser, "");
1253         strcpy(snode, NODENAME);
1254         strcpy(lnode, HUMANNODE);
1255         if (mode == MT_RFC822) {
1256                 cprintf("X-UIDL: %ld%s", msg_num, nl);
1257                 for (i = 0; i < 256; ++i) {
1258                         if (TheMessage->cm_fields[i]) {
1259                                 mptr = TheMessage->cm_fields[i];
1260
1261                                 if (i == 'A') {
1262                                         strcpy(luser, mptr);
1263                                         strcpy(suser, mptr);
1264                                 }
1265 /****
1266  "Path:" removed for now because it confuses brain-dead Microsoft shitware
1267  into thinking that mail messages are newsgroup messages instead.  When we
1268  add NNTP support back into Citadel we'll have to add code to only output
1269  this field when appropriate.
1270                                 else if (i == 'P') {
1271                                         cprintf("Path: %s%s", mptr, nl);
1272                                 }
1273  ****/
1274                                 else if (i == 'U') {
1275                                         cprintf("Subject: %s%s", mptr, nl);
1276                                         subject_found = 1;
1277                                 }
1278                                 else if (i == 'I')
1279                                         safestrncpy(mid, mptr, sizeof mid);
1280                                 else if (i == 'H')
1281                                         safestrncpy(lnode, mptr, sizeof lnode);
1282                                 else if (i == 'F')
1283                                         safestrncpy(fuser, mptr, sizeof fuser);
1284                                 else if (i == 'O')
1285                                         cprintf("X-Citadel-Room: %s%s",
1286                                                 mptr, nl);
1287                                 else if (i == 'N')
1288                                         safestrncpy(snode, mptr, sizeof snode);
1289                                 else if (i == 'R')
1290                                         cprintf("To: %s%s", mptr, nl);
1291                                 else if (i == 'T') {
1292                                         datestring(datestamp, sizeof datestamp,
1293                                                 atol(mptr), DATESTRING_RFC822);
1294                                         cprintf("Date: %s%s", datestamp, nl);
1295                                 }
1296                         }
1297                 }
1298                 if (subject_found == 0) {
1299                         cprintf("Subject: (no subject)%s", nl);
1300                 }
1301         }
1302
1303         for (i=0; i<strlen(suser); ++i) {
1304                 suser[i] = tolower(suser[i]);
1305                 if (!isalnum(suser[i])) suser[i]='_';
1306         }
1307
1308         if (mode == MT_RFC822) {
1309                 if (!strcasecmp(snode, NODENAME)) {
1310                         strcpy(snode, FQDN);
1311                 }
1312
1313                 /* Construct a fun message id */
1314                 cprintf("Message-ID: <%s", mid);
1315                 if (strchr(mid, '@')==NULL) {
1316                         cprintf("@%s", snode);
1317                 }
1318                 cprintf(">%s", nl);
1319
1320                 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1321
1322                 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1323                         cprintf("From: x@x.org (----)%s", nl);
1324                 }
1325                 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1326                         cprintf("From: x@x.org (anonymous)%s", nl);
1327                 }
1328                 else if (strlen(fuser) > 0) {
1329                         cprintf("From: %s (%s)%s", fuser, luser, nl);
1330                 }
1331                 else {
1332                         cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1333                 }
1334
1335                 cprintf("Organization: %s%s", lnode, nl);
1336
1337                 /* Blank line signifying RFC822 end-of-headers */
1338                 if (TheMessage->cm_format_type != FMT_RFC822) {
1339                         cprintf("%s", nl);
1340                 }
1341         }
1342
1343         /* end header processing loop ... at this point, we're in the text */
1344 START_TEXT:
1345         mptr = TheMessage->cm_fields['M'];
1346
1347         /* Tell the client about the MIME parts in this message */
1348         if (TheMessage->cm_format_type == FMT_RFC822) {
1349                 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1350                         mime_parser(mptr, NULL,
1351                                 *list_this_part,
1352                                 *list_this_pref,
1353                                 *list_this_suff,
1354                                 NULL, 0);
1355                 }
1356                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
1357                         /* FIXME ... we have to put some code in here to avoid
1358                          * printing duplicate header information when both
1359                          * Citadel and RFC822 headers exist.  Preference should
1360                          * probably be given to the RFC822 headers.
1361                          */
1362                         int done_rfc822_hdrs = 0;
1363                         while (ch=*(mptr++), ch!=0) {
1364                                 if (ch==13) {
1365                                         /* do nothing */
1366                                 }
1367                                 else if (ch==10) {
1368                                         if (!done_rfc822_hdrs) {
1369                                                 if (headers_only != HEADERS_NONE) {
1370                                                         cprintf("%s", nl);
1371                                                 }
1372                                         }
1373                                         else {
1374                                                 if (headers_only != HEADERS_ONLY) {
1375                                                         cprintf("%s", nl);
1376                                                 }
1377                                         }
1378                                         if ((*(mptr) == 13) || (*(mptr) == 10)) {
1379                                                 done_rfc822_hdrs = 1;
1380                                         }
1381                                 }
1382                                 else {
1383                                         if (done_rfc822_hdrs) {
1384                                                 if (headers_only != HEADERS_NONE) {
1385                                                         cprintf("%c", ch);
1386                                                 }
1387                                         }
1388                                         else {
1389                                                 if (headers_only != HEADERS_ONLY) {
1390                                                         cprintf("%c", ch);
1391                                                 }
1392                                         }
1393                                         if ((*mptr == 13) || (*mptr == 10)) {
1394                                                 done_rfc822_hdrs = 1;
1395                                         }
1396                                 }
1397                         }
1398                         goto DONE;
1399                 }
1400         }
1401
1402         if (headers_only == HEADERS_ONLY) {
1403                 goto DONE;
1404         }
1405
1406         /* signify start of msg text */
1407         if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1408                 if (do_proto) cprintf("text\n");
1409         }
1410
1411         /* If the format type on disk is 1 (fixed-format), then we want
1412          * everything to be output completely literally ... regardless of
1413          * what message transfer format is in use.
1414          */
1415         if (TheMessage->cm_format_type == FMT_FIXED) {
1416                 if (mode == MT_MIME) {
1417                         cprintf("Content-type: text/plain\n\n");
1418                 }
1419                 strcpy(buf, "");
1420                 while (ch = *mptr++, ch > 0) {
1421                         if (ch == 13)
1422                                 ch = 10;
1423                         if ((ch == 10) || (strlen(buf) > 250)) {
1424                                 cprintf("%s%s", buf, nl);
1425                                 strcpy(buf, "");
1426                         } else {
1427                                 buf[strlen(buf) + 1] = 0;
1428                                 buf[strlen(buf)] = ch;
1429                         }
1430                 }
1431                 if (strlen(buf) > 0)
1432                         cprintf("%s%s", buf, nl);
1433         }
1434
1435         /* If the message on disk is format 0 (Citadel vari-format), we
1436          * output using the formatter at 80 columns.  This is the final output
1437          * form if the transfer format is RFC822, but if the transfer format
1438          * is Citadel proprietary, it'll still work, because the indentation
1439          * for new paragraphs is correct and the client will reformat the
1440          * message to the reader's screen width.
1441          */
1442         if (TheMessage->cm_format_type == FMT_CITADEL) {
1443                 if (mode == MT_MIME) {
1444                         cprintf("Content-type: text/x-citadel-variformat\n\n");
1445                 }
1446                 memfmout(80, mptr, 0, nl);
1447         }
1448
1449         /* If the message on disk is format 4 (MIME), we've gotta hand it
1450          * off to the MIME parser.  The client has already been told that
1451          * this message is format 1 (fixed format), so the callback function
1452          * we use will display those parts as-is.
1453          */
1454         if (TheMessage->cm_format_type == FMT_RFC822) {
1455                 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1456                 memset(ma, 0, sizeof(struct ma_info));
1457
1458                 if (mode == MT_MIME) {
1459                         strcpy(ma->chosen_part, "1");
1460                         mime_parser(mptr, NULL,
1461                                 *choose_preferred, *fixed_output_pre,
1462                                 *fixed_output_post, NULL, 0);
1463                         mime_parser(mptr, NULL,
1464                                 *output_preferred, NULL, NULL, NULL, 0);
1465                 }
1466                 else {
1467                         mime_parser(mptr, NULL,
1468                                 *fixed_output, *fixed_output_pre,
1469                                 *fixed_output_post, NULL, 0);
1470                 }
1471         }
1472
1473 DONE:   /* now we're done */
1474         if (do_proto) cprintf("000\n");
1475         return(om_ok);
1476 }
1477
1478
1479
1480 /*
1481  * display a message (mode 0 - Citadel proprietary)
1482  */
1483 void cmd_msg0(char *cmdbuf)
1484 {
1485         long msgid;
1486         int headers_only = HEADERS_ALL;
1487
1488         msgid = extract_long(cmdbuf, 0);
1489         headers_only = extract_int(cmdbuf, 1);
1490
1491         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1492         return;
1493 }
1494
1495
1496 /*
1497  * display a message (mode 2 - RFC822)
1498  */
1499 void cmd_msg2(char *cmdbuf)
1500 {
1501         long msgid;
1502         int headers_only = HEADERS_ALL;
1503
1504         msgid = extract_long(cmdbuf, 0);
1505         headers_only = extract_int(cmdbuf, 1);
1506
1507         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1508 }
1509
1510
1511
1512 /* 
1513  * display a message (mode 3 - IGnet raw format - internal programs only)
1514  */
1515 void cmd_msg3(char *cmdbuf)
1516 {
1517         long msgnum;
1518         struct CtdlMessage *msg;
1519         struct ser_ret smr;
1520
1521         if (CC->internal_pgm == 0) {
1522                 cprintf("%d This command is for internal programs only.\n",
1523                         ERROR);
1524                 return;
1525         }
1526
1527         msgnum = extract_long(cmdbuf, 0);
1528         msg = CtdlFetchMessage(msgnum);
1529         if (msg == NULL) {
1530                 cprintf("%d Message %ld not found.\n", 
1531                         ERROR, msgnum);
1532                 return;
1533         }
1534
1535         serialize_message(&smr, msg);
1536         CtdlFreeMessage(msg);
1537
1538         if (smr.len == 0) {
1539                 cprintf("%d Unable to serialize message\n",
1540                         ERROR+INTERNAL_ERROR);
1541                 return;
1542         }
1543
1544         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1545         client_write(smr.ser, smr.len);
1546         phree(smr.ser);
1547 }
1548
1549
1550
1551 /* 
1552  * Display a message using MIME content types
1553  */
1554 void cmd_msg4(char *cmdbuf)
1555 {
1556         long msgid;
1557
1558         msgid = extract_long(cmdbuf, 0);
1559         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1560 }
1561
1562
1563
1564 /* 
1565  * Client tells us its preferred message format(s)
1566  */
1567 void cmd_msgp(char *cmdbuf)
1568 {
1569         safestrncpy(CC->preferred_formats, cmdbuf,
1570                         sizeof(CC->preferred_formats));
1571         cprintf("%d ok\n", CIT_OK);
1572 }
1573
1574
1575 /*
1576  * Open a component of a MIME message as a download file 
1577  */
1578 void cmd_opna(char *cmdbuf)
1579 {
1580         long msgid;
1581
1582         CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1583
1584         msgid = extract_long(cmdbuf, 0);
1585         extract(desired_section, cmdbuf, 1);
1586
1587         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1588 }                       
1589
1590
1591 /*
1592  * Save a message pointer into a specified room
1593  * (Returns 0 for success, nonzero for failure)
1594  * roomname may be NULL to use the current room
1595  */
1596 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1597         int i;
1598         char hold_rm[ROOMNAMELEN];
1599         struct cdbdata *cdbfr;
1600         int num_msgs;
1601         long *msglist;
1602         long highest_msg = 0L;
1603         struct CtdlMessage *msg = NULL;
1604
1605         lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1606                 roomname, msgid, flags);
1607
1608         strcpy(hold_rm, CC->room.QRname);
1609
1610         /* We may need to check to see if this message is real */
1611         if (  (flags & SM_VERIFY_GOODNESS)
1612            || (flags & SM_DO_REPL_CHECK)
1613            ) {
1614                 msg = CtdlFetchMessage(msgid);
1615                 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1616         }
1617
1618         /* Perform replication checks if necessary */
1619         if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1620
1621                 if (getroom(&CC->room,
1622                    ((roomname != NULL) ? roomname : CC->room.QRname) )
1623                    != 0) {
1624                         lprintf(9, "No such room <%s>\n", roomname);
1625                         if (msg != NULL) CtdlFreeMessage(msg);
1626                         return(ERROR + ROOM_NOT_FOUND);
1627                 }
1628
1629                 if (ReplicationChecks(msg) != 0) {
1630                         getroom(&CC->room, hold_rm);
1631                         if (msg != NULL) CtdlFreeMessage(msg);
1632                         lprintf(9, "Did replication, and newer exists\n");
1633                         return(0);
1634                 }
1635         }
1636
1637         /* Now the regular stuff */
1638         if (lgetroom(&CC->room,
1639            ((roomname != NULL) ? roomname : CC->room.QRname) )
1640            != 0) {
1641                 lprintf(9, "No such room <%s>\n", roomname);
1642                 if (msg != NULL) CtdlFreeMessage(msg);
1643                 return(ERROR + ROOM_NOT_FOUND);
1644         }
1645
1646         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1647         if (cdbfr == NULL) {
1648                 msglist = NULL;
1649                 num_msgs = 0;
1650         } else {
1651                 msglist = mallok(cdbfr->len);
1652                 if (msglist == NULL)
1653                         lprintf(3, "ERROR malloc msglist!\n");
1654                 num_msgs = cdbfr->len / sizeof(long);
1655                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1656                 cdb_free(cdbfr);
1657         }
1658
1659
1660         /* Make sure the message doesn't already exist in this room.  It
1661          * is absolutely taboo to have more than one reference to the same
1662          * message in a room.
1663          */
1664         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1665                 if (msglist[i] == msgid) {
1666                         lputroom(&CC->room);    /* unlock the room */
1667                         getroom(&CC->room, hold_rm);
1668                         if (msg != NULL) CtdlFreeMessage(msg);
1669                         return(ERROR + ALREADY_EXISTS);
1670                 }
1671         }
1672
1673         /* Now add the new message */
1674         ++num_msgs;
1675         msglist = reallok(msglist,
1676                           (num_msgs * sizeof(long)));
1677
1678         if (msglist == NULL) {
1679                 lprintf(3, "ERROR: can't realloc message list!\n");
1680         }
1681         msglist[num_msgs - 1] = msgid;
1682
1683         /* Sort the message list, so all the msgid's are in order */
1684         num_msgs = sort_msglist(msglist, num_msgs);
1685
1686         /* Determine the highest message number */
1687         highest_msg = msglist[num_msgs - 1];
1688
1689         /* Write it back to disk. */
1690         cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long),
1691                   msglist, num_msgs * sizeof(long));
1692
1693         /* Free up the memory we used. */
1694         phree(msglist);
1695
1696         /* Update the highest-message pointer and unlock the room. */
1697         CC->room.QRhighest = highest_msg;
1698         lputroom(&CC->room);
1699         getroom(&CC->room, hold_rm);
1700
1701         /* Bump the reference count for this message. */
1702         if ((flags & SM_DONT_BUMP_REF)==0) {
1703                 AdjRefCount(msgid, +1);
1704         }
1705
1706         /* Return success. */
1707         if (msg != NULL) CtdlFreeMessage(msg);
1708         return (0);
1709 }
1710
1711
1712
1713 /*
1714  * Message base operation to send a message to the master file
1715  * (returns new message number)
1716  *
1717  * This is the back end for CtdlSubmitMsg() and should not be directly
1718  * called by server-side modules.
1719  *
1720  */
1721 long send_message(struct CtdlMessage *msg,      /* pointer to buffer */
1722                 FILE *save_a_copy)              /* save a copy to disk? */
1723 {
1724         long newmsgid;
1725         long retval;
1726         char msgidbuf[SIZ];
1727         struct ser_ret smr;
1728
1729         /* Get a new message number */
1730         newmsgid = get_new_message_number();
1731         snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1732
1733         /* Generate an ID if we don't have one already */
1734         if (msg->cm_fields['I']==NULL) {
1735                 msg->cm_fields['I'] = strdoop(msgidbuf);
1736         }
1737         
1738         serialize_message(&smr, msg);
1739
1740         if (smr.len == 0) {
1741                 cprintf("%d Unable to serialize message\n",
1742                         ERROR+INTERNAL_ERROR);
1743                 return (-1L);
1744         }
1745
1746         /* Write our little bundle of joy into the message base */
1747         if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1748                       smr.ser, smr.len) < 0) {
1749                 lprintf(2, "Can't store message\n");
1750                 retval = 0L;
1751         } else {
1752                 retval = newmsgid;
1753         }
1754
1755         /* If the caller specified that a copy should be saved to a particular
1756          * file handle, do that now too.
1757          */
1758         if (save_a_copy != NULL) {
1759                 fwrite(smr.ser, smr.len, 1, save_a_copy);
1760         }
1761
1762         /* Free the memory we used for the serialized message */
1763         phree(smr.ser);
1764
1765         /* Return the *local* message ID to the caller
1766          * (even if we're storing an incoming network message)
1767          */
1768         return(retval);
1769 }
1770
1771
1772
1773 /*
1774  * Serialize a struct CtdlMessage into the format used on disk and network.
1775  * 
1776  * This function loads up a "struct ser_ret" (defined in server.h) which
1777  * contains the length of the serialized message and a pointer to the
1778  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
1779  */
1780 void serialize_message(struct ser_ret *ret,             /* return values */
1781                         struct CtdlMessage *msg)        /* unserialized msg */
1782 {
1783         size_t wlen;
1784         int i;
1785         static char *forder = FORDER;
1786
1787         if (is_valid_message(msg) == 0) return;         /* self check */
1788
1789         ret->len = 3;
1790         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1791                 ret->len = ret->len +
1792                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
1793
1794         lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1795         ret->ser = mallok(ret->len);
1796         if (ret->ser == NULL) {
1797                 ret->len = 0;
1798                 return;
1799         }
1800
1801         ret->ser[0] = 0xFF;
1802         ret->ser[1] = msg->cm_anon_type;
1803         ret->ser[2] = msg->cm_format_type;
1804         wlen = 3;
1805
1806         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1807                 ret->ser[wlen++] = (char)forder[i];
1808                 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1809                 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1810         }
1811         if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1812                 (long)ret->len, (long)wlen);
1813
1814         return;
1815 }
1816
1817
1818
1819 /*
1820  * Back end for the ReplicationChecks() function
1821  */
1822 void check_repl(long msgnum, void *userdata) {
1823         struct CtdlMessage *msg;
1824         time_t timestamp = (-1L);
1825
1826         lprintf(9, "check_repl() found message %ld\n", msgnum);
1827         msg = CtdlFetchMessage(msgnum);
1828         if (msg == NULL) return;
1829         if (msg->cm_fields['T'] != NULL) {
1830                 timestamp = atol(msg->cm_fields['T']);
1831         }
1832         CtdlFreeMessage(msg);
1833
1834         if (timestamp > msg_repl->highest) {
1835                 msg_repl->highest = timestamp;  /* newer! */
1836                 lprintf(9, "newer!\n");
1837                 return;
1838         }
1839         lprintf(9, "older!\n");
1840
1841         /* Existing isn't newer?  Then delete the old one(s). */
1842         CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1843 }
1844
1845
1846 /*
1847  * Check to see if any messages already exist which carry the same Extended ID
1848  * as this one.  
1849  *
1850  * If any are found:
1851  * -> With older timestamps: delete them and return 0.  Message will be saved.
1852  * -> With newer timestamps: return 1.  Message save will be aborted.
1853  */
1854 int ReplicationChecks(struct CtdlMessage *msg) {
1855         struct CtdlMessage *template;
1856         int abort_this = 0;
1857
1858         lprintf(9, "ReplicationChecks() started\n");
1859         /* No extended id?  Don't do anything. */
1860         if (msg->cm_fields['E'] == NULL) return 0;
1861         if (strlen(msg->cm_fields['E']) == 0) return 0;
1862         lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1863
1864         CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1865         strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1866         msg_repl->highest = atol(msg->cm_fields['T']);
1867
1868         template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1869         memset(template, 0, sizeof(struct CtdlMessage));
1870         template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1871
1872         CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1873
1874         /* If a newer message exists with the same Extended ID, abort
1875          * this save.
1876          */
1877         if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1878                 abort_this = 1;
1879                 }
1880
1881         CtdlFreeMessage(template);
1882         lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1883         return(abort_this);
1884 }
1885
1886
1887
1888
1889 /*
1890  * Save a message to disk and submit it into the delivery system.
1891  */
1892 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
1893                 struct recptypes *recps,        /* recipients (if mail) */
1894                 char *force                     /* force a particular room? */
1895 ) {
1896         char aaa[SIZ];
1897         char hold_rm[ROOMNAMELEN];
1898         char actual_rm[ROOMNAMELEN];
1899         char force_room[ROOMNAMELEN];
1900         char content_type[SIZ];                 /* We have to learn this */
1901         char recipient[SIZ];
1902         long newmsgid;
1903         char *mptr = NULL;
1904         struct ctdluser userbuf;
1905         int a, i;
1906         struct MetaData smi;
1907         FILE *network_fp = NULL;
1908         static int seqnum = 1;
1909         struct CtdlMessage *imsg = NULL;
1910         char *instr;
1911         struct ser_ret smr;
1912         char *hold_R, *hold_D;
1913
1914         lprintf(9, "CtdlSubmitMsg() called\n");
1915         if (is_valid_message(msg) == 0) return(-1);     /* self check */
1916
1917         /* If this message has no timestamp, we take the liberty of
1918          * giving it one, right now.
1919          */
1920         if (msg->cm_fields['T'] == NULL) {
1921                 lprintf(9, "Generating timestamp\n");
1922                 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1923                 msg->cm_fields['T'] = strdoop(aaa);
1924         }
1925
1926         /* If this message has no path, we generate one.
1927          */
1928         if (msg->cm_fields['P'] == NULL) {
1929                 lprintf(9, "Generating path\n");
1930                 if (msg->cm_fields['A'] != NULL) {
1931                         msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1932                         for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1933                                 if (isspace(msg->cm_fields['P'][a])) {
1934                                         msg->cm_fields['P'][a] = ' ';
1935                                 }
1936                         }
1937                 }
1938                 else {
1939                         msg->cm_fields['P'] = strdoop("unknown");
1940                 }
1941         }
1942
1943         if (force == NULL) {
1944                 strcpy(force_room, "");
1945         }
1946         else {
1947                 strcpy(force_room, force);
1948         }
1949
1950         /* Learn about what's inside, because it's what's inside that counts */
1951         lprintf(9, "Learning what's inside\n");
1952         if (msg->cm_fields['M'] == NULL) {
1953                 lprintf(1, "ERROR: attempt to save message with NULL body\n");
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(9, "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(9, "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(9, "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(9, "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(9, "Performing replication checks\n");
2026         if (ReplicationChecks(msg) > 0) return(-1);
2027
2028         /* Save it to disk */
2029         lprintf(9, "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(9, "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(9, "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(3, "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(9, "Delivering to local room <%s>\n", recipient);
2070                 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2071         }
2072
2073         /* Bump this user's messages posted counter. */
2074         lprintf(9, "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(9, "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(9, "No user <%s>\n", recipient);
2095                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2096                 }
2097         }
2098
2099         /* Perform "after save" hooks */
2100         lprintf(9, "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(9, "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(9, "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(9, "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(9, "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(9, "validate_recipients()\n");
2611         lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2612         lprintf(9, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
2613         lprintf(9, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2614         lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2615         lprintf(9, " 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(9, "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(7, "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(9, "%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, 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
2934         num = extract_long(args, 0);
2935         extract(targ, args, 1);
2936         targ[ROOMNAMELEN - 1] = 0;
2937         is_copy = extract_int(args, 2);
2938
2939         if (getroom(&qtemp, targ) != 0) {
2940                 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2941                 return;
2942         }
2943
2944         getuser(&CC->user, CC->curr_user);
2945         /* Aides can move/copy */
2946         if ((CC->user.axlevel < 6)
2947             /* Roomaides can move/copy */
2948             && (CC->user.usernum != CC->room.QRroomaide)
2949             /* Permit move/copy to/from personal rooms */
2950             && (!((CC->room.QRflags & QR_MAILBOX)
2951                             && (qtemp.QRflags & QR_MAILBOX)))
2952             /* Permit only copy from public to personal room */
2953             && (!(is_copy && !(CC->room.QRflags & QR_MAILBOX)
2954                             && (qtemp.QRflags & QR_MAILBOX)))) {
2955                 cprintf("%d Higher access required.\n",
2956                         ERROR + HIGHER_ACCESS_REQUIRED);
2957                 return;
2958         }
2959
2960         err = CtdlCopyMsgToRoom(num, targ);
2961         if (err != 0) {
2962                 cprintf("%d Cannot store message in %s: error %d\n",
2963                         err, targ, err);
2964                 return;
2965         }
2966
2967         /* Now delete the message from the source room,
2968          * if this is a 'move' rather than a 'copy' operation.
2969          */
2970         if (is_copy == 0) {
2971                 CtdlDeleteMessages(CC->room.QRname, num, "");
2972         }
2973
2974         cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
2975 }
2976
2977
2978
2979 /*
2980  * GetMetaData()  -  Get the supplementary record for a message
2981  */
2982 void GetMetaData(struct MetaData *smibuf, long msgnum)
2983 {
2984
2985         struct cdbdata *cdbsmi;
2986         long TheIndex;
2987
2988         memset(smibuf, 0, sizeof(struct MetaData));
2989         smibuf->meta_msgnum = msgnum;
2990         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
2991
2992         /* Use the negative of the message number for its supp record index */
2993         TheIndex = (0L - msgnum);
2994
2995         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2996         if (cdbsmi == NULL) {
2997                 return;         /* record not found; go with defaults */
2998         }
2999         memcpy(smibuf, cdbsmi->ptr,
3000                ((cdbsmi->len > sizeof(struct MetaData)) ?
3001                 sizeof(struct MetaData) : cdbsmi->len));
3002         cdb_free(cdbsmi);
3003         return;
3004 }
3005
3006
3007 /*
3008  * PutMetaData()  -  (re)write supplementary record for a message
3009  */
3010 void PutMetaData(struct MetaData *smibuf)
3011 {
3012         long TheIndex;
3013
3014         /* Use the negative of the message number for the metadata db index */
3015         TheIndex = (0L - smibuf->meta_msgnum);
3016
3017         lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
3018                 smibuf->meta_msgnum, smibuf->meta_refcount);
3019
3020         cdb_store(CDB_MSGMAIN,
3021                   &TheIndex, sizeof(long),
3022                   smibuf, sizeof(struct MetaData));
3023
3024 }
3025
3026 /*
3027  * AdjRefCount  -  change the reference count for a message;
3028  *                 delete the message if it reaches zero
3029  */
3030 void AdjRefCount(long msgnum, int incr)
3031 {
3032
3033         struct MetaData smi;
3034         long delnum;
3035
3036         /* This is a *tight* critical section; please keep it that way, as
3037          * it may get called while nested in other critical sections.  
3038          * Complicating this any further will surely cause deadlock!
3039          */
3040         begin_critical_section(S_SUPPMSGMAIN);
3041         GetMetaData(&smi, msgnum);
3042         lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
3043                 msgnum, smi.meta_refcount);
3044         smi.meta_refcount += incr;
3045         PutMetaData(&smi);
3046         end_critical_section(S_SUPPMSGMAIN);
3047         lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
3048                 msgnum, smi.meta_refcount);
3049
3050         /* If the reference count is now zero, delete the message
3051          * (and its supplementary record as well).
3052          */
3053         if (smi.meta_refcount == 0) {
3054                 lprintf(9, "Deleting message <%ld>\n", msgnum);
3055                 delnum = msgnum;
3056                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3057
3058                 /* We have to delete the metadata record too! */
3059                 delnum = (0L - msgnum);
3060                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3061         }
3062 }
3063
3064 /*
3065  * Write a generic object to this room
3066  *
3067  * Note: this could be much more efficient.  Right now we use two temporary
3068  * files, and still pull the message into memory as with all others.
3069  */
3070 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
3071                         char *content_type,     /* MIME type of this object */
3072                         char *tempfilename,     /* Where to fetch it from */
3073                         struct ctdluser *is_mailbox,    /* Mailbox room? */
3074                         int is_binary,          /* Is encoding necessary? */
3075                         int is_unique,          /* Del others of this type? */
3076                         unsigned int flags      /* Internal save flags */
3077                         )
3078 {
3079
3080         FILE *fp;
3081         struct ctdlroom qrbuf;
3082         char roomname[ROOMNAMELEN];
3083         struct CtdlMessage *msg;
3084
3085         char *raw_message = NULL;
3086         char *encoded_message = NULL;
3087         off_t raw_length = 0;
3088
3089         if (is_mailbox != NULL)
3090                 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3091         else
3092                 safestrncpy(roomname, req_room, sizeof(roomname));
3093         lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3094
3095
3096         fp = fopen(tempfilename, "rb");
3097         if (fp == NULL) {
3098                 lprintf(5, "Cannot open %s: %s\n",
3099                         tempfilename, strerror(errno));
3100                 return;
3101         }
3102         fseek(fp, 0L, SEEK_END);
3103         raw_length = ftell(fp);
3104         rewind(fp);
3105         lprintf(9, "Raw length is %ld\n", (long)raw_length);
3106
3107         raw_message = mallok((size_t)raw_length + 2);
3108         fread(raw_message, (size_t)raw_length, 1, fp);
3109         fclose(fp);
3110
3111         if (is_binary) {
3112                 encoded_message = mallok((size_t)
3113                         (((raw_length * 134) / 100) + 4096 ) );
3114         }
3115         else {
3116                 encoded_message = mallok((size_t)(raw_length + 4096));
3117         }
3118
3119         sprintf(encoded_message, "Content-type: %s\n", content_type);
3120
3121         if (is_binary) {
3122                 sprintf(&encoded_message[strlen(encoded_message)],
3123                         "Content-transfer-encoding: base64\n\n"
3124                 );
3125         }
3126         else {
3127                 sprintf(&encoded_message[strlen(encoded_message)],
3128                         "Content-transfer-encoding: 7bit\n\n"
3129                 );
3130         }
3131
3132         if (is_binary) {
3133                 CtdlEncodeBase64(
3134                         &encoded_message[strlen(encoded_message)],
3135                         raw_message,
3136                         (int)raw_length
3137                 );
3138         }
3139         else {
3140                 raw_message[raw_length] = 0;
3141                 memcpy(
3142                         &encoded_message[strlen(encoded_message)],
3143                         raw_message,
3144                         (int)(raw_length+1)
3145                 );
3146         }
3147
3148         phree(raw_message);
3149
3150         lprintf(9, "Allocating\n");
3151         msg = mallok(sizeof(struct CtdlMessage));
3152         memset(msg, 0, sizeof(struct CtdlMessage));
3153         msg->cm_magic = CTDLMESSAGE_MAGIC;
3154         msg->cm_anon_type = MES_NORMAL;
3155         msg->cm_format_type = 4;
3156         msg->cm_fields['A'] = strdoop(CC->user.fullname);
3157         msg->cm_fields['O'] = strdoop(req_room);
3158         msg->cm_fields['N'] = strdoop(config.c_nodename);
3159         msg->cm_fields['H'] = strdoop(config.c_humannode);
3160         msg->cm_flags = flags;
3161         
3162         msg->cm_fields['M'] = encoded_message;
3163
3164         /* Create the requested room if we have to. */
3165         if (getroom(&qrbuf, roomname) != 0) {
3166                 create_room(roomname, 
3167                         ( (is_mailbox != NULL) ? 5 : 3 ),
3168                         "", 0, 1, 0);
3169         }
3170         /* If the caller specified this object as unique, delete all
3171          * other objects of this type that are currently in the room.
3172          */
3173         if (is_unique) {
3174                 lprintf(9, "Deleted %d other msgs of this type\n",
3175                         CtdlDeleteMessages(roomname, 0L, content_type));
3176         }
3177         /* Now write the data */
3178         CtdlSubmitMsg(msg, NULL, roomname);
3179         CtdlFreeMessage(msg);
3180 }
3181
3182
3183
3184
3185
3186
3187 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3188         config_msgnum = msgnum;
3189 }
3190
3191
3192 char *CtdlGetSysConfig(char *sysconfname) {
3193         char hold_rm[ROOMNAMELEN];
3194         long msgnum;
3195         char *conf;
3196         struct CtdlMessage *msg;
3197         char buf[SIZ];
3198         
3199         strcpy(hold_rm, CC->room.QRname);
3200         if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3201                 getroom(&CC->room, hold_rm);
3202                 return NULL;
3203         }
3204
3205
3206         /* We want the last (and probably only) config in this room */
3207         begin_critical_section(S_CONFIG);
3208         config_msgnum = (-1L);
3209         CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3210                 CtdlGetSysConfigBackend, NULL);
3211         msgnum = config_msgnum;
3212         end_critical_section(S_CONFIG);
3213
3214         if (msgnum < 0L) {
3215                 conf = NULL;
3216         }
3217         else {
3218                 msg = CtdlFetchMessage(msgnum);
3219                 if (msg != NULL) {
3220                         conf = strdoop(msg->cm_fields['M']);
3221                         CtdlFreeMessage(msg);
3222                 }
3223                 else {
3224                         conf = NULL;
3225                 }
3226         }
3227
3228         getroom(&CC->room, hold_rm);
3229
3230         if (conf != NULL) do {
3231                 extract_token(buf, conf, 0, '\n');
3232                 strcpy(conf, &conf[strlen(buf)+1]);
3233         } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3234
3235         return(conf);
3236 }
3237
3238 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3239         char temp[PATH_MAX];
3240         FILE *fp;
3241
3242         strcpy(temp, tmpnam(NULL));
3243
3244         fp = fopen(temp, "w");
3245         if (fp == NULL) return;
3246         fprintf(fp, "%s", sysconfdata);
3247         fclose(fp);
3248
3249         /* this handy API function does all the work for us */
3250         CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3251         unlink(temp);
3252 }
3253
3254
3255 /*
3256  * Determine whether a given Internet address belongs to the current user
3257  */
3258 int CtdlIsMe(char *addr) {
3259         struct recptypes *recp;
3260         int i;
3261
3262         recp = validate_recipients(addr);
3263         if (recp == NULL) return(0);
3264
3265         if (recp->num_local == 0) {
3266                 phree(recp);
3267                 return(0);
3268         }
3269
3270         for (i=0; i<recp->num_local; ++i) {
3271                 extract(addr, recp->recp_local, i);
3272                 if (!strcasecmp(addr, CC->user.fullname)) {
3273                         phree(recp);
3274                         return(1);
3275                 }
3276         }
3277
3278         phree(recp);
3279         return(0);
3280 }
3281
3282
3283 /*
3284  * Citadel protocol command to do the same
3285  */
3286 void cmd_isme(char *argbuf) {
3287         char addr[SIZ];
3288
3289         if (CtdlAccessCheck(ac_logged_in)) return;
3290         extract(addr, argbuf, 0);
3291
3292         if (CtdlIsMe(addr)) {
3293                 cprintf("%d %s\n", CIT_OK, addr);
3294         }
3295         else {
3296                 cprintf("%d Not you.\n", ERROR);
3297         }
3298
3299 }