Merge branch 'master' of ssh://git.citadel.org/appl/gitroot/citadel
[citadel.git] / citadel / msgbase.c
1 /*
2  * Implements the message store.
3  *
4  * Copyright (c) 1987-2010 by the citadel.org team
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "sysdep.h"
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <stdio.h>
25 #include <fcntl.h>
26
27 #if TIME_WITH_SYS_TIME
28 # include <sys/time.h>
29 # include <time.h>
30 #else
31 # if HAVE_SYS_TIME_H
32 #  include <sys/time.h>
33 # else
34 #  include <time.h>
35 # endif
36 #endif
37
38
39 #include <ctype.h>
40 #include <string.h>
41 #include <limits.h>
42 #include <errno.h>
43 #include <stdarg.h>
44 #include <sys/stat.h>
45 #include <sys/types.h>
46 #include <regex.h>
47 #include <libcitadel.h>
48 #include "citadel.h"
49 #include "server.h"
50 #include "serv_extensions.h"
51 #include "database.h"
52 #include "msgbase.h"
53 #include "support.h"
54 #include "sysdep_decls.h"
55 #include "citserver.h"
56 #include "room_ops.h"
57 #include "user_ops.h"
58 #include "file_ops.h"
59 #include "config.h"
60 #include "control.h"
61 #include "genstamp.h"
62 #include "internet_addressing.h"
63 #include "euidindex.h"
64 #include "journaling.h"
65 #include "citadel_dirs.h"
66 #include "clientsocket.h"
67 #include "serv_network.h"
68 #include "threads.h"
69
70 #include "ctdl_module.h"
71
72 long config_msgnum;
73 struct addresses_to_be_filed *atbf = NULL;
74
75 /* This temp file holds the queue of operations for AdjRefCount() */
76 static FILE *arcfp = NULL;
77
78 /* 
79  * This really belongs in serv_network.c, but I don't know how to export
80  * symbols between modules.
81  */
82 struct FilterList *filterlist = NULL;
83
84
85 /*
86  * These are the four-character field headers we use when outputting
87  * messages in Citadel format (as opposed to RFC822 format).
88  */
89 char *msgkeys[] = {
90         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
91         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
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, 
99         "from",
100         NULL, NULL, NULL,
101         "exti",
102         "rfca",
103         NULL, 
104         "hnod",
105         "msgn",
106         "jrnl",
107         NULL,
108         "list",
109         "text",
110         "node",
111         "room",
112         "path",
113         NULL,
114         "rcpt",
115         "spec",
116         "time",
117         "subj",
118         NULL,
119         "wefw",
120         NULL,
121         "cccc",
122         NULL
123 };
124
125 /*
126  * This function is self explanatory.
127  * (What can I say, I'm in a weird mood today...)
128  */
129 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
130 {
131         int i;
132
133         for (i = 0; i < strlen(name); ++i) {
134                 if (name[i] == '@') {
135                         while (isspace(name[i - 1]) && i > 0) {
136                                 strcpy(&name[i - 1], &name[i]);
137                                 --i;
138                         }
139                         while (isspace(name[i + 1])) {
140                                 strcpy(&name[i + 1], &name[i + 2]);
141                         }
142                 }
143         }
144 }
145
146
147 /*
148  * Aliasing for network mail.
149  * (Error messages have been commented out, because this is a server.)
150  */
151 int alias(char *name)
152 {                               /* process alias and routing info for mail */
153         FILE *fp;
154         int a, i;
155         char aaa[SIZ], bbb[SIZ];
156         char *ignetcfg = NULL;
157         char *ignetmap = NULL;
158         int at = 0;
159         char node[64];
160         char testnode[64];
161         char buf[SIZ];
162
163         char original_name[256];
164         safestrncpy(original_name, name, sizeof original_name);
165
166         striplt(name);
167         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
168         stripallbut(name, '<', '>');
169
170         fp = fopen(file_mail_aliases, "r");
171         if (fp == NULL) {
172                 fp = fopen("/dev/null", "r");
173         }
174         if (fp == NULL) {
175                 return (MES_ERROR);
176         }
177         strcpy(aaa, "");
178         strcpy(bbb, "");
179         while (fgets(aaa, sizeof aaa, fp) != NULL) {
180                 while (isspace(name[0]))
181                         strcpy(name, &name[1]);
182                 aaa[strlen(aaa) - 1] = 0;
183                 strcpy(bbb, "");
184                 for (a = 0; a < strlen(aaa); ++a) {
185                         if (aaa[a] == ',') {
186                                 strcpy(bbb, &aaa[a + 1]);
187                                 aaa[a] = 0;
188                         }
189                 }
190                 if (!strcasecmp(name, aaa))
191                         strcpy(name, bbb);
192         }
193         fclose(fp);
194
195         /* Hit the Global Address Book */
196         if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
197                 strcpy(name, aaa);
198         }
199
200         if (strcasecmp(original_name, name)) {
201                 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
202         }
203
204         /* Change "user @ xxx" to "user" if xxx is an alias for this host */
205         for (a=0; a<strlen(name); ++a) {
206                 if (name[a] == '@') {
207                         if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
208                                 name[a] = 0;
209                                 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
210                         }
211                 }
212         }
213
214         /* determine local or remote type, see citadel.h */
215         at = haschar(name, '@');
216         if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
217         if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
218         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
219
220         /* figure out the delivery mode */
221         extract_token(node, name, 1, '@', sizeof node);
222
223         /* If there are one or more dots in the nodename, we assume that it
224          * is an FQDN and will attempt SMTP delivery to the Internet.
225          */
226         if (haschar(node, '.') > 0) {
227                 return(MES_INTERNET);
228         }
229
230         /* Otherwise we look in the IGnet maps for a valid Citadel node.
231          * Try directly-connected nodes first...
232          */
233         ignetcfg = CtdlGetSysConfig(IGNETCFG);
234         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
235                 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
236                 extract_token(testnode, buf, 0, '|', sizeof testnode);
237                 if (!strcasecmp(node, testnode)) {
238                         free(ignetcfg);
239                         return(MES_IGNET);
240                 }
241         }
242         free(ignetcfg);
243
244         /*
245          * Then try nodes that are two or more hops away.
246          */
247         ignetmap = CtdlGetSysConfig(IGNETMAP);
248         for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
249                 extract_token(buf, ignetmap, i, '\n', sizeof buf);
250                 extract_token(testnode, buf, 0, '|', sizeof testnode);
251                 if (!strcasecmp(node, testnode)) {
252                         free(ignetmap);
253                         return(MES_IGNET);
254                 }
255         }
256         free(ignetmap);
257
258         /* If we get to this point it's an invalid node name */
259         return (MES_ERROR);
260 }
261
262
263 /*
264  * Back end for the MSGS command: output message number only.
265  */
266 void simple_listing(long msgnum, void *userdata)
267 {
268         cprintf("%ld\n", msgnum);
269 }
270
271
272
273 /*
274  * Back end for the MSGS command: output header summary.
275  */
276 void headers_listing(long msgnum, void *userdata)
277 {
278         struct CtdlMessage *msg;
279
280         msg = CtdlFetchMessage(msgnum, 0);
281         if (msg == NULL) {
282                 cprintf("%ld|0|||||\n", msgnum);
283                 return;
284         }
285
286         cprintf("%ld|%s|%s|%s|%s|%s|\n",
287                 msgnum,
288                 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
289                 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
290                 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
291                 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
292                 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
293         );
294         CtdlFreeMessage(msg);
295 }
296
297 /*
298  * Back end for the MSGS command: output EUID header.
299  */
300 void headers_euid(long msgnum, void *userdata)
301 {
302         struct CtdlMessage *msg;
303
304         msg = CtdlFetchMessage(msgnum, 0);
305         if (msg == NULL) {
306                 cprintf("%ld||\n", msgnum);
307                 return;
308         }
309
310         cprintf("%ld|%s|%s\n", 
311                 msgnum, 
312                 (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""),
313                 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"));
314         CtdlFreeMessage(msg);
315 }
316
317
318
319
320
321 /* Determine if a given message matches the fields in a message template.
322  * Return 0 for a successful match.
323  */
324 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
325         int i;
326
327         /* If there aren't any fields in the template, all messages will
328          * match.
329          */
330         if (template == NULL) return(0);
331
332         /* Null messages are bogus. */
333         if (msg == NULL) return(1);
334
335         for (i='A'; i<='Z'; ++i) {
336                 if (template->cm_fields[i] != NULL) {
337                         if (msg->cm_fields[i] == NULL) {
338                                 /* Considered equal if temmplate is empty string */
339                                 if (IsEmptyStr(template->cm_fields[i])) continue;
340                                 return 1;
341                         }
342                         if (strcasecmp(msg->cm_fields[i],
343                                 template->cm_fields[i])) return 1;
344                 }
345         }
346
347         /* All compares succeeded: we have a match! */
348         return 0;
349 }
350
351
352
353 /*
354  * Retrieve the "seen" message list for the current room.
355  */
356 void CtdlGetSeen(char *buf, int which_set) {
357         visit vbuf;
358
359         /* Learn about the user and room in question */
360         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
361
362         if (which_set == ctdlsetseen_seen)
363                 safestrncpy(buf, vbuf.v_seen, SIZ);
364         if (which_set == ctdlsetseen_answered)
365                 safestrncpy(buf, vbuf.v_answered, SIZ);
366 }
367
368
369
370 /*
371  * Manipulate the "seen msgs" string (or other message set strings)
372  */
373 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
374                 int target_setting, int which_set,
375                 struct ctdluser *which_user, struct ctdlroom *which_room) {
376         struct cdbdata *cdbfr;
377         int i, k;
378         int is_seen = 0;
379         int was_seen = 0;
380         long lo = (-1L);
381         long hi = (-1L);
382         visit vbuf;
383         long *msglist;
384         int num_msgs = 0;
385         StrBuf *vset;
386         StrBuf *setstr;
387         StrBuf *lostr;
388         StrBuf *histr;
389         const char *pvset;
390         char *is_set;   /* actually an array of booleans */
391
392         /* Don't bother doing *anything* if we were passed a list of zero messages */
393         if (num_target_msgnums < 1) {
394                 return;
395         }
396
397         /* If no room was specified, we go with the current room. */
398         if (!which_room) {
399                 which_room = &CC->room;
400         }
401
402         /* If no user was specified, we go with the current user. */
403         if (!which_user) {
404                 which_user = &CC->user;
405         }
406
407         CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
408                 num_target_msgnums, target_msgnums[0],
409                 (target_setting ? "SET" : "CLEAR"),
410                 which_set,
411                 which_room->QRname);
412
413         /* Learn about the user and room in question */
414         CtdlGetRelationship(&vbuf, which_user, which_room);
415
416         /* Load the message list */
417         cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
418         if (cdbfr != NULL) {
419                 msglist = (long *) cdbfr->ptr;
420                 cdbfr->ptr = NULL;      /* CtdlSetSeen() now owns this memory */
421                 num_msgs = cdbfr->len / sizeof(long);
422                 cdb_free(cdbfr);
423         } else {
424                 return; /* No messages at all?  No further action. */
425         }
426
427         is_set = malloc(num_msgs * sizeof(char));
428         memset(is_set, 0, (num_msgs * sizeof(char)) );
429
430         /* Decide which message set we're manipulating */
431         switch(which_set) {
432         case ctdlsetseen_seen:
433                 vset = NewStrBufPlain(vbuf.v_seen, -1);
434                 break;
435         case ctdlsetseen_answered:
436                 vset = NewStrBufPlain(vbuf.v_answered, -1);
437                 break;
438         default:
439                 vset = NewStrBuf();
440         }
441
442
443 #if 0   /* This is a special diagnostic section.  Do not allow it to run during normal operation. */
444         CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
445         for (i=0; i<num_msgs; ++i) {
446                 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
447         }
448         CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
449         for (k=0; k<num_target_msgnums; ++k) {
450                 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
451         }
452 #endif
453
454         CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
455
456         /* Translate the existing sequence set into an array of booleans */
457         setstr = NewStrBuf();
458         lostr = NewStrBuf();
459         histr = NewStrBuf();
460         pvset = NULL;
461         while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
462
463                 StrBufExtract_token(lostr, setstr, 0, ':');
464                 if (StrBufNum_tokens(setstr, ':') >= 2) {
465                         StrBufExtract_token(histr, setstr, 1, ':');
466                 }
467                 else {
468                         FlushStrBuf(histr);
469                         StrBufAppendBuf(histr, lostr, 0);
470                 }
471                 lo = StrTol(lostr);
472                 if (!strcmp(ChrPtr(histr), "*")) {
473                         hi = LONG_MAX;
474                 }
475                 else {
476                         hi = StrTol(histr);
477                 }
478
479                 for (i = 0; i < num_msgs; ++i) {
480                         if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
481                                 is_set[i] = 1;
482                         }
483                 }
484         }
485         FreeStrBuf(&setstr);
486         FreeStrBuf(&lostr);
487         FreeStrBuf(&histr);
488
489
490         /* Now translate the array of booleans back into a sequence set */
491         FlushStrBuf(vset);
492         was_seen = 0;
493         lo = (-1);
494         hi = (-1);
495
496         for (i=0; i<num_msgs; ++i) {
497                 is_seen = is_set[i];
498
499                 /* Apply changes */
500                 for (k=0; k<num_target_msgnums; ++k) {
501                         if (msglist[i] == target_msgnums[k]) {
502                                 is_seen = target_setting;
503                         }
504                 }
505
506                 if ((was_seen == 0) && (is_seen == 1)) {
507                         lo = msglist[i];
508                 }
509                 else if ((was_seen == 1) && (is_seen == 0)) {
510                         hi = msglist[i-1];
511
512                         if (StrLength(vset) > 0) {
513                                 StrBufAppendBufPlain(vset, HKEY(","), 0);
514                         }
515                         if (lo == hi) {
516                                 StrBufAppendPrintf(vset, "%ld", hi);
517                         }
518                         else {
519                                 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
520                         }
521                 }
522
523                 if ((is_seen) && (i == num_msgs - 1)) {
524                         if (StrLength(vset) > 0) {
525                                 StrBufAppendBufPlain(vset, HKEY(","), 0);
526                         }
527                         if ((i==0) || (was_seen == 0)) {
528                                 StrBufAppendPrintf(vset, "%ld", msglist[i]);
529                         }
530                         else {
531                                 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
532                         }
533                 }
534
535                 was_seen = is_seen;
536         }
537
538         /*
539          * We will have to stuff this string back into a 4096 byte buffer, so if it's
540          * larger than that now, truncate it by removing tokens from the beginning.
541          * The limit of 100 iterations is there to prevent an infinite loop in case
542          * something unexpected happens.
543          */
544         int number_of_truncations = 0;
545         while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
546                 StrBufRemove_token(vset, 0, ',');
547                 ++number_of_truncations;
548         }
549
550         /*
551          * If we're truncating the sequence set of messages marked with the 'seen' flag,
552          * we want the earliest messages (the truncated ones) to be marked, not unmarked.
553          * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
554          */
555         if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
556                 StrBuf *first_tok;
557                 first_tok = NewStrBuf();
558                 StrBufExtract_token(first_tok, vset, 0, ',');
559                 StrBufRemove_token(vset, 0, ',');
560
561                 if (StrBufNum_tokens(first_tok, ':') > 1) {
562                         StrBufRemove_token(first_tok, 0, ':');
563                 }
564                 
565                 StrBuf *new_set;
566                 new_set = NewStrBuf();
567                 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
568                 StrBufAppendBuf(new_set, first_tok, 0);
569                 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
570                 StrBufAppendBuf(new_set, vset, 0);
571
572                 FreeStrBuf(&vset);
573                 FreeStrBuf(&first_tok);
574                 vset = new_set;
575         }
576
577         CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
578
579         /* Decide which message set we're manipulating */
580         switch (which_set) {
581                 case ctdlsetseen_seen:
582                         safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
583                         break;
584                 case ctdlsetseen_answered:
585                         safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
586                         break;
587         }
588
589         free(is_set);
590         free(msglist);
591         CtdlSetRelationship(&vbuf, which_user, which_room);
592         FreeStrBuf(&vset);
593 }
594
595
596 /*
597  * API function to perform an operation for each qualifying message in the
598  * current room.  (Returns the number of messages processed.)
599  */
600 int CtdlForEachMessage(int mode, long ref, char *search_string,
601                         char *content_type,
602                         struct CtdlMessage *compare,
603                         ForEachMsgCallback CallBack,
604                         void *userdata)
605 {
606
607         int a, i, j;
608         visit vbuf;
609         struct cdbdata *cdbfr;
610         long *msglist = NULL;
611         int num_msgs = 0;
612         int num_processed = 0;
613         long thismsg;
614         struct MetaData smi;
615         struct CtdlMessage *msg = NULL;
616         int is_seen = 0;
617         long lastold = 0L;
618         int printed_lastold = 0;
619         int num_search_msgs = 0;
620         long *search_msgs = NULL;
621         regex_t re;
622         int need_to_free_re = 0;
623         regmatch_t pm;
624
625         if ((content_type) && (!IsEmptyStr(content_type))) {
626                 regcomp(&re, content_type, 0);
627                 need_to_free_re = 1;
628         }
629
630         /* Learn about the user and room in question */
631         CtdlGetUser(&CC->user, CC->curr_user);
632         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
633
634         /* Load the message list */
635         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
636         if (cdbfr != NULL) {
637                 msglist = (long *) cdbfr->ptr;
638                 num_msgs = cdbfr->len / sizeof(long);
639         } else {
640                 if (need_to_free_re) regfree(&re);
641                 return 0;       /* No messages at all?  No further action. */
642         }
643
644
645         /*
646          * Now begin the traversal.
647          */
648         if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
649
650                 /* If the caller is looking for a specific MIME type, filter
651                  * out all messages which are not of the type requested.
652                  */
653                 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
654
655                         /* This call to GetMetaData() sits inside this loop
656                          * so that we only do the extra database read per msg
657                          * if we need to.  Doing the extra read all the time
658                          * really kills the server.  If we ever need to use
659                          * metadata for another search criterion, we need to
660                          * move the read somewhere else -- but still be smart
661                          * enough to only do the read if the caller has
662                          * specified something that will need it.
663                          */
664                         GetMetaData(&smi, msglist[a]);
665
666                         /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
667                         if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
668                                 msglist[a] = 0L;
669                         }
670                 }
671         }
672
673         num_msgs = sort_msglist(msglist, num_msgs);
674
675         /* If a template was supplied, filter out the messages which
676          * don't match.  (This could induce some delays!)
677          */
678         if (num_msgs > 0) {
679                 if (compare != NULL) {
680                         for (a = 0; a < num_msgs; ++a) {
681                                 msg = CtdlFetchMessage(msglist[a], 1);
682                                 if (msg != NULL) {
683                                         if (CtdlMsgCmp(msg, compare)) {
684                                                 msglist[a] = 0L;
685                                         }
686                                         CtdlFreeMessage(msg);
687                                 }
688                         }
689                 }
690         }
691
692         /* If a search string was specified, get a message list from
693          * the full text index and remove messages which aren't on both
694          * lists.
695          *
696          * How this works:
697          * Since the lists are sorted and strictly ascending, and the
698          * output list is guaranteed to be shorter than or equal to the
699          * input list, we overwrite the bottom of the input list.  This
700          * eliminates the need to memmove big chunks of the list over and
701          * over again.
702          */
703         if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
704
705                 /* Call search module via hook mechanism.
706                  * NULL means use any search function available.
707                  * otherwise replace with a char * to name of search routine
708                  */
709                 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
710
711                 if (num_search_msgs > 0) {
712         
713                         int orig_num_msgs;
714
715                         orig_num_msgs = num_msgs;
716                         num_msgs = 0;
717                         for (i=0; i<orig_num_msgs; ++i) {
718                                 for (j=0; j<num_search_msgs; ++j) {
719                                         if (msglist[i] == search_msgs[j]) {
720                                                 msglist[num_msgs++] = msglist[i];
721                                         }
722                                 }
723                         }
724                 }
725                 else {
726                         num_msgs = 0;   /* No messages qualify */
727                 }
728                 if (search_msgs != NULL) free(search_msgs);
729
730                 /* Now that we've purged messages which don't contain the search
731                  * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
732                  * point on.
733                  */
734                 mode = MSGS_ALL;
735         }
736
737         /*
738          * Now iterate through the message list, according to the
739          * criteria supplied by the caller.
740          */
741         if (num_msgs > 0)
742                 for (a = 0; a < num_msgs; ++a) {
743                         thismsg = msglist[a];
744                         if (mode == MSGS_ALL) {
745                                 is_seen = 0;
746                         }
747                         else {
748                                 is_seen = is_msg_in_sequence_set(
749                                                         vbuf.v_seen, thismsg);
750                                 if (is_seen) lastold = thismsg;
751                         }
752                         if ((thismsg > 0L)
753                             && (
754
755                                        (mode == MSGS_ALL)
756                                        || ((mode == MSGS_OLD) && (is_seen))
757                                        || ((mode == MSGS_NEW) && (!is_seen))
758                                        || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
759                                    || ((mode == MSGS_FIRST) && (a < ref))
760                                 || ((mode == MSGS_GT) && (thismsg > ref))
761                                 || ((mode == MSGS_LT) && (thismsg < ref))
762                                 || ((mode == MSGS_EQ) && (thismsg == ref))
763                             )
764                             ) {
765                                 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
766                                         if (CallBack)
767                                                 CallBack(lastold, userdata);
768                                         printed_lastold = 1;
769                                         ++num_processed;
770                                 }
771                                 if (CallBack) CallBack(thismsg, userdata);
772                                 ++num_processed;
773                         }
774                 }
775         cdb_free(cdbfr);        /* Clean up */
776         if (need_to_free_re) regfree(&re);
777         return num_processed;
778 }
779
780
781
782 /*
783  * cmd_msgs()  -  get list of message #'s in this room
784  *              implements the MSGS server command using CtdlForEachMessage()
785  */
786 void cmd_msgs(char *cmdbuf)
787 {
788         int mode = 0;
789         char which[16];
790         char buf[256];
791         char tfield[256];
792         char tvalue[256];
793         int cm_ref = 0;
794         int i;
795         int with_template = 0;
796         struct CtdlMessage *template = NULL;
797         char search_string[1024];
798         ForEachMsgCallback CallBack;
799
800         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
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 ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
839                 cprintf("%d Full text index is not enabled on this server.\n",
840                         ERROR + CMD_NOT_SUPPORTED);
841                 return;
842         }
843
844         if (with_template) {
845                 unbuffer_output();
846                 cprintf("%d Send template then receive message list\n",
847                         START_CHAT_MODE);
848                 template = (struct CtdlMessage *)
849                         malloc(sizeof(struct CtdlMessage));
850                 memset(template, 0, sizeof(struct CtdlMessage));
851                 template->cm_magic = CTDLMESSAGE_MAGIC;
852                 template->cm_anon_type = MES_NORMAL;
853
854                 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
855                         extract_token(tfield, buf, 0, '|', sizeof tfield);
856                         extract_token(tvalue, buf, 1, '|', sizeof tvalue);
857                         for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
858                                 if (!strcasecmp(tfield, msgkeys[i])) {
859                                         template->cm_fields[i] =
860                                                 strdup(tvalue);
861                                 }
862                         }
863                 }
864                 buffer_output();
865         }
866         else {
867                 cprintf("%d  \n", LISTING_FOLLOWS);
868         }
869
870         CtdlForEachMessage(mode,
871                            ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
872                            ( (mode == MSGS_SEARCH) ? search_string : NULL ),
873                            NULL,
874                            template,
875                            CallBack,
876                            NULL);
877         if (template != NULL) CtdlFreeMessage(template);
878         cprintf("000\n");
879 }
880
881
882
883
884 /* 
885  * help_subst()  -  support routine for help file viewer
886  */
887 void help_subst(char *strbuf, char *source, char *dest)
888 {
889         char workbuf[SIZ];
890         int p;
891
892         while (p = pattern2(strbuf, source), (p >= 0)) {
893                 strcpy(workbuf, &strbuf[p + strlen(source)]);
894                 strcpy(&strbuf[p], dest);
895                 strcat(strbuf, workbuf);
896         }
897 }
898
899
900 void do_help_subst(char *buffer)
901 {
902         char buf2[16];
903
904         help_subst(buffer, "^nodename", config.c_nodename);
905         help_subst(buffer, "^humannode", config.c_humannode);
906         help_subst(buffer, "^fqdn", config.c_fqdn);
907         help_subst(buffer, "^username", CC->user.fullname);
908         snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
909         help_subst(buffer, "^usernum", buf2);
910         help_subst(buffer, "^sysadm", config.c_sysadm);
911         help_subst(buffer, "^variantname", CITADEL);
912         snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
913         help_subst(buffer, "^maxsessions", buf2);
914         help_subst(buffer, "^bbsdir", ctdl_message_dir);
915 }
916
917
918
919 /*
920  * memfmout()  -  Citadel text formatter and paginator.
921  *           Although the original purpose of this routine was to format
922  *           text to the reader's screen width, all we're really using it
923  *           for here is to format text out to 80 columns before sending it
924  *           to the client.  The client software may reformat it again.
925  */
926 void memfmout(
927         char *mptr,             /* where are we going to get our text from? */
928         const char *nl          /* string to terminate lines with */
929 ) {
930         int column = 0;
931         unsigned char ch = 0;
932         char outbuf[1024];
933         int len = 0;
934         int nllen = 0;
935
936         if (!mptr) return;
937         nllen = strlen(nl);
938         while (ch=*(mptr++), ch != 0) {
939
940                 if (ch == '\n') {
941                         client_write(outbuf, len);
942                         len = 0;
943                         client_write(nl, nllen);
944                         column = 0;
945                 }
946                 else if (ch == '\r') {
947                         /* Ignore carriage returns.  Newlines are always LF or CRLF but never CR. */
948                 }
949                 else if (isspace(ch)) {
950                         if (column > 72) {              /* Beyond 72 columns, break on the next space */
951                                 client_write(outbuf, len);
952                                 len = 0;
953                                 client_write(nl, nllen);
954                                 column = 0;
955                         }
956                         else {
957                                 outbuf[len++] = ch;
958                                 ++column;
959                         }
960                 }
961                 else {
962                         outbuf[len++] = ch;
963                         ++column;
964                         if (column > 1000) {            /* Beyond 1000 columns, break anywhere */
965                                 client_write(outbuf, len);
966                                 len = 0;
967                                 client_write(nl, nllen);
968                                 column = 0;
969                         }
970                 }
971         }
972         if (len) {
973                 client_write(outbuf, len);
974                 len = 0;
975                 client_write(nl, nllen);
976                 column = 0;
977         }
978 }
979
980
981
982 /*
983  * Callback function for mime parser that simply lists the part
984  */
985 void list_this_part(char *name, char *filename, char *partnum, char *disp,
986                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
987                     char *cbid, void *cbuserdata)
988 {
989         struct ma_info *ma;
990         
991         ma = (struct ma_info *)cbuserdata;
992         if (ma->is_ma == 0) {
993                 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
994                         name, 
995                         filename, 
996                         partnum, 
997                         disp, 
998                         cbtype, 
999                         (long)length, 
1000                         cbid, 
1001                         cbcharset);
1002         }
1003 }
1004
1005 /* 
1006  * Callback function for multipart prefix
1007  */
1008 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1009                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1010                     char *cbid, void *cbuserdata)
1011 {
1012         struct ma_info *ma;
1013         
1014         ma = (struct ma_info *)cbuserdata;
1015         if (!strcasecmp(cbtype, "multipart/alternative")) {
1016                 ++ma->is_ma;
1017         }
1018
1019         if (ma->is_ma == 0) {
1020                 cprintf("pref=%s|%s\n", partnum, cbtype);
1021         }
1022 }
1023
1024 /* 
1025  * Callback function for multipart sufffix
1026  */
1027 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1028                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1029                     char *cbid, void *cbuserdata)
1030 {
1031         struct ma_info *ma;
1032         
1033         ma = (struct ma_info *)cbuserdata;
1034         if (ma->is_ma == 0) {
1035                 cprintf("suff=%s|%s\n", partnum, cbtype);
1036         }
1037         if (!strcasecmp(cbtype, "multipart/alternative")) {
1038                 --ma->is_ma;
1039         }
1040 }
1041
1042
1043 /*
1044  * Callback function for mime parser that opens a section for downloading
1045  */
1046 void mime_download(char *name, char *filename, char *partnum, char *disp,
1047                    void *content, char *cbtype, char *cbcharset, size_t length,
1048                    char *encoding, char *cbid, void *cbuserdata)
1049 {
1050         int rv = 0;
1051
1052         /* Silently go away if there's already a download open. */
1053         if (CC->download_fp != NULL)
1054                 return;
1055
1056         if (
1057                 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1058         ||      (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1059         ) {
1060                 CC->download_fp = tmpfile();
1061                 if (CC->download_fp == NULL)
1062                         return;
1063         
1064                 rv = fwrite(content, length, 1, CC->download_fp);
1065                 fflush(CC->download_fp);
1066                 rewind(CC->download_fp);
1067         
1068                 OpenCmdResult(filename, cbtype);
1069         }
1070 }
1071
1072
1073
1074 /*
1075  * Callback function for mime parser that outputs a section all at once.
1076  * We can specify the desired section by part number *or* content-id.
1077  */
1078 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1079                    void *content, char *cbtype, char *cbcharset, size_t length,
1080                    char *encoding, char *cbid, void *cbuserdata)
1081 {
1082         int *found_it = (int *)cbuserdata;
1083
1084         if (
1085                 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1086         ||      (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1087         ) {
1088                 *found_it = 1;
1089                 cprintf("%d %d|-1|%s|%s|%s\n",
1090                         BINARY_FOLLOWS,
1091                         (int)length,
1092                         filename,
1093                         cbtype,
1094                         cbcharset
1095                 );
1096                 client_write(content, length);
1097         }
1098 }
1099
1100
1101
1102 /*
1103  * Load a message from disk into memory.
1104  * This is used by CtdlOutputMsg() and other fetch functions.
1105  *
1106  * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1107  *       using the CtdlMessageFree() function.
1108  */
1109 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1110 {
1111         struct cdbdata *dmsgtext;
1112         struct CtdlMessage *ret = NULL;
1113         char *mptr;
1114         char *upper_bound;
1115         cit_uint8_t ch;
1116         cit_uint8_t field_header;
1117
1118         CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1119
1120         dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1121         if (dmsgtext == NULL) {
1122                 return NULL;
1123         }
1124         mptr = dmsgtext->ptr;
1125         upper_bound = mptr + dmsgtext->len;
1126
1127         /* Parse the three bytes that begin EVERY message on disk.
1128          * The first is always 0xFF, the on-disk magic number.
1129          * The second is the anonymous/public type byte.
1130          * The third is the format type byte (vari, fixed, or MIME).
1131          */
1132         ch = *mptr++;
1133         if (ch != 255) {
1134                 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1135                 cdb_free(dmsgtext);
1136                 return NULL;
1137         }
1138         ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1139         memset(ret, 0, sizeof(struct CtdlMessage));
1140
1141         ret->cm_magic = CTDLMESSAGE_MAGIC;
1142         ret->cm_anon_type = *mptr++;    /* Anon type byte */
1143         ret->cm_format_type = *mptr++;  /* Format type byte */
1144
1145         /*
1146          * The rest is zero or more arbitrary fields.  Load them in.
1147          * We're done when we encounter either a zero-length field or
1148          * have just processed the 'M' (message text) field.
1149          */
1150         do {
1151                 if (mptr >= upper_bound) {
1152                         break;
1153                 }
1154                 field_header = *mptr++;
1155                 ret->cm_fields[field_header] = strdup(mptr);
1156
1157                 while (*mptr++ != 0);   /* advance to next field */
1158
1159         } while ((mptr < upper_bound) && (field_header != 'M'));
1160
1161         cdb_free(dmsgtext);
1162
1163         /* Always make sure there's something in the msg text field.  If
1164          * it's NULL, the message text is most likely stored separately,
1165          * so go ahead and fetch that.  Failing that, just set a dummy
1166          * body so other code doesn't barf.
1167          */
1168         if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1169                 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1170                 if (dmsgtext != NULL) {
1171                         ret->cm_fields['M'] = dmsgtext->ptr;
1172                         dmsgtext->ptr = NULL;
1173                         cdb_free(dmsgtext);
1174                 }
1175         }
1176         if (ret->cm_fields['M'] == NULL) {
1177                 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1178         }
1179
1180         /* Perform "before read" hooks (aborting if any return nonzero) */
1181         if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1182                 CtdlFreeMessage(ret);
1183                 return NULL;
1184         }
1185
1186         return (ret);
1187 }
1188
1189
1190 /*
1191  * Returns 1 if the supplied pointer points to a valid Citadel message.
1192  * If the pointer is NULL or the magic number check fails, returns 0.
1193  */
1194 int is_valid_message(struct CtdlMessage *msg) {
1195         if (msg == NULL)
1196                 return 0;
1197         if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1198                 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1199                 return 0;
1200         }
1201         return 1;
1202 }
1203
1204
1205 /*
1206  * 'Destructor' for struct CtdlMessage
1207  */
1208 void CtdlFreeMessage(struct CtdlMessage *msg)
1209 {
1210         int i;
1211
1212         if (is_valid_message(msg) == 0) 
1213         {
1214                 if (msg != NULL) free (msg);
1215                 return;
1216         }
1217
1218         for (i = 0; i < 256; ++i)
1219                 if (msg->cm_fields[i] != NULL) {
1220                         free(msg->cm_fields[i]);
1221                 }
1222
1223         msg->cm_magic = 0;      /* just in case */
1224         free(msg);
1225 }
1226
1227
1228 /*
1229  * Pre callback function for multipart/alternative
1230  *
1231  * NOTE: this differs from the standard behavior for a reason.  Normally when
1232  *       displaying multipart/alternative you want to show the _last_ usable
1233  *       format in the message.  Here we show the _first_ one, because it's
1234  *       usually text/plain.  Since this set of functions is designed for text
1235  *       output to non-MIME-aware clients, this is the desired behavior.
1236  *
1237  */
1238 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1239                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1240                 char *cbid, void *cbuserdata)
1241 {
1242         struct ma_info *ma;
1243         
1244         ma = (struct ma_info *)cbuserdata;
1245         CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);    
1246         if (!strcasecmp(cbtype, "multipart/alternative")) {
1247                 ++ma->is_ma;
1248                 ma->did_print = 0;
1249         }
1250         if (!strcasecmp(cbtype, "message/rfc822")) {
1251                 ++ma->freeze;
1252         }
1253 }
1254
1255 /*
1256  * Post callback function for multipart/alternative
1257  */
1258 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1259                 void *content, char *cbtype, char *cbcharset, size_t length,
1260                 char *encoding, char *cbid, void *cbuserdata)
1261 {
1262         struct ma_info *ma;
1263         
1264         ma = (struct ma_info *)cbuserdata;
1265         CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);   
1266         if (!strcasecmp(cbtype, "multipart/alternative")) {
1267                 --ma->is_ma;
1268                 ma->did_print = 0;
1269         }
1270         if (!strcasecmp(cbtype, "message/rfc822")) {
1271                 --ma->freeze;
1272         }
1273 }
1274
1275 /*
1276  * Inline callback function for mime parser that wants to display text
1277  */
1278 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1279                 void *content, char *cbtype, char *cbcharset, size_t length,
1280                 char *encoding, char *cbid, void *cbuserdata)
1281 {
1282         char *ptr;
1283         char *wptr;
1284         size_t wlen;
1285         struct ma_info *ma;
1286
1287         ma = (struct ma_info *)cbuserdata;
1288
1289         CtdlLogPrintf(CTDL_DEBUG,
1290                 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1291                 partnum, filename, cbtype, (long)length);
1292
1293         /*
1294          * If we're in the middle of a multipart/alternative scope and
1295          * we've already printed another section, skip this one.
1296          */     
1297         if ( (ma->is_ma) && (ma->did_print) ) {
1298                 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1299                 return;
1300         }
1301         ma->did_print = 1;
1302
1303         if ( (!strcasecmp(cbtype, "text/plain")) 
1304            || (IsEmptyStr(cbtype)) ) {
1305                 wptr = content;
1306                 if (length > 0) {
1307                         client_write(wptr, length);
1308                         if (wptr[length-1] != '\n') {
1309                                 cprintf("\n");
1310                         }
1311                 }
1312                 return;
1313         }
1314
1315         if (!strcasecmp(cbtype, "text/html")) {
1316                 ptr = html_to_ascii(content, length, 80, 0);
1317                 wlen = strlen(ptr);
1318                 client_write(ptr, wlen);
1319                 if (ptr[wlen-1] != '\n') {
1320                         cprintf("\n");
1321                 }
1322                 free(ptr);
1323                 return;
1324         }
1325
1326         if (ma->use_fo_hooks) {
1327                 if (PerformFixedOutputHooks(cbtype, content, length)) {
1328                 /* above function returns nonzero if it handled the part */
1329                         return;
1330                 }
1331         }
1332
1333         if (strncasecmp(cbtype, "multipart/", 10)) {
1334                 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1335                         partnum, filename, cbtype, (long)length);
1336                 return;
1337         }
1338 }
1339
1340 /*
1341  * The client is elegant and sophisticated and wants to be choosy about
1342  * MIME content types, so figure out which multipart/alternative part
1343  * we're going to send.
1344  *
1345  * We use a system of weights.  When we find a part that matches one of the
1346  * MIME types we've declared as preferential, we can store it in ma->chosen_part
1347  * and then set ma->chosen_pref to that MIME type's position in our preference
1348  * list.  If we then hit another match, we only replace the first match if
1349  * the preference value is lower.
1350  */
1351 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1352                 void *content, char *cbtype, char *cbcharset, size_t length,
1353                 char *encoding, char *cbid, void *cbuserdata)
1354 {
1355         char buf[1024];
1356         int i;
1357         struct ma_info *ma;
1358         
1359         ma = (struct ma_info *)cbuserdata;
1360
1361         // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1362         //       http://bugzilla.citadel.org/show_bug.cgi?id=220
1363         // I don't know if there are any side effects!  Please TEST TEST TEST
1364         //if (ma->is_ma > 0) {
1365
1366         for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1367                 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1368                 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1369                         if (i < ma->chosen_pref) {
1370                                 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1371                                 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1372                                 ma->chosen_pref = i;
1373                         }
1374                 }
1375         }
1376 }
1377
1378 /*
1379  * Now that we've chosen our preferred part, output it.
1380  */
1381 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1382                 void *content, char *cbtype, char *cbcharset, size_t length,
1383                 char *encoding, char *cbid, void *cbuserdata)
1384 {
1385         int i;
1386         char buf[128];
1387         int add_newline = 0;
1388         char *text_content;
1389         struct ma_info *ma;
1390         
1391         ma = (struct ma_info *)cbuserdata;
1392
1393         /* This is not the MIME part you're looking for... */
1394         if (strcasecmp(partnum, ma->chosen_part)) return;
1395
1396         /* If the content-type of this part is in our preferred formats
1397          * list, we can simply output it verbatim.
1398          */
1399         for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1400                 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1401                 if (!strcasecmp(buf, cbtype)) {
1402                         /* Yeah!  Go!  W00t!! */
1403
1404                         text_content = (char *)content;
1405                         if (text_content[length-1] != '\n') {
1406                                 ++add_newline;
1407                         }
1408                         cprintf("Content-type: %s", cbtype);
1409                         if (!IsEmptyStr(cbcharset)) {
1410                                 cprintf("; charset=%s", cbcharset);
1411                         }
1412                         cprintf("\nContent-length: %d\n",
1413                                 (int)(length + add_newline) );
1414                         if (!IsEmptyStr(encoding)) {
1415                                 cprintf("Content-transfer-encoding: %s\n", encoding);
1416                         }
1417                         else {
1418                                 cprintf("Content-transfer-encoding: 7bit\n");
1419                         }
1420                         cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1421                         cprintf("\n");
1422                         client_write(content, length);
1423                         if (add_newline) cprintf("\n");
1424                         return;
1425                 }
1426         }
1427
1428         /* No translations required or possible: output as text/plain */
1429         cprintf("Content-type: text/plain\n\n");
1430         fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1431                         length, encoding, cbid, cbuserdata);
1432 }
1433
1434
1435 struct encapmsg {
1436         char desired_section[64];
1437         char *msg;
1438         size_t msglen;
1439 };
1440
1441
1442 /*
1443  * Callback function for
1444  */
1445 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1446                    void *content, char *cbtype, char *cbcharset, size_t length,
1447                    char *encoding, char *cbid, void *cbuserdata)
1448 {
1449         struct encapmsg *encap;
1450
1451         encap = (struct encapmsg *)cbuserdata;
1452
1453         /* Only proceed if this is the desired section... */
1454         if (!strcasecmp(encap->desired_section, partnum)) {
1455                 encap->msglen = length;
1456                 encap->msg = malloc(length + 2);
1457                 memcpy(encap->msg, content, length);
1458                 return;
1459         }
1460 }
1461
1462
1463 /* 
1464  * Determine whether the currently logged in session has permission to read
1465  * messages in the current room.
1466  */
1467 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1468         if (    (!(CC->logged_in))
1469                 && (!(CC->internal_pgm))
1470                 && (!config.c_guest_logins)
1471         ) {
1472                 return(om_not_logged_in);
1473         }
1474         return(om_ok);
1475 }
1476
1477
1478 /*
1479  * Get a message off disk.  (returns om_* values found in msgbase.h)
1480  * 
1481  */
1482 int CtdlOutputMsg(long msg_num,         /* message number (local) to fetch */
1483                   int mode,             /* how would you like that message? */
1484                   int headers_only,     /* eschew the message body? */
1485                   int do_proto,         /* do Citadel protocol responses? */
1486                   int crlf,             /* Use CRLF newlines instead of LF? */
1487                   char *section,        /* NULL or a message/rfc822 section */
1488                   int flags             /* various flags; see msgbase.h */
1489 ) {
1490         struct CtdlMessage *TheMessage = NULL;
1491         int retcode = om_no_such_msg;
1492         struct encapmsg encap;
1493         int r;
1494
1495         CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n", 
1496                 msg_num, mode,
1497                 (section ? section : "<>")
1498         );
1499
1500         r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1501         if (r != om_ok) {
1502                 if (do_proto) {
1503                         if (r == om_not_logged_in) {
1504                                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1505                         }
1506                         else {
1507                                 cprintf("%d An unknown error has occurred.\n", ERROR);
1508                         }
1509                 }
1510                 return(r);
1511         }
1512
1513         /* FIXME: check message id against msglist for this room */
1514
1515         /*
1516          * Fetch the message from disk.  If we're in HEADERS_FAST mode,
1517          * request that we don't even bother loading the body into memory.
1518          */
1519         if (headers_only == HEADERS_FAST) {
1520                 TheMessage = CtdlFetchMessage(msg_num, 0);
1521         }
1522         else {
1523                 TheMessage = CtdlFetchMessage(msg_num, 1);
1524         }
1525
1526         if (TheMessage == NULL) {
1527                 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1528                         ERROR + MESSAGE_NOT_FOUND, msg_num);
1529                 return(om_no_such_msg);
1530         }
1531
1532         /* Here is the weird form of this command, to process only an
1533          * encapsulated message/rfc822 section.
1534          */
1535         if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1536                 memset(&encap, 0, sizeof encap);
1537                 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1538                 mime_parser(TheMessage->cm_fields['M'],
1539                         NULL,
1540                         *extract_encapsulated_message,
1541                         NULL, NULL, (void *)&encap, 0
1542                 );
1543                 CtdlFreeMessage(TheMessage);
1544                 TheMessage = NULL;
1545
1546                 if (encap.msg) {
1547                         encap.msg[encap.msglen] = 0;
1548                         TheMessage = convert_internet_message(encap.msg);
1549                         encap.msg = NULL;       /* no free() here, TheMessage owns it now */
1550
1551                         /* Now we let it fall through to the bottom of this
1552                          * function, because TheMessage now contains the
1553                          * encapsulated message instead of the top-level
1554                          * message.  Isn't that neat?
1555                          */
1556
1557                 }
1558                 else {
1559                         if (do_proto) cprintf("%d msg %ld has no part %s\n",
1560                                 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1561                         retcode = om_no_such_msg;
1562                 }
1563
1564         }
1565
1566         /* Ok, output the message now */
1567         retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1568         CtdlFreeMessage(TheMessage);
1569
1570         return(retcode);
1571 }
1572
1573
1574 char *qp_encode_email_addrs(char *source)
1575 {
1576         char *user, *node, *name;
1577         const char headerStr[] = "=?UTF-8?Q?";
1578         char *Encoded;
1579         char *EncodedName;
1580         char *nPtr;
1581         int need_to_encode = 0;
1582         long SourceLen;
1583         long EncodedMaxLen;
1584         long nColons = 0;
1585         long *AddrPtr;
1586         long *AddrUtf8;
1587         long nAddrPtrMax = 50;
1588         long nmax;
1589         int InQuotes = 0;
1590         int i, n;
1591
1592         if (source == NULL) return source;
1593         if (IsEmptyStr(source)) return source;
1594         cit_backtrace();
1595         CtdlLogPrintf(CTDL_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1596
1597         AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1598         AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1599         memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1600         *AddrPtr = 0;
1601         i = 0;
1602         while (!IsEmptyStr (&source[i])) {
1603                 if (nColons >= nAddrPtrMax){
1604                         long *ptr;
1605
1606                         ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1607                         memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1608                         free (AddrPtr), AddrPtr = ptr;
1609
1610                         ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1611                         memset(&ptr[nAddrPtrMax], 0, 
1612                                sizeof (long) * nAddrPtrMax);
1613
1614                         memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1615                         free (AddrUtf8), AddrUtf8 = ptr;
1616                         nAddrPtrMax *= 2;                               
1617                 }
1618                 if (((unsigned char) source[i] < 32) || 
1619                     ((unsigned char) source[i] > 126)) {
1620                         need_to_encode = 1;
1621                         AddrUtf8[nColons] = 1;
1622                 }
1623                 if (source[i] == '"')
1624                         InQuotes = !InQuotes;
1625                 if (!InQuotes && source[i] == ',') {
1626                         AddrPtr[nColons] = i;
1627                         nColons++;
1628                 }
1629                 i++;
1630         }
1631         if (need_to_encode == 0) {
1632                 free(AddrPtr);
1633                 free(AddrUtf8);
1634                 return source;
1635         }
1636
1637         SourceLen = i;
1638         EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1639         Encoded = (char*) malloc (EncodedMaxLen);
1640
1641         for (i = 0; i < nColons; i++)
1642                 source[AddrPtr[i]++] = '\0';
1643         /* TODO: if libidn, this might get larger*/
1644         user = malloc(SourceLen + 1);
1645         node = malloc(SourceLen + 1);
1646         name = malloc(SourceLen + 1);
1647
1648         nPtr = Encoded;
1649         *nPtr = '\0';
1650         for (i = 0; i < nColons && nPtr != NULL; i++) {
1651                 nmax = EncodedMaxLen - (nPtr - Encoded);
1652                 if (AddrUtf8[i]) {
1653                         process_rfc822_addr(&source[AddrPtr[i]], 
1654                                             user,
1655                                             node,
1656                                             name);
1657                         /* TODO: libIDN here ! */
1658                         if (IsEmptyStr(name)) {
1659                                 n = snprintf(nPtr, nmax, 
1660                                              (i==0)?"%s@%s" : ",%s@%s",
1661                                              user, node);
1662                         }
1663                         else {
1664                                 EncodedName = rfc2047encode(name, strlen(name));                        
1665                                 n = snprintf(nPtr, nmax, 
1666                                              (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1667                                              EncodedName, user, node);
1668                                 free(EncodedName);
1669                         }
1670                 }
1671                 else { 
1672                         n = snprintf(nPtr, nmax, 
1673                                      (i==0)?"%s" : ",%s",
1674                                      &source[AddrPtr[i]]);
1675                 }
1676                 if (n > 0 )
1677                         nPtr += n;
1678                 else { 
1679                         char *ptr, *nnPtr;
1680                         ptr = (char*) malloc(EncodedMaxLen * 2);
1681                         memcpy(ptr, Encoded, EncodedMaxLen);
1682                         nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1683                         free(Encoded), Encoded = ptr;
1684                         EncodedMaxLen *= 2;
1685                         i--; /* do it once more with properly lengthened buffer */
1686                 }
1687         }
1688         for (i = 0; i < nColons; i++)
1689                 source[--AddrPtr[i]] = ',';
1690
1691         free(user);
1692         free(node);
1693         free(name);
1694         free(AddrUtf8);
1695         free(AddrPtr);
1696         return Encoded;
1697 }
1698
1699
1700 /* If the last item in a list of recipients was truncated to a partial address,
1701  * remove it completely in order to avoid choking libSieve
1702  */
1703 void sanitize_truncated_recipient(char *str)
1704 {
1705         if (!str) return;
1706         if (num_tokens(str, ',') < 2) return;
1707
1708         int len = strlen(str);
1709         if (len < 900) return;
1710         if (len > 998) str[998] = 0;
1711
1712         char *cptr = strrchr(str, ',');
1713         if (!cptr) return;
1714
1715         char *lptr = strchr(cptr, '<');
1716         char *rptr = strchr(cptr, '>');
1717
1718         if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1719
1720         *cptr = 0;
1721 }
1722
1723
1724 void OutputCtdlMsgHeaders(
1725         struct CtdlMessage *TheMessage,
1726         int do_proto)           /* do Citadel protocol responses? */
1727 {
1728         char allkeys[30];
1729         int i, k, n;
1730         int suppress_f = 0;
1731         char buf[SIZ];
1732         char display_name[256];
1733
1734         /* begin header processing loop for Citadel message format */
1735         safestrncpy(display_name, "<unknown>", sizeof display_name);
1736         if (TheMessage->cm_fields['A']) {
1737                 strcpy(buf, TheMessage->cm_fields['A']);
1738                 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1739                         safestrncpy(display_name, "****", sizeof display_name);
1740                 }
1741                 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1742                         safestrncpy(display_name, "anonymous", sizeof display_name);
1743                 }
1744                 else {
1745                         safestrncpy(display_name, buf, sizeof display_name);
1746                 }
1747                 if ((is_room_aide())
1748                     && ((TheMessage->cm_anon_type == MES_ANONONLY)
1749                         || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1750                         size_t tmp = strlen(display_name);
1751                         snprintf(&display_name[tmp],
1752                                  sizeof display_name - tmp,
1753                                  " [%s]", buf);
1754                 }
1755         }
1756
1757         /* Don't show Internet address for users on the
1758          * local Citadel network.
1759          */
1760         suppress_f = 0;
1761         if (TheMessage->cm_fields['N'] != NULL)
1762                 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1763                         if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1764                                 suppress_f = 1;
1765                         }
1766
1767         /* Now spew the header fields in the order we like them. */
1768         n = safestrncpy(allkeys, FORDER, sizeof allkeys);
1769         for (i=0; i<n; ++i) {
1770                 k = (int) allkeys[i];
1771                 if (k != 'M') {
1772                         if ( (TheMessage->cm_fields[k] != NULL)
1773                              && (msgkeys[k] != NULL) ) {
1774                                 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1775                                         sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1776                                 }
1777                                 if (k == 'A') {
1778                                         if (do_proto) cprintf("%s=%s\n",
1779                                                               msgkeys[k],
1780                                                               display_name);
1781                                 }
1782                                 else if ((k == 'F') && (suppress_f)) {
1783                                         /* do nothing */
1784                                 }
1785                                 /* Masquerade display name if needed */
1786                                 else {
1787                                         if (do_proto) cprintf("%s=%s\n",
1788                                                               msgkeys[k],
1789                                                               TheMessage->cm_fields[k]
1790                                                 );
1791                                 }
1792                         }
1793                 }
1794         }
1795
1796 }
1797
1798 void OutputRFC822MsgHeaders(
1799         struct CtdlMessage *TheMessage,
1800         int flags,              /* should the bessage be exported clean */
1801         const char *nl,
1802         char *mid, long sizeof_mid,
1803         char *suser, long sizeof_suser,
1804         char *luser, long sizeof_luser,
1805         char *fuser, long sizeof_fuser,
1806         char *snode, long sizeof_snode)
1807 {
1808         char datestamp[100];
1809         int subject_found = 0;
1810         char buf[SIZ];
1811         int i, j, k;
1812         char *mptr = NULL;
1813         char *mpptr = NULL;
1814
1815         for (i = 0; i < 256; ++i) {
1816                 if (TheMessage->cm_fields[i]) {
1817                         mptr = mpptr = TheMessage->cm_fields[i];
1818                                 
1819                         if (i == 'A') {
1820                                 safestrncpy(luser, mptr, sizeof_luser);
1821                                 safestrncpy(suser, mptr, sizeof_suser);
1822                         }
1823                         else if (i == 'Y') {
1824                                 if ((flags & QP_EADDR) != 0) {
1825                                         mptr = qp_encode_email_addrs(mptr);
1826                                 }
1827                                 sanitize_truncated_recipient(mptr);
1828                                 cprintf("CC: %s%s", mptr, nl);
1829                         }
1830                         else if (i == 'P') {
1831                                 cprintf("Return-Path: %s%s", mptr, nl);
1832                         }
1833                         else if (i == 'L') {
1834                                 cprintf("List-ID: %s%s", mptr, nl);
1835                         }
1836                         else if (i == 'V') {
1837                                 if ((flags & QP_EADDR) != 0) 
1838                                         mptr = qp_encode_email_addrs(mptr);
1839                                 cprintf("Envelope-To: %s%s", mptr, nl);
1840                         }
1841                         else if (i == 'U') {
1842                                 cprintf("Subject: %s%s", mptr, nl);
1843                                 subject_found = 1;
1844                         }
1845                         else if (i == 'I')
1846                                 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1847                         else if (i == 'F')
1848                                 safestrncpy(fuser, mptr, sizeof_fuser);
1849                         /* else if (i == 'O')
1850                            cprintf("X-Citadel-Room: %s%s",
1851                            mptr, nl); */
1852                         else if (i == 'N')
1853                                 safestrncpy(snode, mptr, sizeof_snode);
1854                         else if (i == 'R')
1855                         {
1856                                 if (haschar(mptr, '@') == 0)
1857                                 {
1858                                         sanitize_truncated_recipient(mptr);
1859                                         cprintf("To: %s@%s", mptr, config.c_fqdn);
1860                                         cprintf("%s", nl);
1861                                 }
1862                                 else
1863                                 {
1864                                         if ((flags & QP_EADDR) != 0) {
1865                                                 mptr = qp_encode_email_addrs(mptr);
1866                                         }
1867                                         sanitize_truncated_recipient(mptr);
1868                                         cprintf("To: %s", mptr);
1869                                         cprintf("%s", nl);
1870                                 }
1871                         }
1872                         else if (i == 'T') {
1873                                 datestring(datestamp, sizeof datestamp,
1874                                            atol(mptr), DATESTRING_RFC822);
1875                                 cprintf("Date: %s%s", datestamp, nl);
1876                         }
1877                         else if (i == 'W') {
1878                                 cprintf("References: ");
1879                                 k = num_tokens(mptr, '|');
1880                                 for (j=0; j<k; ++j) {
1881                                         extract_token(buf, mptr, j, '|', sizeof buf);
1882                                         cprintf("<%s>", buf);
1883                                         if (j == (k-1)) {
1884                                                 cprintf("%s", nl);
1885                                         }
1886                                         else {
1887                                                 cprintf(" ");
1888                                         }
1889                                 }
1890                         }
1891                         else if (i == 'K') {
1892                                 cprintf("Reply-To: <%s>%s", mptr, nl);
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 = MyContext();
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 - unless it
4037          * is the DRAFTS room which does not require recipients
4038          */
4039
4040         if ( (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4041              || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4042         ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4043                 if (CC->user.axlevel < AxProbU) {
4044                         strcpy(recp, "sysop");
4045                         strcpy(cc, "");
4046                         strcpy(bcc, "");
4047                 }
4048
4049                 valid_to = validate_recipients(recp, NULL, 0);
4050                 if (valid_to->num_error > 0) {
4051                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4052                         free_recipients(valid_to);
4053                         return;
4054                 }
4055
4056                 valid_cc = validate_recipients(cc, NULL, 0);
4057                 if (valid_cc->num_error > 0) {
4058                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4059                         free_recipients(valid_to);
4060                         free_recipients(valid_cc);
4061                         return;
4062                 }
4063
4064                 valid_bcc = validate_recipients(bcc, NULL, 0);
4065                 if (valid_bcc->num_error > 0) {
4066                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4067                         free_recipients(valid_to);
4068                         free_recipients(valid_cc);
4069                         free_recipients(valid_bcc);
4070                         return;
4071                 }
4072
4073                 /* Recipient required, but none were specified */
4074                 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4075                         free_recipients(valid_to);
4076                         free_recipients(valid_cc);
4077                         free_recipients(valid_bcc);
4078                         cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4079                         return;
4080                 }
4081
4082                 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4083                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4084                                 cprintf("%d You do not have permission "
4085                                         "to send Internet mail.\n",
4086                                         ERROR + HIGHER_ACCESS_REQUIRED);
4087                                 free_recipients(valid_to);
4088                                 free_recipients(valid_cc);
4089                                 free_recipients(valid_bcc);
4090                                 return;
4091                         }
4092                 }
4093
4094                 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)
4095                    && (CC->user.axlevel < AxNetU) ) {
4096                         cprintf("%d Higher access required for network mail.\n",
4097                                 ERROR + HIGHER_ACCESS_REQUIRED);
4098                         free_recipients(valid_to);
4099                         free_recipients(valid_cc);
4100                         free_recipients(valid_bcc);
4101                         return;
4102                 }
4103         
4104                 if ((RESTRICT_INTERNET == 1)
4105                     && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4106                     && ((CC->user.flags & US_INTERNET) == 0)
4107                     && (!CC->internal_pgm)) {
4108                         cprintf("%d You don't have access to Internet mail.\n",
4109                                 ERROR + HIGHER_ACCESS_REQUIRED);
4110                         free_recipients(valid_to);
4111                         free_recipients(valid_cc);
4112                         free_recipients(valid_bcc);
4113                         return;
4114                 }
4115
4116         }
4117
4118         /* Is this a room which has anonymous-only or anonymous-option? */
4119         anonymous = MES_NORMAL;
4120         if (CC->room.QRflags & QR_ANONONLY) {
4121                 anonymous = MES_ANONONLY;
4122         }
4123         if (CC->room.QRflags & QR_ANONOPT) {
4124                 if (anon_flag == 1) {   /* only if the user requested it */
4125                         anonymous = MES_ANONOPT;
4126                 }
4127         }
4128
4129         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4130                 recp[0] = 0;
4131         }
4132
4133         /* Recommend to the client that the use of a message subject is
4134          * strongly recommended in this room, if either the SUBJECTREQ flag
4135          * is set, or if there is one or more Internet email recipients.
4136          */
4137         if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4138         if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
4139         if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
4140         if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
4141
4142         /* If we're only checking the validity of the request, return
4143          * success without creating the message.
4144          */
4145         if (post == 0) {
4146                 cprintf("%d %s|%d\n", CIT_OK,
4147                         ((valid_to != NULL) ? valid_to->display_recp : ""), 
4148                         subject_required);
4149                 free_recipients(valid_to);
4150                 free_recipients(valid_cc);
4151                 free_recipients(valid_bcc);
4152                 return;
4153         }
4154
4155         /* We don't need these anymore because we'll do it differently below */
4156         free_recipients(valid_to);
4157         free_recipients(valid_cc);
4158         free_recipients(valid_bcc);
4159
4160         /* Read in the message from the client. */
4161         if (do_confirm) {
4162                 cprintf("%d send message\n", START_CHAT_MODE);
4163         } else {
4164                 cprintf("%d send message\n", SEND_LISTING);
4165         }
4166
4167         msg = CtdlMakeMessage(&CC->user, recp, cc,
4168                 CC->room.QRname, anonymous, format_type,
4169                 newusername, newuseremail, subject,
4170                 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4171                 NULL, references);
4172
4173         /* Put together one big recipients struct containing to/cc/bcc all in
4174          * one.  This is for the envelope.
4175          */
4176         char *all_recps = malloc(SIZ * 3);
4177         strcpy(all_recps, recp);
4178         if (!IsEmptyStr(cc)) {
4179                 if (!IsEmptyStr(all_recps)) {
4180                         strcat(all_recps, ",");
4181                 }
4182                 strcat(all_recps, cc);
4183         }
4184         if (!IsEmptyStr(bcc)) {
4185                 if (!IsEmptyStr(all_recps)) {
4186                         strcat(all_recps, ",");
4187                 }
4188                 strcat(all_recps, bcc);
4189         }
4190         if (!IsEmptyStr(all_recps)) {
4191                 valid = validate_recipients(all_recps, NULL, 0);
4192         }
4193         else {
4194                 valid = NULL;
4195         }
4196         free(all_recps);
4197
4198         if (msg != NULL) {
4199                 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4200
4201                 if (do_confirm) {
4202                         cprintf("%ld\n", msgnum);
4203                         if (msgnum >= 0L) {
4204                                 cprintf("Message accepted.\n");
4205                         }
4206                         else {
4207                                 cprintf("Internal error.\n");
4208                         }
4209                         if (msg->cm_fields['E'] != NULL) {
4210                                 cprintf("%s\n", msg->cm_fields['E']);
4211                         } else {
4212                                 cprintf("\n");
4213                         }
4214                         cprintf("000\n");
4215                 }
4216
4217                 CtdlFreeMessage(msg);
4218         }
4219         if (valid != NULL) {
4220                 free_recipients(valid);
4221         }
4222         return;
4223 }
4224
4225
4226
4227 /*
4228  * API function to delete messages which match a set of criteria
4229  * (returns the actual number of messages deleted)
4230  */
4231 int CtdlDeleteMessages(char *room_name,         /* which room */
4232                         long *dmsgnums,         /* array of msg numbers to be deleted */
4233                         int num_dmsgnums,       /* number of msgs to be deleted, or 0 for "any" */
4234                         char *content_type      /* or "" for any.  regular expressions expected. */
4235 )
4236 {
4237         struct ctdlroom qrbuf;
4238         struct cdbdata *cdbfr;
4239         long *msglist = NULL;
4240         long *dellist = NULL;
4241         int num_msgs = 0;
4242         int i, j;
4243         int num_deleted = 0;
4244         int delete_this;
4245         struct MetaData smi;
4246         regex_t re;
4247         regmatch_t pm;
4248         int need_to_free_re = 0;
4249
4250         if (content_type) if (!IsEmptyStr(content_type)) {
4251                 regcomp(&re, content_type, 0);
4252                 need_to_free_re = 1;
4253         }
4254         CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4255                 room_name, num_dmsgnums, content_type);
4256
4257         /* get room record, obtaining a lock... */
4258         if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4259                 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4260                         room_name);
4261                 if (need_to_free_re) regfree(&re);
4262                 return (0);     /* room not found */
4263         }
4264         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4265
4266         if (cdbfr != NULL) {
4267                 dellist = malloc(cdbfr->len);
4268                 msglist = (long *) cdbfr->ptr;
4269                 cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
4270                 num_msgs = cdbfr->len / sizeof(long);
4271                 cdb_free(cdbfr);
4272         }
4273         if (num_msgs > 0) {
4274                 for (i = 0; i < num_msgs; ++i) {
4275                         delete_this = 0x00;
4276
4277                         /* Set/clear a bit for each criterion */
4278
4279                         /* 0 messages in the list or a null list means that we are
4280                          * interested in deleting any messages which meet the other criteria.
4281                          */
4282                         if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4283                                 delete_this |= 0x01;
4284                         }
4285                         else {
4286                                 for (j=0; j<num_dmsgnums; ++j) {
4287                                         if (msglist[i] == dmsgnums[j]) {
4288                                                 delete_this |= 0x01;
4289                                         }
4290                                 }
4291                         }
4292
4293                         if (IsEmptyStr(content_type)) {
4294                                 delete_this |= 0x02;
4295                         } else {
4296                                 GetMetaData(&smi, msglist[i]);
4297                                 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4298                                         delete_this |= 0x02;
4299                                 }
4300                         }
4301
4302                         /* Delete message only if all bits are set */
4303                         if (delete_this == 0x03) {
4304                                 dellist[num_deleted++] = msglist[i];
4305                                 msglist[i] = 0L;
4306                         }
4307                 }
4308
4309                 num_msgs = sort_msglist(msglist, num_msgs);
4310                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4311                           msglist, (int)(num_msgs * sizeof(long)));
4312
4313                 if (num_msgs > 0)
4314                         qrbuf.QRhighest = msglist[num_msgs - 1];
4315                 else
4316                         qrbuf.QRhighest = 0;
4317         }
4318         CtdlPutRoomLock(&qrbuf);
4319
4320         /* Go through the messages we pulled out of the index, and decrement
4321          * their reference counts by 1.  If this is the only room the message
4322          * was in, the reference count will reach zero and the message will
4323          * automatically be deleted from the database.  We do this in a
4324          * separate pass because there might be plug-in hooks getting called,
4325          * and we don't want that happening during an S_ROOMS critical
4326          * section.
4327          */
4328         if (num_deleted) for (i=0; i<num_deleted; ++i) {
4329                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4330                 AdjRefCount(dellist[i], -1);
4331         }
4332
4333         /* Now free the memory we used, and go away. */
4334         if (msglist != NULL) free(msglist);
4335         if (dellist != NULL) free(dellist);
4336         CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4337         if (need_to_free_re) regfree(&re);
4338         return (num_deleted);
4339 }
4340
4341
4342
4343 /*
4344  * Check whether the current user has permission to delete messages from
4345  * the current room (returns 1 for yes, 0 for no)
4346  */
4347 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4348         int ra;
4349         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4350         if (ra & UA_DELETEALLOWED) return(1);
4351         return(0);
4352 }
4353
4354
4355
4356
4357 /*
4358  * Delete message from current room
4359  */
4360 void cmd_dele(char *args)
4361 {
4362         int num_deleted;
4363         int i;
4364         char msgset[SIZ];
4365         char msgtok[32];
4366         long *msgs;
4367         int num_msgs = 0;
4368
4369         extract_token(msgset, args, 0, '|', sizeof msgset);
4370         num_msgs = num_tokens(msgset, ',');
4371         if (num_msgs < 1) {
4372                 cprintf("%d Nothing to do.\n", CIT_OK);
4373                 return;
4374         }
4375
4376         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4377                 cprintf("%d Higher access required.\n",
4378                         ERROR + HIGHER_ACCESS_REQUIRED);
4379                 return;
4380         }
4381
4382         /*
4383          * Build our message set to be moved/copied
4384          */
4385         msgs = malloc(num_msgs * sizeof(long));
4386         for (i=0; i<num_msgs; ++i) {
4387                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4388                 msgs[i] = atol(msgtok);
4389         }
4390
4391         num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4392         free(msgs);
4393
4394         if (num_deleted) {
4395                 cprintf("%d %d message%s deleted.\n", CIT_OK,
4396                         num_deleted, ((num_deleted != 1) ? "s" : ""));
4397         } else {
4398                 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4399         }
4400 }
4401
4402
4403
4404
4405 /*
4406  * move or copy a message to another room
4407  */
4408 void cmd_move(char *args)
4409 {
4410         char msgset[SIZ];
4411         char msgtok[32];
4412         long *msgs;
4413         int num_msgs = 0;
4414
4415         char targ[ROOMNAMELEN];
4416         struct ctdlroom qtemp;
4417         int err;
4418         int is_copy = 0;
4419         int ra;
4420         int permit = 0;
4421         int i;
4422
4423         extract_token(msgset, args, 0, '|', sizeof msgset);
4424         num_msgs = num_tokens(msgset, ',');
4425         if (num_msgs < 1) {
4426                 cprintf("%d Nothing to do.\n", CIT_OK);
4427                 return;
4428         }
4429
4430         extract_token(targ, args, 1, '|', sizeof targ);
4431         convert_room_name_macros(targ, sizeof targ);
4432         targ[ROOMNAMELEN - 1] = 0;
4433         is_copy = extract_int(args, 2);
4434
4435         if (CtdlGetRoom(&qtemp, targ) != 0) {
4436                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4437                 return;
4438         }
4439
4440         if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4441                 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4442                 return;
4443         }
4444
4445         CtdlGetUser(&CC->user, CC->curr_user);
4446         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4447
4448         /* Check for permission to perform this operation.
4449          * Remember: "CC->room" is source, "qtemp" is target.
4450          */
4451         permit = 0;
4452
4453         /* Aides can move/copy */
4454         if (CC->user.axlevel >= AxAideU) permit = 1;
4455
4456         /* Room aides can move/copy */
4457         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4458
4459         /* Permit move/copy from personal rooms */
4460         if ((CC->room.QRflags & QR_MAILBOX)
4461            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4462
4463         /* Permit only copy from public to personal room */
4464         if ( (is_copy)
4465            && (!(CC->room.QRflags & QR_MAILBOX))
4466            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4467
4468         /* Permit message removal from collaborative delete rooms */
4469         if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4470
4471         /* Users allowed to post into the target room may move into it too. */
4472         if ((CC->room.QRflags & QR_MAILBOX) && 
4473             (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
4474
4475         /* User must have access to target room */
4476         if (!(ra & UA_KNOWN))  permit = 0;
4477
4478         if (!permit) {
4479                 cprintf("%d Higher access required.\n",
4480                         ERROR + HIGHER_ACCESS_REQUIRED);
4481                 return;
4482         }
4483
4484         /*
4485          * Build our message set to be moved/copied
4486          */
4487         msgs = malloc(num_msgs * sizeof(long));
4488         for (i=0; i<num_msgs; ++i) {
4489                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4490                 msgs[i] = atol(msgtok);
4491         }
4492
4493         /*
4494          * Do the copy
4495          */
4496         err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4497         if (err != 0) {
4498                 cprintf("%d Cannot store message(s) in %s: error %d\n",
4499                         err, targ, err);
4500                 free(msgs);
4501                 return;
4502         }
4503
4504         /* Now delete the message from the source room,
4505          * if this is a 'move' rather than a 'copy' operation.
4506          */
4507         if (is_copy == 0) {
4508                 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4509         }
4510         free(msgs);
4511
4512         cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4513 }
4514
4515
4516
4517 /*
4518  * GetMetaData()  -  Get the supplementary record for a message
4519  */
4520 void GetMetaData(struct MetaData *smibuf, long msgnum)
4521 {
4522
4523         struct cdbdata *cdbsmi;
4524         long TheIndex;
4525
4526         memset(smibuf, 0, sizeof(struct MetaData));
4527         smibuf->meta_msgnum = msgnum;
4528         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
4529
4530         /* Use the negative of the message number for its supp record index */
4531         TheIndex = (0L - msgnum);
4532
4533         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4534         if (cdbsmi == NULL) {
4535                 return;         /* record not found; go with defaults */
4536         }
4537         memcpy(smibuf, cdbsmi->ptr,
4538                ((cdbsmi->len > sizeof(struct MetaData)) ?
4539                 sizeof(struct MetaData) : cdbsmi->len));
4540         cdb_free(cdbsmi);
4541         return;
4542 }
4543
4544
4545 /*
4546  * PutMetaData()  -  (re)write supplementary record for a message
4547  */
4548 void PutMetaData(struct MetaData *smibuf)
4549 {
4550         long TheIndex;
4551
4552         /* Use the negative of the message number for the metadata db index */
4553         TheIndex = (0L - smibuf->meta_msgnum);
4554
4555         cdb_store(CDB_MSGMAIN,
4556                   &TheIndex, (int)sizeof(long),
4557                   smibuf, (int)sizeof(struct MetaData));
4558
4559 }
4560
4561 /*
4562  * AdjRefCount  -  submit an adjustment to the reference count for a message.
4563  *                 (These are just queued -- we actually process them later.)
4564  */
4565 void AdjRefCount(long msgnum, int incr)
4566 {
4567         struct arcq new_arcq;
4568         int rv = 0;
4569
4570         CtdlLogPrintf(CTDL_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4571                 msgnum, incr
4572         );
4573
4574         begin_critical_section(S_SUPPMSGMAIN);
4575         if (arcfp == NULL) {
4576                 arcfp = fopen(file_arcq, "ab+");
4577         }
4578         end_critical_section(S_SUPPMSGMAIN);
4579
4580         /* msgnum < 0 means that we're trying to close the file */
4581         if (msgnum < 0) {
4582                 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4583                 begin_critical_section(S_SUPPMSGMAIN);
4584                 if (arcfp != NULL) {
4585                         fclose(arcfp);
4586                         arcfp = NULL;
4587                 }
4588                 end_critical_section(S_SUPPMSGMAIN);
4589                 return;
4590         }
4591
4592         /*
4593          * If we can't open the queue, perform the operation synchronously.
4594          */
4595         if (arcfp == NULL) {
4596                 TDAP_AdjRefCount(msgnum, incr);
4597                 return;
4598         }
4599
4600         new_arcq.arcq_msgnum = msgnum;
4601         new_arcq.arcq_delta = incr;
4602         rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4603         fflush(arcfp);
4604
4605         return;
4606 }
4607
4608
4609 /*
4610  * TDAP_ProcessAdjRefCountQueue()
4611  *
4612  * Process the queue of message count adjustments that was created by calls
4613  * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4614  * for each one.  This should be an "off hours" operation.
4615  */
4616 int TDAP_ProcessAdjRefCountQueue(void)
4617 {
4618         char file_arcq_temp[PATH_MAX];
4619         int r;
4620         FILE *fp;
4621         struct arcq arcq_rec;
4622         int num_records_processed = 0;
4623
4624         snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4625
4626         begin_critical_section(S_SUPPMSGMAIN);
4627         if (arcfp != NULL) {
4628                 fclose(arcfp);
4629                 arcfp = NULL;
4630         }
4631
4632         r = link(file_arcq, file_arcq_temp);
4633         if (r != 0) {
4634                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4635                 end_critical_section(S_SUPPMSGMAIN);
4636                 return(num_records_processed);
4637         }
4638
4639         unlink(file_arcq);
4640         end_critical_section(S_SUPPMSGMAIN);
4641
4642         fp = fopen(file_arcq_temp, "rb");
4643         if (fp == NULL) {
4644                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4645                 return(num_records_processed);
4646         }
4647
4648         while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4649                 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4650                 ++num_records_processed;
4651         }
4652
4653         fclose(fp);
4654         r = unlink(file_arcq_temp);
4655         if (r != 0) {
4656                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4657         }
4658
4659         return(num_records_processed);
4660 }
4661
4662
4663
4664 /*
4665  * TDAP_AdjRefCount  -  adjust the reference count for a message.
4666  *                      This one does it "for real" because it's called by
4667  *                      the autopurger function that processes the queue
4668  *                      created by AdjRefCount().   If a message's reference
4669  *                      count becomes zero, we also delete the message from
4670  *                      disk and de-index it.
4671  */
4672 void TDAP_AdjRefCount(long msgnum, int incr)
4673 {
4674
4675         struct MetaData smi;
4676         long delnum;
4677
4678         /* This is a *tight* critical section; please keep it that way, as
4679          * it may get called while nested in other critical sections.  
4680          * Complicating this any further will surely cause deadlock!
4681          */
4682         begin_critical_section(S_SUPPMSGMAIN);
4683         GetMetaData(&smi, msgnum);
4684         smi.meta_refcount += incr;
4685         PutMetaData(&smi);
4686         end_critical_section(S_SUPPMSGMAIN);
4687         CtdlLogPrintf(CTDL_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
4688                 msgnum, incr, smi.meta_refcount
4689         );
4690
4691         /* If the reference count is now zero, delete the message
4692          * (and its supplementary record as well).
4693          */
4694         if (smi.meta_refcount == 0) {
4695                 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4696                 
4697                 /* Call delete hooks with NULL room to show it has gone altogether */
4698                 PerformDeleteHooks(NULL, msgnum);
4699
4700                 /* Remove from message base */
4701                 delnum = msgnum;
4702                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4703                 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4704
4705                 /* Remove metadata record */
4706                 delnum = (0L - msgnum);
4707                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4708         }
4709
4710 }
4711
4712 /*
4713  * Write a generic object to this room
4714  *
4715  * Note: this could be much more efficient.  Right now we use two temporary
4716  * files, and still pull the message into memory as with all others.
4717  */
4718 void CtdlWriteObject(char *req_room,                    /* Room to stuff it in */
4719                         char *content_type,             /* MIME type of this object */
4720                         char *raw_message,              /* Data to be written */
4721                         off_t raw_length,               /* Size of raw_message */
4722                         struct ctdluser *is_mailbox,    /* Mailbox room? */
4723                         int is_binary,                  /* Is encoding necessary? */
4724                         int is_unique,                  /* Del others of this type? */
4725                         unsigned int flags              /* Internal save flags */
4726                         )
4727 {
4728
4729         struct ctdlroom qrbuf;
4730         char roomname[ROOMNAMELEN];
4731         struct CtdlMessage *msg;
4732         char *encoded_message = NULL;
4733
4734         if (is_mailbox != NULL) {
4735                 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4736         }
4737         else {
4738                 safestrncpy(roomname, req_room, sizeof(roomname));
4739         }
4740
4741         CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4742
4743         if (is_binary) {
4744                 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4745         }
4746         else {
4747                 encoded_message = malloc((size_t)(raw_length + 4096));
4748         }
4749
4750         sprintf(encoded_message, "Content-type: %s\n", content_type);
4751
4752         if (is_binary) {
4753                 sprintf(&encoded_message[strlen(encoded_message)],
4754                         "Content-transfer-encoding: base64\n\n"
4755                 );
4756         }
4757         else {
4758                 sprintf(&encoded_message[strlen(encoded_message)],
4759                         "Content-transfer-encoding: 7bit\n\n"
4760                 );
4761         }
4762
4763         if (is_binary) {
4764                 CtdlEncodeBase64(
4765                         &encoded_message[strlen(encoded_message)],
4766                         raw_message,
4767                         (int)raw_length,
4768                         0
4769                 );
4770         }
4771         else {
4772                 memcpy(
4773                         &encoded_message[strlen(encoded_message)],
4774                         raw_message,
4775                         (int)(raw_length+1)
4776                 );
4777         }
4778
4779         CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4780         msg = malloc(sizeof(struct CtdlMessage));
4781         memset(msg, 0, sizeof(struct CtdlMessage));
4782         msg->cm_magic = CTDLMESSAGE_MAGIC;
4783         msg->cm_anon_type = MES_NORMAL;
4784         msg->cm_format_type = 4;
4785         msg->cm_fields['A'] = strdup(CC->user.fullname);
4786         msg->cm_fields['O'] = strdup(req_room);
4787         msg->cm_fields['N'] = strdup(config.c_nodename);
4788         msg->cm_fields['H'] = strdup(config.c_humannode);
4789         msg->cm_flags = flags;
4790         
4791         msg->cm_fields['M'] = encoded_message;
4792
4793         /* Create the requested room if we have to. */
4794         if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4795                 CtdlCreateRoom(roomname, 
4796                         ( (is_mailbox != NULL) ? 5 : 3 ),
4797                         "", 0, 1, 0, VIEW_BBS);
4798         }
4799         /* If the caller specified this object as unique, delete all
4800          * other objects of this type that are currently in the room.
4801          */
4802         if (is_unique) {
4803                 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4804                         CtdlDeleteMessages(roomname, NULL, 0, content_type)
4805                 );
4806         }
4807         /* Now write the data */
4808         CtdlSubmitMsg(msg, NULL, roomname, 0);
4809         CtdlFreeMessage(msg);
4810 }
4811
4812
4813
4814
4815
4816
4817 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4818         config_msgnum = msgnum;
4819 }
4820
4821
4822 char *CtdlGetSysConfig(char *sysconfname) {
4823         char hold_rm[ROOMNAMELEN];
4824         long msgnum;
4825         char *conf;
4826         struct CtdlMessage *msg;
4827         char buf[SIZ];
4828         
4829         strcpy(hold_rm, CC->room.QRname);
4830         if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4831                 CtdlGetRoom(&CC->room, hold_rm);
4832                 return NULL;
4833         }
4834
4835
4836         /* We want the last (and probably only) config in this room */
4837         begin_critical_section(S_CONFIG);
4838         config_msgnum = (-1L);
4839         CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4840                 CtdlGetSysConfigBackend, NULL);
4841         msgnum = config_msgnum;
4842         end_critical_section(S_CONFIG);
4843
4844         if (msgnum < 0L) {
4845                 conf = NULL;
4846         }
4847         else {
4848                 msg = CtdlFetchMessage(msgnum, 1);
4849                 if (msg != NULL) {
4850                         conf = strdup(msg->cm_fields['M']);
4851                         CtdlFreeMessage(msg);
4852                 }
4853                 else {
4854                         conf = NULL;
4855                 }
4856         }
4857
4858         CtdlGetRoom(&CC->room, hold_rm);
4859
4860         if (conf != NULL) do {
4861                 extract_token(buf, conf, 0, '\n', sizeof buf);
4862                 strcpy(conf, &conf[strlen(buf)+1]);
4863         } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4864
4865         return(conf);
4866 }
4867
4868
4869 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4870         CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4871 }
4872
4873
4874 /*
4875  * Determine whether a given Internet address belongs to the current user
4876  */
4877 int CtdlIsMe(char *addr, int addr_buf_len)
4878 {
4879         struct recptypes *recp;
4880         int i;
4881
4882         recp = validate_recipients(addr, NULL, 0);
4883         if (recp == NULL) return(0);
4884
4885         if (recp->num_local == 0) {
4886                 free_recipients(recp);
4887                 return(0);
4888         }
4889
4890         for (i=0; i<recp->num_local; ++i) {
4891                 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4892                 if (!strcasecmp(addr, CC->user.fullname)) {
4893                         free_recipients(recp);
4894                         return(1);
4895                 }
4896         }
4897
4898         free_recipients(recp);
4899         return(0);
4900 }
4901
4902
4903 /*
4904  * Citadel protocol command to do the same
4905  */
4906 void cmd_isme(char *argbuf) {
4907         char addr[256];
4908
4909         if (CtdlAccessCheck(ac_logged_in)) return;
4910         extract_token(addr, argbuf, 0, '|', sizeof addr);
4911
4912         if (CtdlIsMe(addr, sizeof addr)) {
4913                 cprintf("%d %s\n", CIT_OK, addr);
4914         }
4915         else {
4916                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4917         }
4918
4919 }
4920
4921
4922 /*****************************************************************************/
4923 /*                      MODULE INITIALIZATION STUFF                          */
4924 /*****************************************************************************/
4925
4926 CTDL_MODULE_INIT(msgbase)
4927 {
4928         if (!threading) {
4929                 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4930                 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4931                 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4932                 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4933                 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4934                 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4935                 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4936                 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4937                 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4938                 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4939                 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4940                 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4941         }
4942
4943         /* return our Subversion id for the Log */
4944         return "msgbase";
4945 }