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