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