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