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