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