* Slightly more computationally efficient version of memfmout() that doesn't use...
[citadel.git] / citadel / msgbase.c
1 /*
2  * $Id$
3  *
4  * Implements the message store.
5  * 
6  * Copyright (c) 1987-2010 by the citadel.org team
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 3 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include "sysdep.h"
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <stdio.h>
27 #include <fcntl.h>
28
29 #if TIME_WITH_SYS_TIME
30 # include <sys/time.h>
31 # include <time.h>
32 #else
33 # if HAVE_SYS_TIME_H
34 #  include <sys/time.h>
35 # else
36 #  include <time.h>
37 # endif
38 #endif
39
40
41 #include <ctype.h>
42 #include <string.h>
43 #include <limits.h>
44 #include <errno.h>
45 #include <stdarg.h>
46 #include <sys/stat.h>
47 #include <sys/types.h>
48 #include <regex.h>
49 #include <libcitadel.h>
50 #include "citadel.h"
51 #include "server.h"
52 #include "serv_extensions.h"
53 #include "database.h"
54 #include "msgbase.h"
55 #include "support.h"
56 #include "sysdep_decls.h"
57 #include "citserver.h"
58 #include "room_ops.h"
59 #include "user_ops.h"
60 #include "file_ops.h"
61 #include "config.h"
62 #include "control.h"
63 #include "genstamp.h"
64 #include "internet_addressing.h"
65 #include "euidindex.h"
66 #include "journaling.h"
67 #include "citadel_dirs.h"
68 #include "clientsocket.h"
69 #include "serv_network.h"
70 #include "threads.h"
71
72 #include "ctdl_module.h"
73
74 long config_msgnum;
75 struct addresses_to_be_filed *atbf = NULL;
76
77 /* This temp file holds the queue of operations for AdjRefCount() */
78 static FILE *arcfp = NULL;
79
80 /* 
81  * This really belongs in serv_network.c, but I don't know how to export
82  * symbols between modules.
83  */
84 struct FilterList *filterlist = NULL;
85
86
87 /*
88  * These are the four-character field headers we use when outputting
89  * messages in Citadel format (as opposed to RFC822 format).
90  */
91 char *msgkeys[] = {
92         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
93         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
94         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
95         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
96         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
97         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
98         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
99         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
100         NULL, 
101         "from",
102         NULL, NULL, NULL,
103         "exti",
104         "rfca",
105         NULL, 
106         "hnod",
107         "msgn",
108         "jrnl",
109         NULL,
110         "list",
111         "text",
112         "node",
113         "room",
114         "path",
115         NULL,
116         "rcpt",
117         "spec",
118         "time",
119         "subj",
120         NULL,
121         "wefw",
122         NULL,
123         "cccc",
124         NULL
125 };
126
127 /*
128  * This function is self explanatory.
129  * (What can I say, I'm in a weird mood today...)
130  */
131 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
132 {
133         int i;
134
135         for (i = 0; i < strlen(name); ++i) {
136                 if (name[i] == '@') {
137                         while (isspace(name[i - 1]) && i > 0) {
138                                 strcpy(&name[i - 1], &name[i]);
139                                 --i;
140                         }
141                         while (isspace(name[i + 1])) {
142                                 strcpy(&name[i + 1], &name[i + 2]);
143                         }
144                 }
145         }
146 }
147
148
149 /*
150  * Aliasing for network mail.
151  * (Error messages have been commented out, because this is a server.)
152  */
153 int alias(char *name)
154 {                               /* process alias and routing info for mail */
155         FILE *fp;
156         int a, i;
157         char aaa[SIZ], bbb[SIZ];
158         char *ignetcfg = NULL;
159         char *ignetmap = NULL;
160         int at = 0;
161         char node[64];
162         char testnode[64];
163         char buf[SIZ];
164
165         char original_name[256];
166         safestrncpy(original_name, name, sizeof original_name);
167
168         striplt(name);
169         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
170         stripallbut(name, '<', '>');
171
172         fp = fopen(file_mail_aliases, "r");
173         if (fp == NULL) {
174                 fp = fopen("/dev/null", "r");
175         }
176         if (fp == NULL) {
177                 return (MES_ERROR);
178         }
179         strcpy(aaa, "");
180         strcpy(bbb, "");
181         while (fgets(aaa, sizeof aaa, fp) != NULL) {
182                 while (isspace(name[0]))
183                         strcpy(name, &name[1]);
184                 aaa[strlen(aaa) - 1] = 0;
185                 strcpy(bbb, "");
186                 for (a = 0; a < strlen(aaa); ++a) {
187                         if (aaa[a] == ',') {
188                                 strcpy(bbb, &aaa[a + 1]);
189                                 aaa[a] = 0;
190                         }
191                 }
192                 if (!strcasecmp(name, aaa))
193                         strcpy(name, bbb);
194         }
195         fclose(fp);
196
197         /* Hit the Global Address Book */
198         if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
199                 strcpy(name, aaa);
200         }
201
202         if (strcasecmp(original_name, name)) {
203                 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
204         }
205
206         /* Change "user @ xxx" to "user" if xxx is an alias for this host */
207         for (a=0; a<strlen(name); ++a) {
208                 if (name[a] == '@') {
209                         if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
210                                 name[a] = 0;
211                                 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
212                         }
213                 }
214         }
215
216         /* determine local or remote type, see citadel.h */
217         at = haschar(name, '@');
218         if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
219         if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
220         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
221
222         /* figure out the delivery mode */
223         extract_token(node, name, 1, '@', sizeof node);
224
225         /* If there are one or more dots in the nodename, we assume that it
226          * is an FQDN and will attempt SMTP delivery to the Internet.
227          */
228         if (haschar(node, '.') > 0) {
229                 return(MES_INTERNET);
230         }
231
232         /* Otherwise we look in the IGnet maps for a valid Citadel node.
233          * Try directly-connected nodes first...
234          */
235         ignetcfg = CtdlGetSysConfig(IGNETCFG);
236         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
237                 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
238                 extract_token(testnode, buf, 0, '|', sizeof testnode);
239                 if (!strcasecmp(node, testnode)) {
240                         free(ignetcfg);
241                         return(MES_IGNET);
242                 }
243         }
244         free(ignetcfg);
245
246         /*
247          * Then try nodes that are two or more hops away.
248          */
249         ignetmap = CtdlGetSysConfig(IGNETMAP);
250         for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
251                 extract_token(buf, ignetmap, i, '\n', sizeof buf);
252                 extract_token(testnode, buf, 0, '|', sizeof testnode);
253                 if (!strcasecmp(node, testnode)) {
254                         free(ignetmap);
255                         return(MES_IGNET);
256                 }
257         }
258         free(ignetmap);
259
260         /* If we get to this point it's an invalid node name */
261         return (MES_ERROR);
262 }
263
264
265 /*
266  * Back end for the MSGS command: output message number only.
267  */
268 void simple_listing(long msgnum, void *userdata)
269 {
270         cprintf("%ld\n", msgnum);
271 }
272
273
274
275 /*
276  * Back end for the MSGS command: output header summary.
277  */
278 void headers_listing(long msgnum, void *userdata)
279 {
280         struct CtdlMessage *msg;
281
282         msg = CtdlFetchMessage(msgnum, 0);
283         if (msg == NULL) {
284                 cprintf("%ld|0|||||\n", msgnum);
285                 return;
286         }
287
288         cprintf("%ld|%s|%s|%s|%s|%s|\n",
289                 msgnum,
290                 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
291                 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
292                 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
293                 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
294                 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
295         );
296         CtdlFreeMessage(msg);
297 }
298
299 /*
300  * Back end for the MSGS command: output EUID header.
301  */
302 void headers_euid(long msgnum, void *userdata)
303 {
304         struct CtdlMessage *msg;
305
306         msg = CtdlFetchMessage(msgnum, 0);
307         if (msg == NULL) {
308                 cprintf("%ld||\n", msgnum);
309                 return;
310         }
311
312         cprintf("%ld|%s|\n", msgnum, (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""));
313         CtdlFreeMessage(msg);
314 }
315
316
317
318
319
320 /* Determine if a given message matches the fields in a message template.
321  * Return 0 for a successful match.
322  */
323 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
324         int i;
325
326         /* If there aren't any fields in the template, all messages will
327          * match.
328          */
329         if (template == NULL) return(0);
330
331         /* Null messages are bogus. */
332         if (msg == NULL) return(1);
333
334         for (i='A'; i<='Z'; ++i) {
335                 if (template->cm_fields[i] != NULL) {
336                         if (msg->cm_fields[i] == NULL) {
337                                 /* Considered equal if temmplate is empty string */
338                                 if (IsEmptyStr(template->cm_fields[i])) continue;
339                                 return 1;
340                         }
341                         if (strcasecmp(msg->cm_fields[i],
342                                 template->cm_fields[i])) return 1;
343                 }
344         }
345
346         /* All compares succeeded: we have a match! */
347         return 0;
348 }
349
350
351
352 /*
353  * Retrieve the "seen" message list for the current room.
354  */
355 void CtdlGetSeen(char *buf, int which_set) {
356         struct visit vbuf;
357
358         /* Learn about the user and room in question */
359         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
360
361         if (which_set == ctdlsetseen_seen)
362                 safestrncpy(buf, vbuf.v_seen, SIZ);
363         if (which_set == ctdlsetseen_answered)
364                 safestrncpy(buf, vbuf.v_answered, SIZ);
365 }
366
367
368
369 /*
370  * Manipulate the "seen msgs" string (or other message set strings)
371  */
372 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
373                 int target_setting, int which_set,
374                 struct ctdluser *which_user, struct ctdlroom *which_room) {
375         struct cdbdata *cdbfr;
376         int i, k;
377         int is_seen = 0;
378         int was_seen = 0;
379         long lo = (-1L);
380         long hi = (-1L);
381         struct visit vbuf;
382         long *msglist;
383         int num_msgs = 0;
384         StrBuf *vset;
385         StrBuf *setstr;
386         StrBuf *lostr;
387         StrBuf *histr;
388         const char *pvset;
389         char *is_set;   /* actually an array of booleans */
390
391         /* Don't bother doing *anything* if we were passed a list of zero messages */
392         if (num_target_msgnums < 1) {
393                 return;
394         }
395
396         /* If no room was specified, we go with the current room. */
397         if (!which_room) {
398                 which_room = &CC->room;
399         }
400
401         /* If no user was specified, we go with the current user. */
402         if (!which_user) {
403                 which_user = &CC->user;
404         }
405
406         CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
407                 num_target_msgnums, target_msgnums[0],
408                 (target_setting ? "SET" : "CLEAR"),
409                 which_set,
410                 which_room->QRname);
411
412         /* Learn about the user and room in question */
413         CtdlGetRelationship(&vbuf, which_user, which_room);
414
415         /* Load the message list */
416         cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
417         if (cdbfr != NULL) {
418                 msglist = (long *) cdbfr->ptr;
419                 cdbfr->ptr = NULL;      /* CtdlSetSeen() now owns this memory */
420                 num_msgs = cdbfr->len / sizeof(long);
421                 cdb_free(cdbfr);
422         } else {
423                 return; /* No messages at all?  No further action. */
424         }
425
426         is_set = malloc(num_msgs * sizeof(char));
427         memset(is_set, 0, (num_msgs * sizeof(char)) );
428
429         /* Decide which message set we're manipulating */
430         switch(which_set) {
431         case ctdlsetseen_seen:
432                 vset = NewStrBufPlain(vbuf.v_seen, -1);
433                 break;
434         case ctdlsetseen_answered:
435                 vset = NewStrBufPlain(vbuf.v_answered, -1);
436                 break;
437         default:
438                 vset = NewStrBuf();
439         }
440
441
442 #if 0   /* This is a special diagnostic section.  Do not allow it to run during normal operation. */
443         CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
444         for (i=0; i<num_msgs; ++i) {
445                 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
446         }
447         CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
448         for (k=0; k<num_target_msgnums; ++k) {
449                 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
450         }
451 #endif
452
453         CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
454
455         /* Translate the existing sequence set into an array of booleans */
456         setstr = NewStrBuf();
457         lostr = NewStrBuf();
458         histr = NewStrBuf();
459         pvset = NULL;
460         while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
461
462                 StrBufExtract_token(lostr, setstr, 0, ':');
463                 if (StrBufNum_tokens(setstr, ':') >= 2) {
464                         StrBufExtract_token(histr, setstr, 1, ':');
465                 }
466                 else {
467                         FlushStrBuf(histr);
468                         StrBufAppendBuf(histr, lostr, 0);
469                 }
470                 lo = StrTol(lostr);
471                 if (!strcmp(ChrPtr(histr), "*")) {
472                         hi = LONG_MAX;
473                 }
474                 else {
475                         hi = StrTol(histr);
476                 }
477
478                 for (i = 0; i < num_msgs; ++i) {
479                         if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
480                                 is_set[i] = 1;
481                         }
482                 }
483         }
484         FreeStrBuf(&setstr);
485         FreeStrBuf(&lostr);
486         FreeStrBuf(&histr);
487
488
489         /* Now translate the array of booleans back into a sequence set */
490         FlushStrBuf(vset);
491         was_seen = 0;
492         lo = (-1);
493         hi = (-1);
494
495         for (i=0; i<num_msgs; ++i) {
496                 is_seen = is_set[i];
497
498                 /* Apply changes */
499                 for (k=0; k<num_target_msgnums; ++k) {
500                         if (msglist[i] == target_msgnums[k]) {
501                                 is_seen = target_setting;
502                         }
503                 }
504
505                 if ((was_seen == 0) && (is_seen == 1)) {
506                         lo = msglist[i];
507                 }
508                 else if ((was_seen == 1) && (is_seen == 0)) {
509                         hi = msglist[i-1];
510
511                         if (StrLength(vset) > 0) {
512                                 StrBufAppendBufPlain(vset, HKEY(","), 0);
513                         }
514                         if (lo == hi) {
515                                 StrBufAppendPrintf(vset, "%ld", hi);
516                         }
517                         else {
518                                 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
519                         }
520                 }
521
522                 if ((is_seen) && (i == num_msgs - 1)) {
523                         if (StrLength(vset) > 0) {
524                                 StrBufAppendBufPlain(vset, HKEY(","), 0);
525                         }
526                         if ((i==0) || (was_seen == 0)) {
527                                 StrBufAppendPrintf(vset, "%ld", msglist[i]);
528                         }
529                         else {
530                                 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
531                         }
532                 }
533
534                 was_seen = is_seen;
535         }
536
537         /*
538          * We will have to stuff this string back into a 4096 byte buffer, so if it's
539          * larger than that now, truncate it by removing tokens from the beginning.
540          * The limit of 100 iterations is there to prevent an infinite loop in case
541          * something unexpected happens.
542          */
543         int number_of_truncations = 0;
544         while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
545                 StrBufRemove_token(vset, 0, ',');
546                 ++number_of_truncations;
547         }
548
549         /*
550          * If we're truncating the sequence set of messages marked with the 'seen' flag,
551          * we want the earliest messages (the truncated ones) to be marked, not unmarked.
552          * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
553          */
554         if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
555                 StrBuf *first_tok;
556                 first_tok = NewStrBuf();
557                 StrBufExtract_token(first_tok, vset, 0, ',');
558                 StrBufRemove_token(vset, 0, ',');
559
560                 if (StrBufNum_tokens(first_tok, ':') > 1) {
561                         StrBufRemove_token(first_tok, 0, ':');
562                 }
563                 
564                 StrBuf *new_set;
565                 new_set = NewStrBuf();
566                 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
567                 StrBufAppendBuf(new_set, first_tok, 0);
568                 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
569                 StrBufAppendBuf(new_set, vset, 0);
570
571                 FreeStrBuf(&vset);
572                 FreeStrBuf(&first_tok);
573                 vset = new_set;
574         }
575
576         CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
577
578         /* Decide which message set we're manipulating */
579         switch (which_set) {
580                 case ctdlsetseen_seen:
581                         safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
582                         break;
583                 case ctdlsetseen_answered:
584                         safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
585                         break;
586         }
587
588         free(is_set);
589         free(msglist);
590         CtdlSetRelationship(&vbuf, which_user, which_room);
591         FreeStrBuf(&vset);
592 }
593
594
595 /*
596  * API function to perform an operation for each qualifying message in the
597  * current room.  (Returns the number of messages processed.)
598  */
599 int CtdlForEachMessage(int mode, long ref, char *search_string,
600                         char *content_type,
601                         struct CtdlMessage *compare,
602                         ForEachMsgCallback CallBack,
603                         void *userdata)
604 {
605
606         int a, i, j;
607         struct visit vbuf;
608         struct cdbdata *cdbfr;
609         long *msglist = NULL;
610         int num_msgs = 0;
611         int num_processed = 0;
612         long thismsg;
613         struct MetaData smi;
614         struct CtdlMessage *msg = NULL;
615         int is_seen = 0;
616         long lastold = 0L;
617         int printed_lastold = 0;
618         int num_search_msgs = 0;
619         long *search_msgs = NULL;
620         regex_t re;
621         int need_to_free_re = 0;
622         regmatch_t pm;
623
624         if ((content_type) && (!IsEmptyStr(content_type))) {
625                 regcomp(&re, content_type, 0);
626                 need_to_free_re = 1;
627         }
628
629         /* Learn about the user and room in question */
630         CtdlGetUser(&CC->user, CC->curr_user);
631         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
632
633         /* Load the message list */
634         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
635         if (cdbfr != NULL) {
636                 msglist = (long *) cdbfr->ptr;
637                 num_msgs = cdbfr->len / sizeof(long);
638         } else {
639                 if (need_to_free_re) regfree(&re);
640                 return 0;       /* No messages at all?  No further action. */
641         }
642
643
644         /*
645          * Now begin the traversal.
646          */
647         if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
648
649                 /* If the caller is looking for a specific MIME type, filter
650                  * out all messages which are not of the type requested.
651                  */
652                 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
653
654                         /* This call to GetMetaData() sits inside this loop
655                          * so that we only do the extra database read per msg
656                          * if we need to.  Doing the extra read all the time
657                          * really kills the server.  If we ever need to use
658                          * metadata for another search criterion, we need to
659                          * move the read somewhere else -- but still be smart
660                          * enough to only do the read if the caller has
661                          * specified something that will need it.
662                          */
663                         GetMetaData(&smi, msglist[a]);
664
665                         /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
666                         if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
667                                 msglist[a] = 0L;
668                         }
669                 }
670         }
671
672         num_msgs = sort_msglist(msglist, num_msgs);
673
674         /* If a template was supplied, filter out the messages which
675          * don't match.  (This could induce some delays!)
676          */
677         if (num_msgs > 0) {
678                 if (compare != NULL) {
679                         for (a = 0; a < num_msgs; ++a) {
680                                 msg = CtdlFetchMessage(msglist[a], 1);
681                                 if (msg != NULL) {
682                                         if (CtdlMsgCmp(msg, compare)) {
683                                                 msglist[a] = 0L;
684                                         }
685                                         CtdlFreeMessage(msg);
686                                 }
687                         }
688                 }
689         }
690
691         /* If a search string was specified, get a message list from
692          * the full text index and remove messages which aren't on both
693          * lists.
694          *
695          * How this works:
696          * Since the lists are sorted and strictly ascending, and the
697          * output list is guaranteed to be shorter than or equal to the
698          * input list, we overwrite the bottom of the input list.  This
699          * eliminates the need to memmove big chunks of the list over and
700          * over again.
701          */
702         if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
703
704                 /* Call search module via hook mechanism.
705                  * NULL means use any search function available.
706                  * otherwise replace with a char * to name of search routine
707                  */
708                 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
709
710                 if (num_search_msgs > 0) {
711         
712                         int orig_num_msgs;
713
714                         orig_num_msgs = num_msgs;
715                         num_msgs = 0;
716                         for (i=0; i<orig_num_msgs; ++i) {
717                                 for (j=0; j<num_search_msgs; ++j) {
718                                         if (msglist[i] == search_msgs[j]) {
719                                                 msglist[num_msgs++] = msglist[i];
720                                         }
721                                 }
722                         }
723                 }
724                 else {
725                         num_msgs = 0;   /* No messages qualify */
726                 }
727                 if (search_msgs != NULL) free(search_msgs);
728
729                 /* Now that we've purged messages which don't contain the search
730                  * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
731                  * point on.
732                  */
733                 mode = MSGS_ALL;
734         }
735
736         /*
737          * Now iterate through the message list, according to the
738          * criteria supplied by the caller.
739          */
740         if (num_msgs > 0)
741                 for (a = 0; a < num_msgs; ++a) {
742                         thismsg = msglist[a];
743                         if (mode == MSGS_ALL) {
744                                 is_seen = 0;
745                         }
746                         else {
747                                 is_seen = is_msg_in_sequence_set(
748                                                         vbuf.v_seen, thismsg);
749                                 if (is_seen) lastold = thismsg;
750                         }
751                         if ((thismsg > 0L)
752                             && (
753
754                                        (mode == MSGS_ALL)
755                                        || ((mode == MSGS_OLD) && (is_seen))
756                                        || ((mode == MSGS_NEW) && (!is_seen))
757                                        || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
758                                    || ((mode == MSGS_FIRST) && (a < ref))
759                                 || ((mode == MSGS_GT) && (thismsg > ref))
760                                 || ((mode == MSGS_LT) && (thismsg < ref))
761                                 || ((mode == MSGS_EQ) && (thismsg == ref))
762                             )
763                             ) {
764                                 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
765                                         if (CallBack)
766                                                 CallBack(lastold, userdata);
767                                         printed_lastold = 1;
768                                         ++num_processed;
769                                 }
770                                 if (CallBack) CallBack(thismsg, userdata);
771                                 ++num_processed;
772                         }
773                 }
774         cdb_free(cdbfr);        /* Clean up */
775         if (need_to_free_re) regfree(&re);
776         return num_processed;
777 }
778
779
780
781 /*
782  * cmd_msgs()  -  get list of message #'s in this room
783  *              implements the MSGS server command using CtdlForEachMessage()
784  */
785 void cmd_msgs(char *cmdbuf)
786 {
787         int mode = 0;
788         char which[16];
789         char buf[256];
790         char tfield[256];
791         char tvalue[256];
792         int cm_ref = 0;
793         int i;
794         int with_template = 0;
795         struct CtdlMessage *template = NULL;
796         char search_string[1024];
797         ForEachMsgCallback CallBack;
798
799         extract_token(which, cmdbuf, 0, '|', sizeof which);
800         cm_ref = extract_int(cmdbuf, 1);
801         extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
802         with_template = extract_int(cmdbuf, 2);
803         switch (extract_int(cmdbuf, 3))
804         {
805         default:
806         case MSG_HDRS_BRIEF:
807                 CallBack = simple_listing;
808                 break;
809         case MSG_HDRS_ALL:
810                 CallBack = headers_listing;
811                 break;
812         case MSG_HDRS_EUID:
813                 CallBack = headers_euid;
814                 break;
815         }
816
817         strcat(which, "   ");
818         if (!strncasecmp(which, "OLD", 3))
819                 mode = MSGS_OLD;
820         else if (!strncasecmp(which, "NEW", 3))
821                 mode = MSGS_NEW;
822         else if (!strncasecmp(which, "FIRST", 5))
823                 mode = MSGS_FIRST;
824         else if (!strncasecmp(which, "LAST", 4))
825                 mode = MSGS_LAST;
826         else if (!strncasecmp(which, "GT", 2))
827                 mode = MSGS_GT;
828         else if (!strncasecmp(which, "LT", 2))
829                 mode = MSGS_LT;
830         else if (!strncasecmp(which, "SEARCH", 6))
831                 mode = MSGS_SEARCH;
832         else
833                 mode = MSGS_ALL;
834
835         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
836                 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
837                 return;
838         }
839
840         if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
841                 cprintf("%d Full text index is not enabled on this server.\n",
842                         ERROR + CMD_NOT_SUPPORTED);
843                 return;
844         }
845
846         if (with_template) {
847                 unbuffer_output();
848                 cprintf("%d Send template then receive message list\n",
849                         START_CHAT_MODE);
850                 template = (struct CtdlMessage *)
851                         malloc(sizeof(struct CtdlMessage));
852                 memset(template, 0, sizeof(struct CtdlMessage));
853                 template->cm_magic = CTDLMESSAGE_MAGIC;
854                 template->cm_anon_type = MES_NORMAL;
855
856                 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
857                         extract_token(tfield, buf, 0, '|', sizeof tfield);
858                         extract_token(tvalue, buf, 1, '|', sizeof tvalue);
859                         for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
860                                 if (!strcasecmp(tfield, msgkeys[i])) {
861                                         template->cm_fields[i] =
862                                                 strdup(tvalue);
863                                 }
864                         }
865                 }
866                 buffer_output();
867         }
868         else {
869                 cprintf("%d  \n", LISTING_FOLLOWS);
870         }
871
872         CtdlForEachMessage(mode,
873                            ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
874                            ( (mode == MSGS_SEARCH) ? search_string : NULL ),
875                            NULL,
876                            template,
877                            CallBack,
878                            NULL);
879         if (template != NULL) CtdlFreeMessage(template);
880         cprintf("000\n");
881 }
882
883
884
885
886 /* 
887  * help_subst()  -  support routine for help file viewer
888  */
889 void help_subst(char *strbuf, char *source, char *dest)
890 {
891         char workbuf[SIZ];
892         int p;
893
894         while (p = pattern2(strbuf, source), (p >= 0)) {
895                 strcpy(workbuf, &strbuf[p + strlen(source)]);
896                 strcpy(&strbuf[p], dest);
897                 strcat(strbuf, workbuf);
898         }
899 }
900
901
902 void do_help_subst(char *buffer)
903 {
904         char buf2[16];
905
906         help_subst(buffer, "^nodename", config.c_nodename);
907         help_subst(buffer, "^humannode", config.c_humannode);
908         help_subst(buffer, "^fqdn", config.c_fqdn);
909         help_subst(buffer, "^username", CC->user.fullname);
910         snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
911         help_subst(buffer, "^usernum", buf2);
912         help_subst(buffer, "^sysadm", config.c_sysadm);
913         help_subst(buffer, "^variantname", CITADEL);
914         snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
915         help_subst(buffer, "^maxsessions", buf2);
916         help_subst(buffer, "^bbsdir", ctdl_message_dir);
917 }
918
919
920
921 /*
922  * memfmout()  -  Citadel text formatter and paginator.
923  *           Although the original purpose of this routine was to format
924  *           text to the reader's screen width, all we're really using it
925  *           for here is to format text out to 80 columns before sending it
926  *           to the client.  The client software may reformat it again.
927  */
928 void memfmout(
929         char *mptr,             /* where are we going to get our text from? */
930         const char *nl          /* string to terminate lines with */
931 ) {
932         int column = 0;
933         char ch = 0;
934         char outbuf[1024];
935         int len = 0;
936         int nllen = 0;
937
938         if (!mptr) return;
939         nllen = strlen(nl);
940         while (ch=*(mptr++), ch > 0) {
941
942                 if (ch == '\n') {
943                         client_write(outbuf, len);
944                         len = 0;
945                         client_write(nl, nllen);
946                         column = 0;
947                 }
948                 else if (ch == '\r') {
949                         /* Ignore carriage returns.  Newlines are always LF or CRLF but never CR. */
950                 }
951                 else if (isspace(ch)) {
952                         if (column > 72) {              /* Beyond 72 columns, break on the next space */
953                                 client_write(outbuf, len);
954                                 len = 0;
955                                 client_write(nl, nllen);
956                                 column = 0;
957                         }
958                         else {
959                                 outbuf[len++] = ch;
960                                 ++column;
961                         }
962                 }
963                 else {
964                         outbuf[len++] = ch;
965                         ++column;
966                         if (column > 1000) {            /* Beyond 1000 columns, break anywhere */
967                                 client_write(outbuf, len);
968                                 len = 0;
969                                 client_write(nl, nllen);
970                                 column = 0;
971                         }
972                 }
973         }
974 }
975
976
977
978 /*
979  * Callback function for mime parser that simply lists the part
980  */
981 void list_this_part(char *name, char *filename, char *partnum, char *disp,
982                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
983                     char *cbid, void *cbuserdata)
984 {
985         struct ma_info *ma;
986         
987         ma = (struct ma_info *)cbuserdata;
988         if (ma->is_ma == 0) {
989                 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
990                         name, 
991                         filename, 
992                         partnum, 
993                         disp, 
994                         cbtype, 
995                         (long)length, 
996                         cbid, 
997                         cbcharset);
998         }
999 }
1000
1001 /* 
1002  * Callback function for multipart prefix
1003  */
1004 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1005                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1006                     char *cbid, void *cbuserdata)
1007 {
1008         struct ma_info *ma;
1009         
1010         ma = (struct ma_info *)cbuserdata;
1011         if (!strcasecmp(cbtype, "multipart/alternative")) {
1012                 ++ma->is_ma;
1013         }
1014
1015         if (ma->is_ma == 0) {
1016                 cprintf("pref=%s|%s\n", partnum, cbtype);
1017         }
1018 }
1019
1020 /* 
1021  * Callback function for multipart sufffix
1022  */
1023 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1024                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1025                     char *cbid, void *cbuserdata)
1026 {
1027         struct ma_info *ma;
1028         
1029         ma = (struct ma_info *)cbuserdata;
1030         if (ma->is_ma == 0) {
1031                 cprintf("suff=%s|%s\n", partnum, cbtype);
1032         }
1033         if (!strcasecmp(cbtype, "multipart/alternative")) {
1034                 --ma->is_ma;
1035         }
1036 }
1037
1038
1039 /*
1040  * Callback function for mime parser that opens a section for downloading
1041  */
1042 void mime_download(char *name, char *filename, char *partnum, char *disp,
1043                    void *content, char *cbtype, char *cbcharset, size_t length,
1044                    char *encoding, char *cbid, void *cbuserdata)
1045 {
1046         int rv = 0;
1047
1048         /* Silently go away if there's already a download open. */
1049         if (CC->download_fp != NULL)
1050                 return;
1051
1052         if (
1053                 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1054         ||      (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1055         ) {
1056                 CC->download_fp = tmpfile();
1057                 if (CC->download_fp == NULL)
1058                         return;
1059         
1060                 rv = fwrite(content, length, 1, CC->download_fp);
1061                 fflush(CC->download_fp);
1062                 rewind(CC->download_fp);
1063         
1064                 OpenCmdResult(filename, cbtype);
1065         }
1066 }
1067
1068
1069
1070 /*
1071  * Callback function for mime parser that outputs a section all at once.
1072  * We can specify the desired section by part number *or* content-id.
1073  */
1074 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1075                    void *content, char *cbtype, char *cbcharset, size_t length,
1076                    char *encoding, char *cbid, void *cbuserdata)
1077 {
1078         int *found_it = (int *)cbuserdata;
1079
1080         if (
1081                 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1082         ||      (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1083         ) {
1084                 *found_it = 1;
1085                 cprintf("%d %d|-1|%s|%s|%s\n",
1086                         BINARY_FOLLOWS,
1087                         (int)length,
1088                         filename,
1089                         cbtype,
1090                         cbcharset
1091                 );
1092                 client_write(content, length);
1093         }
1094 }
1095
1096
1097
1098 /*
1099  * Load a message from disk into memory.
1100  * This is used by CtdlOutputMsg() and other fetch functions.
1101  *
1102  * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1103  *       using the CtdlMessageFree() function.
1104  */
1105 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1106 {
1107         struct cdbdata *dmsgtext;
1108         struct CtdlMessage *ret = NULL;
1109         char *mptr;
1110         char *upper_bound;
1111         cit_uint8_t ch;
1112         cit_uint8_t field_header;
1113
1114         CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1115
1116         dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1117         if (dmsgtext == NULL) {
1118                 return NULL;
1119         }
1120         mptr = dmsgtext->ptr;
1121         upper_bound = mptr + dmsgtext->len;
1122
1123         /* Parse the three bytes that begin EVERY message on disk.
1124          * The first is always 0xFF, the on-disk magic number.
1125          * The second is the anonymous/public type byte.
1126          * The third is the format type byte (vari, fixed, or MIME).
1127          */
1128         ch = *mptr++;
1129         if (ch != 255) {
1130                 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1131                 cdb_free(dmsgtext);
1132                 return NULL;
1133         }
1134         ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1135         memset(ret, 0, sizeof(struct CtdlMessage));
1136
1137         ret->cm_magic = CTDLMESSAGE_MAGIC;
1138         ret->cm_anon_type = *mptr++;    /* Anon type byte */
1139         ret->cm_format_type = *mptr++;  /* Format type byte */
1140
1141         /*
1142          * The rest is zero or more arbitrary fields.  Load them in.
1143          * We're done when we encounter either a zero-length field or
1144          * have just processed the 'M' (message text) field.
1145          */
1146         do {
1147                 if (mptr >= upper_bound) {
1148                         break;
1149                 }
1150                 field_header = *mptr++;
1151                 ret->cm_fields[field_header] = strdup(mptr);
1152
1153                 while (*mptr++ != 0);   /* advance to next field */
1154
1155         } while ((mptr < upper_bound) && (field_header != 'M'));
1156
1157         cdb_free(dmsgtext);
1158
1159         /* Always make sure there's something in the msg text field.  If
1160          * it's NULL, the message text is most likely stored separately,
1161          * so go ahead and fetch that.  Failing that, just set a dummy
1162          * body so other code doesn't barf.
1163          */
1164         if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1165                 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1166                 if (dmsgtext != NULL) {
1167                         ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1168                         cdb_free(dmsgtext);
1169                 }
1170         }
1171         if (ret->cm_fields['M'] == NULL) {
1172                 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1173         }
1174
1175         /* Perform "before read" hooks (aborting if any return nonzero) */
1176         if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1177                 CtdlFreeMessage(ret);
1178                 return NULL;
1179         }
1180
1181         return (ret);
1182 }
1183
1184
1185 /*
1186  * Returns 1 if the supplied pointer points to a valid Citadel message.
1187  * If the pointer is NULL or the magic number check fails, returns 0.
1188  */
1189 int is_valid_message(struct CtdlMessage *msg) {
1190         if (msg == NULL)
1191                 return 0;
1192         if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1193                 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1194                 return 0;
1195         }
1196         return 1;
1197 }
1198
1199
1200 /*
1201  * 'Destructor' for struct CtdlMessage
1202  */
1203 void CtdlFreeMessage(struct CtdlMessage *msg)
1204 {
1205         int i;
1206
1207         if (is_valid_message(msg) == 0) 
1208         {
1209                 if (msg != NULL) free (msg);
1210                 return;
1211         }
1212
1213         for (i = 0; i < 256; ++i)
1214                 if (msg->cm_fields[i] != NULL) {
1215                         free(msg->cm_fields[i]);
1216                 }
1217
1218         msg->cm_magic = 0;      /* just in case */
1219         free(msg);
1220 }
1221
1222
1223 /*
1224  * Pre callback function for multipart/alternative
1225  *
1226  * NOTE: this differs from the standard behavior for a reason.  Normally when
1227  *       displaying multipart/alternative you want to show the _last_ usable
1228  *       format in the message.  Here we show the _first_ one, because it's
1229  *       usually text/plain.  Since this set of functions is designed for text
1230  *       output to non-MIME-aware clients, this is the desired behavior.
1231  *
1232  */
1233 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1234                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1235                 char *cbid, void *cbuserdata)
1236 {
1237         struct ma_info *ma;
1238         
1239         ma = (struct ma_info *)cbuserdata;
1240         CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);    
1241         if (!strcasecmp(cbtype, "multipart/alternative")) {
1242                 ++ma->is_ma;
1243                 ma->did_print = 0;
1244         }
1245         if (!strcasecmp(cbtype, "message/rfc822")) {
1246                 ++ma->freeze;
1247         }
1248 }
1249
1250 /*
1251  * Post callback function for multipart/alternative
1252  */
1253 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1254                 void *content, char *cbtype, char *cbcharset, size_t length,
1255                 char *encoding, char *cbid, void *cbuserdata)
1256 {
1257         struct ma_info *ma;
1258         
1259         ma = (struct ma_info *)cbuserdata;
1260         CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);   
1261         if (!strcasecmp(cbtype, "multipart/alternative")) {
1262                 --ma->is_ma;
1263                 ma->did_print = 0;
1264         }
1265         if (!strcasecmp(cbtype, "message/rfc822")) {
1266                 --ma->freeze;
1267         }
1268 }
1269
1270 /*
1271  * Inline callback function for mime parser that wants to display text
1272  */
1273 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1274                 void *content, char *cbtype, char *cbcharset, size_t length,
1275                 char *encoding, char *cbid, void *cbuserdata)
1276 {
1277         char *ptr;
1278         char *wptr;
1279         size_t wlen;
1280         struct ma_info *ma;
1281
1282         ma = (struct ma_info *)cbuserdata;
1283
1284         CtdlLogPrintf(CTDL_DEBUG,
1285                 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1286                 partnum, filename, cbtype, (long)length);
1287
1288         /*
1289          * If we're in the middle of a multipart/alternative scope and
1290          * we've already printed another section, skip this one.
1291          */     
1292         if ( (ma->is_ma) && (ma->did_print) ) {
1293                 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1294                 return;
1295         }
1296         ma->did_print = 1;
1297
1298         if ( (!strcasecmp(cbtype, "text/plain")) 
1299            || (IsEmptyStr(cbtype)) ) {
1300                 wptr = content;
1301                 if (length > 0) {
1302                         client_write(wptr, length);
1303                         if (wptr[length-1] != '\n') {
1304                                 cprintf("\n");
1305                         }
1306                 }
1307                 return;
1308         }
1309
1310         if (!strcasecmp(cbtype, "text/html")) {
1311                 ptr = html_to_ascii(content, length, 80, 0);
1312                 wlen = strlen(ptr);
1313                 client_write(ptr, wlen);
1314                 if (ptr[wlen-1] != '\n') {
1315                         cprintf("\n");
1316                 }
1317                 free(ptr);
1318                 return;
1319         }
1320
1321         if (ma->use_fo_hooks) {
1322                 if (PerformFixedOutputHooks(cbtype, content, length)) {
1323                 /* above function returns nonzero if it handled the part */
1324                         return;
1325                 }
1326         }
1327
1328         if (strncasecmp(cbtype, "multipart/", 10)) {
1329                 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1330                         partnum, filename, cbtype, (long)length);
1331                 return;
1332         }
1333 }
1334
1335 /*
1336  * The client is elegant and sophisticated and wants to be choosy about
1337  * MIME content types, so figure out which multipart/alternative part
1338  * we're going to send.
1339  *
1340  * We use a system of weights.  When we find a part that matches one of the
1341  * MIME types we've declared as preferential, we can store it in ma->chosen_part
1342  * and then set ma->chosen_pref to that MIME type's position in our preference
1343  * list.  If we then hit another match, we only replace the first match if
1344  * the preference value is lower.
1345  */
1346 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1347                 void *content, char *cbtype, char *cbcharset, size_t length,
1348                 char *encoding, char *cbid, void *cbuserdata)
1349 {
1350         char buf[1024];
1351         int i;
1352         struct ma_info *ma;
1353         
1354         ma = (struct ma_info *)cbuserdata;
1355
1356         // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1357         //       http://bugzilla.citadel.org/show_bug.cgi?id=220
1358         // I don't know if there are any side effects!  Please TEST TEST TEST
1359         //if (ma->is_ma > 0) {
1360
1361         for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1362                 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1363                 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1364                         if (i < ma->chosen_pref) {
1365                                 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1366                                 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1367                                 ma->chosen_pref = i;
1368                         }
1369                 }
1370         }
1371 }
1372
1373 /*
1374  * Now that we've chosen our preferred part, output it.
1375  */
1376 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1377                 void *content, char *cbtype, char *cbcharset, size_t length,
1378                 char *encoding, char *cbid, void *cbuserdata)
1379 {
1380         int i;
1381         char buf[128];
1382         int add_newline = 0;
1383         char *text_content;
1384         struct ma_info *ma;
1385         
1386         ma = (struct ma_info *)cbuserdata;
1387
1388         /* This is not the MIME part you're looking for... */
1389         if (strcasecmp(partnum, ma->chosen_part)) return;
1390
1391         /* If the content-type of this part is in our preferred formats
1392          * list, we can simply output it verbatim.
1393          */
1394         for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1395                 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1396                 if (!strcasecmp(buf, cbtype)) {
1397                         /* Yeah!  Go!  W00t!! */
1398
1399                         text_content = (char *)content;
1400                         if (text_content[length-1] != '\n') {
1401                                 ++add_newline;
1402                         }
1403                         cprintf("Content-type: %s", cbtype);
1404                         if (!IsEmptyStr(cbcharset)) {
1405                                 cprintf("; charset=%s", cbcharset);
1406                         }
1407                         cprintf("\nContent-length: %d\n",
1408                                 (int)(length + add_newline) );
1409                         if (!IsEmptyStr(encoding)) {
1410                                 cprintf("Content-transfer-encoding: %s\n", encoding);
1411                         }
1412                         else {
1413                                 cprintf("Content-transfer-encoding: 7bit\n");
1414                         }
1415                         cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1416                         cprintf("\n");
1417                         client_write(content, length);
1418                         if (add_newline) cprintf("\n");
1419                         return;
1420                 }
1421         }
1422
1423         /* No translations required or possible: output as text/plain */
1424         cprintf("Content-type: text/plain\n\n");
1425         fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1426                         length, encoding, cbid, cbuserdata);
1427 }
1428
1429
1430 struct encapmsg {
1431         char desired_section[64];
1432         char *msg;
1433         size_t msglen;
1434 };
1435
1436
1437 /*
1438  * Callback function for
1439  */
1440 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1441                    void *content, char *cbtype, char *cbcharset, size_t length,
1442                    char *encoding, char *cbid, void *cbuserdata)
1443 {
1444         struct encapmsg *encap;
1445
1446         encap = (struct encapmsg *)cbuserdata;
1447
1448         /* Only proceed if this is the desired section... */
1449         if (!strcasecmp(encap->desired_section, partnum)) {
1450                 encap->msglen = length;
1451                 encap->msg = malloc(length + 2);
1452                 memcpy(encap->msg, content, length);
1453                 return;
1454         }
1455
1456 }
1457
1458
1459
1460
1461
1462
1463 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1464         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1465                 return(om_not_logged_in);
1466         }
1467         return(om_ok);
1468 }
1469
1470
1471 /*
1472  * Get a message off disk.  (returns om_* values found in msgbase.h)
1473  * 
1474  */
1475 int CtdlOutputMsg(long msg_num,         /* message number (local) to fetch */
1476                   int mode,             /* how would you like that message? */
1477                   int headers_only,     /* eschew the message body? */
1478                   int do_proto,         /* do Citadel protocol responses? */
1479                   int crlf,             /* Use CRLF newlines instead of LF? */
1480                   char *section,        /* NULL or a message/rfc822 section */
1481                   int flags             /* various flags; see msgbase.h */
1482 ) {
1483         struct CtdlMessage *TheMessage = NULL;
1484         int retcode = om_no_such_msg;
1485         struct encapmsg encap;
1486         int r;
1487
1488         CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n", 
1489                 msg_num, mode,
1490                 (section ? section : "<>")
1491         );
1492
1493         r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1494         if (r != om_ok) {
1495                 if (do_proto) {
1496                         if (r == om_not_logged_in) {
1497                                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1498                         }
1499                         else {
1500                                 cprintf("%d An unknown error has occurred.\n", ERROR);
1501                         }
1502                 }
1503                 return(r);
1504         }
1505
1506         /* FIXME: check message id against msglist for this room */
1507
1508         /*
1509          * Fetch the message from disk.  If we're in HEADERS_FAST mode,
1510          * request that we don't even bother loading the body into memory.
1511          */
1512         if (headers_only == HEADERS_FAST) {
1513                 TheMessage = CtdlFetchMessage(msg_num, 0);
1514         }
1515         else {
1516                 TheMessage = CtdlFetchMessage(msg_num, 1);
1517         }
1518
1519         if (TheMessage == NULL) {
1520                 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1521                         ERROR + MESSAGE_NOT_FOUND, msg_num);
1522                 return(om_no_such_msg);
1523         }
1524
1525         /* Here is the weird form of this command, to process only an
1526          * encapsulated message/rfc822 section.
1527          */
1528         if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1529                 memset(&encap, 0, sizeof encap);
1530                 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1531                 mime_parser(TheMessage->cm_fields['M'],
1532                         NULL,
1533                         *extract_encapsulated_message,
1534                         NULL, NULL, (void *)&encap, 0
1535                 );
1536                 CtdlFreeMessage(TheMessage);
1537                 TheMessage = NULL;
1538
1539                 if (encap.msg) {
1540                         encap.msg[encap.msglen] = 0;
1541                         TheMessage = convert_internet_message(encap.msg);
1542                         encap.msg = NULL;       /* no free() here, TheMessage owns it now */
1543
1544                         /* Now we let it fall through to the bottom of this
1545                          * function, because TheMessage now contains the
1546                          * encapsulated message instead of the top-level
1547                          * message.  Isn't that neat?
1548                          */
1549
1550                 }
1551                 else {
1552                         if (do_proto) cprintf("%d msg %ld has no part %s\n",
1553                                 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1554                         retcode = om_no_such_msg;
1555                 }
1556
1557         }
1558
1559         /* Ok, output the message now */
1560         retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1561         CtdlFreeMessage(TheMessage);
1562
1563         return(retcode);
1564 }
1565
1566
1567 char *qp_encode_email_addrs(char *source)
1568 {
1569         char user[256], node[256], name[256];
1570         const char headerStr[] = "=?UTF-8?Q?";
1571         char *Encoded;
1572         char *EncodedName;
1573         char *nPtr;
1574         int need_to_encode = 0;
1575         long SourceLen;
1576         long EncodedMaxLen;
1577         long nColons = 0;
1578         long *AddrPtr;
1579         long *AddrUtf8;
1580         long nAddrPtrMax = 50;
1581         long nmax;
1582         int InQuotes = 0;
1583         int i, n;
1584
1585         if (source == NULL) return source;
1586         if (IsEmptyStr(source)) return source;
1587
1588         AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1589         AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1590         memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1591         *AddrPtr = 0;
1592         i = 0;
1593         while (!IsEmptyStr (&source[i])) {
1594                 if (nColons >= nAddrPtrMax){
1595                         long *ptr;
1596
1597                         ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1598                         memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1599                         free (AddrPtr), AddrPtr = ptr;
1600
1601                         ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1602                         memset(&ptr[nAddrPtrMax], 0, 
1603                                sizeof (long) * nAddrPtrMax);
1604
1605                         memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1606                         free (AddrUtf8), AddrUtf8 = ptr;
1607                         nAddrPtrMax *= 2;                               
1608                 }
1609                 if (((unsigned char) source[i] < 32) || 
1610                     ((unsigned char) source[i] > 126)) {
1611                         need_to_encode = 1;
1612                         AddrUtf8[nColons] = 1;
1613                 }
1614                 if (source[i] == '"')
1615                         InQuotes = !InQuotes;
1616                 if (!InQuotes && source[i] == ',') {
1617                         AddrPtr[nColons] = i;
1618                         nColons++;
1619                 }
1620                 i++;
1621         }
1622         if (need_to_encode == 0) {
1623                 free(AddrPtr);
1624                 free(AddrUtf8);
1625                 return source;
1626         }
1627
1628         SourceLen = i;
1629         EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1630         Encoded = (char*) malloc (EncodedMaxLen);
1631
1632         for (i = 0; i < nColons; i++)
1633                 source[AddrPtr[i]++] = '\0';
1634
1635         nPtr = Encoded;
1636         *nPtr = '\0';
1637         for (i = 0; i < nColons && nPtr != NULL; i++) {
1638                 nmax = EncodedMaxLen - (nPtr - Encoded);
1639                 if (AddrUtf8[i]) {
1640                         process_rfc822_addr(&source[AddrPtr[i]], 
1641                                             user,
1642                                             node,
1643                                             name);
1644                         /* TODO: libIDN here ! */
1645                         if (IsEmptyStr(name)) {
1646                                 n = snprintf(nPtr, nmax, 
1647                                              (i==0)?"%s@%s" : ",%s@%s",
1648                                              user, node);
1649                         }
1650                         else {
1651                                 EncodedName = rfc2047encode(name, strlen(name));                        
1652                                 n = snprintf(nPtr, nmax, 
1653                                              (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1654                                              EncodedName, user, node);
1655                                 free(EncodedName);
1656                         }
1657                 }
1658                 else { 
1659                         n = snprintf(nPtr, nmax, 
1660                                      (i==0)?"%s" : ",%s",
1661                                      &source[AddrPtr[i]]);
1662                 }
1663                 if (n > 0 )
1664                         nPtr += n;
1665                 else { 
1666                         char *ptr, *nnPtr;
1667                         ptr = (char*) malloc(EncodedMaxLen * 2);
1668                         memcpy(ptr, Encoded, EncodedMaxLen);
1669                         nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1670                         free(Encoded), Encoded = ptr;
1671                         EncodedMaxLen *= 2;
1672                         i--; /* do it once more with properly lengthened buffer */
1673                 }
1674         }
1675         for (i = 0; i < nColons; i++)
1676                 source[--AddrPtr[i]] = ',';
1677         free(AddrUtf8);
1678         free(AddrPtr);
1679         return Encoded;
1680 }
1681
1682
1683 /* If the last item in a list of recipients was truncated to a partial address,
1684  * remove it completely in order to avoid choking libSieve
1685  */
1686 void sanitize_truncated_recipient(char *str)
1687 {
1688         if (!str) return;
1689         if (num_tokens(str, ',') < 2) return;
1690
1691         int len = strlen(str);
1692         if (len < 900) return;
1693         if (len > 998) str[998] = 0;
1694
1695         char *cptr = strrchr(str, ',');
1696         if (!cptr) return;
1697
1698         char *lptr = strchr(cptr, '<');
1699         char *rptr = strchr(cptr, '>');
1700
1701         if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1702
1703         *cptr = 0;
1704 }
1705
1706
1707
1708 /*
1709  * Get a message off disk.  (returns om_* values found in msgbase.h)
1710  */
1711 int CtdlOutputPreLoadedMsg(
1712                 struct CtdlMessage *TheMessage,
1713                 int mode,               /* how would you like that message? */
1714                 int headers_only,       /* eschew the message body? */
1715                 int do_proto,           /* do Citadel protocol responses? */
1716                 int crlf,               /* Use CRLF newlines instead of LF? */
1717                 int flags               /* should the bessage be exported clean? */
1718 ) {
1719         int i, j, k;
1720         char buf[SIZ];
1721         cit_uint8_t ch, prev_ch;
1722         char allkeys[30];
1723         char display_name[256];
1724         char *mptr, *mpptr;
1725         const char *nl; /* newline string */
1726         int suppress_f = 0;
1727         int subject_found = 0;
1728         struct ma_info ma;
1729
1730         /* Buffers needed for RFC822 translation.  These are all filled
1731          * using functions that are bounds-checked, and therefore we can
1732          * make them substantially smaller than SIZ.
1733          */
1734         char suser[100];
1735         char luser[100];
1736         char fuser[100];
1737         char snode[100];
1738         char mid[100];
1739         char datestamp[100];
1740
1741         CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1742                 ((TheMessage == NULL) ? "NULL" : "not null"),
1743                 mode, headers_only, do_proto, crlf);
1744
1745         strcpy(mid, "unknown");
1746         nl = (crlf ? "\r\n" : "\n");
1747
1748         if (!is_valid_message(TheMessage)) {
1749                 CtdlLogPrintf(CTDL_ERR,
1750                         "ERROR: invalid preloaded message for output\n");
1751                 cit_backtrace ();
1752                 return(om_no_such_msg);
1753         }
1754
1755         /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1756          * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1757          */
1758         if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
1759                 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
1760         }
1761                 
1762         /* Are we downloading a MIME component? */
1763         if (mode == MT_DOWNLOAD) {
1764                 if (TheMessage->cm_format_type != FMT_RFC822) {
1765                         if (do_proto)
1766                                 cprintf("%d This is not a MIME message.\n",
1767                                 ERROR + ILLEGAL_VALUE);
1768                 } else if (CC->download_fp != NULL) {
1769                         if (do_proto) cprintf(
1770                                 "%d You already have a download open.\n",
1771                                 ERROR + RESOURCE_BUSY);
1772                 } else {
1773                         /* Parse the message text component */
1774                         mptr = TheMessage->cm_fields['M'];
1775                         mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1776                         /* If there's no file open by this time, the requested
1777                          * section wasn't found, so print an error
1778                          */
1779                         if (CC->download_fp == NULL) {
1780                                 if (do_proto) cprintf(
1781                                         "%d Section %s not found.\n",
1782                                         ERROR + FILE_NOT_FOUND,
1783                                         CC->download_desired_section);
1784                         }
1785                 }
1786                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1787         }
1788
1789         /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1790          * in a single server operation instead of opening a download file.
1791          */
1792         if (mode == MT_SPEW_SECTION) {
1793                 if (TheMessage->cm_format_type != FMT_RFC822) {
1794                         if (do_proto)
1795                                 cprintf("%d This is not a MIME message.\n",
1796                                 ERROR + ILLEGAL_VALUE);
1797                 } else {
1798                         /* Parse the message text component */
1799                         int found_it = 0;
1800
1801                         mptr = TheMessage->cm_fields['M'];
1802                         mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1803                         /* If section wasn't found, print an error
1804                          */
1805                         if (!found_it) {
1806                                 if (do_proto) cprintf(
1807                                         "%d Section %s not found.\n",
1808                                         ERROR + FILE_NOT_FOUND,
1809                                         CC->download_desired_section);
1810                         }
1811                 }
1812                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1813         }
1814
1815         /* now for the user-mode message reading loops */
1816         if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1817
1818         /* Does the caller want to skip the headers? */
1819         if (headers_only == HEADERS_NONE) goto START_TEXT;
1820
1821         /* Tell the client which format type we're using. */
1822         if ( (mode == MT_CITADEL) && (do_proto) ) {
1823                 cprintf("type=%d\n", TheMessage->cm_format_type);
1824         }
1825
1826         /* nhdr=yes means that we're only displaying headers, no body */
1827         if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1828            && ((mode == MT_CITADEL) || (mode == MT_MIME))
1829            && (do_proto)
1830            ) {
1831                 cprintf("nhdr=yes\n");
1832         }
1833
1834         /* begin header processing loop for Citadel message format */
1835
1836         if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1837
1838                 safestrncpy(display_name, "<unknown>", sizeof display_name);
1839                 if (TheMessage->cm_fields['A']) {
1840                         strcpy(buf, TheMessage->cm_fields['A']);
1841                         if (TheMessage->cm_anon_type == MES_ANONONLY) {
1842                                 safestrncpy(display_name, "****", sizeof display_name);
1843                         }
1844                         else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1845                                 safestrncpy(display_name, "anonymous", sizeof display_name);
1846                         }
1847                         else {
1848                                 safestrncpy(display_name, buf, sizeof display_name);
1849                         }
1850                         if ((is_room_aide())
1851                             && ((TheMessage->cm_anon_type == MES_ANONONLY)
1852                              || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1853                                 size_t tmp = strlen(display_name);
1854                                 snprintf(&display_name[tmp],
1855                                          sizeof display_name - tmp,
1856                                          " [%s]", buf);
1857                         }
1858                 }
1859
1860                 /* Don't show Internet address for users on the
1861                  * local Citadel network.
1862                  */
1863                 suppress_f = 0;
1864                 if (TheMessage->cm_fields['N'] != NULL)
1865                    if (!IsEmptyStr(TheMessage->cm_fields['N']))
1866                       if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1867                         suppress_f = 1;
1868                 }
1869
1870                 /* Now spew the header fields in the order we like them. */
1871                 safestrncpy(allkeys, FORDER, sizeof allkeys);
1872                 for (i=0; i<strlen(allkeys); ++i) {
1873                         k = (int) allkeys[i];
1874                         if (k != 'M') {
1875                                 if ( (TheMessage->cm_fields[k] != NULL)
1876                                    && (msgkeys[k] != NULL) ) {
1877                                         if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1878                                                 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1879                                         }
1880                                         if (k == 'A') {
1881                                                 if (do_proto) cprintf("%s=%s\n",
1882                                                         msgkeys[k],
1883                                                         display_name);
1884                                         }
1885                                         else if ((k == 'F') && (suppress_f)) {
1886                                                 /* do nothing */
1887                                         }
1888                                         /* Masquerade display name if needed */
1889                                         else {
1890                                                 if (do_proto) cprintf("%s=%s\n",
1891                                                         msgkeys[k],
1892                                                         TheMessage->cm_fields[k]
1893                                         );
1894                                         }
1895                                 }
1896                         }
1897                 }
1898
1899         }
1900
1901         /* begin header processing loop for RFC822 transfer format */
1902
1903         strcpy(suser, "");
1904         strcpy(luser, "");
1905         strcpy(fuser, "");
1906         strcpy(snode, NODENAME);
1907         if (mode == MT_RFC822) {
1908                 for (i = 0; i < 256; ++i) {
1909                         if (TheMessage->cm_fields[i]) {
1910                                 mptr = mpptr = TheMessage->cm_fields[i];
1911                                 
1912                                 if (i == 'A') {
1913                                         safestrncpy(luser, mptr, sizeof luser);
1914                                         safestrncpy(suser, mptr, sizeof suser);
1915                                 }
1916                                 else if (i == 'Y') {
1917                                         if ((flags & QP_EADDR) != 0) {
1918                                                 mptr = qp_encode_email_addrs(mptr);
1919                                         }
1920                                         sanitize_truncated_recipient(mptr);
1921                                         cprintf("CC: %s%s", mptr, nl);
1922                                 }
1923                                 else if (i == 'P') {
1924                                         cprintf("Return-Path: %s%s", mptr, nl);
1925                                 }
1926                                 else if (i == 'L') {
1927                                         cprintf("List-ID: %s%s", mptr, nl);
1928                                 }
1929                                 else if (i == 'V') {
1930                                         if ((flags & QP_EADDR) != 0) 
1931                                                 mptr = qp_encode_email_addrs(mptr);
1932                                         cprintf("Envelope-To: %s%s", mptr, nl);
1933                                 }
1934                                 else if (i == 'U') {
1935                                         cprintf("Subject: %s%s", mptr, nl);
1936                                         subject_found = 1;
1937                                 }
1938                                 else if (i == 'I')
1939                                         safestrncpy(mid, mptr, sizeof mid);
1940                                 else if (i == 'F')
1941                                         safestrncpy(fuser, mptr, sizeof fuser);
1942                                 /* else if (i == 'O')
1943                                         cprintf("X-Citadel-Room: %s%s",
1944                                                 mptr, nl); */
1945                                 else if (i == 'N')
1946                                         safestrncpy(snode, mptr, sizeof snode);
1947                                 else if (i == 'R')
1948                                 {
1949                                         if (haschar(mptr, '@') == 0)
1950                                         {
1951                                                 sanitize_truncated_recipient(mptr);
1952                                                 cprintf("To: %s@%s", mptr, config.c_fqdn);
1953                                                 cprintf("%s", nl);
1954                                         }
1955                                         else
1956                                         {
1957                                                 if ((flags & QP_EADDR) != 0) {
1958                                                         mptr = qp_encode_email_addrs(mptr);
1959                                                 }
1960                                                 sanitize_truncated_recipient(mptr);
1961                                                 cprintf("To: %s", mptr);
1962                                                 cprintf("%s", nl);
1963                                         }
1964                                 }
1965                                 else if (i == 'T') {
1966                                         datestring(datestamp, sizeof datestamp,
1967                                                 atol(mptr), DATESTRING_RFC822);
1968                                         cprintf("Date: %s%s", datestamp, nl);
1969                                 }
1970                                 else if (i == 'W') {
1971                                         cprintf("References: ");
1972                                         k = num_tokens(mptr, '|');
1973                                         for (j=0; j<k; ++j) {
1974                                                 extract_token(buf, mptr, j, '|', sizeof buf);
1975                                                 cprintf("<%s>", buf);
1976                                                 if (j == (k-1)) {
1977                                                         cprintf("%s", nl);
1978                                                 }
1979                                                 else {
1980                                                         cprintf(" ");
1981                                                 }
1982                                         }
1983                                 }
1984                                 if (mptr != mpptr)
1985                                         free (mptr);
1986                         }
1987                 }
1988                 if (subject_found == 0) {
1989                         cprintf("Subject: (no subject)%s", nl);
1990                 }
1991         }
1992
1993         for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1994                 suser[i] = tolower(suser[i]);
1995                 if (!isalnum(suser[i])) suser[i]='_';
1996         }
1997
1998         if (mode == MT_RFC822) {
1999                 if (!strcasecmp(snode, NODENAME)) {
2000                         safestrncpy(snode, FQDN, sizeof snode);
2001                 }
2002
2003                 /* Construct a fun message id */
2004                 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2005                 if (strchr(mid, '@')==NULL) {
2006                         cprintf("@%s", snode);
2007                 }
2008                 cprintf(">%s", nl);
2009
2010                 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2011                         cprintf("From: \"----\" <x@x.org>%s", nl);
2012                 }
2013                 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2014                         cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2015                 }
2016                 else if (!IsEmptyStr(fuser)) {
2017                         cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2018                 }
2019                 else {
2020                         cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2021                 }
2022
2023                 /* Blank line signifying RFC822 end-of-headers */
2024                 if (TheMessage->cm_format_type != FMT_RFC822) {
2025                         cprintf("%s", nl);
2026                 }
2027         }
2028
2029         /* end header processing loop ... at this point, we're in the text */
2030 START_TEXT:
2031         if (headers_only == HEADERS_FAST) goto DONE;
2032         mptr = TheMessage->cm_fields['M'];
2033
2034         /* Tell the client about the MIME parts in this message */
2035         if (TheMessage->cm_format_type == FMT_RFC822) {
2036                 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2037                         memset(&ma, 0, sizeof(struct ma_info));
2038                         mime_parser(mptr, NULL,
2039                                 (do_proto ? *list_this_part : NULL),
2040                                 (do_proto ? *list_this_pref : NULL),
2041                                 (do_proto ? *list_this_suff : NULL),
2042                                 (void *)&ma, 0);
2043                 }
2044                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
2045                         char *start_of_text = NULL;
2046                         start_of_text = strstr(mptr, "\n\r\n");
2047                         if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
2048                         if (start_of_text == NULL) start_of_text = mptr;
2049                         ++start_of_text;
2050                         start_of_text = strstr(start_of_text, "\n");
2051                         ++start_of_text;
2052
2053                         char outbuf[1024];
2054                         int outlen = 0;
2055                         int nllen = strlen(nl);
2056                         prev_ch = 0;
2057                         while (ch=*mptr, ch!=0) {
2058                                 if (ch==13) {
2059                                         /* do nothing */
2060                                 }
2061                                 else {
2062                                         if (
2063                                                 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2064                                            ||   ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2065                                            ||   ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2066                                         ) {
2067                                                 if (ch == 10) {
2068                                                         sprintf(&outbuf[outlen], "%s", nl);
2069                                                         outlen += nllen;
2070                                                 }
2071                                                 else {
2072                                                         outbuf[outlen++] = ch;
2073                                                 }
2074                                         }
2075                                 }
2076                                 if (flags & ESC_DOT)
2077                                 {
2078                                         if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2079                                         {
2080                                                 outbuf[outlen++] = '.';
2081                                         }
2082                                 }
2083                                 prev_ch = ch;
2084                                 ++mptr;
2085                                 if (outlen > 1000) {
2086                                         client_write(outbuf, outlen);
2087                                         outlen = 0;
2088                                 }
2089                         }
2090                         if (outlen > 0) {
2091                                 client_write(outbuf, outlen);
2092                                 outlen = 0;
2093                         }
2094
2095                         goto DONE;
2096                 }
2097         }
2098
2099         if (headers_only == HEADERS_ONLY) {
2100                 goto DONE;
2101         }
2102
2103         /* signify start of msg text */
2104         if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2105                 if (do_proto) cprintf("text\n");
2106         }
2107
2108         /* If the format type on disk is 1 (fixed-format), then we want
2109          * everything to be output completely literally ... regardless of
2110          * what message transfer format is in use.
2111          */
2112         if (TheMessage->cm_format_type == FMT_FIXED) {
2113                 int buflen;
2114                 int xlline = 0;
2115                 int nllen = strlen (nl);
2116                 if (mode == MT_MIME) {
2117                         cprintf("Content-type: text/plain\n\n");
2118                 }
2119                 *buf = '\0';
2120                 buflen = 0;
2121                 while (ch = *mptr++, ch > 0) {
2122                         if (ch == '\n')
2123                                 ch = '\r';
2124
2125                         if ((buflen > 250) && (!xlline)){
2126                                 int tbuflen;
2127                                 tbuflen = buflen;
2128
2129                                 while ((buflen > 0) && 
2130                                        (!isspace(buf[buflen])))
2131                                         buflen --;
2132                                 if (buflen == 0) {
2133                                         xlline = 1;
2134                                 }
2135                                 else {
2136                                         mptr -= tbuflen - buflen;
2137                                         buf[buflen] = '\0';
2138                                         ch = '\r';
2139                                 }
2140                         }
2141                         /* if we reach the outer bounds of our buffer, 
2142                            abort without respect what whe purge. */
2143                         if (xlline && 
2144                             ((isspace(ch)) || 
2145                              (buflen > SIZ - nllen - 2)))
2146                                 ch = '\r';
2147
2148                         if (ch == '\r') {
2149                                 memcpy (&buf[buflen], nl, nllen);
2150                                 buflen += nllen;
2151                                 buf[buflen] = '\0';
2152
2153                                 client_write(buf, buflen);
2154                                 *buf = '\0';
2155                                 buflen = 0;
2156                                 xlline = 0;
2157                         } else {
2158                                 buf[buflen] = ch;
2159                                 buflen++;
2160                         }
2161                 }
2162                 buf[buflen] = '\0';
2163                 if (!IsEmptyStr(buf))
2164                         cprintf("%s%s", buf, nl);
2165         }
2166
2167         /* If the message on disk is format 0 (Citadel vari-format), we
2168          * output using the formatter at 80 columns.  This is the final output
2169          * form if the transfer format is RFC822, but if the transfer format
2170          * is Citadel proprietary, it'll still work, because the indentation
2171          * for new paragraphs is correct and the client will reformat the
2172          * message to the reader's screen width.
2173          */
2174         if (TheMessage->cm_format_type == FMT_CITADEL) {
2175                 if (mode == MT_MIME) {
2176                         cprintf("Content-type: text/x-citadel-variformat\n\n");
2177                 }
2178                 memfmout(mptr, nl);
2179         }
2180
2181         /* If the message on disk is format 4 (MIME), we've gotta hand it
2182          * off to the MIME parser.  The client has already been told that
2183          * this message is format 1 (fixed format), so the callback function
2184          * we use will display those parts as-is.
2185          */
2186         if (TheMessage->cm_format_type == FMT_RFC822) {
2187                 memset(&ma, 0, sizeof(struct ma_info));
2188
2189                 if (mode == MT_MIME) {
2190                         ma.use_fo_hooks = 0;
2191                         strcpy(ma.chosen_part, "1");
2192                         ma.chosen_pref = 9999;
2193                         mime_parser(mptr, NULL,
2194                                 *choose_preferred, *fixed_output_pre,
2195                                 *fixed_output_post, (void *)&ma, 0);
2196                         mime_parser(mptr, NULL,
2197                                 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2198                 }
2199                 else {
2200                         ma.use_fo_hooks = 1;
2201                         mime_parser(mptr, NULL,
2202                                 *fixed_output, *fixed_output_pre,
2203                                 *fixed_output_post, (void *)&ma, 0);
2204                 }
2205
2206         }
2207
2208 DONE:   /* now we're done */
2209         if (do_proto) cprintf("000\n");
2210         return(om_ok);
2211 }
2212
2213
2214
2215 /*
2216  * display a message (mode 0 - Citadel proprietary)
2217  */
2218 void cmd_msg0(char *cmdbuf)
2219 {
2220         long msgid;
2221         int headers_only = HEADERS_ALL;
2222
2223         msgid = extract_long(cmdbuf, 0);
2224         headers_only = extract_int(cmdbuf, 1);
2225
2226         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2227         return;
2228 }
2229
2230
2231 /*
2232  * display a message (mode 2 - RFC822)
2233  */
2234 void cmd_msg2(char *cmdbuf)
2235 {
2236         long msgid;
2237         int headers_only = HEADERS_ALL;
2238
2239         msgid = extract_long(cmdbuf, 0);
2240         headers_only = extract_int(cmdbuf, 1);
2241
2242         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2243 }
2244
2245
2246
2247 /* 
2248  * display a message (mode 3 - IGnet raw format - internal programs only)
2249  */
2250 void cmd_msg3(char *cmdbuf)
2251 {
2252         long msgnum;
2253         struct CtdlMessage *msg = NULL;
2254         struct ser_ret smr;
2255
2256         if (CC->internal_pgm == 0) {
2257                 cprintf("%d This command is for internal programs only.\n",
2258                         ERROR + HIGHER_ACCESS_REQUIRED);
2259                 return;
2260         }
2261
2262         msgnum = extract_long(cmdbuf, 0);
2263         msg = CtdlFetchMessage(msgnum, 1);
2264         if (msg == NULL) {
2265                 cprintf("%d Message %ld not found.\n", 
2266                         ERROR + MESSAGE_NOT_FOUND, msgnum);
2267                 return;
2268         }
2269
2270         serialize_message(&smr, msg);
2271         CtdlFreeMessage(msg);
2272
2273         if (smr.len == 0) {
2274                 cprintf("%d Unable to serialize message\n",
2275                         ERROR + INTERNAL_ERROR);
2276                 return;
2277         }
2278
2279         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2280         client_write((char *)smr.ser, (int)smr.len);
2281         free(smr.ser);
2282 }
2283
2284
2285
2286 /* 
2287  * Display a message using MIME content types
2288  */
2289 void cmd_msg4(char *cmdbuf)
2290 {
2291         long msgid;
2292         char section[64];
2293
2294         msgid = extract_long(cmdbuf, 0);
2295         extract_token(section, cmdbuf, 1, '|', sizeof section);
2296         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2297 }
2298
2299
2300
2301 /* 
2302  * Client tells us its preferred message format(s)
2303  */
2304 void cmd_msgp(char *cmdbuf)
2305 {
2306         if (!strcasecmp(cmdbuf, "dont_decode")) {
2307                 CC->msg4_dont_decode = 1;
2308                 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2309         }
2310         else {
2311                 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2312                 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2313         }
2314 }
2315
2316
2317 /*
2318  * Open a component of a MIME message as a download file 
2319  */
2320 void cmd_opna(char *cmdbuf)
2321 {
2322         long msgid;
2323         char desired_section[128];
2324
2325         msgid = extract_long(cmdbuf, 0);
2326         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2327         safestrncpy(CC->download_desired_section, desired_section,
2328                 sizeof CC->download_desired_section);
2329         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2330 }                       
2331
2332
2333 /*
2334  * Open a component of a MIME message and transmit it all at once
2335  */
2336 void cmd_dlat(char *cmdbuf)
2337 {
2338         long msgid;
2339         char desired_section[128];
2340
2341         msgid = extract_long(cmdbuf, 0);
2342         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2343         safestrncpy(CC->download_desired_section, desired_section,
2344                 sizeof CC->download_desired_section);
2345         CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2346 }                       
2347
2348
2349 /*
2350  * Save one or more message pointers into a specified room
2351  * (Returns 0 for success, nonzero for failure)
2352  * roomname may be NULL to use the current room
2353  *
2354  * Note that the 'supplied_msg' field may be set to NULL, in which case
2355  * the message will be fetched from disk, by number, if we need to perform
2356  * replication checks.  This adds an additional database read, so if the
2357  * caller already has the message in memory then it should be supplied.  (Obviously
2358  * this mode of operation only works if we're saving a single message.)
2359  */
2360 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2361                                 int do_repl_check, struct CtdlMessage *supplied_msg)
2362 {
2363         int i, j, unique;
2364         char hold_rm[ROOMNAMELEN];
2365         struct cdbdata *cdbfr;
2366         int num_msgs;
2367         long *msglist;
2368         long highest_msg = 0L;
2369
2370         long msgid = 0;
2371         struct CtdlMessage *msg = NULL;
2372
2373         long *msgs_to_be_merged = NULL;
2374         int num_msgs_to_be_merged = 0;
2375
2376         CtdlLogPrintf(CTDL_DEBUG,
2377                 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2378                 roomname, num_newmsgs, do_repl_check);
2379
2380         strcpy(hold_rm, CC->room.QRname);
2381
2382         /* Sanity checks */
2383         if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2384         if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2385         if (num_newmsgs > 1) supplied_msg = NULL;
2386
2387         /* Now the regular stuff */
2388         if (CtdlGetRoomLock(&CC->room,
2389            ((roomname != NULL) ? roomname : CC->room.QRname) )
2390            != 0) {
2391                 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2392                 return(ERROR + ROOM_NOT_FOUND);
2393         }
2394
2395
2396         msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2397         num_msgs_to_be_merged = 0;
2398
2399
2400         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2401         if (cdbfr == NULL) {
2402                 msglist = NULL;
2403                 num_msgs = 0;
2404         } else {
2405                 msglist = (long *) cdbfr->ptr;
2406                 cdbfr->ptr = NULL;      /* CtdlSaveMsgPointerInRoom() now owns this memory */
2407                 num_msgs = cdbfr->len / sizeof(long);
2408                 cdb_free(cdbfr);
2409         }
2410
2411
2412         /* Create a list of msgid's which were supplied by the caller, but do
2413          * not already exist in the target room.  It is absolutely taboo to
2414          * have more than one reference to the same message in a room.
2415          */
2416         for (i=0; i<num_newmsgs; ++i) {
2417                 unique = 1;
2418                 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2419                         if (msglist[j] == newmsgidlist[i]) {
2420                                 unique = 0;
2421                         }
2422                 }
2423                 if (unique) {
2424                         msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2425                 }
2426         }
2427
2428         CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2429
2430         /*
2431          * Now merge the new messages
2432          */
2433         msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2434         if (msglist == NULL) {
2435                 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2436         }
2437         memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2438         num_msgs += num_msgs_to_be_merged;
2439
2440         /* Sort the message list, so all the msgid's are in order */
2441         num_msgs = sort_msglist(msglist, num_msgs);
2442
2443         /* Determine the highest message number */
2444         highest_msg = msglist[num_msgs - 1];
2445
2446         /* Write it back to disk. */
2447         cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2448                   msglist, (int)(num_msgs * sizeof(long)));
2449
2450         /* Free up the memory we used. */
2451         free(msglist);
2452
2453         /* Update the highest-message pointer and unlock the room. */
2454         CC->room.QRhighest = highest_msg;
2455         CtdlPutRoomLock(&CC->room);
2456
2457         /* Perform replication checks if necessary */
2458         if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2459                 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2460
2461                 for (i=0; i<num_msgs_to_be_merged; ++i) {
2462                         msgid = msgs_to_be_merged[i];
2463         
2464                         if (supplied_msg != NULL) {
2465                                 msg = supplied_msg;
2466                         }
2467                         else {
2468                                 msg = CtdlFetchMessage(msgid, 0);
2469                         }
2470         
2471                         if (msg != NULL) {
2472                                 ReplicationChecks(msg);
2473                 
2474                                 /* If the message has an Exclusive ID, index that... */
2475                                 if (msg->cm_fields['E'] != NULL) {
2476                                         index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2477                                 }
2478
2479                                 /* Free up the memory we may have allocated */
2480                                 if (msg != supplied_msg) {
2481                                         CtdlFreeMessage(msg);
2482                                 }
2483                         }
2484         
2485                 }
2486         }
2487
2488         else {
2489                 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2490         }
2491
2492         /* Submit this room for processing by hooks */
2493         PerformRoomHooks(&CC->room);
2494
2495         /* Go back to the room we were in before we wandered here... */
2496         CtdlGetRoom(&CC->room, hold_rm);
2497
2498         /* Bump the reference count for all messages which were merged */
2499         for (i=0; i<num_msgs_to_be_merged; ++i) {
2500                 AdjRefCount(msgs_to_be_merged[i], +1);
2501         }
2502
2503         /* Free up memory... */
2504         if (msgs_to_be_merged != NULL) {
2505                 free(msgs_to_be_merged);
2506         }
2507
2508         /* Return success. */
2509         return (0);
2510 }
2511
2512
2513 /*
2514  * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2515  * a single message.
2516  */
2517 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2518                         int do_repl_check, struct CtdlMessage *supplied_msg)
2519 {
2520         return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2521 }
2522
2523
2524
2525
2526 /*
2527  * Message base operation to save a new message to the message store
2528  * (returns new message number)
2529  *
2530  * This is the back end for CtdlSubmitMsg() and should not be directly
2531  * called by server-side modules.
2532  *
2533  */
2534 long send_message(struct CtdlMessage *msg) {
2535         long newmsgid;
2536         long retval;
2537         char msgidbuf[256];
2538         struct ser_ret smr;
2539         int is_bigmsg = 0;
2540         char *holdM = NULL;
2541
2542         /* Get a new message number */
2543         newmsgid = get_new_message_number();
2544         snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2545
2546         /* Generate an ID if we don't have one already */
2547         if (msg->cm_fields['I']==NULL) {
2548                 msg->cm_fields['I'] = strdup(msgidbuf);
2549         }
2550
2551         /* If the message is big, set its body aside for storage elsewhere */
2552         if (msg->cm_fields['M'] != NULL) {
2553                 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2554                         is_bigmsg = 1;
2555                         holdM = msg->cm_fields['M'];
2556                         msg->cm_fields['M'] = NULL;
2557                 }
2558         }
2559
2560         /* Serialize our data structure for storage in the database */  
2561         serialize_message(&smr, msg);
2562
2563         if (is_bigmsg) {
2564                 msg->cm_fields['M'] = holdM;
2565         }
2566
2567         if (smr.len == 0) {
2568                 cprintf("%d Unable to serialize message\n",
2569                         ERROR + INTERNAL_ERROR);
2570                 return (-1L);
2571         }
2572
2573         /* Write our little bundle of joy into the message base */
2574         if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2575                       smr.ser, smr.len) < 0) {
2576                 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2577                 retval = 0L;
2578         } else {
2579                 if (is_bigmsg) {
2580                         cdb_store(CDB_BIGMSGS,
2581                                 &newmsgid,
2582                                 (int)sizeof(long),
2583                                 holdM,
2584                                 (strlen(holdM) + 1)
2585                         );
2586                 }
2587                 retval = newmsgid;
2588         }
2589
2590         /* Free the memory we used for the serialized message */
2591         free(smr.ser);
2592
2593         /* Return the *local* message ID to the caller
2594          * (even if we're storing an incoming network message)
2595          */
2596         return(retval);
2597 }
2598
2599
2600
2601 /*
2602  * Serialize a struct CtdlMessage into the format used on disk and network.
2603  * 
2604  * This function loads up a "struct ser_ret" (defined in server.h) which
2605  * contains the length of the serialized message and a pointer to the
2606  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
2607  */
2608 void serialize_message(struct ser_ret *ret,             /* return values */
2609                         struct CtdlMessage *msg)        /* unserialized msg */
2610 {
2611         size_t wlen, fieldlen;
2612         int i;
2613         static char *forder = FORDER;
2614
2615         /*
2616          * Check for valid message format
2617          */
2618         if (is_valid_message(msg) == 0) {
2619                 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2620                 ret->len = 0;
2621                 ret->ser = NULL;
2622                 return;
2623         }
2624
2625         ret->len = 3;
2626         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2627                 ret->len = ret->len +
2628                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
2629
2630         ret->ser = malloc(ret->len);
2631         if (ret->ser == NULL) {
2632                 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2633                         (long)ret->len, strerror(errno));
2634                 ret->len = 0;
2635                 ret->ser = NULL;
2636                 return;
2637         }
2638
2639         ret->ser[0] = 0xFF;
2640         ret->ser[1] = msg->cm_anon_type;
2641         ret->ser[2] = msg->cm_format_type;
2642         wlen = 3;
2643
2644         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2645                 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2646                 ret->ser[wlen++] = (char)forder[i];
2647                 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2648                 wlen = wlen + fieldlen + 1;
2649         }
2650         if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2651                 (long)ret->len, (long)wlen);
2652
2653         return;
2654 }
2655
2656
2657 /*
2658  * Serialize a struct CtdlMessage into the format used on disk and network.
2659  * 
2660  * This function loads up a "struct ser_ret" (defined in server.h) which
2661  * contains the length of the serialized message and a pointer to the
2662  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
2663  */
2664 void dump_message(struct CtdlMessage *msg,      /* unserialized msg */
2665                   long Siz)                     /* how many chars ? */
2666 {
2667         size_t wlen;
2668         int i;
2669         static char *forder = FORDER;
2670         char *buf;
2671
2672         /*
2673          * Check for valid message format
2674          */
2675         if (is_valid_message(msg) == 0) {
2676                 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2677                 return;
2678         }
2679
2680         buf = (char*) malloc (Siz + 1);
2681
2682         wlen = 3;
2683         
2684         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2685                         snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i], 
2686                                    msg->cm_fields[(int)forder[i]]);
2687                         client_write (buf, strlen(buf));
2688                 }
2689
2690         return;
2691 }
2692
2693
2694
2695 /*
2696  * Check to see if any messages already exist in the current room which
2697  * carry the same Exclusive ID as this one.  If any are found, delete them.
2698  */
2699 void ReplicationChecks(struct CtdlMessage *msg) {
2700         long old_msgnum = (-1L);
2701
2702         if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2703
2704         CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2705                 CC->room.QRname);
2706
2707         /* No exclusive id?  Don't do anything. */
2708         if (msg == NULL) return;
2709         if (msg->cm_fields['E'] == NULL) return;
2710         if (IsEmptyStr(msg->cm_fields['E'])) return;
2711         /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2712                 msg->cm_fields['E'], CC->room.QRname);*/
2713
2714         old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2715         if (old_msgnum > 0L) {
2716                 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2717                 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2718         }
2719 }
2720
2721
2722
2723 /*
2724  * Save a message to disk and submit it into the delivery system.
2725  */
2726 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
2727                    struct recptypes *recps,     /* recipients (if mail) */
2728                    char *force,                 /* force a particular room? */
2729                    int flags                    /* should the message be exported clean? */
2730 ) {
2731         char submit_filename[128];
2732         char generated_timestamp[32];
2733         char hold_rm[ROOMNAMELEN];
2734         char actual_rm[ROOMNAMELEN];
2735         char force_room[ROOMNAMELEN];
2736         char content_type[SIZ];                 /* We have to learn this */
2737         char recipient[SIZ];
2738         long newmsgid;
2739         const char *mptr = NULL;
2740         struct ctdluser userbuf;
2741         int a, i;
2742         struct MetaData smi;
2743         FILE *network_fp = NULL;
2744         static int seqnum = 1;
2745         struct CtdlMessage *imsg = NULL;
2746         char *instr = NULL;
2747         size_t instr_alloc = 0;
2748         struct ser_ret smr;
2749         char *hold_R, *hold_D;
2750         char *collected_addresses = NULL;
2751         struct addresses_to_be_filed *aptr = NULL;
2752         char *saved_rfc822_version = NULL;
2753         int qualified_for_journaling = 0;
2754         CitContext *CCC = CC;           /* CachedCitContext - performance boost */
2755         char bounce_to[1024] = "";
2756         size_t tmp = 0;
2757         int rv = 0;
2758
2759         CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2760         if (is_valid_message(msg) == 0) return(-1);     /* self check */
2761
2762         /* If this message has no timestamp, we take the liberty of
2763          * giving it one, right now.
2764          */
2765         if (msg->cm_fields['T'] == NULL) {
2766                 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2767                 msg->cm_fields['T'] = strdup(generated_timestamp);
2768         }
2769
2770         /* If this message has no path, we generate one.
2771          */
2772         if (msg->cm_fields['P'] == NULL) {
2773                 if (msg->cm_fields['A'] != NULL) {
2774                         msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2775                         for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2776                                 if (isspace(msg->cm_fields['P'][a])) {
2777                                         msg->cm_fields['P'][a] = ' ';
2778                                 }
2779                         }
2780                 }
2781                 else {
2782                         msg->cm_fields['P'] = strdup("unknown");
2783                 }
2784         }
2785
2786         if (force == NULL) {
2787                 strcpy(force_room, "");
2788         }
2789         else {
2790                 strcpy(force_room, force);
2791         }
2792
2793         /* Learn about what's inside, because it's what's inside that counts */
2794         if (msg->cm_fields['M'] == NULL) {
2795                 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2796                 return(-2);
2797         }
2798
2799         switch (msg->cm_format_type) {
2800         case 0:
2801                 strcpy(content_type, "text/x-citadel-variformat");
2802                 break;
2803         case 1:
2804                 strcpy(content_type, "text/plain");
2805                 break;
2806         case 4:
2807                 strcpy(content_type, "text/plain");
2808                 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2809                 if (mptr != NULL) {
2810                         char *aptr;
2811                         safestrncpy(content_type, &mptr[13], sizeof content_type);
2812                         striplt(content_type);
2813                         aptr = content_type;
2814                         while (!IsEmptyStr(aptr)) {
2815                                 if ((*aptr == ';')
2816                                     || (*aptr == ' ')
2817                                     || (*aptr == 13)
2818                                     || (*aptr == 10)) {
2819                                         *aptr = 0;
2820                                 }
2821                                 else aptr++;
2822                         }
2823                 }
2824         }
2825
2826         /* Goto the correct room */
2827         CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2828         strcpy(hold_rm, CCC->room.QRname);
2829         strcpy(actual_rm, CCC->room.QRname);
2830         if (recps != NULL) {
2831                 strcpy(actual_rm, SENTITEMS);
2832         }
2833
2834         /* If the user is a twit, move to the twit room for posting */
2835         if (TWITDETECT) {
2836                 if (CCC->user.axlevel == AxProbU) {
2837                         strcpy(hold_rm, actual_rm);
2838                         strcpy(actual_rm, config.c_twitroom);
2839                         CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2840                 }
2841         }
2842
2843         /* ...or if this message is destined for Aide> then go there. */
2844         if (!IsEmptyStr(force_room)) {
2845                 strcpy(actual_rm, force_room);
2846         }
2847
2848         CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2849         if (strcasecmp(actual_rm, CCC->room.QRname)) {
2850                 /* CtdlGetRoom(&CCC->room, actual_rm); */
2851                 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
2852         }
2853
2854         /*
2855          * If this message has no O (room) field, generate one.
2856          */
2857         if (msg->cm_fields['O'] == NULL) {
2858                 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2859         }
2860
2861         /* Perform "before save" hooks (aborting if any return nonzero) */
2862         CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2863         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2864
2865         /*
2866          * If this message has an Exclusive ID, and the room is replication
2867          * checking enabled, then do replication checks.
2868          */
2869         if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2870                 ReplicationChecks(msg);
2871         }
2872
2873         /* Save it to disk */
2874         CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2875         newmsgid = send_message(msg);
2876         if (newmsgid <= 0L) return(-5);
2877
2878         /* Write a supplemental message info record.  This doesn't have to
2879          * be a critical section because nobody else knows about this message
2880          * yet.
2881          */
2882         CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2883         memset(&smi, 0, sizeof(struct MetaData));
2884         smi.meta_msgnum = newmsgid;
2885         smi.meta_refcount = 0;
2886         safestrncpy(smi.meta_content_type, content_type,
2887                         sizeof smi.meta_content_type);
2888
2889         /*
2890          * Measure how big this message will be when rendered as RFC822.
2891          * We do this for two reasons:
2892          * 1. We need the RFC822 length for the new metadata record, so the
2893          *    POP and IMAP services don't have to calculate message lengths
2894          *    while the user is waiting (multiplied by potentially hundreds
2895          *    or thousands of messages).
2896          * 2. If journaling is enabled, we will need an RFC822 version of the
2897          *    message to attach to the journalized copy.
2898          */
2899         if (CCC->redirect_buffer != NULL) {
2900                 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2901                 abort();
2902         }
2903         CCC->redirect_buffer = malloc(SIZ);
2904         CCC->redirect_len = 0;
2905         CCC->redirect_alloc = SIZ;
2906         CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2907         smi.meta_rfc822_length = CCC->redirect_len;
2908         saved_rfc822_version = CCC->redirect_buffer;
2909         CCC->redirect_buffer = NULL;
2910         CCC->redirect_len = 0;
2911         CCC->redirect_alloc = 0;
2912
2913         PutMetaData(&smi);
2914
2915         /* Now figure out where to store the pointers */
2916         CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2917
2918         /* If this is being done by the networker delivering a private
2919          * message, we want to BYPASS saving the sender's copy (because there
2920          * is no local sender; it would otherwise go to the Trashcan).
2921          */
2922         if ((!CCC->internal_pgm) || (recps == NULL)) {
2923                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2924                         CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2925                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2926                 }
2927         }
2928
2929         /* For internet mail, drop a copy in the outbound queue room */
2930         if ((recps != NULL) && (recps->num_internet > 0)) {
2931                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2932         }
2933
2934         /* If other rooms are specified, drop them there too. */
2935         if ((recps != NULL) && (recps->num_room > 0))
2936           for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2937                 extract_token(recipient, recps->recp_room, i,
2938                                         '|', sizeof recipient);
2939                 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2940                 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2941         }
2942
2943         /* Bump this user's messages posted counter. */
2944         CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2945         CtdlGetUserLock(&CCC->user, CCC->curr_user);
2946         CCC->user.posted = CCC->user.posted + 1;
2947         CtdlPutUserLock(&CCC->user);
2948
2949         /* Decide where bounces need to be delivered */
2950         if ((recps != NULL) && (recps->bounce_to != NULL)) {
2951                 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2952         }
2953         else if (CCC->logged_in) {
2954                 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2955         }
2956         else {
2957                 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2958         }
2959
2960         /* If this is private, local mail, make a copy in the
2961          * recipient's mailbox and bump the reference count.
2962          */
2963         if ((recps != NULL) && (recps->num_local > 0))
2964           for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2965                 extract_token(recipient, recps->recp_local, i,
2966                                         '|', sizeof recipient);
2967                 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2968                         recipient);
2969                 if (CtdlGetUser(&userbuf, recipient) == 0) {
2970                         // Add a flag so the Funambol module knows its mail
2971                         msg->cm_fields['W'] = strdup(recipient);
2972                         CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2973                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2974                         CtdlBumpNewMailCounter(userbuf.usernum);
2975                         if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2976                         /* Generate a instruction message for the Funambol notification
2977                          * server, in the same style as the SMTP queue
2978                          */
2979                            instr_alloc = 1024;
2980                            instr = malloc(instr_alloc);
2981                            snprintf(instr, instr_alloc,
2982                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2983                         "bounceto|%s\n",
2984                         SPOOLMIME, newmsgid, (long)time(NULL),
2985                         bounce_to
2986                         );
2987
2988                            imsg = malloc(sizeof(struct CtdlMessage));
2989                            memset(imsg, 0, sizeof(struct CtdlMessage));
2990                            imsg->cm_magic = CTDLMESSAGE_MAGIC;
2991                            imsg->cm_anon_type = MES_NORMAL;
2992                            imsg->cm_format_type = FMT_RFC822;
2993                            imsg->cm_fields['A'] = strdup("Citadel");
2994                            imsg->cm_fields['J'] = strdup("do not journal");
2995                            imsg->cm_fields['M'] = instr;        /* imsg owns this memory now */
2996                            imsg->cm_fields['W'] = strdup(recipient);
2997                            CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2998                            CtdlFreeMessage(imsg);
2999                         }
3000                 }
3001                 else {
3002                         CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
3003                         CtdlSaveMsgPointerInRoom(config.c_aideroom,
3004                                 newmsgid, 0, msg);
3005                 }
3006         }
3007
3008         /* Perform "after save" hooks */
3009         CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
3010         PerformMessageHooks(msg, EVT_AFTERSAVE);
3011
3012         /* For IGnet mail, we have to save a new copy into the spooler for
3013          * each recipient, with the R and D fields set to the recipient and
3014          * destination-node.  This has two ugly side effects: all other
3015          * recipients end up being unlisted in this recipient's copy of the
3016          * message, and it has to deliver multiple messages to the same
3017          * node.  We'll revisit this again in a year or so when everyone has
3018          * a network spool receiver that can handle the new style messages.
3019          */
3020         if ((recps != NULL) && (recps->num_ignet > 0))
3021           for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3022                 extract_token(recipient, recps->recp_ignet, i,
3023                                 '|', sizeof recipient);
3024
3025                 hold_R = msg->cm_fields['R'];
3026                 hold_D = msg->cm_fields['D'];
3027                 msg->cm_fields['R'] = malloc(SIZ);
3028                 msg->cm_fields['D'] = malloc(128);
3029                 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3030                 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3031                 
3032                 serialize_message(&smr, msg);
3033                 if (smr.len > 0) {
3034                         snprintf(submit_filename, sizeof submit_filename,
3035                                          "%s/netmail.%04lx.%04x.%04x",
3036                                          ctdl_netin_dir,
3037                                          (long) getpid(), CCC->cs_pid, ++seqnum);
3038                         network_fp = fopen(submit_filename, "wb+");
3039                         if (network_fp != NULL) {
3040                                 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3041                                 fclose(network_fp);
3042                         }
3043                         free(smr.ser);
3044                 }
3045
3046                 free(msg->cm_fields['R']);
3047                 free(msg->cm_fields['D']);
3048                 msg->cm_fields['R'] = hold_R;
3049                 msg->cm_fields['D'] = hold_D;
3050         }
3051
3052         /* Go back to the room we started from */
3053         CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3054         if (strcasecmp(hold_rm, CCC->room.QRname))
3055                 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3056
3057         /* For internet mail, generate delivery instructions.
3058          * Yes, this is recursive.  Deal with it.  Infinite recursion does
3059          * not happen because the delivery instructions message does not
3060          * contain a recipient.
3061          */
3062         if ((recps != NULL) && (recps->num_internet > 0)) {
3063                 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3064                 instr_alloc = 1024;
3065                 instr = malloc(instr_alloc);
3066                 snprintf(instr, instr_alloc,
3067                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3068                         "bounceto|%s\n",
3069                         SPOOLMIME, newmsgid, (long)time(NULL),
3070                         bounce_to
3071                 );
3072
3073                 if (recps->envelope_from != NULL) {
3074                         tmp = strlen(instr);
3075                         snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3076                 }
3077
3078                 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3079                         tmp = strlen(instr);
3080                         extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3081                         if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3082                                 instr_alloc = instr_alloc * 2;
3083                                 instr = realloc(instr, instr_alloc);
3084                         }
3085                         snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3086                 }
3087
3088                 imsg = malloc(sizeof(struct CtdlMessage));
3089                 memset(imsg, 0, sizeof(struct CtdlMessage));
3090                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3091                 imsg->cm_anon_type = MES_NORMAL;
3092                 imsg->cm_format_type = FMT_RFC822;
3093                 imsg->cm_fields['A'] = strdup("Citadel");
3094                 imsg->cm_fields['J'] = strdup("do not journal");
3095                 imsg->cm_fields['M'] = instr;   /* imsg owns this memory now */
3096                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3097                 CtdlFreeMessage(imsg);
3098         }
3099
3100         /*
3101          * Any addresses to harvest for someone's address book?
3102          */
3103         if ( (CCC->logged_in) && (recps != NULL) ) {
3104                 collected_addresses = harvest_collected_addresses(msg);
3105         }
3106
3107         if (collected_addresses != NULL) {
3108                 aptr = (struct addresses_to_be_filed *)
3109                         malloc(sizeof(struct addresses_to_be_filed));
3110                 CtdlMailboxName(actual_rm, sizeof actual_rm,
3111                         &CCC->user, USERCONTACTSROOM);
3112                 aptr->roomname = strdup(actual_rm);
3113                 aptr->collected_addresses = collected_addresses;
3114                 begin_critical_section(S_ATBF);
3115                 aptr->next = atbf;
3116                 atbf = aptr;
3117                 end_critical_section(S_ATBF);
3118         }
3119
3120         /*
3121          * Determine whether this message qualifies for journaling.
3122          */
3123         if (msg->cm_fields['J'] != NULL) {
3124                 qualified_for_journaling = 0;
3125         }
3126         else {
3127                 if (recps == NULL) {
3128                         qualified_for_journaling = config.c_journal_pubmsgs;
3129                 }
3130                 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3131                         qualified_for_journaling = config.c_journal_email;
3132                 }
3133                 else {
3134                         qualified_for_journaling = config.c_journal_pubmsgs;
3135                 }
3136         }
3137
3138         /*
3139          * Do we have to perform journaling?  If so, hand off the saved
3140          * RFC822 version will be handed off to the journaler for background
3141          * submit.  Otherwise, we have to free the memory ourselves.
3142          */
3143         if (saved_rfc822_version != NULL) {
3144                 if (qualified_for_journaling) {
3145                         JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3146                 }
3147                 else {
3148                         free(saved_rfc822_version);
3149                 }
3150         }
3151
3152         /* Done. */
3153         return(newmsgid);
3154 }
3155
3156
3157
3158 void aide_message (char *text, char *subject)
3159 {
3160         quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3161 }
3162
3163
3164 /*
3165  * Convenience function for generating small administrative messages.
3166  */
3167 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text, 
3168                         int format_type, const char *subject)
3169 {
3170         struct CtdlMessage *msg;
3171         struct recptypes *recp = NULL;
3172
3173         msg = malloc(sizeof(struct CtdlMessage));
3174         memset(msg, 0, sizeof(struct CtdlMessage));
3175         msg->cm_magic = CTDLMESSAGE_MAGIC;
3176         msg->cm_anon_type = MES_NORMAL;
3177         msg->cm_format_type = format_type;
3178
3179         if (from != NULL) {
3180                 msg->cm_fields['A'] = strdup(from);
3181         }
3182         else if (fromaddr != NULL) {
3183                 msg->cm_fields['A'] = strdup(fromaddr);
3184                 if (strchr(msg->cm_fields['A'], '@')) {
3185                         *strchr(msg->cm_fields['A'], '@') = 0;
3186                 }
3187         }
3188         else {
3189                 msg->cm_fields['A'] = strdup("Citadel");
3190         }
3191
3192         if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3193         if (room != NULL) msg->cm_fields['O'] = strdup(room);
3194         msg->cm_fields['N'] = strdup(NODENAME);
3195         if (to != NULL) {
3196                 msg->cm_fields['R'] = strdup(to);
3197                 recp = validate_recipients(to, NULL, 0);
3198         }
3199         if (subject != NULL) {
3200                 msg->cm_fields['U'] = strdup(subject);
3201         }
3202         msg->cm_fields['M'] = strdup(text);
3203
3204         CtdlSubmitMsg(msg, recp, room, 0);
3205         CtdlFreeMessage(msg);
3206         if (recp != NULL) free_recipients(recp);
3207 }
3208
3209
3210
3211 /*
3212  * Back end function used by CtdlMakeMessage() and similar functions
3213  */
3214 StrBuf *CtdlReadMessageBodyBuf(char *terminator,        /* token signalling EOT */
3215                                long tlen,
3216                                size_t maxlen,           /* maximum message length */
3217                                char *exist,             /* if non-null, append to it;
3218                                                            exist is ALWAYS freed  */
3219                                int crlf,                /* CRLF newlines instead of LF */
3220                                int *sock                /* socket handle or 0 for this session's client socket */
3221                         ) 
3222 {
3223         StrBuf *Message;
3224         StrBuf *LineBuf;
3225         int flushing = 0;
3226         int finished = 0;
3227         int dotdot = 0;
3228
3229         LineBuf = NewStrBufPlain(NULL, SIZ);
3230         if (exist == NULL) {
3231                 Message = NewStrBufPlain(NULL, 4 * SIZ);
3232         }
3233         else {
3234                 Message = NewStrBufPlain(exist, -1);
3235                 free(exist);
3236         }
3237
3238         /* Do we need to change leading ".." to "." for SMTP escaping? */
3239         if ((tlen == 1) && (*terminator == '.')) {
3240                 dotdot = 1;
3241         }
3242
3243         /* read in the lines of message text one by one */
3244         do {
3245                 if (sock != NULL) {
3246                         if ((CtdlSockGetLine(sock, LineBuf) < 0) ||
3247                             (*sock == -1))
3248                                 finished = 1;
3249                 }
3250                 else {
3251                         if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3252                 }
3253                 if ((StrLength(LineBuf) == tlen) && 
3254                     (!strcmp(ChrPtr(LineBuf), terminator)))
3255                         finished = 1;
3256
3257                 if ( (!flushing) && (!finished) ) {
3258                         if (crlf) {
3259                                 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3260                         }
3261                         else {
3262                                 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3263                         }
3264                         
3265                         /* Unescape SMTP-style input of two dots at the beginning of the line */
3266                         if ((dotdot) &&
3267                             (StrLength(LineBuf) == 2) && 
3268                             (!strcmp(ChrPtr(LineBuf), "..")))
3269                         {
3270                                 StrBufCutLeft(LineBuf, 1);
3271                         }
3272                         
3273                         StrBufAppendBuf(Message, LineBuf, 0);
3274                 }
3275
3276                 /* if we've hit the max msg length, flush the rest */
3277                 if (StrLength(Message) >= maxlen) flushing = 1;
3278
3279         } while (!finished);
3280         FreeStrBuf(&LineBuf);
3281         return Message;
3282 }
3283
3284
3285 /*
3286  * Back end function used by CtdlMakeMessage() and similar functions
3287  */
3288 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
3289                           long tlen,
3290                           size_t maxlen,                /* maximum message length */
3291                           char *exist,          /* if non-null, append to it;
3292                                                    exist is ALWAYS freed  */
3293                           int crlf,             /* CRLF newlines instead of LF */
3294                           int *sock             /* socket handle or 0 for this session's client socket */
3295         ) 
3296 {
3297         StrBuf *Message;
3298
3299         Message = CtdlReadMessageBodyBuf(terminator,
3300                                          tlen,
3301                                          maxlen,
3302                                          exist,
3303                                          crlf,
3304                                          sock);
3305         if (Message == NULL)
3306                 return NULL;
3307         else
3308                 return SmashStrBuf(&Message);
3309 }
3310
3311
3312 /*
3313  * Build a binary message to be saved on disk.
3314  * (NOTE: if you supply 'preformatted_text', the buffer you give it
3315  * will become part of the message.  This means you are no longer
3316  * responsible for managing that memory -- it will be freed along with
3317  * the rest of the fields when CtdlFreeMessage() is called.)
3318  */
3319
3320 struct CtdlMessage *CtdlMakeMessage(
3321         struct ctdluser *author,        /* author's user structure */
3322         char *recipient,                /* NULL if it's not mail */
3323         char *recp_cc,                  /* NULL if it's not mail */
3324         char *room,                     /* room where it's going */
3325         int type,                       /* see MES_ types in header file */
3326         int format_type,                /* variformat, plain text, MIME... */
3327         char *fake_name,                /* who we're masquerading as */
3328         char *my_email,                 /* which of my email addresses to use (empty is ok) */
3329         char *subject,                  /* Subject (optional) */
3330         char *supplied_euid,            /* ...or NULL if this is irrelevant */
3331         char *preformatted_text,        /* ...or NULL to read text from client */
3332         char *references                /* Thread references */
3333 ) {
3334         char dest_node[256];
3335         char buf[1024];
3336         struct CtdlMessage *msg;
3337
3338         msg = malloc(sizeof(struct CtdlMessage));
3339         memset(msg, 0, sizeof(struct CtdlMessage));
3340         msg->cm_magic = CTDLMESSAGE_MAGIC;
3341         msg->cm_anon_type = type;
3342         msg->cm_format_type = format_type;
3343
3344         /* Don't confuse the poor folks if it's not routed mail. */
3345         strcpy(dest_node, "");
3346
3347         if (recipient != NULL) striplt(recipient);
3348         if (recp_cc != NULL) striplt(recp_cc);
3349
3350         /* Path or Return-Path */
3351         if (my_email == NULL) my_email = "";
3352
3353         if (!IsEmptyStr(my_email)) {
3354                 msg->cm_fields['P'] = strdup(my_email);
3355         }
3356         else {
3357                 snprintf(buf, sizeof buf, "%s", author->fullname);
3358                 msg->cm_fields['P'] = strdup(buf);
3359         }
3360         convert_spaces_to_underscores(msg->cm_fields['P']);
3361
3362         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
3363         msg->cm_fields['T'] = strdup(buf);
3364
3365         if ((fake_name != NULL) && (fake_name[0])) {            /* author */
3366                 msg->cm_fields['A'] = strdup(fake_name);
3367         }
3368         else {
3369                 msg->cm_fields['A'] = strdup(author->fullname);
3370         }
3371
3372         if (CC->room.QRflags & QR_MAILBOX) {            /* room */
3373                 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3374         }
3375         else {
3376                 msg->cm_fields['O'] = strdup(CC->room.QRname);
3377         }
3378
3379         msg->cm_fields['N'] = strdup(NODENAME);         /* nodename */
3380         msg->cm_fields['H'] = strdup(HUMANNODE);                /* hnodename */
3381
3382         if ((recipient != NULL) && (recipient[0] != 0)) {
3383                 msg->cm_fields['R'] = strdup(recipient);
3384         }
3385         if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3386                 msg->cm_fields['Y'] = strdup(recp_cc);
3387         }
3388         if (dest_node[0] != 0) {
3389                 msg->cm_fields['D'] = strdup(dest_node);
3390         }
3391
3392         if (!IsEmptyStr(my_email)) {
3393                 msg->cm_fields['F'] = strdup(my_email);
3394         }
3395         else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3396                 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3397         }
3398
3399         if (subject != NULL) {
3400                 long length;
3401                 striplt(subject);
3402                 length = strlen(subject);
3403                 if (length > 0) {
3404                         long i;
3405                         long IsAscii;
3406                         IsAscii = -1;
3407                         i = 0;
3408                         while ((subject[i] != '\0') &&
3409                                (IsAscii = isascii(subject[i]) != 0 ))
3410                                 i++;
3411                         if (IsAscii != 0)
3412                                 msg->cm_fields['U'] = strdup(subject);
3413                         else /* ok, we've got utf8 in the string. */
3414                         {
3415                                 msg->cm_fields['U'] = rfc2047encode(subject, length);
3416                         }
3417
3418                 }
3419         }
3420
3421         if (supplied_euid != NULL) {
3422                 msg->cm_fields['E'] = strdup(supplied_euid);
3423         }
3424
3425         if (references != NULL) {
3426                 if (!IsEmptyStr(references)) {
3427                         msg->cm_fields['W'] = strdup(references);
3428                 }
3429         }
3430
3431         if (preformatted_text != NULL) {
3432                 msg->cm_fields['M'] = preformatted_text;
3433         }
3434         else {
3435                 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3436         }
3437
3438         return(msg);
3439 }
3440
3441
3442 /*
3443  * Check to see whether we have permission to post a message in the current
3444  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3445  * returns 0 on success.
3446  */
3447 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, 
3448                                           size_t n, 
3449                                           const char* RemoteIdentifier,
3450                                           int PostPublic) {
3451         int ra;
3452
3453         if (!(CC->logged_in) && 
3454             (PostPublic == POST_LOGGED_IN)) {
3455                 snprintf(errmsgbuf, n, "Not logged in.");
3456                 return (ERROR + NOT_LOGGED_IN);
3457         }
3458         else if (PostPublic == CHECK_EXISTANCE) {
3459                 return (0); // We're Evaling whether a recipient exists
3460         }
3461         else if (!(CC->logged_in)) {
3462                 
3463                 if ((CC->room.QRflags & QR_READONLY)) {
3464                         snprintf(errmsgbuf, n, "Not logged in.");
3465                         return (ERROR + NOT_LOGGED_IN);
3466                 }
3467                 if (CC->room.QRflags2 & QR2_MODERATED) {
3468                         snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3469                         return (ERROR + NOT_LOGGED_IN);
3470                 }
3471                 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3472                         SpoolControl *sc;
3473                         char filename[SIZ];
3474                         int found;
3475
3476                         if (RemoteIdentifier == NULL)
3477                         {
3478                                 snprintf(errmsgbuf, n, "Need sender to permit access.");
3479                                 return (ERROR + USERNAME_REQUIRED);
3480                         }
3481
3482                         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3483                         begin_critical_section(S_NETCONFIGS);
3484                         if (!read_spoolcontrol_file(&sc, filename))
3485                         {
3486                                 end_critical_section(S_NETCONFIGS);
3487                                 snprintf(errmsgbuf, n,
3488                                         "This mailing list only accepts posts from subscribers.");
3489                                 return (ERROR + NO_SUCH_USER);
3490                         }
3491                         end_critical_section(S_NETCONFIGS);
3492                         found = is_recipient (sc, RemoteIdentifier);
3493                         free_spoolcontrol_struct(&sc);
3494                         if (found) {
3495                                 return (0);
3496                         }
3497                         else {
3498                                 snprintf(errmsgbuf, n,
3499                                         "This mailing list only accepts posts from subscribers.");
3500                                 return (ERROR + NO_SUCH_USER);
3501                         }
3502                 }
3503                 return (0);
3504
3505         }
3506
3507         if ((CC->user.axlevel < AxProbU)
3508             && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3509                 snprintf(errmsgbuf, n, "Need to be validated to enter "
3510                                 "(except in %s> to sysop)", MAILROOM);
3511                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3512         }
3513
3514         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3515         if (!(ra & UA_POSTALLOWED)) {
3516                 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3517                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3518         }
3519
3520         strcpy(errmsgbuf, "Ok");
3521         return(0);
3522 }
3523
3524
3525 /*
3526  * Check to see if the specified user has Internet mail permission
3527  * (returns nonzero if permission is granted)
3528  */
3529 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3530
3531         /* Do not allow twits to send Internet mail */
3532         if (who->axlevel <= AxProbU) return(0);
3533
3534         /* Globally enabled? */
3535         if (config.c_restrict == 0) return(1);
3536
3537         /* User flagged ok? */
3538         if (who->flags & US_INTERNET) return(2);
3539
3540         /* Aide level access? */
3541         if (who->axlevel >= AxAideU) return(3);
3542
3543         /* No mail for you! */
3544         return(0);
3545 }
3546
3547
3548 /*
3549  * Validate recipients, count delivery types and errors, and handle aliasing
3550  * FIXME check for dupes!!!!!
3551  *
3552  * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
3553  * were specified, or the number of addresses found invalid.
3554  *
3555  * Caller needs to free the result using free_recipients()
3556  */
3557 struct recptypes *validate_recipients(const char *supplied_recipients, 
3558                                       const char *RemoteIdentifier, 
3559                                       int Flags) {
3560         struct recptypes *ret;
3561         char *recipients = NULL;
3562         char this_recp[256];
3563         char this_recp_cooked[256];
3564         char append[SIZ];
3565         int num_recps = 0;
3566         int i, j;
3567         int mailtype;
3568         int invalid;
3569         struct ctdluser tempUS;
3570         struct ctdlroom tempQR;
3571         struct ctdlroom tempQR2;
3572         int err = 0;
3573         char errmsg[SIZ];
3574         int in_quotes = 0;
3575
3576         /* Initialize */
3577         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3578         if (ret == NULL) return(NULL);
3579
3580         /* Set all strings to null and numeric values to zero */
3581         memset(ret, 0, sizeof(struct recptypes));
3582
3583         if (supplied_recipients == NULL) {
3584                 recipients = strdup("");
3585         }
3586         else {
3587                 recipients = strdup(supplied_recipients);
3588         }
3589
3590         /* Allocate some memory.  Yes, this allocates 500% more memory than we will
3591          * actually need, but it's healthier for the heap than doing lots of tiny
3592          * realloc() calls instead.
3593          */
3594
3595         ret->errormsg = malloc(strlen(recipients) + 1024);
3596         ret->recp_local = malloc(strlen(recipients) + 1024);
3597         ret->recp_internet = malloc(strlen(recipients) + 1024);
3598         ret->recp_ignet = malloc(strlen(recipients) + 1024);
3599         ret->recp_room = malloc(strlen(recipients) + 1024);
3600         ret->display_recp = malloc(strlen(recipients) + 1024);
3601
3602         ret->errormsg[0] = 0;
3603         ret->recp_local[0] = 0;
3604         ret->recp_internet[0] = 0;
3605         ret->recp_ignet[0] = 0;
3606         ret->recp_room[0] = 0;
3607         ret->display_recp[0] = 0;
3608
3609         ret->recptypes_magic = RECPTYPES_MAGIC;
3610
3611         /* Change all valid separator characters to commas */
3612         for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3613                 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3614                         recipients[i] = ',';
3615                 }
3616         }
3617
3618         /* Now start extracting recipients... */
3619
3620         while (!IsEmptyStr(recipients)) {
3621
3622                 for (i=0; i<=strlen(recipients); ++i) {
3623                         if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3624                         if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3625                                 safestrncpy(this_recp, recipients, i+1);
3626                                 this_recp[i] = 0;
3627                                 if (recipients[i] == ',') {
3628                                         strcpy(recipients, &recipients[i+1]);
3629                                 }
3630                                 else {
3631                                         strcpy(recipients, "");
3632                                 }
3633                                 break;
3634                         }
3635                 }
3636
3637                 striplt(this_recp);
3638                 if (IsEmptyStr(this_recp))
3639                         break;
3640                 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3641                 ++num_recps;
3642                 mailtype = alias(this_recp);
3643                 mailtype = alias(this_recp);
3644                 mailtype = alias(this_recp);
3645                 j = 0;
3646                 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3647                         if (this_recp[j]=='_') {
3648                                 this_recp_cooked[j] = ' ';
3649                         }
3650                         else {
3651                                 this_recp_cooked[j] = this_recp[j];
3652                         }
3653                 }
3654                 this_recp_cooked[j] = '\0';
3655                 invalid = 0;
3656                 errmsg[0] = 0;
3657                 switch(mailtype) {
3658                         case MES_LOCAL:
3659                                 if (!strcasecmp(this_recp, "sysop")) {
3660                                         ++ret->num_room;
3661                                         strcpy(this_recp, config.c_aideroom);
3662                                         if (!IsEmptyStr(ret->recp_room)) {
3663                                                 strcat(ret->recp_room, "|");
3664                                         }
3665                                         strcat(ret->recp_room, this_recp);
3666                                 }
3667                                 else if ( (!strncasecmp(this_recp, "room_", 5))
3668                                       && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3669
3670                                         /* Save room so we can restore it later */
3671                                         tempQR2 = CC->room;
3672                                         CC->room = tempQR;
3673                                         
3674                                         /* Check permissions to send mail to this room */
3675                                         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, 
3676                                                                                     sizeof errmsg, 
3677                                                                                     RemoteIdentifier,
3678                                                                                     Flags
3679                                         );
3680                                         if (err)
3681                                         {
3682                                                 ++ret->num_error;
3683                                                 invalid = 1;
3684                                         } 
3685                                         else {
3686                                                 ++ret->num_room;
3687                                                 if (!IsEmptyStr(ret->recp_room)) {
3688                                                         strcat(ret->recp_room, "|");
3689                                                 }
3690                                                 strcat(ret->recp_room, &this_recp_cooked[5]);
3691                                         }
3692                                         
3693                                         /* Restore room in case something needs it */
3694                                         CC->room = tempQR2;
3695
3696                                 }
3697                                 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3698                                         ++ret->num_local;
3699                                         strcpy(this_recp, tempUS.fullname);
3700                                         if (!IsEmptyStr(ret->recp_local)) {
3701                                                 strcat(ret->recp_local, "|");
3702                                         }
3703                                         strcat(ret->recp_local, this_recp);
3704                                 }
3705                                 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
3706                                         ++ret->num_local;
3707                                         strcpy(this_recp, tempUS.fullname);
3708                                         if (!IsEmptyStr(ret->recp_local)) {
3709                                                 strcat(ret->recp_local, "|");
3710                                         }
3711                                         strcat(ret->recp_local, this_recp);
3712                                 }
3713                                 else {
3714                                         ++ret->num_error;
3715                                         invalid = 1;
3716                                 }
3717                                 break;
3718                         case MES_INTERNET:
3719                                 /* Yes, you're reading this correctly: if the target
3720                                  * domain points back to the local system or an attached
3721                                  * Citadel directory, the address is invalid.  That's
3722                                  * because if the address were valid, we would have
3723                                  * already translated it to a local address by now.
3724                                  */
3725                                 if (IsDirectory(this_recp, 0)) {
3726                                         ++ret->num_error;
3727                                         invalid = 1;
3728                                 }
3729                                 else {
3730                                         ++ret->num_internet;
3731                                         if (!IsEmptyStr(ret->recp_internet)) {
3732                                                 strcat(ret->recp_internet, "|");
3733                                         }
3734                                         strcat(ret->recp_internet, this_recp);
3735                                 }
3736                                 break;
3737                         case MES_IGNET:
3738                                 ++ret->num_ignet;
3739                                 if (!IsEmptyStr(ret->recp_ignet)) {
3740                                         strcat(ret->recp_ignet, "|");
3741                                 }
3742                                 strcat(ret->recp_ignet, this_recp);
3743                                 break;
3744                         case MES_ERROR:
3745                                 ++ret->num_error;
3746                                 invalid = 1;
3747                                 break;
3748                 }
3749                 if (invalid) {
3750                         if (IsEmptyStr(errmsg)) {
3751                                 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3752                         }
3753                         else {
3754                                 snprintf(append, sizeof append, "%s", errmsg);
3755                         }
3756                         if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3757                                 if (!IsEmptyStr(ret->errormsg)) {
3758                                         strcat(ret->errormsg, "; ");
3759                                 }
3760                                 strcat(ret->errormsg, append);
3761                         }
3762                 }
3763                 else {
3764                         if (IsEmptyStr(ret->display_recp)) {
3765                                 strcpy(append, this_recp);
3766                         }
3767                         else {
3768                                 snprintf(append, sizeof append, ", %s", this_recp);
3769                         }
3770                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3771                                 strcat(ret->display_recp, append);
3772                         }
3773                 }
3774         }
3775
3776         if ((ret->num_local + ret->num_internet + ret->num_ignet +
3777            ret->num_room + ret->num_error) == 0) {
3778                 ret->num_error = (-1);
3779                 strcpy(ret->errormsg, "No recipients specified.");
3780         }
3781
3782         CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3783         CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3784         CtdlLogPrintf(CTDL_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
3785         CtdlLogPrintf(CTDL_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3786         CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3787         CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3788
3789         free(recipients);
3790         return(ret);
3791 }
3792
3793
3794 /*
3795  * Destructor for struct recptypes
3796  */
3797 void free_recipients(struct recptypes *valid) {
3798
3799         if (valid == NULL) {
3800                 return;
3801         }
3802
3803         if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3804                 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3805                 abort();
3806         }
3807
3808         if (valid->errormsg != NULL)            free(valid->errormsg);
3809         if (valid->recp_local != NULL)          free(valid->recp_local);
3810         if (valid->recp_internet != NULL)       free(valid->recp_internet);
3811         if (valid->recp_ignet != NULL)          free(valid->recp_ignet);
3812         if (valid->recp_room != NULL)           free(valid->recp_room);
3813         if (valid->display_recp != NULL)        free(valid->display_recp);
3814         if (valid->bounce_to != NULL)           free(valid->bounce_to);
3815         if (valid->envelope_from != NULL)       free(valid->envelope_from);
3816         free(valid);
3817 }
3818
3819
3820
3821 /*
3822  * message entry  -  mode 0 (normal)
3823  */
3824 void cmd_ent0(char *entargs)
3825 {
3826         int post = 0;
3827         char recp[SIZ];
3828         char cc[SIZ];
3829         char bcc[SIZ];
3830         char supplied_euid[128];
3831         int anon_flag = 0;
3832         int format_type = 0;
3833         char newusername[256];
3834         char newuseremail[256];
3835         struct CtdlMessage *msg;
3836         int anonymous = 0;
3837         char errmsg[SIZ];
3838         int err = 0;
3839         struct recptypes *valid = NULL;
3840         struct recptypes *valid_to = NULL;
3841         struct recptypes *valid_cc = NULL;
3842         struct recptypes *valid_bcc = NULL;
3843         char subject[SIZ];
3844         int subject_required = 0;
3845         int do_confirm = 0;
3846         long msgnum;
3847         int i, j;
3848         char buf[256];
3849         int newuseremail_ok = 0;
3850         char references[SIZ];
3851         char *ptr;
3852
3853         unbuffer_output();
3854
3855         post = extract_int(entargs, 0);
3856         extract_token(recp, entargs, 1, '|', sizeof recp);
3857         anon_flag = extract_int(entargs, 2);
3858         format_type = extract_int(entargs, 3);
3859         extract_token(subject, entargs, 4, '|', sizeof subject);
3860         extract_token(newusername, entargs, 5, '|', sizeof newusername);
3861         do_confirm = extract_int(entargs, 6);
3862         extract_token(cc, entargs, 7, '|', sizeof cc);
3863         extract_token(bcc, entargs, 8, '|', sizeof bcc);
3864         switch(CC->room.QRdefaultview) {
3865                 case VIEW_NOTES:
3866                 case VIEW_WIKI:
3867                         extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3868                         break;
3869                 default:
3870                         supplied_euid[0] = 0;
3871                         break;
3872         }
3873         extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3874         extract_token(references, entargs, 11, '|', sizeof references);
3875         for (ptr=references; *ptr != 0; ++ptr) {
3876                 if (*ptr == '!') *ptr = '|';
3877         }
3878
3879         /* first check to make sure the request is valid. */
3880
3881         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3882         if (err)
3883         {
3884                 cprintf("%d %s\n", err, errmsg);
3885                 return;
3886         }
3887
3888         /* Check some other permission type things. */
3889
3890         if (IsEmptyStr(newusername)) {
3891                 strcpy(newusername, CC->user.fullname);
3892         }
3893         if (  (CC->user.axlevel < AxAideU)
3894            && (strcasecmp(newusername, CC->user.fullname))
3895            && (strcasecmp(newusername, CC->cs_inet_fn))
3896         ) {     
3897                 cprintf("%d You don't have permission to author messages as '%s'.\n",
3898                         ERROR + HIGHER_ACCESS_REQUIRED,
3899                         newusername
3900                 );
3901                 return;
3902         }
3903
3904
3905         if (IsEmptyStr(newuseremail)) {
3906                 newuseremail_ok = 1;
3907         }
3908
3909         if (!IsEmptyStr(newuseremail)) {
3910                 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3911                         newuseremail_ok = 1;
3912                 }
3913                 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3914                         j = num_tokens(CC->cs_inet_other_emails, '|');
3915                         for (i=0; i<j; ++i) {
3916                                 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3917                                 if (!strcasecmp(newuseremail, buf)) {
3918                                         newuseremail_ok = 1;
3919                                 }
3920                         }
3921                 }
3922         }
3923
3924         if (!newuseremail_ok) {
3925                 cprintf("%d You don't have permission to author messages as '%s'.\n",
3926                         ERROR + HIGHER_ACCESS_REQUIRED,
3927                         newuseremail
3928                 );
3929                 return;
3930         }
3931
3932         CC->cs_flags |= CS_POSTING;
3933
3934         /* In mailbox rooms we have to behave a little differently --
3935          * make sure the user has specified at least one recipient.  Then
3936          * validate the recipient(s).  We do this for the Mail> room, as
3937          * well as any room which has the "Mailbox" view set.
3938          */
3939
3940         if (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3941            || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3942         ) {
3943                 if (CC->user.axlevel < AxProbU) {
3944                         strcpy(recp, "sysop");
3945                         strcpy(cc, "");
3946                         strcpy(bcc, "");
3947                 }
3948
3949                 valid_to = validate_recipients(recp, NULL, 0);
3950                 if (valid_to->num_error > 0) {
3951                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3952                         free_recipients(valid_to);
3953                         return;
3954                 }
3955
3956                 valid_cc = validate_recipients(cc, NULL, 0);
3957                 if (valid_cc->num_error > 0) {
3958                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3959                         free_recipients(valid_to);
3960                         free_recipients(valid_cc);
3961                         return;
3962                 }
3963
3964                 valid_bcc = validate_recipients(bcc, NULL, 0);
3965                 if (valid_bcc->num_error > 0) {
3966                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3967                         free_recipients(valid_to);
3968                         free_recipients(valid_cc);
3969                         free_recipients(valid_bcc);
3970                         return;
3971                 }
3972
3973                 /* Recipient required, but none were specified */
3974                 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3975                         free_recipients(valid_to);
3976                         free_recipients(valid_cc);
3977                         free_recipients(valid_bcc);
3978                         cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3979                         return;
3980                 }
3981
3982                 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3983                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3984                                 cprintf("%d You do not have permission "
3985                                         "to send Internet mail.\n",
3986                                         ERROR + HIGHER_ACCESS_REQUIRED);
3987                                 free_recipients(valid_to);
3988                                 free_recipients(valid_cc);
3989                                 free_recipients(valid_bcc);
3990                                 return;
3991                         }
3992                 }
3993
3994                 if ( ( (valid_to->num_internet + valid_to->num_ignet + valid_cc->num_internet + valid_cc->num_ignet + valid_bcc->num_internet + valid_bcc->num_ignet) > 0)
3995                    && (CC->user.axlevel < AxNetU) ) {
3996                         cprintf("%d Higher access required for network mail.\n",
3997                                 ERROR + HIGHER_ACCESS_REQUIRED);
3998                         free_recipients(valid_to);
3999                         free_recipients(valid_cc);
4000                         free_recipients(valid_bcc);
4001                         return;
4002                 }
4003         
4004                 if ((RESTRICT_INTERNET == 1)
4005                     && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4006                     && ((CC->user.flags & US_INTERNET) == 0)
4007                     && (!CC->internal_pgm)) {
4008                         cprintf("%d You don't have access to Internet mail.\n",
4009                                 ERROR + HIGHER_ACCESS_REQUIRED);
4010                         free_recipients(valid_to);
4011                         free_recipients(valid_cc);
4012                         free_recipients(valid_bcc);
4013                         return;
4014                 }
4015
4016         }
4017
4018         /* Is this a room which has anonymous-only or anonymous-option? */
4019         anonymous = MES_NORMAL;
4020         if (CC->room.QRflags & QR_ANONONLY) {
4021                 anonymous = MES_ANONONLY;
4022         }
4023         if (CC->room.QRflags & QR_ANONOPT) {
4024                 if (anon_flag == 1) {   /* only if the user requested it */
4025                         anonymous = MES_ANONOPT;
4026                 }
4027         }
4028
4029         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4030                 recp[0] = 0;
4031         }
4032
4033         /* Recommend to the client that the use of a message subject is
4034          * strongly recommended in this room, if either the SUBJECTREQ flag
4035          * is set, or if there is one or more Internet email recipients.
4036          */
4037         if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4038         if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
4039         if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
4040         if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
4041
4042         /* If we're only checking the validity of the request, return
4043          * success without creating the message.
4044          */
4045         if (post == 0) {
4046                 cprintf("%d %s|%d\n", CIT_OK,
4047                         ((valid_to != NULL) ? valid_to->display_recp : ""), 
4048                         subject_required);
4049                 free_recipients(valid_to);
4050                 free_recipients(valid_cc);
4051                 free_recipients(valid_bcc);
4052                 return;
4053         }
4054
4055         /* We don't need these anymore because we'll do it differently below */
4056         free_recipients(valid_to);
4057         free_recipients(valid_cc);
4058         free_recipients(valid_bcc);
4059
4060         /* Read in the message from the client. */
4061         if (do_confirm) {
4062                 cprintf("%d send message\n", START_CHAT_MODE);
4063         } else {
4064                 cprintf("%d send message\n", SEND_LISTING);
4065         }
4066
4067         msg = CtdlMakeMessage(&CC->user, recp, cc,
4068                 CC->room.QRname, anonymous, format_type,
4069                 newusername, newuseremail, subject,
4070                 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4071                 NULL, references);
4072
4073         /* Put together one big recipients struct containing to/cc/bcc all in
4074          * one.  This is for the envelope.
4075          */
4076         char *all_recps = malloc(SIZ * 3);
4077         strcpy(all_recps, recp);
4078         if (!IsEmptyStr(cc)) {
4079                 if (!IsEmptyStr(all_recps)) {
4080                         strcat(all_recps, ",");
4081                 }
4082                 strcat(all_recps, cc);
4083         }
4084         if (!IsEmptyStr(bcc)) {
4085                 if (!IsEmptyStr(all_recps)) {
4086                         strcat(all_recps, ",");
4087                 }
4088                 strcat(all_recps, bcc);
4089         }
4090         if (!IsEmptyStr(all_recps)) {
4091                 valid = validate_recipients(all_recps, NULL, 0);
4092         }
4093         else {
4094                 valid = NULL;
4095         }
4096         free(all_recps);
4097
4098         if (msg != NULL) {
4099                 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4100
4101                 if (do_confirm) {
4102                         cprintf("%ld\n", msgnum);
4103                         if (msgnum >= 0L) {
4104                                 cprintf("Message accepted.\n");
4105                         }
4106                         else {
4107                                 cprintf("Internal error.\n");
4108                         }
4109                         if (msg->cm_fields['E'] != NULL) {
4110                                 cprintf("%s\n", msg->cm_fields['E']);
4111                         } else {
4112                                 cprintf("\n");
4113                         }
4114                         cprintf("000\n");
4115                 }
4116
4117                 CtdlFreeMessage(msg);
4118         }
4119         if (valid != NULL) {
4120                 free_recipients(valid);
4121         }
4122         return;
4123 }
4124
4125
4126
4127 /*
4128  * API function to delete messages which match a set of criteria
4129  * (returns the actual number of messages deleted)
4130  */
4131 int CtdlDeleteMessages(char *room_name,         /* which room */
4132                         long *dmsgnums,         /* array of msg numbers to be deleted */
4133                         int num_dmsgnums,       /* number of msgs to be deleted, or 0 for "any" */
4134                         char *content_type      /* or "" for any.  regular expressions expected. */
4135 )
4136 {
4137         struct ctdlroom qrbuf;
4138         struct cdbdata *cdbfr;
4139         long *msglist = NULL;
4140         long *dellist = NULL;
4141         int num_msgs = 0;
4142         int i, j;
4143         int num_deleted = 0;
4144         int delete_this;
4145         struct MetaData smi;
4146         regex_t re;
4147         regmatch_t pm;
4148         int need_to_free_re = 0;
4149
4150         if (content_type) if (!IsEmptyStr(content_type)) {
4151                 regcomp(&re, content_type, 0);
4152                 need_to_free_re = 1;
4153         }
4154         CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4155                 room_name, num_dmsgnums, content_type);
4156
4157         /* get room record, obtaining a lock... */
4158         if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4159                 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4160                         room_name);
4161                 if (need_to_free_re) regfree(&re);
4162                 return (0);     /* room not found */
4163         }
4164         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4165
4166         if (cdbfr != NULL) {
4167                 dellist = malloc(cdbfr->len);
4168                 msglist = (long *) cdbfr->ptr;
4169                 cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
4170                 num_msgs = cdbfr->len / sizeof(long);
4171                 cdb_free(cdbfr);
4172         }
4173         if (num_msgs > 0) {
4174                 for (i = 0; i < num_msgs; ++i) {
4175                         delete_this = 0x00;
4176
4177                         /* Set/clear a bit for each criterion */
4178
4179                         /* 0 messages in the list or a null list means that we are
4180                          * interested in deleting any messages which meet the other criteria.
4181                          */
4182                         if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4183                                 delete_this |= 0x01;
4184                         }
4185                         else {
4186                                 for (j=0; j<num_dmsgnums; ++j) {
4187                                         if (msglist[i] == dmsgnums[j]) {
4188                                                 delete_this |= 0x01;
4189                                         }
4190                                 }
4191                         }
4192
4193                         if (IsEmptyStr(content_type)) {
4194                                 delete_this |= 0x02;
4195                         } else {
4196                                 GetMetaData(&smi, msglist[i]);
4197                                 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4198                                         delete_this |= 0x02;
4199                                 }
4200                         }
4201
4202                         /* Delete message only if all bits are set */
4203                         if (delete_this == 0x03) {
4204                                 dellist[num_deleted++] = msglist[i];
4205                                 msglist[i] = 0L;
4206                         }
4207                 }
4208
4209                 num_msgs = sort_msglist(msglist, num_msgs);
4210                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4211                           msglist, (int)(num_msgs * sizeof(long)));
4212
4213                 if (num_msgs > 0)
4214                         qrbuf.QRhighest = msglist[num_msgs - 1];
4215                 else
4216                         qrbuf.QRhighest = 0;
4217         }
4218         CtdlPutRoomLock(&qrbuf);
4219
4220         /* Go through the messages we pulled out of the index, and decrement
4221          * their reference counts by 1.  If this is the only room the message
4222          * was in, the reference count will reach zero and the message will
4223          * automatically be deleted from the database.  We do this in a
4224          * separate pass because there might be plug-in hooks getting called,
4225          * and we don't want that happening during an S_ROOMS critical
4226          * section.
4227          */
4228         if (num_deleted) for (i=0; i<num_deleted; ++i) {
4229                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4230                 AdjRefCount(dellist[i], -1);
4231         }
4232
4233         /* Now free the memory we used, and go away. */
4234         if (msglist != NULL) free(msglist);
4235         if (dellist != NULL) free(dellist);
4236         CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4237         if (need_to_free_re) regfree(&re);
4238         return (num_deleted);
4239 }
4240
4241
4242
4243 /*
4244  * Check whether the current user has permission to delete messages from
4245  * the current room (returns 1 for yes, 0 for no)
4246  */
4247 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4248         int ra;
4249         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4250         if (ra & UA_DELETEALLOWED) return(1);
4251         return(0);
4252 }
4253
4254
4255
4256
4257 /*
4258  * Delete message from current room
4259  */
4260 void cmd_dele(char *args)
4261 {
4262         int num_deleted;
4263         int i;
4264         char msgset[SIZ];
4265         char msgtok[32];
4266         long *msgs;
4267         int num_msgs = 0;
4268
4269         extract_token(msgset, args, 0, '|', sizeof msgset);
4270         num_msgs = num_tokens(msgset, ',');
4271         if (num_msgs < 1) {
4272                 cprintf("%d Nothing to do.\n", CIT_OK);
4273                 return;
4274         }
4275
4276         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4277                 cprintf("%d Higher access required.\n",
4278                         ERROR + HIGHER_ACCESS_REQUIRED);
4279                 return;
4280         }
4281
4282         /*
4283          * Build our message set to be moved/copied
4284          */
4285         msgs = malloc(num_msgs * sizeof(long));
4286         for (i=0; i<num_msgs; ++i) {
4287                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4288                 msgs[i] = atol(msgtok);
4289         }
4290
4291         num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4292         free(msgs);
4293
4294         if (num_deleted) {
4295                 cprintf("%d %d message%s deleted.\n", CIT_OK,
4296                         num_deleted, ((num_deleted != 1) ? "s" : ""));
4297         } else {
4298                 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4299         }
4300 }
4301
4302
4303
4304
4305 /*
4306  * move or copy a message to another room
4307  */
4308 void cmd_move(char *args)
4309 {
4310         char msgset[SIZ];
4311         char msgtok[32];
4312         long *msgs;
4313         int num_msgs = 0;
4314
4315         char targ[ROOMNAMELEN];
4316         struct ctdlroom qtemp;
4317         int err;
4318         int is_copy = 0;
4319         int ra;
4320         int permit = 0;
4321         int i;
4322
4323         extract_token(msgset, args, 0, '|', sizeof msgset);
4324         num_msgs = num_tokens(msgset, ',');
4325         if (num_msgs < 1) {
4326                 cprintf("%d Nothing to do.\n", CIT_OK);
4327                 return;
4328         }
4329
4330         extract_token(targ, args, 1, '|', sizeof targ);
4331         convert_room_name_macros(targ, sizeof targ);
4332         targ[ROOMNAMELEN - 1] = 0;
4333         is_copy = extract_int(args, 2);
4334
4335         if (CtdlGetRoom(&qtemp, targ) != 0) {
4336                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4337                 return;
4338         }
4339
4340         if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4341                 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4342                 return;
4343         }
4344
4345         CtdlGetUser(&CC->user, CC->curr_user);
4346         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4347
4348         /* Check for permission to perform this operation.
4349          * Remember: "CC->room" is source, "qtemp" is target.
4350          */
4351         permit = 0;
4352
4353         /* Aides can move/copy */
4354         if (CC->user.axlevel >= AxAideU) permit = 1;
4355
4356         /* Room aides can move/copy */
4357         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4358
4359         /* Permit move/copy from personal rooms */
4360         if ((CC->room.QRflags & QR_MAILBOX)
4361            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4362
4363         /* Permit only copy from public to personal room */
4364         if ( (is_copy)
4365            && (!(CC->room.QRflags & QR_MAILBOX))
4366            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4367
4368         /* Permit message removal from collaborative delete rooms */
4369         if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4370
4371         /* Users allowed to post into the target room may move into it too. */
4372         if ((CC->room.QRflags & QR_MAILBOX) && 
4373             (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
4374
4375         /* User must have access to target room */
4376         if (!(ra & UA_KNOWN))  permit = 0;
4377
4378         if (!permit) {
4379                 cprintf("%d Higher access required.\n",
4380                         ERROR + HIGHER_ACCESS_REQUIRED);
4381                 return;
4382         }
4383
4384         /*
4385          * Build our message set to be moved/copied
4386          */
4387         msgs = malloc(num_msgs * sizeof(long));
4388         for (i=0; i<num_msgs; ++i) {
4389                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4390                 msgs[i] = atol(msgtok);
4391         }
4392
4393         /*
4394          * Do the copy
4395          */
4396         err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4397         if (err != 0) {
4398                 cprintf("%d Cannot store message(s) in %s: error %d\n",
4399                         err, targ, err);
4400                 free(msgs);
4401                 return;
4402         }
4403
4404         /* Now delete the message from the source room,
4405          * if this is a 'move' rather than a 'copy' operation.
4406          */
4407         if (is_copy == 0) {
4408                 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4409         }
4410         free(msgs);
4411
4412         cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4413 }
4414
4415
4416
4417 /*
4418  * GetMetaData()  -  Get the supplementary record for a message
4419  */
4420 void GetMetaData(struct MetaData *smibuf, long msgnum)
4421 {
4422
4423         struct cdbdata *cdbsmi;
4424         long TheIndex;
4425
4426         memset(smibuf, 0, sizeof(struct MetaData));
4427         smibuf->meta_msgnum = msgnum;
4428         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
4429
4430         /* Use the negative of the message number for its supp record index */
4431         TheIndex = (0L - msgnum);
4432
4433         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4434         if (cdbsmi == NULL) {
4435                 return;         /* record not found; go with defaults */
4436         }
4437         memcpy(smibuf, cdbsmi->ptr,
4438                ((cdbsmi->len > sizeof(struct MetaData)) ?
4439                 sizeof(struct MetaData) : cdbsmi->len));
4440         cdb_free(cdbsmi);
4441         return;
4442 }
4443
4444
4445 /*
4446  * PutMetaData()  -  (re)write supplementary record for a message
4447  */
4448 void PutMetaData(struct MetaData *smibuf)
4449 {
4450         long TheIndex;
4451
4452         /* Use the negative of the message number for the metadata db index */
4453         TheIndex = (0L - smibuf->meta_msgnum);
4454
4455         cdb_store(CDB_MSGMAIN,
4456                   &TheIndex, (int)sizeof(long),
4457                   smibuf, (int)sizeof(struct MetaData));
4458
4459 }
4460
4461 /*
4462  * AdjRefCount  -  submit an adjustment to the reference count for a message.
4463  *                 (These are just queued -- we actually process them later.)
4464  */
4465 void AdjRefCount(long msgnum, int incr)
4466 {
4467         struct arcq new_arcq;
4468         int rv = 0;
4469
4470         begin_critical_section(S_SUPPMSGMAIN);
4471         if (arcfp == NULL) {
4472                 arcfp = fopen(file_arcq, "ab+");
4473         }
4474         end_critical_section(S_SUPPMSGMAIN);
4475
4476         /* msgnum < 0 means that we're trying to close the file */
4477         if (msgnum < 0) {
4478                 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4479                 begin_critical_section(S_SUPPMSGMAIN);
4480                 if (arcfp != NULL) {
4481                         fclose(arcfp);
4482                         arcfp = NULL;
4483                 }
4484                 end_critical_section(S_SUPPMSGMAIN);
4485                 return;
4486         }
4487
4488         /*
4489          * If we can't open the queue, perform the operation synchronously.
4490          */
4491         if (arcfp == NULL) {
4492                 TDAP_AdjRefCount(msgnum, incr);
4493                 return;
4494         }
4495
4496         new_arcq.arcq_msgnum = msgnum;
4497         new_arcq.arcq_delta = incr;
4498         rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4499         fflush(arcfp);
4500
4501         return;
4502 }
4503
4504
4505 /*
4506  * TDAP_ProcessAdjRefCountQueue()
4507  *
4508  * Process the queue of message count adjustments that was created by calls
4509  * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4510  * for each one.  This should be an "off hours" operation.
4511  */
4512 int TDAP_ProcessAdjRefCountQueue(void)
4513 {
4514         char file_arcq_temp[PATH_MAX];
4515         int r;
4516         FILE *fp;
4517         struct arcq arcq_rec;
4518         int num_records_processed = 0;
4519
4520         snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4521
4522         begin_critical_section(S_SUPPMSGMAIN);
4523         if (arcfp != NULL) {
4524                 fclose(arcfp);
4525                 arcfp = NULL;
4526         }
4527
4528         r = link(file_arcq, file_arcq_temp);
4529         if (r != 0) {
4530                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4531                 end_critical_section(S_SUPPMSGMAIN);
4532                 return(num_records_processed);
4533         }
4534
4535         unlink(file_arcq);
4536         end_critical_section(S_SUPPMSGMAIN);
4537
4538         fp = fopen(file_arcq_temp, "rb");
4539         if (fp == NULL) {
4540                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4541                 return(num_records_processed);
4542         }
4543
4544         while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4545                 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4546                 ++num_records_processed;
4547         }
4548
4549         fclose(fp);
4550         r = unlink(file_arcq_temp);
4551         if (r != 0) {
4552                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4553         }
4554
4555         return(num_records_processed);
4556 }
4557
4558
4559
4560 /*
4561  * TDAP_AdjRefCount  -  adjust the reference count for a message.
4562  *                      This one does it "for real" because it's called by
4563  *                      the autopurger function that processes the queue
4564  *                      created by AdjRefCount().   If a message's reference
4565  *                      count becomes zero, we also delete the message from
4566  *                      disk and de-index it.
4567  */
4568 void TDAP_AdjRefCount(long msgnum, int incr)
4569 {
4570
4571         struct MetaData smi;
4572         long delnum;
4573
4574         /* This is a *tight* critical section; please keep it that way, as
4575          * it may get called while nested in other critical sections.  
4576          * Complicating this any further will surely cause deadlock!
4577          */
4578         begin_critical_section(S_SUPPMSGMAIN);
4579         GetMetaData(&smi, msgnum);
4580         smi.meta_refcount += incr;
4581         PutMetaData(&smi);
4582         end_critical_section(S_SUPPMSGMAIN);
4583         CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4584                 msgnum, incr, smi.meta_refcount);
4585
4586         /* If the reference count is now zero, delete the message
4587          * (and its supplementary record as well).
4588          */
4589         if (smi.meta_refcount == 0) {
4590                 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4591                 
4592                 /* Call delete hooks with NULL room to show it has gone altogether */
4593                 PerformDeleteHooks(NULL, msgnum);
4594
4595                 /* Remove from message base */
4596                 delnum = msgnum;
4597                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4598                 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4599
4600                 /* Remove metadata record */
4601                 delnum = (0L - msgnum);
4602                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4603         }
4604
4605 }
4606
4607 /*
4608  * Write a generic object to this room
4609  *
4610  * Note: this could be much more efficient.  Right now we use two temporary
4611  * files, and still pull the message into memory as with all others.
4612  */
4613 void CtdlWriteObject(char *req_room,                    /* Room to stuff it in */
4614                         char *content_type,             /* MIME type of this object */
4615                         char *raw_message,              /* Data to be written */
4616                         off_t raw_length,               /* Size of raw_message */
4617                         struct ctdluser *is_mailbox,    /* Mailbox room? */
4618                         int is_binary,                  /* Is encoding necessary? */
4619                         int is_unique,                  /* Del others of this type? */
4620                         unsigned int flags              /* Internal save flags */
4621                         )
4622 {
4623
4624         struct ctdlroom qrbuf;
4625         char roomname[ROOMNAMELEN];
4626         struct CtdlMessage *msg;
4627         char *encoded_message = NULL;
4628
4629         if (is_mailbox != NULL) {
4630                 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4631         }
4632         else {
4633                 safestrncpy(roomname, req_room, sizeof(roomname));
4634         }
4635
4636         CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4637
4638         if (is_binary) {
4639                 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4640         }
4641         else {
4642                 encoded_message = malloc((size_t)(raw_length + 4096));
4643         }
4644
4645         sprintf(encoded_message, "Content-type: %s\n", content_type);
4646
4647         if (is_binary) {
4648                 sprintf(&encoded_message[strlen(encoded_message)],
4649                         "Content-transfer-encoding: base64\n\n"
4650                 );
4651         }
4652         else {
4653                 sprintf(&encoded_message[strlen(encoded_message)],
4654                         "Content-transfer-encoding: 7bit\n\n"
4655                 );
4656         }
4657
4658         if (is_binary) {
4659                 CtdlEncodeBase64(
4660                         &encoded_message[strlen(encoded_message)],
4661                         raw_message,
4662                         (int)raw_length,
4663                         0
4664                 );
4665         }
4666         else {
4667                 memcpy(
4668                         &encoded_message[strlen(encoded_message)],
4669                         raw_message,
4670                         (int)(raw_length+1)
4671                 );
4672         }
4673
4674         CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4675         msg = malloc(sizeof(struct CtdlMessage));
4676         memset(msg, 0, sizeof(struct CtdlMessage));
4677         msg->cm_magic = CTDLMESSAGE_MAGIC;
4678         msg->cm_anon_type = MES_NORMAL;
4679         msg->cm_format_type = 4;
4680         msg->cm_fields['A'] = strdup(CC->user.fullname);
4681         msg->cm_fields['O'] = strdup(req_room);
4682         msg->cm_fields['N'] = strdup(config.c_nodename);
4683         msg->cm_fields['H'] = strdup(config.c_humannode);
4684         msg->cm_flags = flags;
4685         
4686         msg->cm_fields['M'] = encoded_message;
4687
4688         /* Create the requested room if we have to. */
4689         if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4690                 CtdlCreateRoom(roomname, 
4691                         ( (is_mailbox != NULL) ? 5 : 3 ),
4692                         "", 0, 1, 0, VIEW_BBS);
4693         }
4694         /* If the caller specified this object as unique, delete all
4695          * other objects of this type that are currently in the room.
4696          */
4697         if (is_unique) {
4698                 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4699                         CtdlDeleteMessages(roomname, NULL, 0, content_type)
4700                 );
4701         }
4702         /* Now write the data */
4703         CtdlSubmitMsg(msg, NULL, roomname, 0);
4704         CtdlFreeMessage(msg);
4705 }
4706
4707
4708
4709
4710
4711
4712 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4713         config_msgnum = msgnum;
4714 }
4715
4716
4717 char *CtdlGetSysConfig(char *sysconfname) {
4718         char hold_rm[ROOMNAMELEN];
4719         long msgnum;
4720         char *conf;
4721         struct CtdlMessage *msg;
4722         char buf[SIZ];
4723         
4724         strcpy(hold_rm, CC->room.QRname);
4725         if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4726                 CtdlGetRoom(&CC->room, hold_rm);
4727                 return NULL;
4728         }
4729
4730
4731         /* We want the last (and probably only) config in this room */
4732         begin_critical_section(S_CONFIG);
4733         config_msgnum = (-1L);
4734         CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4735                 CtdlGetSysConfigBackend, NULL);
4736         msgnum = config_msgnum;
4737         end_critical_section(S_CONFIG);
4738
4739         if (msgnum < 0L) {
4740                 conf = NULL;
4741         }
4742         else {
4743                 msg = CtdlFetchMessage(msgnum, 1);
4744                 if (msg != NULL) {
4745                         conf = strdup(msg->cm_fields['M']);
4746                         CtdlFreeMessage(msg);
4747                 }
4748                 else {
4749                         conf = NULL;
4750                 }
4751         }
4752
4753         CtdlGetRoom(&CC->room, hold_rm);
4754
4755         if (conf != NULL) do {
4756                 extract_token(buf, conf, 0, '\n', sizeof buf);
4757                 strcpy(conf, &conf[strlen(buf)+1]);
4758         } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4759
4760         return(conf);
4761 }
4762
4763
4764 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4765         CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4766 }
4767
4768
4769 /*
4770  * Determine whether a given Internet address belongs to the current user
4771  */
4772 int CtdlIsMe(char *addr, int addr_buf_len)
4773 {
4774         struct recptypes *recp;
4775         int i;
4776
4777         recp = validate_recipients(addr, NULL, 0);
4778         if (recp == NULL) return(0);
4779
4780         if (recp->num_local == 0) {
4781                 free_recipients(recp);
4782                 return(0);
4783         }
4784
4785         for (i=0; i<recp->num_local; ++i) {
4786                 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4787                 if (!strcasecmp(addr, CC->user.fullname)) {
4788                         free_recipients(recp);
4789                         return(1);
4790                 }
4791         }
4792
4793         free_recipients(recp);
4794         return(0);
4795 }
4796
4797
4798 /*
4799  * Citadel protocol command to do the same
4800  */
4801 void cmd_isme(char *argbuf) {
4802         char addr[256];
4803
4804         if (CtdlAccessCheck(ac_logged_in)) return;
4805         extract_token(addr, argbuf, 0, '|', sizeof addr);
4806
4807         if (CtdlIsMe(addr, sizeof addr)) {
4808                 cprintf("%d %s\n", CIT_OK, addr);
4809         }
4810         else {
4811                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4812         }
4813
4814 }
4815
4816
4817 /*****************************************************************************/
4818 /*                      MODULE INITIALIZATION STUFF                          */
4819 /*****************************************************************************/
4820
4821 CTDL_MODULE_INIT(msgbase)
4822 {
4823         if (!threading) {
4824                 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4825                 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4826                 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4827                 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4828                 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4829                 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4830                 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4831                 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4832                 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4833                 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4834                 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4835                 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4836         }
4837
4838         /* return our Subversion id for the Log */
4839         return "$Id$";
4840 }