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