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