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