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