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