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