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