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