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