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