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