* New command 'WIKI history|<pagename>' to list the revision history for a wiki page.
[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
3117
3118 /*
3119  * Convenience function for generating small administrative messages.
3120  */
3121 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text, 
3122                         int format_type, const char *subject)
3123 {
3124         struct CtdlMessage *msg;
3125         struct recptypes *recp = NULL;
3126
3127         msg = malloc(sizeof(struct CtdlMessage));
3128         memset(msg, 0, sizeof(struct CtdlMessage));
3129         msg->cm_magic = CTDLMESSAGE_MAGIC;
3130         msg->cm_anon_type = MES_NORMAL;
3131         msg->cm_format_type = format_type;
3132
3133         if (from != NULL) {
3134                 msg->cm_fields['A'] = strdup(from);
3135         }
3136         else if (fromaddr != NULL) {
3137                 msg->cm_fields['A'] = strdup(fromaddr);
3138                 if (strchr(msg->cm_fields['A'], '@')) {
3139                         *strchr(msg->cm_fields['A'], '@') = 0;
3140                 }
3141         }
3142         else {
3143                 msg->cm_fields['A'] = strdup("Citadel");
3144         }
3145
3146         if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3147         if (room != NULL) msg->cm_fields['O'] = strdup(room);
3148         msg->cm_fields['N'] = strdup(NODENAME);
3149         if (to != NULL) {
3150                 msg->cm_fields['R'] = strdup(to);
3151                 recp = validate_recipients(to, NULL, 0);
3152         }
3153         if (subject != NULL) {
3154                 msg->cm_fields['U'] = strdup(subject);
3155         }
3156         msg->cm_fields['M'] = strdup(text);
3157
3158         CtdlSubmitMsg(msg, recp, room, 0);
3159         CtdlFreeMessage(msg);
3160         if (recp != NULL) free_recipients(recp);
3161 }
3162
3163
3164
3165 /*
3166  * Back end function used by CtdlMakeMessage() and similar functions
3167  */
3168 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
3169                         size_t maxlen,          /* maximum message length */
3170                         char *exist,            /* if non-null, append to it;
3171                                                    exist is ALWAYS freed  */
3172                         int crlf,               /* CRLF newlines instead of LF */
3173                         int sock                /* socket handle or 0 for this session's client socket */
3174                         ) {
3175         char buf[1024];
3176         int linelen;
3177         size_t message_len = 0;
3178         size_t buffer_len = 0;
3179         char *ptr;
3180         char *m;
3181         int flushing = 0;
3182         int finished = 0;
3183         int dotdot = 0;
3184
3185         if (exist == NULL) {
3186                 m = malloc(4096);
3187                 m[0] = 0;
3188                 buffer_len = 4096;
3189                 message_len = 0;
3190         }
3191         else {
3192                 message_len = strlen(exist);
3193                 buffer_len = message_len + 4096;
3194                 m = realloc(exist, buffer_len);
3195                 if (m == NULL) {
3196                         free(exist);
3197                         return m;
3198                 }
3199         }
3200
3201         /* Do we need to change leading ".." to "." for SMTP escaping? */
3202         if (!strcmp(terminator, ".")) {
3203                 dotdot = 1;
3204         }
3205
3206         /* flush the input if we have nowhere to store it */
3207         if (m == NULL) {
3208                 flushing = 1;
3209         }
3210
3211         /* read in the lines of message text one by one */
3212         do {
3213                 if (sock > 0) {
3214                         if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3215                 }
3216                 else {
3217                         if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3218                 }
3219                 if (!strcmp(buf, terminator)) finished = 1;
3220                 if (crlf) {
3221                         strcat(buf, "\r\n");
3222                 }
3223                 else {
3224                         strcat(buf, "\n");
3225                 }
3226
3227                 /* Unescape SMTP-style input of two dots at the beginning of the line */
3228                 if (dotdot) {
3229                         if (!strncmp(buf, "..", 2)) {
3230                                 strcpy(buf, &buf[1]);
3231                         }
3232                 }
3233
3234                 if ( (!flushing) && (!finished) ) {
3235                         /* Measure the line */
3236                         linelen = strlen(buf);
3237         
3238                         /* augment the buffer if we have to */
3239                         if ((message_len + linelen) >= buffer_len) {
3240                                 ptr = realloc(m, (buffer_len * 2) );
3241                                 if (ptr == NULL) {      /* flush if can't allocate */
3242                                         flushing = 1;
3243                                 } else {
3244                                         buffer_len = (buffer_len * 2);
3245                                         m = ptr;
3246                                         CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3247                                 }
3248                         }
3249         
3250                         /* Add the new line to the buffer.  NOTE: this loop must avoid
3251                         * using functions like strcat() and strlen() because they
3252                         * traverse the entire buffer upon every call, and doing that
3253                         * for a multi-megabyte message slows it down beyond usability.
3254                         */
3255                         strcpy(&m[message_len], buf);
3256                         message_len += linelen;
3257                 }
3258
3259                 /* if we've hit the max msg length, flush the rest */
3260                 if (message_len >= maxlen) flushing = 1;
3261
3262         } while (!finished);
3263         return(m);
3264 }
3265
3266
3267
3268
3269 /*
3270  * Build a binary message to be saved on disk.
3271  * (NOTE: if you supply 'preformatted_text', the buffer you give it
3272  * will become part of the message.  This means you are no longer
3273  * responsible for managing that memory -- it will be freed along with
3274  * the rest of the fields when CtdlFreeMessage() is called.)
3275  */
3276
3277 struct CtdlMessage *CtdlMakeMessage(
3278         struct ctdluser *author,        /* author's user structure */
3279         char *recipient,                /* NULL if it's not mail */
3280         char *recp_cc,                  /* NULL if it's not mail */
3281         char *room,                     /* room where it's going */
3282         int type,                       /* see MES_ types in header file */
3283         int format_type,                /* variformat, plain text, MIME... */
3284         char *fake_name,                /* who we're masquerading as */
3285         char *my_email,                 /* which of my email addresses to use (empty is ok) */
3286         char *subject,                  /* Subject (optional) */
3287         char *supplied_euid,            /* ...or NULL if this is irrelevant */
3288         char *preformatted_text,        /* ...or NULL to read text from client */
3289         char *references                /* Thread references */
3290 ) {
3291         char dest_node[256];
3292         char buf[1024];
3293         struct CtdlMessage *msg;
3294
3295         msg = malloc(sizeof(struct CtdlMessage));
3296         memset(msg, 0, sizeof(struct CtdlMessage));
3297         msg->cm_magic = CTDLMESSAGE_MAGIC;
3298         msg->cm_anon_type = type;
3299         msg->cm_format_type = format_type;
3300
3301         /* Don't confuse the poor folks if it's not routed mail. */
3302         strcpy(dest_node, "");
3303
3304         if (recipient != NULL) striplt(recipient);
3305         if (recp_cc != NULL) striplt(recp_cc);
3306
3307         /* Path or Return-Path */
3308         if (my_email == NULL) my_email = "";
3309
3310         if (!IsEmptyStr(my_email)) {
3311                 msg->cm_fields['P'] = strdup(my_email);
3312         }
3313         else {
3314                 snprintf(buf, sizeof buf, "%s", author->fullname);
3315                 msg->cm_fields['P'] = strdup(buf);
3316         }
3317         convert_spaces_to_underscores(msg->cm_fields['P']);
3318
3319         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
3320         msg->cm_fields['T'] = strdup(buf);
3321
3322         if ((fake_name != NULL) && (fake_name[0])) {            /* author */
3323                 msg->cm_fields['A'] = strdup(fake_name);
3324         }
3325         else {
3326                 msg->cm_fields['A'] = strdup(author->fullname);
3327         }
3328
3329         if (CC->room.QRflags & QR_MAILBOX) {            /* room */
3330                 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3331         }
3332         else {
3333                 msg->cm_fields['O'] = strdup(CC->room.QRname);
3334         }
3335
3336         msg->cm_fields['N'] = strdup(NODENAME);         /* nodename */
3337         msg->cm_fields['H'] = strdup(HUMANNODE);                /* hnodename */
3338
3339         if ((recipient != NULL) && (recipient[0] != 0)) {
3340                 msg->cm_fields['R'] = strdup(recipient);
3341         }
3342         if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3343                 msg->cm_fields['Y'] = strdup(recp_cc);
3344         }
3345         if (dest_node[0] != 0) {
3346                 msg->cm_fields['D'] = strdup(dest_node);
3347         }
3348
3349         if (!IsEmptyStr(my_email)) {
3350                 msg->cm_fields['F'] = strdup(my_email);
3351         }
3352         else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3353                 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3354         }
3355
3356         if (subject != NULL) {
3357                 long length;
3358                 striplt(subject);
3359                 length = strlen(subject);
3360                 if (length > 0) {
3361                         long i;
3362                         long IsAscii;
3363                         IsAscii = -1;
3364                         i = 0;
3365                         while ((subject[i] != '\0') &&
3366                                (IsAscii = isascii(subject[i]) != 0 ))
3367                                 i++;
3368                         if (IsAscii != 0)
3369                                 msg->cm_fields['U'] = strdup(subject);
3370                         else /* ok, we've got utf8 in the string. */
3371                         {
3372                                 msg->cm_fields['U'] = rfc2047encode(subject, length);
3373                         }
3374
3375                 }
3376         }
3377
3378         if (supplied_euid != NULL) {
3379                 msg->cm_fields['E'] = strdup(supplied_euid);
3380         }
3381
3382         if (references != NULL) {
3383                 if (!IsEmptyStr(references)) {
3384                         msg->cm_fields['W'] = strdup(references);
3385                 }
3386         }
3387
3388         if (preformatted_text != NULL) {
3389                 msg->cm_fields['M'] = preformatted_text;
3390         }
3391         else {
3392                 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3393         }
3394
3395         return(msg);
3396 }
3397
3398
3399 /*
3400  * Check to see whether we have permission to post a message in the current
3401  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3402  * returns 0 on success.
3403  */
3404 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, 
3405                                           size_t n, 
3406                                           const char* RemoteIdentifier,
3407                                           int PostPublic) {
3408         int ra;
3409
3410         if (!(CC->logged_in) && 
3411             (PostPublic == POST_LOGGED_IN)) {
3412                 snprintf(errmsgbuf, n, "Not logged in.");
3413                 return (ERROR + NOT_LOGGED_IN);
3414         }
3415         else if (PostPublic == CHECK_EXISTANCE) {
3416                 return (0); // We're Evaling whether a recipient exists
3417         }
3418         else if (!(CC->logged_in)) {
3419                 
3420                 if ((CC->room.QRflags & QR_READONLY)) {
3421                         snprintf(errmsgbuf, n, "Not logged in.");
3422                         return (ERROR + NOT_LOGGED_IN);
3423                 }
3424                 if (CC->room.QRflags2 & QR2_MODERATED) {
3425                         snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3426                         return (ERROR + NOT_LOGGED_IN);
3427                 }
3428                 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3429                         SpoolControl *sc;
3430                         char filename[SIZ];
3431                         int found;
3432
3433                         if (RemoteIdentifier == NULL)
3434                         {
3435                                 snprintf(errmsgbuf, n, "Need sender to permit access.");
3436                                 return (ERROR + USERNAME_REQUIRED);
3437                         }
3438
3439                         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3440                         begin_critical_section(S_NETCONFIGS);
3441                         if (!read_spoolcontrol_file(&sc, filename))
3442                         {
3443                                 end_critical_section(S_NETCONFIGS);
3444                                 snprintf(errmsgbuf, n,
3445                                         "This mailing list only accepts posts from subscribers.");
3446                                 return (ERROR + NO_SUCH_USER);
3447                         }
3448                         end_critical_section(S_NETCONFIGS);
3449                         found = is_recipient (sc, RemoteIdentifier);
3450                         free_spoolcontrol_struct(&sc);
3451                         if (found) {
3452                                 return (0);
3453                         }
3454                         else {
3455                                 snprintf(errmsgbuf, n,
3456                                         "This mailing list only accepts posts from subscribers.");
3457                                 return (ERROR + NO_SUCH_USER);
3458                         }
3459                 }
3460                 return (0);
3461
3462         }
3463
3464         if ((CC->user.axlevel < 2)
3465             && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3466                 snprintf(errmsgbuf, n, "Need to be validated to enter "
3467                                 "(except in %s> to sysop)", MAILROOM);
3468                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3469         }
3470
3471         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3472         if (!(ra & UA_POSTALLOWED)) {
3473                 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3474                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3475         }
3476
3477         strcpy(errmsgbuf, "Ok");
3478         return(0);
3479 }
3480
3481
3482 /*
3483  * Check to see if the specified user has Internet mail permission
3484  * (returns nonzero if permission is granted)
3485  */
3486 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3487
3488         /* Do not allow twits to send Internet mail */
3489         if (who->axlevel <= 2) return(0);
3490
3491         /* Globally enabled? */
3492         if (config.c_restrict == 0) return(1);
3493
3494         /* User flagged ok? */
3495         if (who->flags & US_INTERNET) return(2);
3496
3497         /* Aide level access? */
3498         if (who->axlevel >= 6) return(3);
3499
3500         /* No mail for you! */
3501         return(0);
3502 }
3503
3504
3505 /*
3506  * Validate recipients, count delivery types and errors, and handle aliasing
3507  * FIXME check for dupes!!!!!
3508  *
3509  * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
3510  * were specified, or the number of addresses found invalid.
3511  *
3512  * Caller needs to free the result using free_recipients()
3513  */
3514 struct recptypes *validate_recipients(char *supplied_recipients, 
3515                                       const char *RemoteIdentifier, 
3516                                       int Flags) {
3517         struct recptypes *ret;
3518         char *recipients = NULL;
3519         char this_recp[256];
3520         char this_recp_cooked[256];
3521         char append[SIZ];
3522         int num_recps = 0;
3523         int i, j;
3524         int mailtype;
3525         int invalid;
3526         struct ctdluser tempUS;
3527         struct ctdlroom tempQR;
3528         struct ctdlroom tempQR2;
3529         int err = 0;
3530         char errmsg[SIZ];
3531         int in_quotes = 0;
3532
3533         /* Initialize */
3534         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3535         if (ret == NULL) return(NULL);
3536
3537         /* Set all strings to null and numeric values to zero */
3538         memset(ret, 0, sizeof(struct recptypes));
3539
3540         if (supplied_recipients == NULL) {
3541                 recipients = strdup("");
3542         }
3543         else {
3544                 recipients = strdup(supplied_recipients);
3545         }
3546
3547         /* Allocate some memory.  Yes, this allocates 500% more memory than we will
3548          * actually need, but it's healthier for the heap than doing lots of tiny
3549          * realloc() calls instead.
3550          */
3551
3552         ret->errormsg = malloc(strlen(recipients) + 1024);
3553         ret->recp_local = malloc(strlen(recipients) + 1024);
3554         ret->recp_internet = malloc(strlen(recipients) + 1024);
3555         ret->recp_ignet = malloc(strlen(recipients) + 1024);
3556         ret->recp_room = malloc(strlen(recipients) + 1024);
3557         ret->display_recp = malloc(strlen(recipients) + 1024);
3558
3559         ret->errormsg[0] = 0;
3560         ret->recp_local[0] = 0;
3561         ret->recp_internet[0] = 0;
3562         ret->recp_ignet[0] = 0;
3563         ret->recp_room[0] = 0;
3564         ret->display_recp[0] = 0;
3565
3566         ret->recptypes_magic = RECPTYPES_MAGIC;
3567
3568         /* Change all valid separator characters to commas */
3569         for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3570                 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3571                         recipients[i] = ',';
3572                 }
3573         }
3574
3575         /* Now start extracting recipients... */
3576
3577         while (!IsEmptyStr(recipients)) {
3578
3579                 for (i=0; i<=strlen(recipients); ++i) {
3580                         if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3581                         if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3582                                 safestrncpy(this_recp, recipients, i+1);
3583                                 this_recp[i] = 0;
3584                                 if (recipients[i] == ',') {
3585                                         strcpy(recipients, &recipients[i+1]);
3586                                 }
3587                                 else {
3588                                         strcpy(recipients, "");
3589                                 }
3590                                 break;
3591                         }
3592                 }
3593
3594                 striplt(this_recp);
3595                 if (IsEmptyStr(this_recp))
3596                         break;
3597                 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3598                 ++num_recps;
3599                 mailtype = alias(this_recp);
3600                 mailtype = alias(this_recp);
3601                 mailtype = alias(this_recp);
3602                 j = 0;
3603                 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3604                         if (this_recp[j]=='_') {
3605                                 this_recp_cooked[j] = ' ';
3606                         }
3607                         else {
3608                                 this_recp_cooked[j] = this_recp[j];
3609                         }
3610                 }
3611                 this_recp_cooked[j] = '\0';
3612                 invalid = 0;
3613                 errmsg[0] = 0;
3614                 switch(mailtype) {
3615                         case MES_LOCAL:
3616                                 if (!strcasecmp(this_recp, "sysop")) {
3617                                         ++ret->num_room;
3618                                         strcpy(this_recp, config.c_aideroom);
3619                                         if (!IsEmptyStr(ret->recp_room)) {
3620                                                 strcat(ret->recp_room, "|");
3621                                         }
3622                                         strcat(ret->recp_room, this_recp);
3623                                 }
3624                                 else if ( (!strncasecmp(this_recp, "room_", 5))
3625                                       && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3626
3627                                         /* Save room so we can restore it later */
3628                                         tempQR2 = CC->room;
3629                                         CC->room = tempQR;
3630                                         
3631                                         /* Check permissions to send mail to this room */
3632                                         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, 
3633                                                                                     sizeof errmsg, 
3634                                                                                     RemoteIdentifier,
3635                                                                                     Flags
3636                                         );
3637                                         if (err)
3638                                         {
3639                                                 ++ret->num_error;
3640                                                 invalid = 1;
3641                                         } 
3642                                         else {
3643                                                 ++ret->num_room;
3644                                                 if (!IsEmptyStr(ret->recp_room)) {
3645                                                         strcat(ret->recp_room, "|");
3646                                                 }
3647                                                 strcat(ret->recp_room, &this_recp_cooked[5]);
3648                                         }
3649                                         
3650                                         /* Restore room in case something needs it */
3651                                         CC->room = tempQR2;
3652
3653                                 }
3654                                 else if (getuser(&tempUS, this_recp) == 0) {
3655                                         ++ret->num_local;
3656                                         strcpy(this_recp, tempUS.fullname);
3657                                         if (!IsEmptyStr(ret->recp_local)) {
3658                                                 strcat(ret->recp_local, "|");
3659                                         }
3660                                         strcat(ret->recp_local, this_recp);
3661                                 }
3662                                 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3663                                         ++ret->num_local;
3664                                         strcpy(this_recp, tempUS.fullname);
3665                                         if (!IsEmptyStr(ret->recp_local)) {
3666                                                 strcat(ret->recp_local, "|");
3667                                         }
3668                                         strcat(ret->recp_local, this_recp);
3669                                 }
3670                                 else {
3671                                         ++ret->num_error;
3672                                         invalid = 1;
3673                                 }
3674                                 break;
3675                         case MES_INTERNET:
3676                                 /* Yes, you're reading this correctly: if the target
3677                                  * domain points back to the local system or an attached
3678                                  * Citadel directory, the address is invalid.  That's
3679                                  * because if the address were valid, we would have
3680                                  * already translated it to a local address by now.
3681                                  */
3682                                 if (IsDirectory(this_recp, 0)) {
3683                                         ++ret->num_error;
3684                                         invalid = 1;
3685                                 }
3686                                 else {
3687                                         ++ret->num_internet;
3688                                         if (!IsEmptyStr(ret->recp_internet)) {
3689                                                 strcat(ret->recp_internet, "|");
3690                                         }
3691                                         strcat(ret->recp_internet, this_recp);
3692                                 }
3693                                 break;
3694                         case MES_IGNET:
3695                                 ++ret->num_ignet;
3696                                 if (!IsEmptyStr(ret->recp_ignet)) {
3697                                         strcat(ret->recp_ignet, "|");
3698                                 }
3699                                 strcat(ret->recp_ignet, this_recp);
3700                                 break;
3701                         case MES_ERROR:
3702                                 ++ret->num_error;
3703                                 invalid = 1;
3704                                 break;
3705                 }
3706                 if (invalid) {
3707                         if (IsEmptyStr(errmsg)) {
3708                                 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3709                         }
3710                         else {
3711                                 snprintf(append, sizeof append, "%s", errmsg);
3712                         }
3713                         if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3714                                 if (!IsEmptyStr(ret->errormsg)) {
3715                                         strcat(ret->errormsg, "; ");
3716                                 }
3717                                 strcat(ret->errormsg, append);
3718                         }
3719                 }
3720                 else {
3721                         if (IsEmptyStr(ret->display_recp)) {
3722                                 strcpy(append, this_recp);
3723                         }
3724                         else {
3725                                 snprintf(append, sizeof append, ", %s", this_recp);
3726                         }
3727                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3728                                 strcat(ret->display_recp, append);
3729                         }
3730                 }
3731         }
3732
3733         if ((ret->num_local + ret->num_internet + ret->num_ignet +
3734            ret->num_room + ret->num_error) == 0) {
3735                 ret->num_error = (-1);
3736                 strcpy(ret->errormsg, "No recipients specified.");
3737         }
3738
3739         CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3740         CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3741         CtdlLogPrintf(CTDL_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
3742         CtdlLogPrintf(CTDL_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3743         CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3744         CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3745
3746         free(recipients);
3747         return(ret);
3748 }
3749
3750
3751 /*
3752  * Destructor for struct recptypes
3753  */
3754 void free_recipients(struct recptypes *valid) {
3755
3756         if (valid == NULL) {
3757                 return;
3758         }
3759
3760         if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3761                 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3762                 abort();
3763         }
3764
3765         if (valid->errormsg != NULL)            free(valid->errormsg);
3766         if (valid->recp_local != NULL)          free(valid->recp_local);
3767         if (valid->recp_internet != NULL)       free(valid->recp_internet);
3768         if (valid->recp_ignet != NULL)          free(valid->recp_ignet);
3769         if (valid->recp_room != NULL)           free(valid->recp_room);
3770         if (valid->display_recp != NULL)        free(valid->display_recp);
3771         if (valid->bounce_to != NULL)           free(valid->bounce_to);
3772         if (valid->envelope_from != NULL)       free(valid->envelope_from);
3773         free(valid);
3774 }
3775
3776
3777
3778 /*
3779  * message entry  -  mode 0 (normal)
3780  */
3781 void cmd_ent0(char *entargs)
3782 {
3783         int post = 0;
3784         char recp[SIZ];
3785         char cc[SIZ];
3786         char bcc[SIZ];
3787         char supplied_euid[128];
3788         int anon_flag = 0;
3789         int format_type = 0;
3790         char newusername[256];
3791         char newuseremail[256];
3792         struct CtdlMessage *msg;
3793         int anonymous = 0;
3794         char errmsg[SIZ];
3795         int err = 0;
3796         struct recptypes *valid = NULL;
3797         struct recptypes *valid_to = NULL;
3798         struct recptypes *valid_cc = NULL;
3799         struct recptypes *valid_bcc = NULL;
3800         char subject[SIZ];
3801         int subject_required = 0;
3802         int do_confirm = 0;
3803         long msgnum;
3804         int i, j;
3805         char buf[256];
3806         int newuseremail_ok = 0;
3807         char references[SIZ];
3808         char *ptr;
3809
3810         unbuffer_output();
3811
3812         post = extract_int(entargs, 0);
3813         extract_token(recp, entargs, 1, '|', sizeof recp);
3814         anon_flag = extract_int(entargs, 2);
3815         format_type = extract_int(entargs, 3);
3816         extract_token(subject, entargs, 4, '|', sizeof subject);
3817         extract_token(newusername, entargs, 5, '|', sizeof newusername);
3818         do_confirm = extract_int(entargs, 6);
3819         extract_token(cc, entargs, 7, '|', sizeof cc);
3820         extract_token(bcc, entargs, 8, '|', sizeof bcc);
3821         switch(CC->room.QRdefaultview) {
3822                 case VIEW_NOTES:
3823                 case VIEW_WIKI:
3824                         extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3825                         break;
3826                 default:
3827                         supplied_euid[0] = 0;
3828                         break;
3829         }
3830         extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3831         extract_token(references, entargs, 11, '|', sizeof references);
3832         for (ptr=references; *ptr != 0; ++ptr) {
3833                 if (*ptr == '!') *ptr = '|';
3834         }
3835
3836         /* first check to make sure the request is valid. */
3837
3838         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3839         if (err)
3840         {
3841                 cprintf("%d %s\n", err, errmsg);
3842                 return;
3843         }
3844
3845         /* Check some other permission type things. */
3846
3847         if (IsEmptyStr(newusername)) {
3848                 strcpy(newusername, CC->user.fullname);
3849         }
3850         if (  (CC->user.axlevel < 6)
3851            && (strcasecmp(newusername, CC->user.fullname))
3852            && (strcasecmp(newusername, CC->cs_inet_fn))
3853         ) {     
3854                 cprintf("%d You don't have permission to author messages as '%s'.\n",
3855                         ERROR + HIGHER_ACCESS_REQUIRED,
3856                         newusername
3857                 );
3858                 return;
3859         }
3860
3861
3862         if (IsEmptyStr(newuseremail)) {
3863                 newuseremail_ok = 1;
3864         }
3865
3866         if (!IsEmptyStr(newuseremail)) {
3867                 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3868                         newuseremail_ok = 1;
3869                 }
3870                 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3871                         j = num_tokens(CC->cs_inet_other_emails, '|');
3872                         for (i=0; i<j; ++i) {
3873                                 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3874                                 if (!strcasecmp(newuseremail, buf)) {
3875                                         newuseremail_ok = 1;
3876                                 }
3877                         }
3878                 }
3879         }
3880
3881         if (!newuseremail_ok) {
3882                 cprintf("%d You don't have permission to author messages as '%s'.\n",
3883                         ERROR + HIGHER_ACCESS_REQUIRED,
3884                         newuseremail
3885                 );
3886                 return;
3887         }
3888
3889         CC->cs_flags |= CS_POSTING;
3890
3891         /* In mailbox rooms we have to behave a little differently --
3892          * make sure the user has specified at least one recipient.  Then
3893          * validate the recipient(s).  We do this for the Mail> room, as
3894          * well as any room which has the "Mailbox" view set.
3895          */
3896
3897         if (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3898            || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3899         ) {
3900                 if (CC->user.axlevel < 2) {
3901                         strcpy(recp, "sysop");
3902                         strcpy(cc, "");
3903                         strcpy(bcc, "");
3904                 }
3905
3906                 valid_to = validate_recipients(recp, NULL, 0);
3907                 if (valid_to->num_error > 0) {
3908                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3909                         free_recipients(valid_to);
3910                         return;
3911                 }
3912
3913                 valid_cc = validate_recipients(cc, NULL, 0);
3914                 if (valid_cc->num_error > 0) {
3915                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3916                         free_recipients(valid_to);
3917                         free_recipients(valid_cc);
3918                         return;
3919                 }
3920
3921                 valid_bcc = validate_recipients(bcc, NULL, 0);
3922                 if (valid_bcc->num_error > 0) {
3923                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3924                         free_recipients(valid_to);
3925                         free_recipients(valid_cc);
3926                         free_recipients(valid_bcc);
3927                         return;
3928                 }
3929
3930                 /* Recipient required, but none were specified */
3931                 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3932                         free_recipients(valid_to);
3933                         free_recipients(valid_cc);
3934                         free_recipients(valid_bcc);
3935                         cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3936                         return;
3937                 }
3938
3939                 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3940                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3941                                 cprintf("%d You do not have permission "
3942                                         "to send Internet mail.\n",
3943                                         ERROR + HIGHER_ACCESS_REQUIRED);
3944                                 free_recipients(valid_to);
3945                                 free_recipients(valid_cc);
3946                                 free_recipients(valid_bcc);
3947                                 return;
3948                         }
3949                 }
3950
3951                 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)
3952                    && (CC->user.axlevel < 4) ) {
3953                         cprintf("%d Higher access required for network mail.\n",
3954                                 ERROR + HIGHER_ACCESS_REQUIRED);
3955                         free_recipients(valid_to);
3956                         free_recipients(valid_cc);
3957                         free_recipients(valid_bcc);
3958                         return;
3959                 }
3960         
3961                 if ((RESTRICT_INTERNET == 1)
3962                     && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3963                     && ((CC->user.flags & US_INTERNET) == 0)
3964                     && (!CC->internal_pgm)) {
3965                         cprintf("%d You don't have access to Internet mail.\n",
3966                                 ERROR + HIGHER_ACCESS_REQUIRED);
3967                         free_recipients(valid_to);
3968                         free_recipients(valid_cc);
3969                         free_recipients(valid_bcc);
3970                         return;
3971                 }
3972
3973         }
3974
3975         /* Is this a room which has anonymous-only or anonymous-option? */
3976         anonymous = MES_NORMAL;
3977         if (CC->room.QRflags & QR_ANONONLY) {
3978                 anonymous = MES_ANONONLY;
3979         }
3980         if (CC->room.QRflags & QR_ANONOPT) {
3981                 if (anon_flag == 1) {   /* only if the user requested it */
3982                         anonymous = MES_ANONOPT;
3983                 }
3984         }
3985
3986         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3987                 recp[0] = 0;
3988         }
3989
3990         /* Recommend to the client that the use of a message subject is
3991          * strongly recommended in this room, if either the SUBJECTREQ flag
3992          * is set, or if there is one or more Internet email recipients.
3993          */
3994         if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3995         if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
3996         if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
3997         if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
3998
3999         /* If we're only checking the validity of the request, return
4000          * success without creating the message.
4001          */
4002         if (post == 0) {
4003                 cprintf("%d %s|%d\n", CIT_OK,
4004                         ((valid_to != NULL) ? valid_to->display_recp : ""), 
4005                         subject_required);
4006                 free_recipients(valid_to);
4007                 free_recipients(valid_cc);
4008                 free_recipients(valid_bcc);
4009                 return;
4010         }
4011
4012         /* We don't need these anymore because we'll do it differently below */
4013         free_recipients(valid_to);
4014         free_recipients(valid_cc);
4015         free_recipients(valid_bcc);
4016
4017         /* Read in the message from the client. */
4018         if (do_confirm) {
4019                 cprintf("%d send message\n", START_CHAT_MODE);
4020         } else {
4021                 cprintf("%d send message\n", SEND_LISTING);
4022         }
4023
4024         msg = CtdlMakeMessage(&CC->user, recp, cc,
4025                 CC->room.QRname, anonymous, format_type,
4026                 newusername, newuseremail, subject,
4027                 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4028                 NULL, references);
4029
4030         /* Put together one big recipients struct containing to/cc/bcc all in
4031          * one.  This is for the envelope.
4032          */
4033         char *all_recps = malloc(SIZ * 3);
4034         strcpy(all_recps, recp);
4035         if (!IsEmptyStr(cc)) {
4036                 if (!IsEmptyStr(all_recps)) {
4037                         strcat(all_recps, ",");
4038                 }
4039                 strcat(all_recps, cc);
4040         }
4041         if (!IsEmptyStr(bcc)) {
4042                 if (!IsEmptyStr(all_recps)) {
4043                         strcat(all_recps, ",");
4044                 }
4045                 strcat(all_recps, bcc);
4046         }
4047         if (!IsEmptyStr(all_recps)) {
4048                 valid = validate_recipients(all_recps, NULL, 0);
4049         }
4050         else {
4051                 valid = NULL;
4052         }
4053         free(all_recps);
4054
4055         if (msg != NULL) {
4056                 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4057
4058                 if (do_confirm) {
4059                         cprintf("%ld\n", msgnum);
4060                         if (msgnum >= 0L) {
4061                                 cprintf("Message accepted.\n");
4062                         }
4063                         else {
4064                                 cprintf("Internal error.\n");
4065                         }
4066                         if (msg->cm_fields['E'] != NULL) {
4067                                 cprintf("%s\n", msg->cm_fields['E']);
4068                         } else {
4069                                 cprintf("\n");
4070                         }
4071                         cprintf("000\n");
4072                 }
4073
4074                 CtdlFreeMessage(msg);
4075         }
4076         if (valid != NULL) {
4077                 free_recipients(valid);
4078         }
4079         return;
4080 }
4081
4082
4083
4084 /*
4085  * API function to delete messages which match a set of criteria
4086  * (returns the actual number of messages deleted)
4087  */
4088 int CtdlDeleteMessages(char *room_name,         /* which room */
4089                         long *dmsgnums,         /* array of msg numbers to be deleted */
4090                         int num_dmsgnums,       /* number of msgs to be deleted, or 0 for "any" */
4091                         char *content_type      /* or "" for any.  regular expressions expected. */
4092 )
4093 {
4094         struct ctdlroom qrbuf;
4095         struct cdbdata *cdbfr;
4096         long *msglist = NULL;
4097         long *dellist = NULL;
4098         int num_msgs = 0;
4099         int i, j;
4100         int num_deleted = 0;
4101         int delete_this;
4102         struct MetaData smi;
4103         regex_t re;
4104         regmatch_t pm;
4105         int need_to_free_re = 0;
4106
4107         if (content_type) if (!IsEmptyStr(content_type)) {
4108                 regcomp(&re, content_type, 0);
4109                 need_to_free_re = 1;
4110         }
4111         CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4112                 room_name, num_dmsgnums, content_type);
4113
4114         /* get room record, obtaining a lock... */
4115         if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4116                 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4117                         room_name);
4118                 if (need_to_free_re) regfree(&re);
4119                 return (0);     /* room not found */
4120         }
4121         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4122
4123         if (cdbfr != NULL) {
4124                 dellist = malloc(cdbfr->len);
4125                 msglist = (long *) cdbfr->ptr;
4126                 cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
4127                 num_msgs = cdbfr->len / sizeof(long);
4128                 cdb_free(cdbfr);
4129         }
4130         if (num_msgs > 0) {
4131                 for (i = 0; i < num_msgs; ++i) {
4132                         delete_this = 0x00;
4133
4134                         /* Set/clear a bit for each criterion */
4135
4136                         /* 0 messages in the list or a null list means that we are
4137                          * interested in deleting any messages which meet the other criteria.
4138                          */
4139                         if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4140                                 delete_this |= 0x01;
4141                         }
4142                         else {
4143                                 for (j=0; j<num_dmsgnums; ++j) {
4144                                         if (msglist[i] == dmsgnums[j]) {
4145                                                 delete_this |= 0x01;
4146                                         }
4147                                 }
4148                         }
4149
4150                         if (IsEmptyStr(content_type)) {
4151                                 delete_this |= 0x02;
4152                         } else {
4153                                 GetMetaData(&smi, msglist[i]);
4154                                 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4155                                         delete_this |= 0x02;
4156                                 }
4157                         }
4158
4159                         /* Delete message only if all bits are set */
4160                         if (delete_this == 0x03) {
4161                                 dellist[num_deleted++] = msglist[i];
4162                                 msglist[i] = 0L;
4163                         }
4164                 }
4165
4166                 num_msgs = sort_msglist(msglist, num_msgs);
4167                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4168                           msglist, (int)(num_msgs * sizeof(long)));
4169
4170                 qrbuf.QRhighest = msglist[num_msgs - 1];
4171         }
4172         CtdlPutRoomLock(&qrbuf);
4173
4174         /* Go through the messages we pulled out of the index, and decrement
4175          * their reference counts by 1.  If this is the only room the message
4176          * was in, the reference count will reach zero and the message will
4177          * automatically be deleted from the database.  We do this in a
4178          * separate pass because there might be plug-in hooks getting called,
4179          * and we don't want that happening during an S_ROOMS critical
4180          * section.
4181          */
4182         if (num_deleted) for (i=0; i<num_deleted; ++i) {
4183                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4184                 AdjRefCount(dellist[i], -1);
4185         }
4186
4187         /* Now free the memory we used, and go away. */
4188         if (msglist != NULL) free(msglist);
4189         if (dellist != NULL) free(dellist);
4190         CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4191         if (need_to_free_re) regfree(&re);
4192         return (num_deleted);
4193 }
4194
4195
4196
4197 /*
4198  * Check whether the current user has permission to delete messages from
4199  * the current room (returns 1 for yes, 0 for no)
4200  */
4201 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4202         int ra;
4203         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4204         if (ra & UA_DELETEALLOWED) return(1);
4205         return(0);
4206 }
4207
4208
4209
4210
4211 /*
4212  * Delete message from current room
4213  */
4214 void cmd_dele(char *args)
4215 {
4216         int num_deleted;
4217         int i;
4218         char msgset[SIZ];
4219         char msgtok[32];
4220         long *msgs;
4221         int num_msgs = 0;
4222
4223         extract_token(msgset, args, 0, '|', sizeof msgset);
4224         num_msgs = num_tokens(msgset, ',');
4225         if (num_msgs < 1) {
4226                 cprintf("%d Nothing to do.\n", CIT_OK);
4227                 return;
4228         }
4229
4230         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4231                 cprintf("%d Higher access required.\n",
4232                         ERROR + HIGHER_ACCESS_REQUIRED);
4233                 return;
4234         }
4235
4236         /*
4237          * Build our message set to be moved/copied
4238          */
4239         msgs = malloc(num_msgs * sizeof(long));
4240         for (i=0; i<num_msgs; ++i) {
4241                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4242                 msgs[i] = atol(msgtok);
4243         }
4244
4245         num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4246         free(msgs);
4247
4248         if (num_deleted) {
4249                 cprintf("%d %d message%s deleted.\n", CIT_OK,
4250                         num_deleted, ((num_deleted != 1) ? "s" : ""));
4251         } else {
4252                 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4253         }
4254 }
4255
4256
4257
4258
4259 /*
4260  * move or copy a message to another room
4261  */
4262 void cmd_move(char *args)
4263 {
4264         char msgset[SIZ];
4265         char msgtok[32];
4266         long *msgs;
4267         int num_msgs = 0;
4268
4269         char targ[ROOMNAMELEN];
4270         struct ctdlroom qtemp;
4271         int err;
4272         int is_copy = 0;
4273         int ra;
4274         int permit = 0;
4275         int i;
4276
4277         extract_token(msgset, args, 0, '|', sizeof msgset);
4278         num_msgs = num_tokens(msgset, ',');
4279         if (num_msgs < 1) {
4280                 cprintf("%d Nothing to do.\n", CIT_OK);
4281                 return;
4282         }
4283
4284         extract_token(targ, args, 1, '|', sizeof targ);
4285         convert_room_name_macros(targ, sizeof targ);
4286         targ[ROOMNAMELEN - 1] = 0;
4287         is_copy = extract_int(args, 2);
4288
4289         if (CtdlGetRoom(&qtemp, targ) != 0) {
4290                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4291                 return;
4292         }
4293
4294         if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4295                 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4296                 return;
4297         }
4298
4299         getuser(&CC->user, CC->curr_user);
4300         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4301
4302         /* Check for permission to perform this operation.
4303          * Remember: "CC->room" is source, "qtemp" is target.
4304          */
4305         permit = 0;
4306
4307         /* Aides can move/copy */
4308         if (CC->user.axlevel >= 6) permit = 1;
4309
4310         /* Room aides can move/copy */
4311         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4312
4313         /* Permit move/copy from personal rooms */
4314         if ((CC->room.QRflags & QR_MAILBOX)
4315            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4316
4317         /* Permit only copy from public to personal room */
4318         if ( (is_copy)
4319            && (!(CC->room.QRflags & QR_MAILBOX))
4320            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4321
4322         /* Permit message removal from collaborative delete rooms */
4323         if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4324
4325         /* Users allowed to post into the target room may move into it too. */
4326         if ((CC->room.QRflags & QR_MAILBOX) && 
4327             (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
4328
4329         /* User must have access to target room */
4330         if (!(ra & UA_KNOWN))  permit = 0;
4331
4332         if (!permit) {
4333                 cprintf("%d Higher access required.\n",
4334                         ERROR + HIGHER_ACCESS_REQUIRED);
4335                 return;
4336         }
4337
4338         /*
4339          * Build our message set to be moved/copied
4340          */
4341         msgs = malloc(num_msgs * sizeof(long));
4342         for (i=0; i<num_msgs; ++i) {
4343                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4344                 msgs[i] = atol(msgtok);
4345         }
4346
4347         /*
4348          * Do the copy
4349          */
4350         err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4351         if (err != 0) {
4352                 cprintf("%d Cannot store message(s) in %s: error %d\n",
4353                         err, targ, err);
4354                 free(msgs);
4355                 return;
4356         }
4357
4358         /* Now delete the message from the source room,
4359          * if this is a 'move' rather than a 'copy' operation.
4360          */
4361         if (is_copy == 0) {
4362                 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4363         }
4364         free(msgs);
4365
4366         cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4367 }
4368
4369
4370
4371 /*
4372  * GetMetaData()  -  Get the supplementary record for a message
4373  */
4374 void GetMetaData(struct MetaData *smibuf, long msgnum)
4375 {
4376
4377         struct cdbdata *cdbsmi;
4378         long TheIndex;
4379
4380         memset(smibuf, 0, sizeof(struct MetaData));
4381         smibuf->meta_msgnum = msgnum;
4382         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
4383
4384         /* Use the negative of the message number for its supp record index */
4385         TheIndex = (0L - msgnum);
4386
4387         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4388         if (cdbsmi == NULL) {
4389                 return;         /* record not found; go with defaults */
4390         }
4391         memcpy(smibuf, cdbsmi->ptr,
4392                ((cdbsmi->len > sizeof(struct MetaData)) ?
4393                 sizeof(struct MetaData) : cdbsmi->len));
4394         cdb_free(cdbsmi);
4395         return;
4396 }
4397
4398
4399 /*
4400  * PutMetaData()  -  (re)write supplementary record for a message
4401  */
4402 void PutMetaData(struct MetaData *smibuf)
4403 {
4404         long TheIndex;
4405
4406         /* Use the negative of the message number for the metadata db index */
4407         TheIndex = (0L - smibuf->meta_msgnum);
4408
4409         cdb_store(CDB_MSGMAIN,
4410                   &TheIndex, (int)sizeof(long),
4411                   smibuf, (int)sizeof(struct MetaData));
4412
4413 }
4414
4415 /*
4416  * AdjRefCount  -  submit an adjustment to the reference count for a message.
4417  *                 (These are just queued -- we actually process them later.)
4418  */
4419 void AdjRefCount(long msgnum, int incr)
4420 {
4421         struct arcq new_arcq;
4422         int rv = 0;
4423
4424         begin_critical_section(S_SUPPMSGMAIN);
4425         if (arcfp == NULL) {
4426                 arcfp = fopen(file_arcq, "ab+");
4427         }
4428         end_critical_section(S_SUPPMSGMAIN);
4429
4430         /* msgnum < 0 means that we're trying to close the file */
4431         if (msgnum < 0) {
4432                 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4433                 begin_critical_section(S_SUPPMSGMAIN);
4434                 if (arcfp != NULL) {
4435                         fclose(arcfp);
4436                         arcfp = NULL;
4437                 }
4438                 end_critical_section(S_SUPPMSGMAIN);
4439                 return;
4440         }
4441
4442         /*
4443          * If we can't open the queue, perform the operation synchronously.
4444          */
4445         if (arcfp == NULL) {
4446                 TDAP_AdjRefCount(msgnum, incr);
4447                 return;
4448         }
4449
4450         new_arcq.arcq_msgnum = msgnum;
4451         new_arcq.arcq_delta = incr;
4452         rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4453         fflush(arcfp);
4454
4455         return;
4456 }
4457
4458
4459 /*
4460  * TDAP_ProcessAdjRefCountQueue()
4461  *
4462  * Process the queue of message count adjustments that was created by calls
4463  * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4464  * for each one.  This should be an "off hours" operation.
4465  */
4466 int TDAP_ProcessAdjRefCountQueue(void)
4467 {
4468         char file_arcq_temp[PATH_MAX];
4469         int r;
4470         FILE *fp;
4471         struct arcq arcq_rec;
4472         int num_records_processed = 0;
4473
4474         snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4475
4476         begin_critical_section(S_SUPPMSGMAIN);
4477         if (arcfp != NULL) {
4478                 fclose(arcfp);
4479                 arcfp = NULL;
4480         }
4481
4482         r = link(file_arcq, file_arcq_temp);
4483         if (r != 0) {
4484                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4485                 end_critical_section(S_SUPPMSGMAIN);
4486                 return(num_records_processed);
4487         }
4488
4489         unlink(file_arcq);
4490         end_critical_section(S_SUPPMSGMAIN);
4491
4492         fp = fopen(file_arcq_temp, "rb");
4493         if (fp == NULL) {
4494                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4495                 return(num_records_processed);
4496         }
4497
4498         while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4499                 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4500                 ++num_records_processed;
4501         }
4502
4503         fclose(fp);
4504         r = unlink(file_arcq_temp);
4505         if (r != 0) {
4506                 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4507         }
4508
4509         return(num_records_processed);
4510 }
4511
4512
4513
4514 /*
4515  * TDAP_AdjRefCount  -  adjust the reference count for a message.
4516  *                      This one does it "for real" because it's called by
4517  *                      the autopurger function that processes the queue
4518  *                      created by AdjRefCount().   If a message's reference
4519  *                      count becomes zero, we also delete the message from
4520  *                      disk and de-index it.
4521  */
4522 void TDAP_AdjRefCount(long msgnum, int incr)
4523 {
4524
4525         struct MetaData smi;
4526         long delnum;
4527
4528         /* This is a *tight* critical section; please keep it that way, as
4529          * it may get called while nested in other critical sections.  
4530          * Complicating this any further will surely cause deadlock!
4531          */
4532         begin_critical_section(S_SUPPMSGMAIN);
4533         GetMetaData(&smi, msgnum);
4534         smi.meta_refcount += incr;
4535         PutMetaData(&smi);
4536         end_critical_section(S_SUPPMSGMAIN);
4537         CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4538                 msgnum, incr, smi.meta_refcount);
4539
4540         /* If the reference count is now zero, delete the message
4541          * (and its supplementary record as well).
4542          */
4543         if (smi.meta_refcount == 0) {
4544                 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4545                 
4546                 /* Call delete hooks with NULL room to show it has gone altogether */
4547                 PerformDeleteHooks(NULL, msgnum);
4548
4549                 /* Remove from message base */
4550                 delnum = msgnum;
4551                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4552                 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4553
4554                 /* Remove metadata record */
4555                 delnum = (0L - msgnum);
4556                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4557         }
4558
4559 }
4560
4561 /*
4562  * Write a generic object to this room
4563  *
4564  * Note: this could be much more efficient.  Right now we use two temporary
4565  * files, and still pull the message into memory as with all others.
4566  */
4567 void CtdlWriteObject(char *req_room,                    /* Room to stuff it in */
4568                         char *content_type,             /* MIME type of this object */
4569                         char *raw_message,              /* Data to be written */
4570                         off_t raw_length,               /* Size of raw_message */
4571                         struct ctdluser *is_mailbox,    /* Mailbox room? */
4572                         int is_binary,                  /* Is encoding necessary? */
4573                         int is_unique,                  /* Del others of this type? */
4574                         unsigned int flags              /* Internal save flags */
4575                         )
4576 {
4577
4578         struct ctdlroom qrbuf;
4579         char roomname[ROOMNAMELEN];
4580         struct CtdlMessage *msg;
4581         char *encoded_message = NULL;
4582
4583         if (is_mailbox != NULL) {
4584                 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4585         }
4586         else {
4587                 safestrncpy(roomname, req_room, sizeof(roomname));
4588         }
4589
4590         CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4591
4592         if (is_binary) {
4593                 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4594         }
4595         else {
4596                 encoded_message = malloc((size_t)(raw_length + 4096));
4597         }
4598
4599         sprintf(encoded_message, "Content-type: %s\n", content_type);
4600
4601         if (is_binary) {
4602                 sprintf(&encoded_message[strlen(encoded_message)],
4603                         "Content-transfer-encoding: base64\n\n"
4604                 );
4605         }
4606         else {
4607                 sprintf(&encoded_message[strlen(encoded_message)],
4608                         "Content-transfer-encoding: 7bit\n\n"
4609                 );
4610         }
4611
4612         if (is_binary) {
4613                 CtdlEncodeBase64(
4614                         &encoded_message[strlen(encoded_message)],
4615                         raw_message,
4616                         (int)raw_length,
4617                         0
4618                 );
4619         }
4620         else {
4621                 memcpy(
4622                         &encoded_message[strlen(encoded_message)],
4623                         raw_message,
4624                         (int)(raw_length+1)
4625                 );
4626         }
4627
4628         CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4629         msg = malloc(sizeof(struct CtdlMessage));
4630         memset(msg, 0, sizeof(struct CtdlMessage));
4631         msg->cm_magic = CTDLMESSAGE_MAGIC;
4632         msg->cm_anon_type = MES_NORMAL;
4633         msg->cm_format_type = 4;
4634         msg->cm_fields['A'] = strdup(CC->user.fullname);
4635         msg->cm_fields['O'] = strdup(req_room);
4636         msg->cm_fields['N'] = strdup(config.c_nodename);
4637         msg->cm_fields['H'] = strdup(config.c_humannode);
4638         msg->cm_flags = flags;
4639         
4640         msg->cm_fields['M'] = encoded_message;
4641
4642         /* Create the requested room if we have to. */
4643         if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4644                 CtdlCreateRoom(roomname, 
4645                         ( (is_mailbox != NULL) ? 5 : 3 ),
4646                         "", 0, 1, 0, VIEW_BBS);
4647         }
4648         /* If the caller specified this object as unique, delete all
4649          * other objects of this type that are currently in the room.
4650          */
4651         if (is_unique) {
4652                 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4653                         CtdlDeleteMessages(roomname, NULL, 0, content_type)
4654                 );
4655         }
4656         /* Now write the data */
4657         CtdlSubmitMsg(msg, NULL, roomname, 0);
4658         CtdlFreeMessage(msg);
4659 }
4660
4661
4662
4663
4664
4665
4666 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4667         config_msgnum = msgnum;
4668 }
4669
4670
4671 char *CtdlGetSysConfig(char *sysconfname) {
4672         char hold_rm[ROOMNAMELEN];
4673         long msgnum;
4674         char *conf;
4675         struct CtdlMessage *msg;
4676         char buf[SIZ];
4677         
4678         strcpy(hold_rm, CC->room.QRname);
4679         if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4680                 CtdlGetRoom(&CC->room, hold_rm);
4681                 return NULL;
4682         }
4683
4684
4685         /* We want the last (and probably only) config in this room */
4686         begin_critical_section(S_CONFIG);
4687         config_msgnum = (-1L);
4688         CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4689                 CtdlGetSysConfigBackend, NULL);
4690         msgnum = config_msgnum;
4691         end_critical_section(S_CONFIG);
4692
4693         if (msgnum < 0L) {
4694                 conf = NULL;
4695         }
4696         else {
4697                 msg = CtdlFetchMessage(msgnum, 1);
4698                 if (msg != NULL) {
4699                         conf = strdup(msg->cm_fields['M']);
4700                         CtdlFreeMessage(msg);
4701                 }
4702                 else {
4703                         conf = NULL;
4704                 }
4705         }
4706
4707         CtdlGetRoom(&CC->room, hold_rm);
4708
4709         if (conf != NULL) do {
4710                 extract_token(buf, conf, 0, '\n', sizeof buf);
4711                 strcpy(conf, &conf[strlen(buf)+1]);
4712         } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4713
4714         return(conf);
4715 }
4716
4717
4718 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4719         CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4720 }
4721
4722
4723 /*
4724  * Determine whether a given Internet address belongs to the current user
4725  */
4726 int CtdlIsMe(char *addr, int addr_buf_len)
4727 {
4728         struct recptypes *recp;
4729         int i;
4730
4731         recp = validate_recipients(addr, NULL, 0);
4732         if (recp == NULL) return(0);
4733
4734         if (recp->num_local == 0) {
4735                 free_recipients(recp);
4736                 return(0);
4737         }
4738
4739         for (i=0; i<recp->num_local; ++i) {
4740                 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4741                 if (!strcasecmp(addr, CC->user.fullname)) {
4742                         free_recipients(recp);
4743                         return(1);
4744                 }
4745         }
4746
4747         free_recipients(recp);
4748         return(0);
4749 }
4750
4751
4752 /*
4753  * Citadel protocol command to do the same
4754  */
4755 void cmd_isme(char *argbuf) {
4756         char addr[256];
4757
4758         if (CtdlAccessCheck(ac_logged_in)) return;
4759         extract_token(addr, argbuf, 0, '|', sizeof addr);
4760
4761         if (CtdlIsMe(addr, sizeof addr)) {
4762                 cprintf("%d %s\n", CIT_OK, addr);
4763         }
4764         else {
4765                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4766         }
4767
4768 }
4769
4770
4771 /*****************************************************************************/
4772 /*                      MODULE INITIALIZATION STUFF                          */
4773 /*****************************************************************************/
4774
4775 CTDL_MODULE_INIT(msgbase)
4776 {
4777         CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4778         CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4779         CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4780         CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4781         CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4782         CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4783         CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4784         CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4785         CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4786         CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4787         CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4788         CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4789
4790         /* return our Subversion id for the Log */
4791         return "$Id$";
4792 }