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