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