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