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