* headers_euid() ooops, 'T' is a string!
[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         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                         if (mptr != mpptr)
1895                                 free (mptr);
1896                 }
1897         }
1898         if (subject_found == 0) {
1899                 cprintf("Subject: (no subject)%s", nl);
1900         }
1901 }
1902
1903
1904 void Dump_RFC822HeadersBody(
1905         struct CtdlMessage *TheMessage,
1906         int headers_only,       /* eschew the message body? */
1907         int flags,              /* should the bessage be exported clean? */
1908
1909         const char *nl)
1910 {
1911         cit_uint8_t prev_ch;
1912         int eoh = 0;
1913         const char *StartOfText = StrBufNOTNULL;
1914         char outbuf[1024];
1915         int outlen = 0;
1916         int nllen = strlen(nl);
1917         char *mptr;
1918
1919         mptr = TheMessage->cm_fields['M'];
1920
1921
1922         prev_ch = '\0';
1923         while (*mptr != '\0') {
1924                 if (*mptr == '\r') {
1925                         /* do nothing */
1926                 }
1927                 else {
1928                         if ((!eoh) &&
1929                             (*mptr == '\n'))
1930                         {
1931                                 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1932                                 if (!eoh)
1933                                         eoh = *(mptr+1) == '\n';
1934                                 if (eoh)
1935                                 {
1936                                         StartOfText = mptr;
1937                                         StartOfText = strchr(StartOfText, '\n');
1938                                         StartOfText = strchr(StartOfText, '\n');
1939                                 }
1940                         }
1941                         if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1942                             ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1943                             ((headers_only != HEADERS_NONE) && 
1944                              (headers_only != HEADERS_ONLY))
1945                                 ) {
1946                                 if (*mptr == '\n') {
1947                                         memcpy(&outbuf[outlen], nl, nllen);
1948                                         outlen += nllen;
1949                                         outbuf[outlen] = '\0';
1950                                 }
1951                                 else {
1952                                         outbuf[outlen++] = *mptr;
1953                                 }
1954                         }
1955                 }
1956                 if (flags & ESC_DOT)
1957                 {
1958                         if ((prev_ch == '\n') && 
1959                             (*mptr == '.') && 
1960                             ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
1961                         {
1962                                 outbuf[outlen++] = '.';
1963                         }
1964                         prev_ch = *mptr;
1965                 }
1966                 ++mptr;
1967                 if (outlen > 1000) {
1968                         client_write(outbuf, outlen);
1969                         outlen = 0;
1970                 }
1971         }
1972         if (outlen > 0) {
1973                 client_write(outbuf, outlen);
1974                 outlen = 0;
1975         }
1976 }
1977
1978
1979
1980 /* If the format type on disk is 1 (fixed-format), then we want
1981  * everything to be output completely literally ... regardless of
1982  * what message transfer format is in use.
1983  */
1984 void DumpFormatFixed(
1985         struct CtdlMessage *TheMessage,
1986         int mode,               /* how would you like that message? */
1987         const char *nl)
1988 {
1989         cit_uint8_t ch;
1990         char buf[SIZ];
1991         int buflen;
1992         int xlline = 0;
1993         int nllen = strlen (nl);
1994         char *mptr;
1995
1996         mptr = TheMessage->cm_fields['M'];
1997         
1998         if (mode == MT_MIME) {
1999                 cprintf("Content-type: text/plain\n\n");
2000         }
2001         *buf = '\0';
2002         buflen = 0;
2003         while (ch = *mptr++, ch > 0) {
2004                 if (ch == '\n')
2005                         ch = '\r';
2006
2007                 if ((buflen > 250) && (!xlline)){
2008                         int tbuflen;
2009                         tbuflen = buflen;
2010
2011                         while ((buflen > 0) && 
2012                                (!isspace(buf[buflen])))
2013                                 buflen --;
2014                         if (buflen == 0) {
2015                                 xlline = 1;
2016                         }
2017                         else {
2018                                 mptr -= tbuflen - buflen;
2019                                 buf[buflen] = '\0';
2020                                 ch = '\r';
2021                         }
2022                 }
2023                 /* if we reach the outer bounds of our buffer, 
2024                    abort without respect what whe purge. */
2025                 if (xlline && 
2026                     ((isspace(ch)) || 
2027                      (buflen > SIZ - nllen - 2)))
2028                         ch = '\r';
2029
2030                 if (ch == '\r') {
2031                         memcpy (&buf[buflen], nl, nllen);
2032                         buflen += nllen;
2033                         buf[buflen] = '\0';
2034
2035                         client_write(buf, buflen);
2036                         *buf = '\0';
2037                         buflen = 0;
2038                         xlline = 0;
2039                 } else {
2040                         buf[buflen] = ch;
2041                         buflen++;
2042                 }
2043         }
2044         buf[buflen] = '\0';
2045         if (!IsEmptyStr(buf))
2046                 cprintf("%s%s", buf, nl);
2047 }
2048
2049 /*
2050  * Get a message off disk.  (returns om_* values found in msgbase.h)
2051  */
2052 int CtdlOutputPreLoadedMsg(
2053                 struct CtdlMessage *TheMessage,
2054                 int mode,               /* how would you like that message? */
2055                 int headers_only,       /* eschew the message body? */
2056                 int do_proto,           /* do Citadel protocol responses? */
2057                 int crlf,               /* Use CRLF newlines instead of LF? */
2058                 int flags               /* should the bessage be exported clean? */
2059 ) {
2060         int i;
2061         char *mptr = NULL;
2062         const char *nl; /* newline string */
2063         struct ma_info ma;
2064
2065         /* Buffers needed for RFC822 translation.  These are all filled
2066          * using functions that are bounds-checked, and therefore we can
2067          * make them substantially smaller than SIZ.
2068          */
2069         char suser[100];
2070         char luser[100];
2071         char fuser[100];
2072         char snode[100];
2073         char mid[100];
2074
2075         CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2076                 ((TheMessage == NULL) ? "NULL" : "not null"),
2077                 mode, headers_only, do_proto, crlf);
2078
2079         strcpy(mid, "unknown");
2080         nl = (crlf ? "\r\n" : "\n");
2081
2082         if (!is_valid_message(TheMessage)) {
2083                 CtdlLogPrintf(CTDL_ERR,
2084                         "ERROR: invalid preloaded message for output\n");
2085                 cit_backtrace ();
2086                 return(om_no_such_msg);
2087         }
2088
2089         /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2090          * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2091          */
2092         if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2093                 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2094         }
2095                 
2096         /* Are we downloading a MIME component? */
2097         if (mode == MT_DOWNLOAD) {
2098                 if (TheMessage->cm_format_type != FMT_RFC822) {
2099                         if (do_proto)
2100                                 cprintf("%d This is not a MIME message.\n",
2101                                 ERROR + ILLEGAL_VALUE);
2102                 } else if (CC->download_fp != NULL) {
2103                         if (do_proto) cprintf(
2104                                 "%d You already have a download open.\n",
2105                                 ERROR + RESOURCE_BUSY);
2106                 } else {
2107                         /* Parse the message text component */
2108                         mptr = TheMessage->cm_fields['M'];
2109                         mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2110                         /* If there's no file open by this time, the requested
2111                          * section wasn't found, so print an error
2112                          */
2113                         if (CC->download_fp == NULL) {
2114                                 if (do_proto) cprintf(
2115                                         "%d Section %s not found.\n",
2116                                         ERROR + FILE_NOT_FOUND,
2117                                         CC->download_desired_section);
2118                         }
2119                 }
2120                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2121         }
2122
2123         /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2124          * in a single server operation instead of opening a download file.
2125          */
2126         if (mode == MT_SPEW_SECTION) {
2127                 if (TheMessage->cm_format_type != FMT_RFC822) {
2128                         if (do_proto)
2129                                 cprintf("%d This is not a MIME message.\n",
2130                                 ERROR + ILLEGAL_VALUE);
2131                 } else {
2132                         /* Parse the message text component */
2133                         int found_it = 0;
2134
2135                         mptr = TheMessage->cm_fields['M'];
2136                         mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2137                         /* If section wasn't found, print an error
2138                          */
2139                         if (!found_it) {
2140                                 if (do_proto) cprintf(
2141                                         "%d Section %s not found.\n",
2142                                         ERROR + FILE_NOT_FOUND,
2143                                         CC->download_desired_section);
2144                         }
2145                 }
2146                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2147         }
2148
2149         /* now for the user-mode message reading loops */
2150         if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2151
2152         /* Does the caller want to skip the headers? */
2153         if (headers_only == HEADERS_NONE) goto START_TEXT;
2154
2155         /* Tell the client which format type we're using. */
2156         if ( (mode == MT_CITADEL) && (do_proto) ) {
2157                 cprintf("type=%d\n", TheMessage->cm_format_type);
2158         }
2159
2160         /* nhdr=yes means that we're only displaying headers, no body */
2161         if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2162            && ((mode == MT_CITADEL) || (mode == MT_MIME))
2163            && (do_proto)
2164            ) {
2165                 cprintf("nhdr=yes\n");
2166         }
2167
2168         if ((mode == MT_CITADEL) || (mode == MT_MIME)) 
2169                 OutputCtdlMsgHeaders(TheMessage, do_proto);
2170
2171
2172         /* begin header processing loop for RFC822 transfer format */
2173         strcpy(suser, "");
2174         strcpy(luser, "");
2175         strcpy(fuser, "");
2176         strcpy(snode, NODENAME);
2177         if (mode == MT_RFC822) 
2178                 OutputRFC822MsgHeaders(
2179                         TheMessage,
2180                         flags,
2181                         nl,
2182                         mid, sizeof(mid),
2183                         suser, sizeof(suser),
2184                         luser, sizeof(luser),
2185                         fuser, sizeof(fuser),
2186                         snode, sizeof(snode)
2187                         );
2188
2189
2190         for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2191                 suser[i] = tolower(suser[i]);
2192                 if (!isalnum(suser[i])) suser[i]='_';
2193         }
2194
2195         if (mode == MT_RFC822) {
2196                 if (!strcasecmp(snode, NODENAME)) {
2197                         safestrncpy(snode, FQDN, sizeof snode);
2198                 }
2199
2200                 /* Construct a fun message id */
2201                 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2202                 if (strchr(mid, '@')==NULL) {
2203                         cprintf("@%s", snode);
2204                 }
2205                 cprintf(">%s", nl);
2206
2207                 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2208                         cprintf("From: \"----\" <x@x.org>%s", nl);
2209                 }
2210                 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2211                         cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2212                 }
2213                 else if (!IsEmptyStr(fuser)) {
2214                         cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2215                 }
2216                 else {
2217                         cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2218                 }
2219
2220                 /* Blank line signifying RFC822 end-of-headers */
2221                 if (TheMessage->cm_format_type != FMT_RFC822) {
2222                         cprintf("%s", nl);
2223                 }
2224         }
2225
2226         /* end header processing loop ... at this point, we're in the text */
2227 START_TEXT:
2228         if (headers_only == HEADERS_FAST) goto DONE;
2229
2230         /* Tell the client about the MIME parts in this message */
2231         if (TheMessage->cm_format_type == FMT_RFC822) {
2232                 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2233                         mptr = TheMessage->cm_fields['M'];
2234                         memset(&ma, 0, sizeof(struct ma_info));
2235                         mime_parser(mptr, NULL,
2236                                 (do_proto ? *list_this_part : NULL),
2237                                 (do_proto ? *list_this_pref : NULL),
2238                                 (do_proto ? *list_this_suff : NULL),
2239                                 (void *)&ma, 0);
2240                 }
2241                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
2242                         Dump_RFC822HeadersBody(
2243                                 TheMessage,
2244                                 headers_only,
2245                                 flags,
2246                                 nl);
2247                         goto DONE;
2248                 }
2249         }
2250
2251         if (headers_only == HEADERS_ONLY) {
2252                 goto DONE;
2253         }
2254
2255         /* signify start of msg text */
2256         if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2257                 if (do_proto) cprintf("text\n");
2258         }
2259
2260         if (TheMessage->cm_format_type == FMT_FIXED) 
2261                 DumpFormatFixed(
2262                         TheMessage,
2263                         mode,           /* how would you like that message? */
2264                         nl);
2265
2266         /* If the message on disk is format 0 (Citadel vari-format), we
2267          * output using the formatter at 80 columns.  This is the final output
2268          * form if the transfer format is RFC822, but if the transfer format
2269          * is Citadel proprietary, it'll still work, because the indentation
2270          * for new paragraphs is correct and the client will reformat the
2271          * message to the reader's screen width.
2272          */
2273         if (TheMessage->cm_format_type == FMT_CITADEL) {
2274                 mptr = TheMessage->cm_fields['M'];
2275
2276                 if (mode == MT_MIME) {
2277                         cprintf("Content-type: text/x-citadel-variformat\n\n");
2278                 }
2279                 memfmout(mptr, nl);
2280         }
2281
2282         /* If the message on disk is format 4 (MIME), we've gotta hand it
2283          * off to the MIME parser.  The client has already been told that
2284          * this message is format 1 (fixed format), so the callback function
2285          * we use will display those parts as-is.
2286          */
2287         if (TheMessage->cm_format_type == FMT_RFC822) {
2288                 memset(&ma, 0, sizeof(struct ma_info));
2289
2290                 if (mode == MT_MIME) {
2291                         ma.use_fo_hooks = 0;
2292                         strcpy(ma.chosen_part, "1");
2293                         ma.chosen_pref = 9999;
2294                         mime_parser(mptr, NULL,
2295                                 *choose_preferred, *fixed_output_pre,
2296                                 *fixed_output_post, (void *)&ma, 0);
2297                         mime_parser(mptr, NULL,
2298                                 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2299                 }
2300                 else {
2301                         ma.use_fo_hooks = 1;
2302                         mime_parser(mptr, NULL,
2303                                 *fixed_output, *fixed_output_pre,
2304                                 *fixed_output_post, (void *)&ma, 0);
2305                 }
2306
2307         }
2308
2309 DONE:   /* now we're done */
2310         if (do_proto) cprintf("000\n");
2311         return(om_ok);
2312 }
2313
2314
2315 /*
2316  * display a message (mode 0 - Citadel proprietary)
2317  */
2318 void cmd_msg0(char *cmdbuf)
2319 {
2320         long msgid;
2321         int headers_only = HEADERS_ALL;
2322
2323         msgid = extract_long(cmdbuf, 0);
2324         headers_only = extract_int(cmdbuf, 1);
2325
2326         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2327         return;
2328 }
2329
2330
2331 /*
2332  * display a message (mode 2 - RFC822)
2333  */
2334 void cmd_msg2(char *cmdbuf)
2335 {
2336         long msgid;
2337         int headers_only = HEADERS_ALL;
2338
2339         msgid = extract_long(cmdbuf, 0);
2340         headers_only = extract_int(cmdbuf, 1);
2341
2342         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2343 }
2344
2345
2346
2347 /* 
2348  * display a message (mode 3 - IGnet raw format - internal programs only)
2349  */
2350 void cmd_msg3(char *cmdbuf)
2351 {
2352         long msgnum;
2353         struct CtdlMessage *msg = NULL;
2354         struct ser_ret smr;
2355
2356         if (CC->internal_pgm == 0) {
2357                 cprintf("%d This command is for internal programs only.\n",
2358                         ERROR + HIGHER_ACCESS_REQUIRED);
2359                 return;
2360         }
2361
2362         msgnum = extract_long(cmdbuf, 0);
2363         msg = CtdlFetchMessage(msgnum, 1);
2364         if (msg == NULL) {
2365                 cprintf("%d Message %ld not found.\n", 
2366                         ERROR + MESSAGE_NOT_FOUND, msgnum);
2367                 return;
2368         }
2369
2370         serialize_message(&smr, msg);
2371         CtdlFreeMessage(msg);
2372
2373         if (smr.len == 0) {
2374                 cprintf("%d Unable to serialize message\n",
2375                         ERROR + INTERNAL_ERROR);
2376                 return;
2377         }
2378
2379         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2380         client_write((char *)smr.ser, (int)smr.len);
2381         free(smr.ser);
2382 }
2383
2384
2385
2386 /* 
2387  * Display a message using MIME content types
2388  */
2389 void cmd_msg4(char *cmdbuf)
2390 {
2391         long msgid;
2392         char section[64];
2393
2394         msgid = extract_long(cmdbuf, 0);
2395         extract_token(section, cmdbuf, 1, '|', sizeof section);
2396         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2397 }
2398
2399
2400
2401 /* 
2402  * Client tells us its preferred message format(s)
2403  */
2404 void cmd_msgp(char *cmdbuf)
2405 {
2406         if (!strcasecmp(cmdbuf, "dont_decode")) {
2407                 CC->msg4_dont_decode = 1;
2408                 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2409         }
2410         else {
2411                 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2412                 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2413         }
2414 }
2415
2416
2417 /*
2418  * Open a component of a MIME message as a download file 
2419  */
2420 void cmd_opna(char *cmdbuf)
2421 {
2422         long msgid;
2423         char desired_section[128];
2424
2425         msgid = extract_long(cmdbuf, 0);
2426         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2427         safestrncpy(CC->download_desired_section, desired_section,
2428                 sizeof CC->download_desired_section);
2429         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2430 }                       
2431
2432
2433 /*
2434  * Open a component of a MIME message and transmit it all at once
2435  */
2436 void cmd_dlat(char *cmdbuf)
2437 {
2438         long msgid;
2439         char desired_section[128];
2440
2441         msgid = extract_long(cmdbuf, 0);
2442         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2443         safestrncpy(CC->download_desired_section, desired_section,
2444                 sizeof CC->download_desired_section);
2445         CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2446 }                       
2447
2448
2449 /*
2450  * Save one or more message pointers into a specified room
2451  * (Returns 0 for success, nonzero for failure)
2452  * roomname may be NULL to use the current room
2453  *
2454  * Note that the 'supplied_msg' field may be set to NULL, in which case
2455  * the message will be fetched from disk, by number, if we need to perform
2456  * replication checks.  This adds an additional database read, so if the
2457  * caller already has the message in memory then it should be supplied.  (Obviously
2458  * this mode of operation only works if we're saving a single message.)
2459  */
2460 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2461                         int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2462 ) {
2463         int i, j, unique;
2464         char hold_rm[ROOMNAMELEN];
2465         struct cdbdata *cdbfr;
2466         int num_msgs;
2467         long *msglist;
2468         long highest_msg = 0L;
2469
2470         long msgid = 0;
2471         struct CtdlMessage *msg = NULL;
2472
2473         long *msgs_to_be_merged = NULL;
2474         int num_msgs_to_be_merged = 0;
2475
2476         CtdlLogPrintf(CTDL_DEBUG,
2477                 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2478                 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2479         );
2480
2481         strcpy(hold_rm, CC->room.QRname);
2482
2483         /* Sanity checks */
2484         if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2485         if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2486         if (num_newmsgs > 1) supplied_msg = NULL;
2487
2488         /* Now the regular stuff */
2489         if (CtdlGetRoomLock(&CC->room,
2490            ((roomname != NULL) ? roomname : CC->room.QRname) )
2491            != 0) {
2492                 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2493                 return(ERROR + ROOM_NOT_FOUND);
2494         }
2495
2496
2497         msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2498         num_msgs_to_be_merged = 0;
2499
2500
2501         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2502         if (cdbfr == NULL) {
2503                 msglist = NULL;
2504                 num_msgs = 0;
2505         } else {
2506                 msglist = (long *) cdbfr->ptr;
2507                 cdbfr->ptr = NULL;      /* CtdlSaveMsgPointerInRoom() now owns this memory */
2508                 num_msgs = cdbfr->len / sizeof(long);
2509                 cdb_free(cdbfr);
2510         }
2511
2512
2513         /* Create a list of msgid's which were supplied by the caller, but do
2514          * not already exist in the target room.  It is absolutely taboo to
2515          * have more than one reference to the same message in a room.
2516          */
2517         for (i=0; i<num_newmsgs; ++i) {
2518                 unique = 1;
2519                 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2520                         if (msglist[j] == newmsgidlist[i]) {
2521                                 unique = 0;
2522                         }
2523                 }
2524                 if (unique) {
2525                         msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2526                 }
2527         }
2528
2529         CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2530
2531         /*
2532          * Now merge the new messages
2533          */
2534         msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2535         if (msglist == NULL) {
2536                 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2537         }
2538         memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2539         num_msgs += num_msgs_to_be_merged;
2540
2541         /* Sort the message list, so all the msgid's are in order */
2542         num_msgs = sort_msglist(msglist, num_msgs);
2543
2544         /* Determine the highest message number */
2545         highest_msg = msglist[num_msgs - 1];
2546
2547         /* Write it back to disk. */
2548         cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2549                   msglist, (int)(num_msgs * sizeof(long)));
2550
2551         /* Free up the memory we used. */
2552         free(msglist);
2553
2554         /* Update the highest-message pointer and unlock the room. */
2555         CC->room.QRhighest = highest_msg;
2556         CtdlPutRoomLock(&CC->room);
2557
2558         /* Perform replication checks if necessary */
2559         if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2560                 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2561
2562                 for (i=0; i<num_msgs_to_be_merged; ++i) {
2563                         msgid = msgs_to_be_merged[i];
2564         
2565                         if (supplied_msg != NULL) {
2566                                 msg = supplied_msg;
2567                         }
2568                         else {
2569                                 msg = CtdlFetchMessage(msgid, 0);
2570                         }
2571         
2572                         if (msg != NULL) {
2573                                 ReplicationChecks(msg);
2574                 
2575                                 /* If the message has an Exclusive ID, index that... */
2576                                 if (msg->cm_fields['E'] != NULL) {
2577                                         index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2578                                 }
2579
2580                                 /* Free up the memory we may have allocated */
2581                                 if (msg != supplied_msg) {
2582                                         CtdlFreeMessage(msg);
2583                                 }
2584                         }
2585         
2586                 }
2587         }
2588
2589         else {
2590                 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2591         }
2592
2593         /* Submit this room for processing by hooks */
2594         PerformRoomHooks(&CC->room);
2595
2596         /* Go back to the room we were in before we wandered here... */
2597         CtdlGetRoom(&CC->room, hold_rm);
2598
2599         /* Bump the reference count for all messages which were merged */
2600         if (!suppress_refcount_adj) {
2601                 for (i=0; i<num_msgs_to_be_merged; ++i) {
2602                         AdjRefCount(msgs_to_be_merged[i], +1);
2603                 }
2604         }
2605
2606         /* Free up memory... */
2607         if (msgs_to_be_merged != NULL) {
2608                 free(msgs_to_be_merged);
2609         }
2610
2611         /* Return success. */
2612         return (0);
2613 }
2614
2615
2616 /*
2617  * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2618  * a single message.
2619  */
2620 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2621                         int do_repl_check, struct CtdlMessage *supplied_msg)
2622 {
2623         return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2624 }
2625
2626
2627
2628
2629 /*
2630  * Message base operation to save a new message to the message store
2631  * (returns new message number)
2632  *
2633  * This is the back end for CtdlSubmitMsg() and should not be directly
2634  * called by server-side modules.
2635  *
2636  */
2637 long send_message(struct CtdlMessage *msg) {
2638         long newmsgid;
2639         long retval;
2640         char msgidbuf[256];
2641         struct ser_ret smr;
2642         int is_bigmsg = 0;
2643         char *holdM = NULL;
2644
2645         /* Get a new message number */
2646         newmsgid = get_new_message_number();
2647         snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2648
2649         /* Generate an ID if we don't have one already */
2650         if (msg->cm_fields['I']==NULL) {
2651                 msg->cm_fields['I'] = strdup(msgidbuf);
2652         }
2653
2654         /* If the message is big, set its body aside for storage elsewhere */
2655         if (msg->cm_fields['M'] != NULL) {
2656                 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2657                         is_bigmsg = 1;
2658                         holdM = msg->cm_fields['M'];
2659                         msg->cm_fields['M'] = NULL;
2660                 }
2661         }
2662
2663         /* Serialize our data structure for storage in the database */  
2664         serialize_message(&smr, msg);
2665
2666         if (is_bigmsg) {
2667                 msg->cm_fields['M'] = holdM;
2668         }
2669
2670         if (smr.len == 0) {
2671                 cprintf("%d Unable to serialize message\n",
2672                         ERROR + INTERNAL_ERROR);
2673                 return (-1L);
2674         }
2675
2676         /* Write our little bundle of joy into the message base */
2677         if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2678                       smr.ser, smr.len) < 0) {
2679                 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2680                 retval = 0L;
2681         } else {
2682                 if (is_bigmsg) {
2683                         cdb_store(CDB_BIGMSGS,
2684                                 &newmsgid,
2685                                 (int)sizeof(long),
2686                                 holdM,
2687                                 (strlen(holdM) + 1)
2688                         );
2689                 }
2690                 retval = newmsgid;
2691         }
2692
2693         /* Free the memory we used for the serialized message */
2694         free(smr.ser);
2695
2696         /* Return the *local* message ID to the caller
2697          * (even if we're storing an incoming network message)
2698          */
2699         return(retval);
2700 }
2701
2702
2703
2704 /*
2705  * Serialize a struct CtdlMessage into the format used on disk and network.
2706  * 
2707  * This function loads up a "struct ser_ret" (defined in server.h) which
2708  * contains the length of the serialized message and a pointer to the
2709  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
2710  */
2711 void serialize_message(struct ser_ret *ret,             /* return values */
2712                         struct CtdlMessage *msg)        /* unserialized msg */
2713 {
2714         size_t wlen, fieldlen;
2715         int i;
2716         static char *forder = FORDER;
2717
2718         /*
2719          * Check for valid message format
2720          */
2721         if (is_valid_message(msg) == 0) {
2722                 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2723                 ret->len = 0;
2724                 ret->ser = NULL;
2725                 return;
2726         }
2727
2728         ret->len = 3;
2729         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2730                 ret->len = ret->len +
2731                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
2732
2733         ret->ser = malloc(ret->len);
2734         if (ret->ser == NULL) {
2735                 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2736                         (long)ret->len, strerror(errno));
2737                 ret->len = 0;
2738                 ret->ser = NULL;
2739                 return;
2740         }
2741
2742         ret->ser[0] = 0xFF;
2743         ret->ser[1] = msg->cm_anon_type;
2744         ret->ser[2] = msg->cm_format_type;
2745         wlen = 3;
2746
2747         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2748                 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2749                 ret->ser[wlen++] = (char)forder[i];
2750                 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2751                 wlen = wlen + fieldlen + 1;
2752         }
2753         if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2754                 (long)ret->len, (long)wlen);
2755
2756         return;
2757 }
2758
2759
2760 /*
2761  * Serialize a struct CtdlMessage into the format used on disk and network.
2762  * 
2763  * This function loads up a "struct ser_ret" (defined in server.h) which
2764  * contains the length of the serialized message and a pointer to the
2765  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
2766  */
2767 void dump_message(struct CtdlMessage *msg,      /* unserialized msg */
2768                   long Siz)                     /* how many chars ? */
2769 {
2770         size_t wlen;
2771         int i;
2772         static char *forder = FORDER;
2773         char *buf;
2774
2775         /*
2776          * Check for valid message format
2777          */
2778         if (is_valid_message(msg) == 0) {
2779                 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2780                 return;
2781         }
2782
2783         buf = (char*) malloc (Siz + 1);
2784
2785         wlen = 3;
2786         
2787         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2788                         snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i], 
2789                                    msg->cm_fields[(int)forder[i]]);
2790                         client_write (buf, strlen(buf));
2791                 }
2792
2793         return;
2794 }
2795
2796
2797
2798 /*
2799  * Check to see if any messages already exist in the current room which
2800  * carry the same Exclusive ID as this one.  If any are found, delete them.
2801  */
2802 void ReplicationChecks(struct CtdlMessage *msg) {
2803         long old_msgnum = (-1L);
2804
2805         if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2806
2807         CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2808                 CC->room.QRname);
2809
2810         /* No exclusive id?  Don't do anything. */
2811         if (msg == NULL) return;
2812         if (msg->cm_fields['E'] == NULL) return;
2813         if (IsEmptyStr(msg->cm_fields['E'])) return;
2814         /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2815                 msg->cm_fields['E'], CC->room.QRname);*/
2816
2817         old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2818         if (old_msgnum > 0L) {
2819                 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2820                 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2821         }
2822 }
2823
2824
2825
2826 /*
2827  * Save a message to disk and submit it into the delivery system.
2828  */
2829 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
2830                    struct recptypes *recps,     /* recipients (if mail) */
2831                    char *force,                 /* force a particular room? */
2832                    int flags                    /* should the message be exported clean? */
2833 ) {
2834         char submit_filename[128];
2835         char generated_timestamp[32];
2836         char hold_rm[ROOMNAMELEN];
2837         char actual_rm[ROOMNAMELEN];
2838         char force_room[ROOMNAMELEN];
2839         char content_type[SIZ];                 /* We have to learn this */
2840         char recipient[SIZ];
2841         long newmsgid;
2842         const char *mptr = NULL;
2843         struct ctdluser userbuf;
2844         int a, i;
2845         struct MetaData smi;
2846         FILE *network_fp = NULL;
2847         static int seqnum = 1;
2848         struct CtdlMessage *imsg = NULL;
2849         char *instr = NULL;
2850         size_t instr_alloc = 0;
2851         struct ser_ret smr;
2852         char *hold_R, *hold_D;
2853         char *collected_addresses = NULL;
2854         struct addresses_to_be_filed *aptr = NULL;
2855         StrBuf *saved_rfc822_version = NULL;
2856         int qualified_for_journaling = 0;
2857         CitContext *CCC = CC;           /* CachedCitContext - performance boost */
2858         char bounce_to[1024] = "";
2859         size_t tmp = 0;
2860         int rv = 0;
2861
2862         CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2863         if (is_valid_message(msg) == 0) return(-1);     /* self check */
2864
2865         /* If this message has no timestamp, we take the liberty of
2866          * giving it one, right now.
2867          */
2868         if (msg->cm_fields['T'] == NULL) {
2869                 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2870                 msg->cm_fields['T'] = strdup(generated_timestamp);
2871         }
2872
2873         /* If this message has no path, we generate one.
2874          */
2875         if (msg->cm_fields['P'] == NULL) {
2876                 if (msg->cm_fields['A'] != NULL) {
2877                         msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2878                         for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2879                                 if (isspace(msg->cm_fields['P'][a])) {
2880                                         msg->cm_fields['P'][a] = ' ';
2881                                 }
2882                         }
2883                 }
2884                 else {
2885                         msg->cm_fields['P'] = strdup("unknown");
2886                 }
2887         }
2888
2889         if (force == NULL) {
2890                 strcpy(force_room, "");
2891         }
2892         else {
2893                 strcpy(force_room, force);
2894         }
2895
2896         /* Learn about what's inside, because it's what's inside that counts */
2897         if (msg->cm_fields['M'] == NULL) {
2898                 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2899                 return(-2);
2900         }
2901
2902         switch (msg->cm_format_type) {
2903         case 0:
2904                 strcpy(content_type, "text/x-citadel-variformat");
2905                 break;
2906         case 1:
2907                 strcpy(content_type, "text/plain");
2908                 break;
2909         case 4:
2910                 strcpy(content_type, "text/plain");
2911                 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2912                 if (mptr != NULL) {
2913                         char *aptr;
2914                         safestrncpy(content_type, &mptr[13], sizeof content_type);
2915                         striplt(content_type);
2916                         aptr = content_type;
2917                         while (!IsEmptyStr(aptr)) {
2918                                 if ((*aptr == ';')
2919                                     || (*aptr == ' ')
2920                                     || (*aptr == 13)
2921                                     || (*aptr == 10)) {
2922                                         *aptr = 0;
2923                                 }
2924                                 else aptr++;
2925                         }
2926                 }
2927         }
2928
2929         /* Goto the correct room */
2930         CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2931         strcpy(hold_rm, CCC->room.QRname);
2932         strcpy(actual_rm, CCC->room.QRname);
2933         if (recps != NULL) {
2934                 strcpy(actual_rm, SENTITEMS);
2935         }
2936
2937         /* If the user is a twit, move to the twit room for posting */
2938         if (TWITDETECT) {
2939                 if (CCC->user.axlevel == AxProbU) {
2940                         strcpy(hold_rm, actual_rm);
2941                         strcpy(actual_rm, config.c_twitroom);
2942                         CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2943                 }
2944         }
2945
2946         /* ...or if this message is destined for Aide> then go there. */
2947         if (!IsEmptyStr(force_room)) {
2948                 strcpy(actual_rm, force_room);
2949         }
2950
2951         CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2952         if (strcasecmp(actual_rm, CCC->room.QRname)) {
2953                 /* CtdlGetRoom(&CCC->room, actual_rm); */
2954                 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
2955         }
2956
2957         /*
2958          * If this message has no O (room) field, generate one.
2959          */
2960         if (msg->cm_fields['O'] == NULL) {
2961                 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2962         }
2963
2964         /* Perform "before save" hooks (aborting if any return nonzero) */
2965         CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2966         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2967
2968         /*
2969          * If this message has an Exclusive ID, and the room is replication
2970          * checking enabled, then do replication checks.
2971          */
2972         if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2973                 ReplicationChecks(msg);
2974         }
2975
2976         /* Save it to disk */
2977         CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2978         newmsgid = send_message(msg);
2979         if (newmsgid <= 0L) return(-5);
2980
2981         /* Write a supplemental message info record.  This doesn't have to
2982          * be a critical section because nobody else knows about this message
2983          * yet.
2984          */
2985         CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2986         memset(&smi, 0, sizeof(struct MetaData));
2987         smi.meta_msgnum = newmsgid;
2988         smi.meta_refcount = 0;
2989         safestrncpy(smi.meta_content_type, content_type,
2990                         sizeof smi.meta_content_type);
2991
2992         /*
2993          * Measure how big this message will be when rendered as RFC822.
2994          * We do this for two reasons:
2995          * 1. We need the RFC822 length for the new metadata record, so the
2996          *    POP and IMAP services don't have to calculate message lengths
2997          *    while the user is waiting (multiplied by potentially hundreds
2998          *    or thousands of messages).
2999          * 2. If journaling is enabled, we will need an RFC822 version of the
3000          *    message to attach to the journalized copy.
3001          */
3002         if (CCC->redirect_buffer != NULL) {
3003                 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3004                 abort();
3005         }
3006         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3007         CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3008         smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3009         saved_rfc822_version = CCC->redirect_buffer;
3010         CCC->redirect_buffer = NULL;
3011
3012         PutMetaData(&smi);
3013
3014         /* Now figure out where to store the pointers */
3015         CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
3016
3017         /* If this is being done by the networker delivering a private
3018          * message, we want to BYPASS saving the sender's copy (because there
3019          * is no local sender; it would otherwise go to the Trashcan).
3020          */
3021         if ((!CCC->internal_pgm) || (recps == NULL)) {
3022                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3023                         CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
3024                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3025                 }
3026         }
3027
3028         /* For internet mail, drop a copy in the outbound queue room */
3029         if ((recps != NULL) && (recps->num_internet > 0)) {
3030                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3031         }
3032
3033         /* If other rooms are specified, drop them there too. */
3034         if ((recps != NULL) && (recps->num_room > 0))
3035           for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3036                 extract_token(recipient, recps->recp_room, i,
3037                                         '|', sizeof recipient);
3038                 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
3039                 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3040         }
3041
3042         /* Bump this user's messages posted counter. */
3043         CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
3044         CtdlGetUserLock(&CCC->user, CCC->curr_user);
3045         CCC->user.posted = CCC->user.posted + 1;
3046         CtdlPutUserLock(&CCC->user);
3047
3048         /* Decide where bounces need to be delivered */
3049         if ((recps != NULL) && (recps->bounce_to != NULL)) {
3050                 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3051         }
3052         else if (CCC->logged_in) {
3053                 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3054         }
3055         else {
3056                 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3057         }
3058
3059         /* If this is private, local mail, make a copy in the
3060          * recipient's mailbox and bump the reference count.
3061          */
3062         if ((recps != NULL) && (recps->num_local > 0))
3063           for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3064                 extract_token(recipient, recps->recp_local, i,
3065                                         '|', sizeof recipient);
3066                 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
3067                         recipient);
3068                 if (CtdlGetUser(&userbuf, recipient) == 0) {
3069                         // Add a flag so the Funambol module knows its mail
3070                         msg->cm_fields['W'] = strdup(recipient);
3071                         CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3072                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3073                         CtdlBumpNewMailCounter(userbuf.usernum);
3074                         if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3075                         /* Generate a instruction message for the Funambol notification
3076                          * server, in the same style as the SMTP queue
3077                          */
3078                            instr_alloc = 1024;
3079                            instr = malloc(instr_alloc);
3080                            snprintf(instr, instr_alloc,
3081                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3082                         "bounceto|%s\n",
3083                         SPOOLMIME, newmsgid, (long)time(NULL),
3084                         bounce_to
3085                         );
3086
3087                            imsg = malloc(sizeof(struct CtdlMessage));
3088                            memset(imsg, 0, sizeof(struct CtdlMessage));
3089                            imsg->cm_magic = CTDLMESSAGE_MAGIC;
3090                            imsg->cm_anon_type = MES_NORMAL;
3091                            imsg->cm_format_type = FMT_RFC822;
3092                            imsg->cm_fields['A'] = strdup("Citadel");
3093                            imsg->cm_fields['J'] = strdup("do not journal");
3094                            imsg->cm_fields['M'] = instr;        /* imsg owns this memory now */
3095                            imsg->cm_fields['W'] = strdup(recipient);
3096                            CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3097                            CtdlFreeMessage(imsg);
3098                         }
3099                 }
3100                 else {
3101                         CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
3102                         CtdlSaveMsgPointerInRoom(config.c_aideroom,
3103                                 newmsgid, 0, msg);
3104                 }
3105         }
3106
3107         /* Perform "after save" hooks */
3108         CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
3109         PerformMessageHooks(msg, EVT_AFTERSAVE);
3110
3111         /* For IGnet mail, we have to save a new copy into the spooler for
3112          * each recipient, with the R and D fields set to the recipient and
3113          * destination-node.  This has two ugly side effects: all other
3114          * recipients end up being unlisted in this recipient's copy of the
3115          * message, and it has to deliver multiple messages to the same
3116          * node.  We'll revisit this again in a year or so when everyone has
3117          * a network spool receiver that can handle the new style messages.
3118          */
3119         if ((recps != NULL) && (recps->num_ignet > 0))
3120           for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3121                 extract_token(recipient, recps->recp_ignet, i,
3122                                 '|', sizeof recipient);
3123
3124                 hold_R = msg->cm_fields['R'];
3125                 hold_D = msg->cm_fields['D'];
3126                 msg->cm_fields['R'] = malloc(SIZ);
3127                 msg->cm_fields['D'] = malloc(128);
3128                 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3129                 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3130                 
3131                 serialize_message(&smr, msg);
3132                 if (smr.len > 0) {
3133                         snprintf(submit_filename, sizeof submit_filename,
3134                                          "%s/netmail.%04lx.%04x.%04x",
3135                                          ctdl_netin_dir,
3136                                          (long) getpid(), CCC->cs_pid, ++seqnum);
3137                         network_fp = fopen(submit_filename, "wb+");
3138                         if (network_fp != NULL) {
3139                                 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3140                                 fclose(network_fp);
3141                         }
3142                         free(smr.ser);
3143                 }
3144
3145                 free(msg->cm_fields['R']);
3146                 free(msg->cm_fields['D']);
3147                 msg->cm_fields['R'] = hold_R;
3148                 msg->cm_fields['D'] = hold_D;
3149         }
3150
3151         /* Go back to the room we started from */
3152         CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3153         if (strcasecmp(hold_rm, CCC->room.QRname))
3154                 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3155
3156         /* For internet mail, generate delivery instructions.
3157          * Yes, this is recursive.  Deal with it.  Infinite recursion does
3158          * not happen because the delivery instructions message does not
3159          * contain a recipient.
3160          */
3161         if ((recps != NULL) && (recps->num_internet > 0)) {
3162                 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3163                 instr_alloc = 1024;
3164                 instr = malloc(instr_alloc);
3165                 snprintf(instr, instr_alloc,
3166                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3167                         "bounceto|%s\n",
3168                         SPOOLMIME, newmsgid, (long)time(NULL),
3169                         bounce_to
3170                 );
3171
3172                 if (recps->envelope_from != NULL) {
3173                         tmp = strlen(instr);
3174                         snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3175                 }
3176
3177                 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3178                         tmp = strlen(instr);
3179                         extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3180                         if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3181                                 instr_alloc = instr_alloc * 2;
3182                                 instr = realloc(instr, instr_alloc);
3183                         }
3184                         snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3185                 }
3186
3187                 imsg = malloc(sizeof(struct CtdlMessage));
3188                 memset(imsg, 0, sizeof(struct CtdlMessage));
3189                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3190                 imsg->cm_anon_type = MES_NORMAL;
3191                 imsg->cm_format_type = FMT_RFC822;
3192                 imsg->cm_fields['A'] = strdup("Citadel");
3193                 imsg->cm_fields['J'] = strdup("do not journal");
3194                 imsg->cm_fields['M'] = instr;   /* imsg owns this memory now */
3195                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3196                 CtdlFreeMessage(imsg);
3197         }
3198
3199         /*
3200          * Any addresses to harvest for someone's address book?
3201          */
3202         if ( (CCC->logged_in) && (recps != NULL) ) {
3203                 collected_addresses = harvest_collected_addresses(msg);
3204         }
3205
3206         if (collected_addresses != NULL) {
3207                 aptr = (struct addresses_to_be_filed *)
3208                         malloc(sizeof(struct addresses_to_be_filed));
3209                 CtdlMailboxName(actual_rm, sizeof actual_rm,
3210                         &CCC->user, USERCONTACTSROOM);
3211                 aptr->roomname = strdup(actual_rm);
3212                 aptr->collected_addresses = collected_addresses;
3213                 begin_critical_section(S_ATBF);
3214                 aptr->next = atbf;
3215                 atbf = aptr;
3216                 end_critical_section(S_ATBF);
3217         }
3218
3219         /*
3220          * Determine whether this message qualifies for journaling.
3221          */
3222         if (msg->cm_fields['J'] != NULL) {
3223                 qualified_for_journaling = 0;
3224         }
3225         else {
3226                 if (recps == NULL) {
3227                         qualified_for_journaling = config.c_journal_pubmsgs;
3228                 }
3229                 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3230                         qualified_for_journaling = config.c_journal_email;
3231                 }
3232                 else {
3233                         qualified_for_journaling = config.c_journal_pubmsgs;
3234                 }
3235         }
3236
3237         /*
3238          * Do we have to perform journaling?  If so, hand off the saved
3239          * RFC822 version will be handed off to the journaler for background
3240          * submit.  Otherwise, we have to free the memory ourselves.
3241          */
3242         if (saved_rfc822_version != NULL) {
3243                 if (qualified_for_journaling) {
3244                         JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3245                 }
3246                 else {
3247                         FreeStrBuf(&saved_rfc822_version);
3248                 }
3249         }
3250
3251         /* Done. */
3252         return(newmsgid);
3253 }
3254
3255
3256
3257 void aide_message (char *text, char *subject)
3258 {
3259         quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3260 }
3261
3262
3263 /*
3264  * Convenience function for generating small administrative messages.
3265  */
3266 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text, 
3267                         int format_type, const char *subject)
3268 {
3269         struct CtdlMessage *msg;
3270         struct recptypes *recp = NULL;
3271
3272         msg = malloc(sizeof(struct CtdlMessage));
3273         memset(msg, 0, sizeof(struct CtdlMessage));
3274         msg->cm_magic = CTDLMESSAGE_MAGIC;
3275         msg->cm_anon_type = MES_NORMAL;
3276         msg->cm_format_type = format_type;
3277
3278         if (from != NULL) {
3279                 msg->cm_fields['A'] = strdup(from);
3280         }
3281         else if (fromaddr != NULL) {
3282                 msg->cm_fields['A'] = strdup(fromaddr);
3283                 if (strchr(msg->cm_fields['A'], '@')) {
3284                         *strchr(msg->cm_fields['A'], '@') = 0;
3285                 }
3286         }
3287         else {
3288                 msg->cm_fields['A'] = strdup("Citadel");
3289         }
3290
3291         if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3292         if (room != NULL) msg->cm_fields['O'] = strdup(room);
3293         msg->cm_fields['N'] = strdup(NODENAME);
3294         if (to != NULL) {
3295                 msg->cm_fields['R'] = strdup(to);
3296                 recp = validate_recipients(to, NULL, 0);
3297         }
3298         if (subject != NULL) {
3299                 msg->cm_fields['U'] = strdup(subject);
3300         }
3301         msg->cm_fields['M'] = strdup(text);
3302
3303         CtdlSubmitMsg(msg, recp, room, 0);
3304         CtdlFreeMessage(msg);
3305         if (recp != NULL) free_recipients(recp);
3306 }
3307
3308
3309
3310 /*
3311  * Back end function used by CtdlMakeMessage() and similar functions
3312  */
3313 StrBuf *CtdlReadMessageBodyBuf(char *terminator,        /* token signalling EOT */
3314                                long tlen,
3315                                size_t maxlen,           /* maximum message length */
3316                                char *exist,             /* if non-null, append to it;
3317                                                            exist is ALWAYS freed  */
3318                                int crlf,                /* CRLF newlines instead of LF */
3319                                int *sock                /* socket handle or 0 for this session's client socket */
3320                         ) 
3321 {
3322         StrBuf *Message;
3323         StrBuf *LineBuf;
3324         int flushing = 0;
3325         int finished = 0;
3326         int dotdot = 0;
3327
3328         LineBuf = NewStrBufPlain(NULL, SIZ);
3329         if (exist == NULL) {
3330                 Message = NewStrBufPlain(NULL, 4 * SIZ);
3331         }
3332         else {
3333                 Message = NewStrBufPlain(exist, -1);
3334                 free(exist);
3335         }
3336
3337         /* Do we need to change leading ".." to "." for SMTP escaping? */
3338         if ((tlen == 1) && (*terminator == '.')) {
3339                 dotdot = 1;
3340         }
3341
3342         /* read in the lines of message text one by one */
3343         do {
3344                 if (sock != NULL) {
3345                         if ((CtdlSockGetLine(sock, LineBuf) < 0) ||
3346                             (*sock == -1))
3347                                 finished = 1;
3348                 }
3349                 else {
3350                         if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3351                 }
3352                 if ((StrLength(LineBuf) == tlen) && 
3353                     (!strcmp(ChrPtr(LineBuf), terminator)))
3354                         finished = 1;
3355
3356                 if ( (!flushing) && (!finished) ) {
3357                         if (crlf) {
3358                                 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3359                         }
3360                         else {
3361                                 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3362                         }
3363                         
3364                         /* Unescape SMTP-style input of two dots at the beginning of the line */
3365                         if ((dotdot) &&
3366                             (StrLength(LineBuf) == 2) && 
3367                             (!strcmp(ChrPtr(LineBuf), "..")))
3368                         {
3369                                 StrBufCutLeft(LineBuf, 1);
3370                         }
3371                         
3372                         StrBufAppendBuf(Message, LineBuf, 0);
3373                 }
3374
3375                 /* if we've hit the max msg length, flush the rest */
3376                 if (StrLength(Message) >= maxlen) flushing = 1;
3377
3378         } while (!finished);
3379         FreeStrBuf(&LineBuf);
3380         return Message;
3381 }
3382
3383
3384 /*
3385  * Back end function used by CtdlMakeMessage() and similar functions
3386  */
3387 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
3388                           long tlen,
3389                           size_t maxlen,                /* maximum message length */
3390                           char *exist,          /* if non-null, append to it;
3391                                                    exist is ALWAYS freed  */
3392                           int crlf,             /* CRLF newlines instead of LF */
3393                           int *sock             /* socket handle or 0 for this session's client socket */
3394         ) 
3395 {
3396         StrBuf *Message;
3397
3398         Message = CtdlReadMessageBodyBuf(terminator,
3399                                          tlen,
3400                                          maxlen,
3401                                          exist,
3402                                          crlf,
3403                                          sock);
3404         if (Message == NULL)
3405                 return NULL;
3406         else
3407                 return SmashStrBuf(&Message);
3408 }
3409
3410
3411 /*
3412  * Build a binary message to be saved on disk.
3413  * (NOTE: if you supply 'preformatted_text', the buffer you give it
3414  * will become part of the message.  This means you are no longer
3415  * responsible for managing that memory -- it will be freed along with
3416  * the rest of the fields when CtdlFreeMessage() is called.)
3417  */
3418
3419 struct CtdlMessage *CtdlMakeMessage(
3420         struct ctdluser *author,        /* author's user structure */
3421         char *recipient,                /* NULL if it's not mail */
3422         char *recp_cc,                  /* NULL if it's not mail */
3423         char *room,                     /* room where it's going */
3424         int type,                       /* see MES_ types in header file */
3425         int format_type,                /* variformat, plain text, MIME... */
3426         char *fake_name,                /* who we're masquerading as */
3427         char *my_email,                 /* which of my email addresses to use (empty is ok) */
3428         char *subject,                  /* Subject (optional) */
3429         char *supplied_euid,            /* ...or NULL if this is irrelevant */
3430         char *preformatted_text,        /* ...or NULL to read text from client */
3431         char *references                /* Thread references */
3432 ) {
3433         char dest_node[256];
3434         char buf[1024];
3435         struct CtdlMessage *msg;
3436
3437         msg = malloc(sizeof(struct CtdlMessage));
3438         memset(msg, 0, sizeof(struct CtdlMessage));
3439         msg->cm_magic = CTDLMESSAGE_MAGIC;
3440         msg->cm_anon_type = type;
3441         msg->cm_format_type = format_type;
3442
3443         /* Don't confuse the poor folks if it's not routed mail. */
3444         strcpy(dest_node, "");
3445
3446         if (recipient != NULL) striplt(recipient);
3447         if (recp_cc != NULL) striplt(recp_cc);
3448
3449         /* Path or Return-Path */
3450         if (my_email == NULL) my_email = "";
3451
3452         if (!IsEmptyStr(my_email)) {
3453                 msg->cm_fields['P'] = strdup(my_email);
3454         }
3455         else {
3456                 snprintf(buf, sizeof buf, "%s", author->fullname);
3457                 msg->cm_fields['P'] = strdup(buf);
3458         }
3459         convert_spaces_to_underscores(msg->cm_fields['P']);
3460
3461         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
3462         msg->cm_fields['T'] = strdup(buf);
3463
3464         if ((fake_name != NULL) && (fake_name[0])) {            /* author */
3465                 msg->cm_fields['A'] = strdup(fake_name);
3466         }
3467         else {
3468                 msg->cm_fields['A'] = strdup(author->fullname);
3469         }
3470
3471         if (CC->room.QRflags & QR_MAILBOX) {            /* room */
3472                 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3473         }
3474         else {
3475                 msg->cm_fields['O'] = strdup(CC->room.QRname);
3476         }
3477
3478         msg->cm_fields['N'] = strdup(NODENAME);         /* nodename */
3479         msg->cm_fields['H'] = strdup(HUMANNODE);                /* hnodename */
3480
3481         if ((recipient != NULL) && (recipient[0] != 0)) {
3482                 msg->cm_fields['R'] = strdup(recipient);
3483         }
3484         if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3485                 msg->cm_fields['Y'] = strdup(recp_cc);
3486         }
3487         if (dest_node[0] != 0) {
3488                 msg->cm_fields['D'] = strdup(dest_node);
3489         }
3490
3491         if (!IsEmptyStr(my_email)) {
3492                 msg->cm_fields['F'] = strdup(my_email);
3493         }
3494         else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3495                 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3496         }
3497
3498         if (subject != NULL) {
3499                 long length;
3500                 striplt(subject);
3501                 length = strlen(subject);
3502                 if (length > 0) {
3503                         long i;
3504                         long IsAscii;
3505                         IsAscii = -1;
3506                         i = 0;
3507                         while ((subject[i] != '\0') &&
3508                                (IsAscii = isascii(subject[i]) != 0 ))
3509                                 i++;
3510                         if (IsAscii != 0)
3511                                 msg->cm_fields['U'] = strdup(subject);
3512                         else /* ok, we've got utf8 in the string. */
3513                         {
3514                                 msg->cm_fields['U'] = rfc2047encode(subject, length);
3515                         }
3516
3517                 }
3518         }
3519
3520         if (supplied_euid != NULL) {
3521                 msg->cm_fields['E'] = strdup(supplied_euid);
3522         }
3523
3524         if (references != NULL) {
3525                 if (!IsEmptyStr(references)) {
3526                         msg->cm_fields['W'] = strdup(references);
3527                 }
3528         }
3529
3530         if (preformatted_text != NULL) {
3531                 msg->cm_fields['M'] = preformatted_text;
3532         }
3533         else {
3534                 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3535         }
3536
3537         return(msg);
3538 }
3539
3540
3541 /*
3542  * Check to see whether we have permission to post a message in the current
3543  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3544  * returns 0 on success.
3545  */
3546 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, 
3547                                           size_t n, 
3548                                           const char* RemoteIdentifier,
3549                                           int PostPublic) {
3550         int ra;
3551
3552         if (!(CC->logged_in) && 
3553             (PostPublic == POST_LOGGED_IN)) {
3554                 snprintf(errmsgbuf, n, "Not logged in.");
3555                 return (ERROR + NOT_LOGGED_IN);
3556         }
3557         else if (PostPublic == CHECK_EXISTANCE) {
3558                 return (0); // We're Evaling whether a recipient exists
3559         }
3560         else if (!(CC->logged_in)) {
3561                 
3562                 if ((CC->room.QRflags & QR_READONLY)) {
3563                         snprintf(errmsgbuf, n, "Not logged in.");
3564                         return (ERROR + NOT_LOGGED_IN);
3565                 }
3566                 if (CC->room.QRflags2 & QR2_MODERATED) {
3567                         snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3568                         return (ERROR + NOT_LOGGED_IN);
3569                 }
3570                 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3571                         SpoolControl *sc;
3572                         char filename[SIZ];
3573                         int found;
3574
3575                         if (RemoteIdentifier == NULL)
3576                         {
3577                                 snprintf(errmsgbuf, n, "Need sender to permit access.");
3578                                 return (ERROR + USERNAME_REQUIRED);
3579                         }
3580
3581                         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3582                         begin_critical_section(S_NETCONFIGS);
3583                         if (!read_spoolcontrol_file(&sc, filename))
3584                         {
3585                                 end_critical_section(S_NETCONFIGS);
3586                                 snprintf(errmsgbuf, n,
3587                                         "This mailing list only accepts posts from subscribers.");
3588                                 return (ERROR + NO_SUCH_USER);
3589                         }
3590                         end_critical_section(S_NETCONFIGS);
3591                         found = is_recipient (sc, RemoteIdentifier);
3592                         free_spoolcontrol_struct(&sc);
3593                         if (found) {
3594                                 return (0);
3595                         }
3596                         else {
3597                                 snprintf(errmsgbuf, n,
3598                                         "This mailing list only accepts posts from subscribers.");
3599                                 return (ERROR + NO_SUCH_USER);
3600                         }
3601                 }
3602                 return (0);
3603
3604         }
3605
3606         if ((CC->user.axlevel < AxProbU)
3607             && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3608                 snprintf(errmsgbuf, n, "Need to be validated to enter "
3609                                 "(except in %s> to sysop)", MAILROOM);
3610                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3611         }
3612
3613         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3614         if (!(ra & UA_POSTALLOWED)) {
3615                 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3616                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3617         }
3618
3619         strcpy(errmsgbuf, "Ok");
3620         return(0);
3621 }
3622
3623
3624 /*
3625  * Check to see if the specified user has Internet mail permission
3626  * (returns nonzero if permission is granted)
3627  */
3628 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3629
3630         /* Do not allow twits to send Internet mail */
3631         if (who->axlevel <= AxProbU) return(0);
3632
3633         /* Globally enabled? */
3634         if (config.c_restrict == 0) return(1);
3635
3636         /* User flagged ok? */
3637         if (who->flags & US_INTERNET) return(2);
3638
3639         /* Aide level access? */
3640         if (who->axlevel >= AxAideU) return(3);
3641
3642         /* No mail for you! */
3643         return(0);
3644 }
3645
3646
3647 /*
3648  * Validate recipients, count delivery types and errors, and handle aliasing
3649  * FIXME check for dupes!!!!!
3650  *
3651  * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
3652  * were specified, or the number of addresses found invalid.
3653  *
3654  * Caller needs to free the result using free_recipients()
3655  */
3656 struct recptypes *validate_recipients(const char *supplied_recipients, 
3657                                       const char *RemoteIdentifier, 
3658                                       int Flags) {
3659         struct recptypes *ret;
3660         char *recipients = NULL;
3661         char this_recp[256];
3662         char this_recp_cooked[256];
3663         char append[SIZ];
3664         int num_recps = 0;
3665         int i, j;
3666         int mailtype;
3667         int invalid;
3668         struct ctdluser tempUS;
3669         struct ctdlroom tempQR;
3670         struct ctdlroom tempQR2;
3671         int err = 0;
3672         char errmsg[SIZ];
3673         int in_quotes = 0;
3674
3675         /* Initialize */
3676         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3677         if (ret == NULL) return(NULL);
3678
3679         /* Set all strings to null and numeric values to zero */
3680         memset(ret, 0, sizeof(struct recptypes));
3681
3682         if (supplied_recipients == NULL) {
3683                 recipients = strdup("");
3684         }
3685         else {
3686                 recipients = strdup(supplied_recipients);
3687         }
3688
3689         /* Allocate some memory.  Yes, this allocates 500% more memory than we will
3690          * actually need, but it's healthier for the heap than doing lots of tiny
3691          * realloc() calls instead.
3692          */
3693
3694         ret->errormsg = malloc(strlen(recipients) + 1024);
3695         ret->recp_local = malloc(strlen(recipients) + 1024);
3696         ret->recp_internet = malloc(strlen(recipients) + 1024);
3697         ret->recp_ignet = malloc(strlen(recipients) + 1024);
3698         ret->recp_room = malloc(strlen(recipients) + 1024);
3699         ret->display_recp = malloc(strlen(recipients) + 1024);
3700
3701         ret->errormsg[0] = 0;
3702         ret->recp_local[0] = 0;
3703         ret->recp_internet[0] = 0;
3704         ret->recp_ignet[0] = 0;
3705         ret->recp_room[0] = 0;
3706         ret->display_recp[0] = 0;
3707
3708         ret->recptypes_magic = RECPTYPES_MAGIC;
3709
3710         /* Change all valid separator characters to commas */
3711         for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3712                 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3713                         recipients[i] = ',';
3714                 }
3715         }
3716
3717         /* Now start extracting recipients... */
3718
3719         while (!IsEmptyStr(recipients)) {
3720
3721                 for (i=0; i<=strlen(recipients); ++i) {
3722                         if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3723                         if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3724                                 safestrncpy(this_recp, recipients, i+1);
3725                                 this_recp[i] = 0;
3726                                 if (recipients[i] == ',') {
3727                                         strcpy(recipients, &recipients[i+1]);
3728                                 }
3729                                 else {
3730                                         strcpy(recipients, "");
3731                                 }
3732                                 break;
3733                         }
3734                 }
3735
3736                 striplt(this_recp);
3737                 if (IsEmptyStr(this_recp))
3738                         break;
3739                 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3740                 ++num_recps;
3741                 mailtype = alias(this_recp);
3742                 mailtype = alias(this_recp);
3743                 mailtype = alias(this_recp);
3744                 j = 0;
3745                 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3746                         if (this_recp[j]=='_') {
3747                                 this_recp_cooked[j] = ' ';
3748                         }
3749                         else {
3750                                 this_recp_cooked[j] = this_recp[j];
3751                         }
3752                 }
3753                 this_recp_cooked[j] = '\0';
3754                 invalid = 0;
3755                 errmsg[0] = 0;
3756                 switch(mailtype) {
3757                         case MES_LOCAL:
3758                                 if (!strcasecmp(this_recp, "sysop")) {
3759                                         ++ret->num_room;
3760                                         strcpy(this_recp, config.c_aideroom);
3761                                         if (!IsEmptyStr(ret->recp_room)) {
3762                                                 strcat(ret->recp_room, "|");
3763                                         }
3764                                         strcat(ret->recp_room, this_recp);
3765                                 }
3766                                 else if ( (!strncasecmp(this_recp, "room_", 5))
3767                                       && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3768
3769                                         /* Save room so we can restore it later */
3770                                         tempQR2 = CC->room;
3771                                         CC->room = tempQR;
3772                                         
3773                                         /* Check permissions to send mail to this room */
3774                                         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, 
3775                                                                                     sizeof errmsg, 
3776                                                                                     RemoteIdentifier,
3777                                                                                     Flags
3778                                         );
3779                                         if (err)
3780                                         {
3781                                                 ++ret->num_error;
3782                                                 invalid = 1;
3783                                         } 
3784                                         else {
3785                                                 ++ret->num_room;
3786                                                 if (!IsEmptyStr(ret->recp_room)) {
3787                                                         strcat(ret->recp_room, "|");
3788                                                 }
3789                                                 strcat(ret->recp_room, &this_recp_cooked[5]);
3790                                         }
3791                                         
3792                                         /* Restore room in case something needs it */
3793                                         CC->room = tempQR2;
3794
3795                                 }
3796                                 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3797                                         ++ret->num_local;
3798                                         strcpy(this_recp, tempUS.fullname);
3799                                         if (!IsEmptyStr(ret->recp_local)) {
3800                                                 strcat(ret->recp_local, "|");
3801                                         }
3802                                         strcat(ret->recp_local, this_recp);
3803                                 }
3804                                 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
3805                                         ++ret->num_local;
3806                                         strcpy(this_recp, tempUS.fullname);
3807                                         if (!IsEmptyStr(ret->recp_local)) {
3808                                                 strcat(ret->recp_local, "|");
3809                                         }
3810                                         strcat(ret->recp_local, this_recp);
3811                                 }
3812                                 else {
3813                                         ++ret->num_error;
3814                                         invalid = 1;
3815                                 }
3816                                 break;
3817                         case MES_INTERNET:
3818                                 /* Yes, you're reading this correctly: if the target
3819                                  * domain points back to the local system or an attached
3820                                  * Citadel directory, the address is invalid.  That's
3821                                  * because if the address were valid, we would have
3822                                  * already translated it to a local address by now.
3823                                  */
3824                                 if (IsDirectory(this_recp, 0)) {
3825                                         ++ret->num_error;
3826                                         invalid = 1;
3827                                 }
3828                                 else {
3829                                         ++ret->num_internet;
3830                                         if (!IsEmptyStr(ret->recp_internet)) {
3831                                                 strcat(ret->recp_internet, "|");
3832                                         }
3833                                         strcat(ret->recp_internet, this_recp);
3834                                 }
3835                                 break;
3836                         case MES_IGNET:
3837                                 ++ret->num_ignet;
3838                                 if (!IsEmptyStr(ret->recp_ignet)) {
3839                                         strcat(ret->recp_ignet, "|");
3840                                 }
3841                                 strcat(ret->recp_ignet, this_recp);
3842                                 break;
3843                         case MES_ERROR:
3844                                 ++ret->num_error;
3845                                 invalid = 1;
3846                                 break;
3847                 }
3848                 if (invalid) {
3849                         if (IsEmptyStr(errmsg)) {
3850                                 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3851                         }
3852                         else {
3853                                 snprintf(append, sizeof append, "%s", errmsg);
3854                         }
3855                         if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3856                                 if (!IsEmptyStr(ret->errormsg)) {
3857                                         strcat(ret->errormsg, "; ");
3858                                 }
3859                                 strcat(ret->errormsg, append);
3860                         }
3861                 }
3862                 else {
3863                         if (IsEmptyStr(ret->display_recp)) {
3864                                 strcpy(append, this_recp);
3865                         }
3866                         else {
3867                                 snprintf(append, sizeof append, ", %s", this_recp);
3868                         }
3869                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3870                                 strcat(ret->display_recp, append);
3871                         }
3872                 }
3873         }
3874
3875         if ((ret->num_local + ret->num_internet + ret->num_ignet +
3876            ret->num_room + ret->num_error) == 0) {
3877                 ret->num_error = (-1);
3878                 strcpy(ret->errormsg, "No recipients specified.");
3879         }
3880
3881         CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3882         CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3883         CtdlLogPrintf(CTDL_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
3884         CtdlLogPrintf(CTDL_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3885         CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3886         CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3887
3888         free(recipients);
3889         return(ret);
3890 }
3891
3892
3893 /*
3894  * Destructor for struct recptypes
3895  */
3896 void free_recipients(struct recptypes *valid) {
3897
3898         if (valid == NULL) {
3899                 return;
3900         }
3901
3902         if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3903                 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3904                 abort();
3905         }
3906
3907         if (valid->errormsg != NULL)            free(valid->errormsg);
3908         if (valid->recp_local != NULL)          free(valid->recp_local);
3909         if (valid->recp_internet != NULL)       free(valid->recp_internet);
3910         if (valid->recp_ignet != NULL)          free(valid->recp_ignet);
3911         if (valid->recp_room != NULL)           free(valid->recp_room);
3912         if (valid->display_recp != NULL)        free(valid->display_recp);
3913         if (valid->bounce_to != NULL)           free(valid->bounce_to);
3914         if (valid->envelope_from != NULL)       free(valid->envelope_from);
3915         free(valid);
3916 }
3917
3918
3919
3920 /*
3921  * message entry  -  mode 0 (normal)
3922  */
3923 void cmd_ent0(char *entargs)
3924 {
3925         int post = 0;
3926         char recp[SIZ];
3927         char cc[SIZ];
3928         char bcc[SIZ];
3929         char supplied_euid[128];
3930         int anon_flag = 0;
3931         int format_type = 0;
3932         char newusername[256];
3933         char newuseremail[256];
3934         struct CtdlMessage *msg;
3935         int anonymous = 0;
3936         char errmsg[SIZ];
3937         int err = 0;
3938         struct recptypes *valid = NULL;
3939         struct recptypes *valid_to = NULL;
3940         struct recptypes *valid_cc = NULL;
3941         struct recptypes *valid_bcc = NULL;
3942         char subject[SIZ];
3943         int subject_required = 0;
3944         int do_confirm = 0;
3945         long msgnum;
3946         int i, j;
3947         char buf[256];
3948         int newuseremail_ok = 0;
3949         char references[SIZ];
3950         char *ptr;
3951
3952         unbuffer_output();
3953
3954         post = extract_int(entargs, 0);
3955         extract_token(recp, entargs, 1, '|', sizeof recp);
3956         anon_flag = extract_int(entargs, 2);
3957         format_type = extract_int(entargs, 3);
3958         extract_token(subject, entargs, 4, '|', sizeof subject);
3959         extract_token(newusername, entargs, 5, '|', sizeof newusername);
3960         do_confirm = extract_int(entargs, 6);
3961         extract_token(cc, entargs, 7, '|', sizeof cc);
3962         extract_token(bcc, entargs, 8, '|', sizeof bcc);
3963         switch(CC->room.QRdefaultview) {
3964                 case VIEW_NOTES:
3965                 case VIEW_WIKI:
3966                         extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3967                         break;
3968                 default:
3969                         supplied_euid[0] = 0;
3970                         break;
3971         }
3972         extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3973         extract_token(references, entargs, 11, '|', sizeof references);
3974         for (ptr=references; *ptr != 0; ++ptr) {
3975                 if (*ptr == '!') *ptr = '|';
3976         }
3977
3978         /* first check to make sure the request is valid. */
3979
3980         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3981         if (err)
3982         {
3983                 cprintf("%d %s\n", err, errmsg);
3984                 return;
3985         }
3986
3987         /* Check some other permission type things. */
3988
3989         if (IsEmptyStr(newusername)) {
3990                 strcpy(newusername, CC->user.fullname);
3991         }
3992         if (  (CC->user.axlevel < AxAideU)
3993            && (strcasecmp(newusername, CC->user.fullname))
3994            && (strcasecmp(newusername, CC->cs_inet_fn))
3995         ) {     
3996                 cprintf("%d You don't have permission to author messages as '%s'.\n",
3997                         ERROR + HIGHER_ACCESS_REQUIRED,
3998                         newusername
3999                 );
4000                 return;
4001         }
4002
4003
4004         if (IsEmptyStr(newuseremail)) {
4005                 newuseremail_ok = 1;
4006         }
4007
4008         if (!IsEmptyStr(newuseremail)) {
4009                 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4010                         newuseremail_ok = 1;
4011                 }
4012                 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4013                         j = num_tokens(CC->cs_inet_other_emails, '|');
4014                         for (i=0; i<j; ++i) {
4015                                 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4016                                 if (!strcasecmp(newuseremail, buf)) {
4017                                         newuseremail_ok = 1;
4018                                 }
4019                         }
4020                 }
4021         }
4022
4023         if (!newuseremail_ok) {
4024                 cprintf("%d You don't have permission to author messages as '%s'.\n",
4025                         ERROR + HIGHER_ACCESS_REQUIRED,
4026                         newuseremail
4027                 );
4028                 return;
4029         }
4030
4031         CC->cs_flags |= CS_POSTING;
4032
4033         /* In mailbox rooms we have to behave a little differently --
4034          * make sure the user has specified at least one recipient.  Then
4035          * validate the recipient(s).  We do this for the Mail> room, as
4036          * well as any room which has the "Mailbox" view set.
4037          */
4038
4039         if (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4040            || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4041         ) {
4042                 if (CC->user.axlevel < AxProbU) {
4043                         strcpy(recp, "sysop");
4044                         strcpy(cc, "");
4045                         strcpy(bcc, "");
4046                 }
4047
4048                 valid_to = validate_recipients(recp, NULL, 0);
4049                 if (valid_to->num_error > 0) {
4050                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4051                         free_recipients(valid_to);
4052                         return;
4053                 }
4054
4055                 valid_cc = validate_recipients(cc, NULL, 0);
4056                 if (valid_cc->num_error > 0) {
4057                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4058                         free_recipients(valid_to);
4059                         free_recipients(valid_cc);
4060                         return;
4061                 }
4062
4063                 valid_bcc = validate_recipients(bcc, NULL, 0);
4064                 if (valid_bcc->num_error > 0) {
4065                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4066                         free_recipients(valid_to);
4067                         free_recipients(valid_cc);
4068                         free_recipients(valid_bcc);
4069                         return;
4070                 }
4071
4072                 /* Recipient required, but none were specified */
4073                 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4074                         free_recipients(valid_to);
4075                         free_recipients(valid_cc);
4076                         free_recipients(valid_bcc);
4077                         cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4078                         return;
4079                 }
4080
4081                 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4082                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4083                                 cprintf("%d You do not have permission "
4084                                         "to send Internet mail.\n",
4085                                         ERROR + HIGHER_ACCESS_REQUIRED);
4086                                 free_recipients(valid_to);
4087                                 free_recipients(valid_cc);
4088                                 free_recipients(valid_bcc);
4089                                 return;
4090                         }
4091                 }
4092
4093                 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)
4094                    && (CC->user.axlevel < AxNetU) ) {
4095                         cprintf("%d Higher access required for network mail.\n",
4096                                 ERROR + HIGHER_ACCESS_REQUIRED);
4097                         free_recipients(valid_to);
4098                         free_recipients(valid_cc);
4099                         free_recipients(valid_bcc);
4100                         return;
4101                 }
4102         
4103                 if ((RESTRICT_INTERNET == 1)
4104                     && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4105                     && ((CC->user.flags & US_INTERNET) == 0)
4106                     && (!CC->internal_pgm)) {
4107                         cprintf("%d You don't have access to Internet mail.\n",
4108                                 ERROR + HIGHER_ACCESS_REQUIRED);
4109                         free_recipients(valid_to);
4110                         free_recipients(valid_cc);
4111                         free_recipients(valid_bcc);
4112                         return;
4113                 }
4114
4115         }
4116
4117         /* Is this a room which has anonymous-only or anonymous-option? */
4118         anonymous = MES_NORMAL;
4119         if (CC->room.QRflags & QR_ANONONLY) {
4120                 anonymous = MES_ANONONLY;
4121         }
4122         if (CC->room.QRflags & QR_ANONOPT) {
4123                 if (anon_flag == 1) {   /* only if the user requested it */
4124                         anonymous = MES_ANONOPT;
4125                 }
4126         }
4127
4128         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4129                 recp[0] = 0;
4130         }
4131
4132         /* Recommend to the client that the use of a message subject is
4133          * strongly recommended in this room, if either the SUBJECTREQ flag
4134          * is set, or if there is one or more Internet email recipients.
4135          */
4136         if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4137         if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
4138         if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
4139         if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
4140
4141         /* If we're only checking the validity of the request, return
4142          * success without creating the message.
4143          */
4144         if (post == 0) {
4145                 cprintf("%d %s|%d\n", CIT_OK,
4146                         ((valid_to != NULL) ? valid_to->display_recp : ""), 
4147                         subject_required);
4148                 free_recipients(valid_to);
4149                 free_recipients(valid_cc);
4150                 free_recipients(valid_bcc);
4151                 return;
4152         }
4153
4154         /* We don't need these anymore because we'll do it differently below */
4155         free_recipients(valid_to);
4156         free_recipients(valid_cc);
4157         free_recipients(valid_bcc);
4158
4159         /* Read in the message from the client. */
4160         if (do_confirm) {
4161                 cprintf("%d send message\n", START_CHAT_MODE);
4162         } else {
4163                 cprintf("%d send message\n", SEND_LISTING);
4164         }
4165
4166         msg = CtdlMakeMessage(&CC->user, recp, cc,
4167                 CC->room.QRname, anonymous, format_type,
4168                 newusername, newuseremail, subject,
4169                 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4170                 NULL, references);
4171
4172         /* Put together one big recipients struct containing to/cc/bcc all in
4173          * one.  This is for the envelope.
4174          */
4175         char *all_recps = malloc(SIZ * 3);
4176         strcpy(all_recps, recp);
4177         if (!IsEmptyStr(cc)) {
4178                 if (!IsEmptyStr(all_recps)) {
4179                         strcat(all_recps, ",");
4180                 }
4181                 strcat(all_recps, cc);
4182         }
4183         if (!IsEmptyStr(bcc)) {
4184                 if (!IsEmptyStr(all_recps)) {
4185                         strcat(all_recps, ",");
4186                 }
4187                 strcat(all_recps, bcc);
4188         }
4189         if (!IsEmptyStr(all_recps)) {
4190                 valid = validate_recipients(all_recps, NULL, 0);
4191         }
4192         else {
4193                 valid = NULL;
4194         }
4195         free(all_recps);
4196
4197         if (msg != NULL) {
4198                 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4199
4200                 if (do_confirm) {
4201                         cprintf("%ld\n", msgnum);
4202                         if (msgnum >= 0L) {
4203                                 cprintf("Message accepted.\n");
4204                         }
4205                         else {
4206                                 cprintf("Internal error.\n");
4207                         }
4208                         if (msg->cm_fields['E'] != NULL) {
4209                                 cprintf("%s\n", msg->cm_fields['E']);
4210                         } else {
4211                                 cprintf("\n");
4212                         }
4213                         cprintf("000\n");
4214                 }
4215
4216                 CtdlFreeMessage(msg);
4217         }
4218         if (valid != NULL) {
4219                 free_recipients(valid);
4220         }
4221         return;
4222 }
4223
4224
4225
4226 /*
4227  * API function to delete messages which match a set of criteria
4228  * (returns the actual number of messages deleted)
4229  */
4230 int CtdlDeleteMessages(char *room_name,         /* which room */
4231                         long *dmsgnums,         /* array of msg numbers to be deleted */
4232                         int num_dmsgnums,       /* number of msgs to be deleted, or 0 for "any" */
4233                         char *content_type      /* or "" for any.  regular expressions expected. */
4234 )
4235 {
4236         struct ctdlroom qrbuf;
4237         struct cdbdata *cdbfr;
4238         long *msglist = NULL;
4239         long *dellist = NULL;
4240         int num_msgs = 0;
4241         int i, j;
4242         int num_deleted = 0;
4243         int delete_this;
4244         struct MetaData smi;
4245         regex_t re;
4246         regmatch_t pm;
4247         int need_to_free_re = 0;
4248
4249         if (content_type) if (!IsEmptyStr(content_type)) {
4250                 regcomp(&re, content_type, 0);
4251                 need_to_free_re = 1;
4252         }
4253         CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4254                 room_name, num_dmsgnums, content_type);
4255
4256         /* get room record, obtaining a lock... */
4257         if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4258                 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4259                         room_name);
4260                 if (need_to_free_re) regfree(&re);
4261                 return (0);     /* room not found */
4262         }
4263         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4264
4265         if (cdbfr != NULL) {
4266                 dellist = malloc(cdbfr->len);
4267                 msglist = (long *) cdbfr->ptr;
4268                 cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
4269                 num_msgs = cdbfr->len / sizeof(long);
4270                 cdb_free(cdbfr);
4271         }
4272         if (num_msgs > 0) {
4273                 for (i = 0; i < num_msgs; ++i) {
4274                         delete_this = 0x00;
4275
4276                         /* Set/clear a bit for each criterion */
4277
4278                         /* 0 messages in the list or a null list means that we are
4279                          * interested in deleting any messages which meet the other criteria.
4280                          */
4281                         if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4282                                 delete_this |= 0x01;
4283                         }
4284                         else {
4285                                 for (j=0; j<num_dmsgnums; ++j) {
4286                                         if (msglist[i] == dmsgnums[j]) {
4287                                                 delete_this |= 0x01;
4288                                         }
4289                                 }
4290                         }
4291
4292                         if (IsEmptyStr(content_type)) {
4293                                 delete_this |= 0x02;
4294                         } else {
4295                                 GetMetaData(&smi, msglist[i]);
4296                                 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4297                                         delete_this |= 0x02;
4298                                 }
4299                         }
4300
4301                         /* Delete message only if all bits are set */
4302                         if (delete_this == 0x03) {
4303                                 dellist[num_deleted++] = msglist[i];
4304                                 msglist[i] = 0L;
4305                         }
4306                 }
4307
4308                 num_msgs = sort_msglist(msglist, num_msgs);
4309                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4310                           msglist, (int)(num_msgs * sizeof(long)));
4311
4312                 if (num_msgs > 0)
4313                         qrbuf.QRhighest = msglist[num_msgs - 1];
4314                 else
4315                         qrbuf.QRhighest = 0;
4316         }
4317         CtdlPutRoomLock(&qrbuf);
4318
4319         /* Go through the messages we pulled out of the index, and decrement
4320          * their reference counts by 1.  If this is the only room the message
4321          * was in, the reference count will reach zero and the message will
4322          * automatically be deleted from the database.  We do this in a
4323          * separate pass because there might be plug-in hooks getting called,
4324          * and we don't want that happening during an S_ROOMS critical
4325          * section.
4326          */
4327         if (num_deleted) for (i=0; i<num_deleted; ++i) {
4328                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4329                 AdjRefCount(dellist[i], -1);
4330         }
4331
4332         /* Now free the memory we used, and go away. */
4333         if (msglist != NULL) free(msglist);
4334         if (dellist != NULL) free(dellist);
4335         CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4336         if (need_to_free_re) regfree(&re);
4337         return (num_deleted);
4338 }
4339
4340
4341
4342 /*
4343  * Check whether the current user has permission to delete messages from
4344  * the current room (returns 1 for yes, 0 for no)
4345  */
4346 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4347         int ra;
4348         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4349         if (ra & UA_DELETEALLOWED) return(1);
4350         return(0);
4351 }
4352
4353
4354
4355
4356 /*
4357  * Delete message from current room
4358  */
4359 void cmd_dele(char *args)
4360 {
4361         int num_deleted;
4362         int i;
4363         char msgset[SIZ];
4364         char msgtok[32];
4365         long *msgs;
4366         int num_msgs = 0;
4367
4368         extract_token(msgset, args, 0, '|', sizeof msgset);
4369         num_msgs = num_tokens(msgset, ',');
4370         if (num_msgs < 1) {
4371                 cprintf("%d Nothing to do.\n", CIT_OK);
4372                 return;
4373         }
4374
4375         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4376                 cprintf("%d Higher access required.\n",
4377                         ERROR + HIGHER_ACCESS_REQUIRED);
4378                 return;
4379         }
4380
4381         /*
4382          * Build our message set to be moved/copied
4383          */
4384         msgs = malloc(num_msgs * sizeof(long));
4385         for (i=0; i<num_msgs; ++i) {
4386                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4387                 msgs[i] = atol(msgtok);
4388         }
4389
4390         num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4391         free(msgs);
4392
4393         if (num_deleted) {
4394                 cprintf("%d %d message%s deleted.\n", CIT_OK,
4395                         num_deleted, ((num_deleted != 1) ? "s" : ""));
4396         } else {
4397                 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4398         }
4399 }
4400
4401
4402
4403
4404 /*
4405  * move or copy a message to another room
4406  */
4407 void cmd_move(char *args)
4408 {
4409         char msgset[SIZ];
4410         char msgtok[32];
4411         long *msgs;
4412         int num_msgs = 0;
4413
4414         char targ[ROOMNAMELEN];
4415         struct ctdlroom qtemp;
4416         int err;
4417         int is_copy = 0;
4418         int ra;
4419         int permit = 0;
4420         int i;
4421
4422         extract_token(msgset, args, 0, '|', sizeof msgset);
4423         num_msgs = num_tokens(msgset, ',');
4424         if (num_msgs < 1) {
4425                 cprintf("%d Nothing to do.\n", CIT_OK);
4426                 return;
4427         }
4428
4429         extract_token(targ, args, 1, '|', sizeof targ);
4430         convert_room_name_macros(targ, sizeof targ);
4431         targ[ROOMNAMELEN - 1] = 0;
4432         is_copy = extract_int(args, 2);
4433
4434         if (CtdlGetRoom(&qtemp, targ) != 0) {
4435                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4436                 return;
4437         }
4438
4439         if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4440                 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4441                 return;
4442         }
4443
4444         CtdlGetUser(&CC->user, CC->curr_user);
4445         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4446
4447         /* Check for permission to perform this operation.
4448          * Remember: "CC->room" is source, "qtemp" is target.
4449          */
4450         permit = 0;
4451
4452         /* Aides can move/copy */
4453         if (CC->user.axlevel >= AxAideU) permit = 1;
4454
4455         /* Room aides can move/copy */
4456         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4457
4458         /* Permit move/copy from personal rooms */
4459         if ((CC->room.QRflags & QR_MAILBOX)
4460            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4461
4462         /* Permit only copy from public to personal room */
4463         if ( (is_copy)
4464            && (!(CC->room.QRflags & QR_MAILBOX))
4465            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4466
4467         /* Permit message removal from collaborative delete rooms */
4468         if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4469
4470         /* Users allowed to post into the target room may move into it too. */
4471         if ((CC->room.QRflags & QR_MAILBOX) && 
4472             (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
4473
4474         /* User must have access to target room */
4475         if (!(ra & UA_KNOWN))  permit = 0;
4476
4477         if (!permit) {
4478                 cprintf("%d Higher access required.\n",
4479                         ERROR + HIGHER_ACCESS_REQUIRED);
4480                 return;
4481         }
4482
4483         /*
4484          * Build our message set to be moved/copied
4485          */
4486         msgs = malloc(num_msgs * sizeof(long));
4487         for (i=0; i<num_msgs; ++i) {
4488                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4489                 msgs[i] = atol(msgtok);
4490         }
4491
4492         /*
4493          * Do the copy
4494          */
4495         err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4496         if (err != 0) {
4497                 cprintf("%d Cannot store message(s) in %s: error %d\n",
4498                         err, targ, err);
4499                 free(msgs);
4500                 return;
4501         }
4502
4503         /* Now delete the message from the source room,
4504          * if this is a 'move' rather than a 'copy' operation.
4505          */
4506         if (is_copy == 0) {
4507                 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4508         }
4509         free(msgs);
4510
4511         cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4512 }
4513
4514
4515
4516 /*
4517  * GetMetaData()  -  Get the supplementary record for a message
4518  */
4519 void GetMetaData(struct MetaData *smibuf, long msgnum)
4520 {
4521
4522         struct cdbdata *cdbsmi;
4523         long TheIndex;
4524
4525         memset(smibuf, 0, sizeof(struct MetaData));
4526         smibuf->meta_msgnum = msgnum;
4527         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
4528
4529         /* Use the negative of the message number for its supp record index */
4530         TheIndex = (0L - msgnum);
4531
4532         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4533         if (cdbsmi == NULL) {
4534                 return;         /* record not found; go with defaults */
4535         }
4536         memcpy(smibuf, cdbsmi->ptr,
4537                ((cdbsmi->len > sizeof(struct MetaData)) ?
4538                 sizeof(struct MetaData) : cdbsmi->len));
4539         cdb_free(cdbsmi);
4540         return;
4541 }
4542
4543
4544 /*
4545  * PutMetaData()  -  (re)write supplementary record for a message
4546  */
4547 void PutMetaData(struct MetaData *smibuf)
4548 {
4549         long TheIndex;
4550
4551         /* Use the negative of the message number for the metadata db index */
4552         TheIndex = (0L - smibuf->meta_msgnum);
4553
4554         cdb_store(CDB_MSGMAIN,
4555                   &TheIndex, (int)sizeof(long),
4556                   smibuf, (int)sizeof(struct MetaData));
4557
4558 }
4559
4560 /*
4561  * AdjRefCount  -  submit an adjustment to the reference count for a message.
4562  *                 (These are just queued -- we actually process them later.)
4563  */
4564 void AdjRefCount(long msgnum, int incr)
4565 {
4566         struct arcq new_arcq;
4567         int rv = 0;
4568
4569         CtdlLogPrintf(CTDL_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4570                 msgnum, incr
4571         );
4572
4573         begin_critical_section(S_SUPPMSGMAIN);
4574         if (arcfp == NULL) {
4575                 arcfp = fopen(file_arcq, "ab+");
4576         }
4577         end_critical_section(S_SUPPMSGMAIN);
4578
4579         /* msgnum < 0 means that we're trying to close the file */
4580         if (msgnum < 0) {
4581                 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4582                 begin_critical_section(S_SUPPMSGMAIN);
4583                 if (arcfp != NULL) {
4584                         fclose(arcfp);
4585                         arcfp = NULL;
4586                 }
4587                 end_critical_section(S_SUPPMSGMAIN);
4588                 return;
4589         }
4590
4591         /*
4592          * If we can't open the queue, perform the operation synchronously.
4593          */
4594         if (arcfp == NULL) {
4595                 TDAP_AdjRefCount(msgnum, incr);
4596                 return;
4597         }
4598
4599         new_arcq.arcq_msgnum = msgnum;
4600         new_arcq.arcq_delta = incr;
4601         rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4602         fflush(arcfp);
4603
4604         return;
4605 }
4606
4607
4608 /*
4609  * TDAP_ProcessAdjRefCountQueue()
4610  *
4611  * Process the queue of message count adjustments that was created by calls
4612  * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4613  * for each one.  This should be an "off hours" operation.
4614  */
4615 int TDAP_ProcessAdjRefCountQueue(void)
4616 {
4617         char file_arcq_temp[PATH_MAX];
4618         int r;
4619         FILE *fp;
4620         struct arcq arcq_rec;
4621         int num_records_processed = 0;
4622
4623         snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4624
4625         begin_critical_section(S_SUPPMSGMAIN);
4626         if (arcfp != NULL) {
4627                 fclose(arcfp);
4628                 arcfp = NULL;
4629         }
4630
4631         r = link(file_arcq, file_arcq_temp);
4632         if (r != 0) {
4633                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4634                 end_critical_section(S_SUPPMSGMAIN);
4635                 return(num_records_processed);
4636         }
4637
4638         unlink(file_arcq);
4639         end_critical_section(S_SUPPMSGMAIN);
4640
4641         fp = fopen(file_arcq_temp, "rb");
4642         if (fp == NULL) {
4643                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4644                 return(num_records_processed);
4645         }
4646
4647         while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4648                 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4649                 ++num_records_processed;
4650         }
4651
4652         fclose(fp);
4653         r = unlink(file_arcq_temp);
4654         if (r != 0) {
4655                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4656         }
4657
4658         return(num_records_processed);
4659 }
4660
4661
4662
4663 /*
4664  * TDAP_AdjRefCount  -  adjust the reference count for a message.
4665  *                      This one does it "for real" because it's called by
4666  *                      the autopurger function that processes the queue
4667  *                      created by AdjRefCount().   If a message's reference
4668  *                      count becomes zero, we also delete the message from
4669  *                      disk and de-index it.
4670  */
4671 void TDAP_AdjRefCount(long msgnum, int incr)
4672 {
4673
4674         struct MetaData smi;
4675         long delnum;
4676
4677         /* This is a *tight* critical section; please keep it that way, as
4678          * it may get called while nested in other critical sections.  
4679          * Complicating this any further will surely cause deadlock!
4680          */
4681         begin_critical_section(S_SUPPMSGMAIN);
4682         GetMetaData(&smi, msgnum);
4683         smi.meta_refcount += incr;
4684         PutMetaData(&smi);
4685         end_critical_section(S_SUPPMSGMAIN);
4686         CtdlLogPrintf(CTDL_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
4687                 msgnum, incr, smi.meta_refcount
4688         );
4689
4690         /* If the reference count is now zero, delete the message
4691          * (and its supplementary record as well).
4692          */
4693         if (smi.meta_refcount == 0) {
4694                 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4695                 
4696                 /* Call delete hooks with NULL room to show it has gone altogether */
4697                 PerformDeleteHooks(NULL, msgnum);
4698
4699                 /* Remove from message base */
4700                 delnum = msgnum;
4701                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4702                 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4703
4704                 /* Remove metadata record */
4705                 delnum = (0L - msgnum);
4706                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4707         }
4708
4709 }
4710
4711 /*
4712  * Write a generic object to this room
4713  *
4714  * Note: this could be much more efficient.  Right now we use two temporary
4715  * files, and still pull the message into memory as with all others.
4716  */
4717 void CtdlWriteObject(char *req_room,                    /* Room to stuff it in */
4718                         char *content_type,             /* MIME type of this object */
4719                         char *raw_message,              /* Data to be written */
4720                         off_t raw_length,               /* Size of raw_message */
4721                         struct ctdluser *is_mailbox,    /* Mailbox room? */
4722                         int is_binary,                  /* Is encoding necessary? */
4723                         int is_unique,                  /* Del others of this type? */
4724                         unsigned int flags              /* Internal save flags */
4725                         )
4726 {
4727
4728         struct ctdlroom qrbuf;
4729         char roomname[ROOMNAMELEN];
4730         struct CtdlMessage *msg;
4731         char *encoded_message = NULL;
4732
4733         if (is_mailbox != NULL) {
4734                 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4735         }
4736         else {
4737                 safestrncpy(roomname, req_room, sizeof(roomname));
4738         }
4739
4740         CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4741
4742         if (is_binary) {
4743                 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4744         }
4745         else {
4746                 encoded_message = malloc((size_t)(raw_length + 4096));
4747         }
4748
4749         sprintf(encoded_message, "Content-type: %s\n", content_type);
4750
4751         if (is_binary) {
4752                 sprintf(&encoded_message[strlen(encoded_message)],
4753                         "Content-transfer-encoding: base64\n\n"
4754                 );
4755         }
4756         else {
4757                 sprintf(&encoded_message[strlen(encoded_message)],
4758                         "Content-transfer-encoding: 7bit\n\n"
4759                 );
4760         }
4761
4762         if (is_binary) {
4763                 CtdlEncodeBase64(
4764                         &encoded_message[strlen(encoded_message)],
4765                         raw_message,
4766                         (int)raw_length,
4767                         0
4768                 );
4769         }
4770         else {
4771                 memcpy(
4772                         &encoded_message[strlen(encoded_message)],
4773                         raw_message,
4774                         (int)(raw_length+1)
4775                 );
4776         }
4777
4778         CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4779         msg = malloc(sizeof(struct CtdlMessage));
4780         memset(msg, 0, sizeof(struct CtdlMessage));
4781         msg->cm_magic = CTDLMESSAGE_MAGIC;
4782         msg->cm_anon_type = MES_NORMAL;
4783         msg->cm_format_type = 4;
4784         msg->cm_fields['A'] = strdup(CC->user.fullname);
4785         msg->cm_fields['O'] = strdup(req_room);
4786         msg->cm_fields['N'] = strdup(config.c_nodename);
4787         msg->cm_fields['H'] = strdup(config.c_humannode);
4788         msg->cm_flags = flags;
4789         
4790         msg->cm_fields['M'] = encoded_message;
4791
4792         /* Create the requested room if we have to. */
4793         if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4794                 CtdlCreateRoom(roomname, 
4795                         ( (is_mailbox != NULL) ? 5 : 3 ),
4796                         "", 0, 1, 0, VIEW_BBS);
4797         }
4798         /* If the caller specified this object as unique, delete all
4799          * other objects of this type that are currently in the room.
4800          */
4801         if (is_unique) {
4802                 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4803                         CtdlDeleteMessages(roomname, NULL, 0, content_type)
4804                 );
4805         }
4806         /* Now write the data */
4807         CtdlSubmitMsg(msg, NULL, roomname, 0);
4808         CtdlFreeMessage(msg);
4809 }
4810
4811
4812
4813
4814
4815
4816 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4817         config_msgnum = msgnum;
4818 }
4819
4820
4821 char *CtdlGetSysConfig(char *sysconfname) {
4822         char hold_rm[ROOMNAMELEN];
4823         long msgnum;
4824         char *conf;
4825         struct CtdlMessage *msg;
4826         char buf[SIZ];
4827         
4828         strcpy(hold_rm, CC->room.QRname);
4829         if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4830                 CtdlGetRoom(&CC->room, hold_rm);
4831                 return NULL;
4832         }
4833
4834
4835         /* We want the last (and probably only) config in this room */
4836         begin_critical_section(S_CONFIG);
4837         config_msgnum = (-1L);
4838         CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4839                 CtdlGetSysConfigBackend, NULL);
4840         msgnum = config_msgnum;
4841         end_critical_section(S_CONFIG);
4842
4843         if (msgnum < 0L) {
4844                 conf = NULL;
4845         }
4846         else {
4847                 msg = CtdlFetchMessage(msgnum, 1);
4848                 if (msg != NULL) {
4849                         conf = strdup(msg->cm_fields['M']);
4850                         CtdlFreeMessage(msg);
4851                 }
4852                 else {
4853                         conf = NULL;
4854                 }
4855         }
4856
4857         CtdlGetRoom(&CC->room, hold_rm);
4858
4859         if (conf != NULL) do {
4860                 extract_token(buf, conf, 0, '\n', sizeof buf);
4861                 strcpy(conf, &conf[strlen(buf)+1]);
4862         } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4863
4864         return(conf);
4865 }
4866
4867
4868 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4869         CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4870 }
4871
4872
4873 /*
4874  * Determine whether a given Internet address belongs to the current user
4875  */
4876 int CtdlIsMe(char *addr, int addr_buf_len)
4877 {
4878         struct recptypes *recp;
4879         int i;
4880
4881         recp = validate_recipients(addr, NULL, 0);
4882         if (recp == NULL) return(0);
4883
4884         if (recp->num_local == 0) {
4885                 free_recipients(recp);
4886                 return(0);
4887         }
4888
4889         for (i=0; i<recp->num_local; ++i) {
4890                 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4891                 if (!strcasecmp(addr, CC->user.fullname)) {
4892                         free_recipients(recp);
4893                         return(1);
4894                 }
4895         }
4896
4897         free_recipients(recp);
4898         return(0);
4899 }
4900
4901
4902 /*
4903  * Citadel protocol command to do the same
4904  */
4905 void cmd_isme(char *argbuf) {
4906         char addr[256];
4907
4908         if (CtdlAccessCheck(ac_logged_in)) return;
4909         extract_token(addr, argbuf, 0, '|', sizeof addr);
4910
4911         if (CtdlIsMe(addr, sizeof addr)) {
4912                 cprintf("%d %s\n", CIT_OK, addr);
4913         }
4914         else {
4915                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4916         }
4917
4918 }
4919
4920
4921 /*****************************************************************************/
4922 /*                      MODULE INITIALIZATION STUFF                          */
4923 /*****************************************************************************/
4924
4925 CTDL_MODULE_INIT(msgbase)
4926 {
4927         if (!threading) {
4928                 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4929                 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4930                 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4931                 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4932                 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4933                 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4934                 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4935                 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4936                 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4937                 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4938                 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4939                 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4940         }
4941
4942         /* return our Subversion id for the Log */
4943         return "$Id$";
4944 }