Revert "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         if (r != om_ok) {
1669                 syslog(LOG_DEBUG, "Denying access to message %ld - not yet listed\n", msg_num);
1670                 if (do_proto) {
1671                         if (r == om_access_denied) {
1672                                 cprintf("%d Message %ld was not found in this room.\n",
1673                                         ERROR + MESSAGE_NOT_FOUND,
1674                                         msg_num
1675                                 );
1676                         }
1677                         else {
1678                                 cprintf("%d An unknown error has occurred.\n", ERROR);
1679                         }
1680                 return(r);
1681                 }
1682 =======
1683         if (r == om_ok) {
1684                 syslog(LOG_DEBUG, "\033[32m PASS \033[0m\n");
1685         }
1686         else {
1687                 syslog(LOG_DEBUG, "\033[31m FAIL \033[0m\n");
1688 >>>>>>> parent of 4ec6a9d... Updating cmd_euid() to use the CtdlForEachMessage() API fixes the security check in blog view and saves some code
1689         }
1690         /* FIXME after testing, this is where we deny access */
1691
1692         /*
1693          * Fetch the message from disk.  If we're in HEADERS_FAST mode,
1694          * request that we don't even bother loading the body into memory.
1695          */
1696         if (headers_only == HEADERS_FAST) {
1697                 TheMessage = CtdlFetchMessage(msg_num, 0);
1698         }
1699         else {
1700                 TheMessage = CtdlFetchMessage(msg_num, 1);
1701         }
1702
1703         if (TheMessage == NULL) {
1704                 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1705                         ERROR + MESSAGE_NOT_FOUND, msg_num);
1706                 return(om_no_such_msg);
1707         }
1708
1709         /* Here is the weird form of this command, to process only an
1710          * encapsulated message/rfc822 section.
1711          */
1712         if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1713                 memset(&encap, 0, sizeof encap);
1714                 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1715                 mime_parser(TheMessage->cm_fields['M'],
1716                         NULL,
1717                         *extract_encapsulated_message,
1718                         NULL, NULL, (void *)&encap, 0
1719                 );
1720                 CtdlFreeMessage(TheMessage);
1721                 TheMessage = NULL;
1722
1723                 if (encap.msg) {
1724                         encap.msg[encap.msglen] = 0;
1725                         TheMessage = convert_internet_message(encap.msg);
1726                         encap.msg = NULL;       /* no free() here, TheMessage owns it now */
1727
1728                         /* Now we let it fall through to the bottom of this
1729                          * function, because TheMessage now contains the
1730                          * encapsulated message instead of the top-level
1731                          * message.  Isn't that neat?
1732                          */
1733
1734                 }
1735                 else {
1736                         if (do_proto) cprintf("%d msg %ld has no part %s\n",
1737                                 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1738                         retcode = om_no_such_msg;
1739                 }
1740
1741         }
1742
1743         /* Ok, output the message now */
1744         retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1745         CtdlFreeMessage(TheMessage);
1746
1747         return(retcode);
1748 }
1749
1750
1751 char *qp_encode_email_addrs(char *source)
1752 {
1753         char *user, *node, *name;
1754         const char headerStr[] = "=?UTF-8?Q?";
1755         char *Encoded;
1756         char *EncodedName;
1757         char *nPtr;
1758         int need_to_encode = 0;
1759         long SourceLen;
1760         long EncodedMaxLen;
1761         long nColons = 0;
1762         long *AddrPtr;
1763         long *AddrUtf8;
1764         long nAddrPtrMax = 50;
1765         long nmax;
1766         int InQuotes = 0;
1767         int i, n;
1768
1769         if (source == NULL) return source;
1770         if (IsEmptyStr(source)) return source;
1771         cit_backtrace();
1772         syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1773
1774         AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1775         AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1776         memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1777         *AddrPtr = 0;
1778         i = 0;
1779         while (!IsEmptyStr (&source[i])) {
1780                 if (nColons >= nAddrPtrMax){
1781                         long *ptr;
1782
1783                         ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1784                         memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1785                         free (AddrPtr), AddrPtr = ptr;
1786
1787                         ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1788                         memset(&ptr[nAddrPtrMax], 0, 
1789                                sizeof (long) * nAddrPtrMax);
1790
1791                         memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1792                         free (AddrUtf8), AddrUtf8 = ptr;
1793                         nAddrPtrMax *= 2;                               
1794                 }
1795                 if (((unsigned char) source[i] < 32) || 
1796                     ((unsigned char) source[i] > 126)) {
1797                         need_to_encode = 1;
1798                         AddrUtf8[nColons] = 1;
1799                 }
1800                 if (source[i] == '"')
1801                         InQuotes = !InQuotes;
1802                 if (!InQuotes && source[i] == ',') {
1803                         AddrPtr[nColons] = i;
1804                         nColons++;
1805                 }
1806                 i++;
1807         }
1808         if (need_to_encode == 0) {
1809                 free(AddrPtr);
1810                 free(AddrUtf8);
1811                 return source;
1812         }
1813
1814         SourceLen = i;
1815         EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1816         Encoded = (char*) malloc (EncodedMaxLen);
1817
1818         for (i = 0; i < nColons; i++)
1819                 source[AddrPtr[i]++] = '\0';
1820         /* TODO: if libidn, this might get larger*/
1821         user = malloc(SourceLen + 1);
1822         node = malloc(SourceLen + 1);
1823         name = malloc(SourceLen + 1);
1824
1825         nPtr = Encoded;
1826         *nPtr = '\0';
1827         for (i = 0; i < nColons && nPtr != NULL; i++) {
1828                 nmax = EncodedMaxLen - (nPtr - Encoded);
1829                 if (AddrUtf8[i]) {
1830                         process_rfc822_addr(&source[AddrPtr[i]], 
1831                                             user,
1832                                             node,
1833                                             name);
1834                         /* TODO: libIDN here ! */
1835                         if (IsEmptyStr(name)) {
1836                                 n = snprintf(nPtr, nmax, 
1837                                              (i==0)?"%s@%s" : ",%s@%s",
1838                                              user, node);
1839                         }
1840                         else {
1841                                 EncodedName = rfc2047encode(name, strlen(name));                        
1842                                 n = snprintf(nPtr, nmax, 
1843                                              (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1844                                              EncodedName, user, node);
1845                                 free(EncodedName);
1846                         }
1847                 }
1848                 else { 
1849                         n = snprintf(nPtr, nmax, 
1850                                      (i==0)?"%s" : ",%s",
1851                                      &source[AddrPtr[i]]);
1852                 }
1853                 if (n > 0 )
1854                         nPtr += n;
1855                 else { 
1856                         char *ptr, *nnPtr;
1857                         ptr = (char*) malloc(EncodedMaxLen * 2);
1858                         memcpy(ptr, Encoded, EncodedMaxLen);
1859                         nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1860                         free(Encoded), Encoded = ptr;
1861                         EncodedMaxLen *= 2;
1862                         i--; /* do it once more with properly lengthened buffer */
1863                 }
1864         }
1865         for (i = 0; i < nColons; i++)
1866                 source[--AddrPtr[i]] = ',';
1867
1868         free(user);
1869         free(node);
1870         free(name);
1871         free(AddrUtf8);
1872         free(AddrPtr);
1873         return Encoded;
1874 }
1875
1876
1877 /* If the last item in a list of recipients was truncated to a partial address,
1878  * remove it completely in order to avoid choking libSieve
1879  */
1880 void sanitize_truncated_recipient(char *str)
1881 {
1882         if (!str) return;
1883         if (num_tokens(str, ',') < 2) return;
1884
1885         int len = strlen(str);
1886         if (len < 900) return;
1887         if (len > 998) str[998] = 0;
1888
1889         char *cptr = strrchr(str, ',');
1890         if (!cptr) return;
1891
1892         char *lptr = strchr(cptr, '<');
1893         char *rptr = strchr(cptr, '>');
1894
1895         if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1896
1897         *cptr = 0;
1898 }
1899
1900
1901 void OutputCtdlMsgHeaders(
1902         struct CtdlMessage *TheMessage,
1903         int do_proto)           /* do Citadel protocol responses? */
1904 {
1905         char allkeys[30];
1906         int i, k, n;
1907         int suppress_f = 0;
1908         char buf[SIZ];
1909         char display_name[256];
1910
1911         /* begin header processing loop for Citadel message format */
1912         safestrncpy(display_name, "<unknown>", sizeof display_name);
1913         if (TheMessage->cm_fields['A']) {
1914                 strcpy(buf, TheMessage->cm_fields['A']);
1915                 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1916                         safestrncpy(display_name, "****", sizeof display_name);
1917                 }
1918                 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1919                         safestrncpy(display_name, "anonymous", sizeof display_name);
1920                 }
1921                 else {
1922                         safestrncpy(display_name, buf, sizeof display_name);
1923                 }
1924                 if ((is_room_aide())
1925                     && ((TheMessage->cm_anon_type == MES_ANONONLY)
1926                         || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1927                         size_t tmp = strlen(display_name);
1928                         snprintf(&display_name[tmp],
1929                                  sizeof display_name - tmp,
1930                                  " [%s]", buf);
1931                 }
1932         }
1933
1934         /* Don't show Internet address for users on the
1935          * local Citadel network.
1936          */
1937         suppress_f = 0;
1938         if (TheMessage->cm_fields['N'] != NULL)
1939                 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1940                         if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1941                                 suppress_f = 1;
1942                         }
1943
1944         /* Now spew the header fields in the order we like them. */
1945         n = safestrncpy(allkeys, FORDER, sizeof allkeys);
1946         for (i=0; i<n; ++i) {
1947                 k = (int) allkeys[i];
1948                 if (k != 'M') {
1949                         if ( (TheMessage->cm_fields[k] != NULL)
1950                              && (msgkeys[k] != NULL) ) {
1951                                 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1952                                         sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1953                                 }
1954                                 if (k == 'A') {
1955                                         if (do_proto) cprintf("%s=%s\n",
1956                                                               msgkeys[k],
1957                                                               display_name);
1958                                 }
1959                                 else if ((k == 'F') && (suppress_f)) {
1960                                         /* do nothing */
1961                                 }
1962                                 /* Masquerade display name if needed */
1963                                 else {
1964                                         if (do_proto) cprintf("%s=%s\n",
1965                                                               msgkeys[k],
1966                                                               TheMessage->cm_fields[k]
1967                                                 );
1968                                 }
1969                         }
1970                 }
1971         }
1972
1973 }
1974
1975 void OutputRFC822MsgHeaders(
1976         struct CtdlMessage *TheMessage,
1977         int flags,              /* should the bessage be exported clean */
1978         const char *nl,
1979         char *mid, long sizeof_mid,
1980         char *suser, long sizeof_suser,
1981         char *luser, long sizeof_luser,
1982         char *fuser, long sizeof_fuser,
1983         char *snode, long sizeof_snode)
1984 {
1985         char datestamp[100];
1986         int subject_found = 0;
1987         char buf[SIZ];
1988         int i, j, k;
1989         char *mptr = NULL;
1990         char *mpptr = NULL;
1991
1992         for (i = 0; i < 256; ++i) {
1993                 if (TheMessage->cm_fields[i]) {
1994                         mptr = mpptr = TheMessage->cm_fields[i];
1995                                 
1996                         if (i == 'A') {
1997                                 safestrncpy(luser, mptr, sizeof_luser);
1998                                 safestrncpy(suser, mptr, sizeof_suser);
1999                         }
2000                         else if (i == 'Y') {
2001                                 if ((flags & QP_EADDR) != 0) {
2002                                         mptr = qp_encode_email_addrs(mptr);
2003                                 }
2004                                 sanitize_truncated_recipient(mptr);
2005                                 cprintf("CC: %s%s", mptr, nl);
2006                         }
2007                         else if (i == 'P') {
2008                                 cprintf("Return-Path: %s%s", mptr, nl);
2009                         }
2010                         else if (i == 'L') {
2011                                 cprintf("List-ID: %s%s", mptr, nl);
2012                         }
2013                         else if (i == 'V') {
2014                                 if ((flags & QP_EADDR) != 0) 
2015                                         mptr = qp_encode_email_addrs(mptr);
2016                                 cprintf("Envelope-To: %s%s", mptr, nl);
2017                         }
2018                         else if (i == 'U') {
2019                                 cprintf("Subject: %s%s", mptr, nl);
2020                                 subject_found = 1;
2021                         }
2022                         else if (i == 'I')
2023                                 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
2024                         else if (i == 'F')
2025                                 safestrncpy(fuser, mptr, sizeof_fuser);
2026                         /* else if (i == 'O')
2027                            cprintf("X-Citadel-Room: %s%s",
2028                            mptr, nl); */
2029                         else if (i == 'N')
2030                                 safestrncpy(snode, mptr, sizeof_snode);
2031                         else if (i == 'R')
2032                         {
2033                                 if (haschar(mptr, '@') == 0)
2034                                 {
2035                                         sanitize_truncated_recipient(mptr);
2036                                         cprintf("To: %s@%s", mptr, config.c_fqdn);
2037                                         cprintf("%s", nl);
2038                                 }
2039                                 else
2040                                 {
2041                                         if ((flags & QP_EADDR) != 0) {
2042                                                 mptr = qp_encode_email_addrs(mptr);
2043                                         }
2044                                         sanitize_truncated_recipient(mptr);
2045                                         cprintf("To: %s", mptr);
2046                                         cprintf("%s", nl);
2047                                 }
2048                         }
2049                         else if (i == 'T') {
2050                                 datestring(datestamp, sizeof datestamp,
2051                                            atol(mptr), DATESTRING_RFC822);
2052                                 cprintf("Date: %s%s", datestamp, nl);
2053                         }
2054                         else if (i == 'W') {
2055                                 cprintf("References: ");
2056                                 k = num_tokens(mptr, '|');
2057                                 for (j=0; j<k; ++j) {
2058                                         extract_token(buf, mptr, j, '|', sizeof buf);
2059                                         cprintf("<%s>", buf);
2060                                         if (j == (k-1)) {
2061                                                 cprintf("%s", nl);
2062                                         }
2063                                         else {
2064                                                 cprintf(" ");
2065                                         }
2066                                 }
2067                         }
2068                         else if (i == 'K') {
2069                                 cprintf("Reply-To: <%s>%s", mptr, nl);
2070                         }
2071                         if (mptr != mpptr)
2072                                 free (mptr);
2073                 }
2074         }
2075         if (subject_found == 0) {
2076                 cprintf("Subject: (no subject)%s", nl);
2077         }
2078 }
2079
2080
2081 void Dump_RFC822HeadersBody(
2082         struct CtdlMessage *TheMessage,
2083         int headers_only,       /* eschew the message body? */
2084         int flags,              /* should the bessage be exported clean? */
2085
2086         const char *nl)
2087 {
2088         cit_uint8_t prev_ch;
2089         int eoh = 0;
2090         const char *StartOfText = StrBufNOTNULL;
2091         char outbuf[1024];
2092         int outlen = 0;
2093         int nllen = strlen(nl);
2094         char *mptr;
2095
2096         mptr = TheMessage->cm_fields['M'];
2097
2098
2099         prev_ch = '\0';
2100         while (*mptr != '\0') {
2101                 if (*mptr == '\r') {
2102                         /* do nothing */
2103                 }
2104                 else {
2105                         if ((!eoh) &&
2106                             (*mptr == '\n'))
2107                         {
2108                                 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2109                                 if (!eoh)
2110                                         eoh = *(mptr+1) == '\n';
2111                                 if (eoh)
2112                                 {
2113                                         StartOfText = mptr;
2114                                         StartOfText = strchr(StartOfText, '\n');
2115                                         StartOfText = strchr(StartOfText, '\n');
2116                                 }
2117                         }
2118                         if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2119                             ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2120                             ((headers_only != HEADERS_NONE) && 
2121                              (headers_only != HEADERS_ONLY))
2122                                 ) {
2123                                 if (*mptr == '\n') {
2124                                         memcpy(&outbuf[outlen], nl, nllen);
2125                                         outlen += nllen;
2126                                         outbuf[outlen] = '\0';
2127                                 }
2128                                 else {
2129                                         outbuf[outlen++] = *mptr;
2130                                 }
2131                         }
2132                 }
2133                 if (flags & ESC_DOT)
2134                 {
2135                         if ((prev_ch == '\n') && 
2136                             (*mptr == '.') && 
2137                             ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2138                         {
2139                                 outbuf[outlen++] = '.';
2140                         }
2141                         prev_ch = *mptr;
2142                 }
2143                 ++mptr;
2144                 if (outlen > 1000) {
2145                         client_write(outbuf, outlen);
2146                         outlen = 0;
2147                 }
2148         }
2149         if (outlen > 0) {
2150                 client_write(outbuf, outlen);
2151                 outlen = 0;
2152         }
2153 }
2154
2155
2156
2157 /* If the format type on disk is 1 (fixed-format), then we want
2158  * everything to be output completely literally ... regardless of
2159  * what message transfer format is in use.
2160  */
2161 void DumpFormatFixed(
2162         struct CtdlMessage *TheMessage,
2163         int mode,               /* how would you like that message? */
2164         const char *nl)
2165 {
2166         cit_uint8_t ch;
2167         char buf[SIZ];
2168         int buflen;
2169         int xlline = 0;
2170         int nllen = strlen (nl);
2171         char *mptr;
2172
2173         mptr = TheMessage->cm_fields['M'];
2174         
2175         if (mode == MT_MIME) {
2176                 cprintf("Content-type: text/plain\n\n");
2177         }
2178         *buf = '\0';
2179         buflen = 0;
2180         while (ch = *mptr++, ch > 0) {
2181                 if (ch == '\n')
2182                         ch = '\r';
2183
2184                 if ((buflen > 250) && (!xlline)){
2185                         int tbuflen;
2186                         tbuflen = buflen;
2187
2188                         while ((buflen > 0) && 
2189                                (!isspace(buf[buflen])))
2190                                 buflen --;
2191                         if (buflen == 0) {
2192                                 xlline = 1;
2193                         }
2194                         else {
2195                                 mptr -= tbuflen - buflen;
2196                                 buf[buflen] = '\0';
2197                                 ch = '\r';
2198                         }
2199                 }
2200                 /* if we reach the outer bounds of our buffer, 
2201                    abort without respect what whe purge. */
2202                 if (xlline && 
2203                     ((isspace(ch)) || 
2204                      (buflen > SIZ - nllen - 2)))
2205                         ch = '\r';
2206
2207                 if (ch == '\r') {
2208                         memcpy (&buf[buflen], nl, nllen);
2209                         buflen += nllen;
2210                         buf[buflen] = '\0';
2211
2212                         client_write(buf, buflen);
2213                         *buf = '\0';
2214                         buflen = 0;
2215                         xlline = 0;
2216                 } else {
2217                         buf[buflen] = ch;
2218                         buflen++;
2219                 }
2220         }
2221         buf[buflen] = '\0';
2222         if (!IsEmptyStr(buf))
2223                 cprintf("%s%s", buf, nl);
2224 }
2225
2226 /*
2227  * Get a message off disk.  (returns om_* values found in msgbase.h)
2228  */
2229 int CtdlOutputPreLoadedMsg(
2230                 struct CtdlMessage *TheMessage,
2231                 int mode,               /* how would you like that message? */
2232                 int headers_only,       /* eschew the message body? */
2233                 int do_proto,           /* do Citadel protocol responses? */
2234                 int crlf,               /* Use CRLF newlines instead of LF? */
2235                 int flags               /* should the bessage be exported clean? */
2236 ) {
2237         int i;
2238         char *mptr = NULL;
2239         const char *nl; /* newline string */
2240         struct ma_info ma;
2241
2242         /* Buffers needed for RFC822 translation.  These are all filled
2243          * using functions that are bounds-checked, and therefore we can
2244          * make them substantially smaller than SIZ.
2245          */
2246         char suser[100];
2247         char luser[100];
2248         char fuser[100];
2249         char snode[100];
2250         char mid[100];
2251
2252         syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2253                 ((TheMessage == NULL) ? "NULL" : "not null"),
2254                 mode, headers_only, do_proto, crlf);
2255
2256         strcpy(mid, "unknown");
2257         nl = (crlf ? "\r\n" : "\n");
2258
2259         if (!is_valid_message(TheMessage)) {
2260                 syslog(LOG_ERR,
2261                         "ERROR: invalid preloaded message for output\n");
2262                 cit_backtrace ();
2263                 return(om_no_such_msg);
2264         }
2265
2266         /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2267          * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2268          */
2269         if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2270                 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2271         }
2272                 
2273         /* Are we downloading a MIME component? */
2274         if (mode == MT_DOWNLOAD) {
2275                 if (TheMessage->cm_format_type != FMT_RFC822) {
2276                         if (do_proto)
2277                                 cprintf("%d This is not a MIME message.\n",
2278                                 ERROR + ILLEGAL_VALUE);
2279                 } else if (CC->download_fp != NULL) {
2280                         if (do_proto) cprintf(
2281                                 "%d You already have a download open.\n",
2282                                 ERROR + RESOURCE_BUSY);
2283                 } else {
2284                         /* Parse the message text component */
2285                         mptr = TheMessage->cm_fields['M'];
2286                         mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2287                         /* If there's no file open by this time, the requested
2288                          * section wasn't found, so print an error
2289                          */
2290                         if (CC->download_fp == NULL) {
2291                                 if (do_proto) cprintf(
2292                                         "%d Section %s not found.\n",
2293                                         ERROR + FILE_NOT_FOUND,
2294                                         CC->download_desired_section);
2295                         }
2296                 }
2297                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2298         }
2299
2300         /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2301          * in a single server operation instead of opening a download file.
2302          */
2303         if (mode == MT_SPEW_SECTION) {
2304                 if (TheMessage->cm_format_type != FMT_RFC822) {
2305                         if (do_proto)
2306                                 cprintf("%d This is not a MIME message.\n",
2307                                 ERROR + ILLEGAL_VALUE);
2308                 } else {
2309                         /* Parse the message text component */
2310                         int found_it = 0;
2311
2312                         mptr = TheMessage->cm_fields['M'];
2313                         mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2314                         /* If section wasn't found, print an error
2315                          */
2316                         if (!found_it) {
2317                                 if (do_proto) cprintf(
2318                                         "%d Section %s not found.\n",
2319                                         ERROR + FILE_NOT_FOUND,
2320                                         CC->download_desired_section);
2321                         }
2322                 }
2323                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2324         }
2325
2326         /* now for the user-mode message reading loops */
2327         if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2328
2329         /* Does the caller want to skip the headers? */
2330         if (headers_only == HEADERS_NONE) goto START_TEXT;
2331
2332         /* Tell the client which format type we're using. */
2333         if ( (mode == MT_CITADEL) && (do_proto) ) {
2334                 cprintf("type=%d\n", TheMessage->cm_format_type);
2335         }
2336
2337         /* nhdr=yes means that we're only displaying headers, no body */
2338         if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2339            && ((mode == MT_CITADEL) || (mode == MT_MIME))
2340            && (do_proto)
2341            ) {
2342                 cprintf("nhdr=yes\n");
2343         }
2344
2345         if ((mode == MT_CITADEL) || (mode == MT_MIME)) 
2346                 OutputCtdlMsgHeaders(TheMessage, do_proto);
2347
2348
2349         /* begin header processing loop for RFC822 transfer format */
2350         strcpy(suser, "");
2351         strcpy(luser, "");
2352         strcpy(fuser, "");
2353         strcpy(snode, NODENAME);
2354         if (mode == MT_RFC822) 
2355                 OutputRFC822MsgHeaders(
2356                         TheMessage,
2357                         flags,
2358                         nl,
2359                         mid, sizeof(mid),
2360                         suser, sizeof(suser),
2361                         luser, sizeof(luser),
2362                         fuser, sizeof(fuser),
2363                         snode, sizeof(snode)
2364                         );
2365
2366
2367         for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2368                 suser[i] = tolower(suser[i]);
2369                 if (!isalnum(suser[i])) suser[i]='_';
2370         }
2371
2372         if (mode == MT_RFC822) {
2373                 if (!strcasecmp(snode, NODENAME)) {
2374                         safestrncpy(snode, FQDN, sizeof snode);
2375                 }
2376
2377                 /* Construct a fun message id */
2378                 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2379                 if (strchr(mid, '@')==NULL) {
2380                         cprintf("@%s", snode);
2381                 }
2382                 cprintf(">%s", nl);
2383
2384                 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2385                         cprintf("From: \"----\" <x@x.org>%s", nl);
2386                 }
2387                 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2388                         cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2389                 }
2390                 else if (!IsEmptyStr(fuser)) {
2391                         cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2392                 }
2393                 else {
2394                         cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2395                 }
2396
2397                 /* Blank line signifying RFC822 end-of-headers */
2398                 if (TheMessage->cm_format_type != FMT_RFC822) {
2399                         cprintf("%s", nl);
2400                 }
2401         }
2402
2403         /* end header processing loop ... at this point, we're in the text */
2404 START_TEXT:
2405         if (headers_only == HEADERS_FAST) goto DONE;
2406
2407         /* Tell the client about the MIME parts in this message */
2408         if (TheMessage->cm_format_type == FMT_RFC822) {
2409                 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2410                         mptr = TheMessage->cm_fields['M'];
2411                         memset(&ma, 0, sizeof(struct ma_info));
2412                         mime_parser(mptr, NULL,
2413                                 (do_proto ? *list_this_part : NULL),
2414                                 (do_proto ? *list_this_pref : NULL),
2415                                 (do_proto ? *list_this_suff : NULL),
2416                                 (void *)&ma, 1);
2417                 }
2418                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
2419                         Dump_RFC822HeadersBody(
2420                                 TheMessage,
2421                                 headers_only,
2422                                 flags,
2423                                 nl);
2424                         goto DONE;
2425                 }
2426         }
2427
2428         if (headers_only == HEADERS_ONLY) {
2429                 goto DONE;
2430         }
2431
2432         /* signify start of msg text */
2433         if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2434                 if (do_proto) cprintf("text\n");
2435         }
2436
2437         if (TheMessage->cm_format_type == FMT_FIXED) 
2438                 DumpFormatFixed(
2439                         TheMessage,
2440                         mode,           /* how would you like that message? */
2441                         nl);
2442
2443         /* If the message on disk is format 0 (Citadel vari-format), we
2444          * output using the formatter at 80 columns.  This is the final output
2445          * form if the transfer format is RFC822, but if the transfer format
2446          * is Citadel proprietary, it'll still work, because the indentation
2447          * for new paragraphs is correct and the client will reformat the
2448          * message to the reader's screen width.
2449          */
2450         if (TheMessage->cm_format_type == FMT_CITADEL) {
2451                 mptr = TheMessage->cm_fields['M'];
2452
2453                 if (mode == MT_MIME) {
2454                         cprintf("Content-type: text/x-citadel-variformat\n\n");
2455                 }
2456                 memfmout(mptr, nl);
2457         }
2458
2459         /* If the message on disk is format 4 (MIME), we've gotta hand it
2460          * off to the MIME parser.  The client has already been told that
2461          * this message is format 1 (fixed format), so the callback function
2462          * we use will display those parts as-is.
2463          */
2464         if (TheMessage->cm_format_type == FMT_RFC822) {
2465                 memset(&ma, 0, sizeof(struct ma_info));
2466
2467                 if (mode == MT_MIME) {
2468                         ma.use_fo_hooks = 0;
2469                         strcpy(ma.chosen_part, "1");
2470                         ma.chosen_pref = 9999;
2471                         ma.dont_decode = CC->msg4_dont_decode;
2472                         mime_parser(mptr, NULL,
2473                                 *choose_preferred, *fixed_output_pre,
2474                                 *fixed_output_post, (void *)&ma, 1);
2475                         mime_parser(mptr, NULL,
2476                                 *output_preferred, NULL, NULL, (void *)&ma, 1);
2477                 }
2478                 else {
2479                         ma.use_fo_hooks = 1;
2480                         mime_parser(mptr, NULL,
2481                                 *fixed_output, *fixed_output_pre,
2482                                 *fixed_output_post, (void *)&ma, 0);
2483                 }
2484
2485         }
2486
2487 DONE:   /* now we're done */
2488         if (do_proto) cprintf("000\n");
2489         return(om_ok);
2490 }
2491
2492
2493 /*
2494  * display a message (mode 0 - Citadel proprietary)
2495  */
2496 void cmd_msg0(char *cmdbuf)
2497 {
2498         long msgid;
2499         int headers_only = HEADERS_ALL;
2500
2501         msgid = extract_long(cmdbuf, 0);
2502         headers_only = extract_int(cmdbuf, 1);
2503
2504         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2505         return;
2506 }
2507
2508
2509 /*
2510  * display a message (mode 2 - RFC822)
2511  */
2512 void cmd_msg2(char *cmdbuf)
2513 {
2514         long msgid;
2515         int headers_only = HEADERS_ALL;
2516
2517         msgid = extract_long(cmdbuf, 0);
2518         headers_only = extract_int(cmdbuf, 1);
2519
2520         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2521 }
2522
2523
2524
2525 /* 
2526  * display a message (mode 3 - IGnet raw format - internal programs only)
2527  */
2528 void cmd_msg3(char *cmdbuf)
2529 {
2530         long msgnum;
2531         struct CtdlMessage *msg = NULL;
2532         struct ser_ret smr;
2533
2534         if (CC->internal_pgm == 0) {
2535                 cprintf("%d This command is for internal programs only.\n",
2536                         ERROR + HIGHER_ACCESS_REQUIRED);
2537                 return;
2538         }
2539
2540         msgnum = extract_long(cmdbuf, 0);
2541         msg = CtdlFetchMessage(msgnum, 1);
2542         if (msg == NULL) {
2543                 cprintf("%d Message %ld not found.\n", 
2544                         ERROR + MESSAGE_NOT_FOUND, msgnum);
2545                 return;
2546         }
2547
2548         serialize_message(&smr, msg);
2549         CtdlFreeMessage(msg);
2550
2551         if (smr.len == 0) {
2552                 cprintf("%d Unable to serialize message\n",
2553                         ERROR + INTERNAL_ERROR);
2554                 return;
2555         }
2556
2557         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2558         client_write((char *)smr.ser, (int)smr.len);
2559         free(smr.ser);
2560 }
2561
2562
2563
2564 /* 
2565  * Display a message using MIME content types
2566  */
2567 void cmd_msg4(char *cmdbuf)
2568 {
2569         long msgid;
2570         char section[64];
2571
2572         msgid = extract_long(cmdbuf, 0);
2573         extract_token(section, cmdbuf, 1, '|', sizeof section);
2574         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2575 }
2576
2577
2578
2579 /* 
2580  * Client tells us its preferred message format(s)
2581  */
2582 void cmd_msgp(char *cmdbuf)
2583 {
2584         if (!strcasecmp(cmdbuf, "dont_decode")) {
2585                 CC->msg4_dont_decode = 1;
2586                 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2587         }
2588         else {
2589                 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2590                 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2591         }
2592 }
2593
2594
2595 /*
2596  * Open a component of a MIME message as a download file 
2597  */
2598 void cmd_opna(char *cmdbuf)
2599 {
2600         long msgid;
2601         char desired_section[128];
2602
2603         msgid = extract_long(cmdbuf, 0);
2604         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2605         safestrncpy(CC->download_desired_section, desired_section,
2606                 sizeof CC->download_desired_section);
2607         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2608 }                       
2609
2610
2611 /*
2612  * Open a component of a MIME message and transmit it all at once
2613  */
2614 void cmd_dlat(char *cmdbuf)
2615 {
2616         long msgid;
2617         char desired_section[128];
2618
2619         msgid = extract_long(cmdbuf, 0);
2620         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2621         safestrncpy(CC->download_desired_section, desired_section,
2622                 sizeof CC->download_desired_section);
2623         CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2624 }                       
2625
2626
2627 /*
2628  * Save one or more message pointers into a specified room
2629  * (Returns 0 for success, nonzero for failure)
2630  * roomname may be NULL to use the current room
2631  *
2632  * Note that the 'supplied_msg' field may be set to NULL, in which case
2633  * the message will be fetched from disk, by number, if we need to perform
2634  * replication checks.  This adds an additional database read, so if the
2635  * caller already has the message in memory then it should be supplied.  (Obviously
2636  * this mode of operation only works if we're saving a single message.)
2637  */
2638 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2639                         int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2640 ) {
2641         int i, j, unique;
2642         char hold_rm[ROOMNAMELEN];
2643         struct cdbdata *cdbfr;
2644         int num_msgs;
2645         long *msglist;
2646         long highest_msg = 0L;
2647
2648         long msgid = 0;
2649         struct CtdlMessage *msg = NULL;
2650
2651         long *msgs_to_be_merged = NULL;
2652         int num_msgs_to_be_merged = 0;
2653
2654         syslog(LOG_DEBUG,
2655                 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2656                 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2657         );
2658
2659         strcpy(hold_rm, CC->room.QRname);
2660
2661         /* Sanity checks */
2662         if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2663         if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2664         if (num_newmsgs > 1) supplied_msg = NULL;
2665
2666         /* Now the regular stuff */
2667         if (CtdlGetRoomLock(&CC->room,
2668            ((roomname != NULL) ? roomname : CC->room.QRname) )
2669            != 0) {
2670                 syslog(LOG_ERR, "No such room <%s>\n", roomname);
2671                 return(ERROR + ROOM_NOT_FOUND);
2672         }
2673
2674
2675         msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2676         num_msgs_to_be_merged = 0;
2677
2678
2679         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2680         if (cdbfr == NULL) {
2681                 msglist = NULL;
2682                 num_msgs = 0;
2683         } else {
2684                 msglist = (long *) cdbfr->ptr;
2685                 cdbfr->ptr = NULL;      /* CtdlSaveMsgPointerInRoom() now owns this memory */
2686                 num_msgs = cdbfr->len / sizeof(long);
2687                 cdb_free(cdbfr);
2688         }
2689
2690
2691         /* Create a list of msgid's which were supplied by the caller, but do
2692          * not already exist in the target room.  It is absolutely taboo to
2693          * have more than one reference to the same message in a room.
2694          */
2695         for (i=0; i<num_newmsgs; ++i) {
2696                 unique = 1;
2697                 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2698                         if (msglist[j] == newmsgidlist[i]) {
2699                                 unique = 0;
2700                         }
2701                 }
2702                 if (unique) {
2703                         msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2704                 }
2705         }
2706
2707         syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2708
2709         /*
2710          * Now merge the new messages
2711          */
2712         msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2713         if (msglist == NULL) {
2714                 syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2715         }
2716         memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2717         num_msgs += num_msgs_to_be_merged;
2718
2719         /* Sort the message list, so all the msgid's are in order */
2720         num_msgs = sort_msglist(msglist, num_msgs);
2721
2722         /* Determine the highest message number */
2723         highest_msg = msglist[num_msgs - 1];
2724
2725         /* Write it back to disk. */
2726         cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2727                   msglist, (int)(num_msgs * sizeof(long)));
2728
2729         /* Free up the memory we used. */
2730         free(msglist);
2731
2732         /* Update the highest-message pointer and unlock the room. */
2733         CC->room.QRhighest = highest_msg;
2734         CtdlPutRoomLock(&CC->room);
2735
2736         /* Perform replication checks if necessary */
2737         if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2738                 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2739
2740                 for (i=0; i<num_msgs_to_be_merged; ++i) {
2741                         msgid = msgs_to_be_merged[i];
2742         
2743                         if (supplied_msg != NULL) {
2744                                 msg = supplied_msg;
2745                         }
2746                         else {
2747                                 msg = CtdlFetchMessage(msgid, 0);
2748                         }
2749         
2750                         if (msg != NULL) {
2751                                 ReplicationChecks(msg);
2752                 
2753                                 /* If the message has an Exclusive ID, index that... */
2754                                 if (msg->cm_fields['E'] != NULL) {
2755                                         index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2756                                 }
2757
2758                                 /* Free up the memory we may have allocated */
2759                                 if (msg != supplied_msg) {
2760                                         CtdlFreeMessage(msg);
2761                                 }
2762                         }
2763         
2764                 }
2765         }
2766
2767         else {
2768                 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2769         }
2770
2771         /* Submit this room for processing by hooks */
2772         PerformRoomHooks(&CC->room);
2773
2774         /* Go back to the room we were in before we wandered here... */
2775         CtdlGetRoom(&CC->room, hold_rm);
2776
2777         /* Bump the reference count for all messages which were merged */
2778         if (!suppress_refcount_adj) {
2779                 for (i=0; i<num_msgs_to_be_merged; ++i) {
2780                         AdjRefCount(msgs_to_be_merged[i], +1);
2781                 }
2782         }
2783
2784         /* Free up memory... */
2785         if (msgs_to_be_merged != NULL) {
2786                 free(msgs_to_be_merged);
2787         }
2788
2789         /* Return success. */
2790         return (0);
2791 }
2792
2793
2794 /*
2795  * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2796  * a single message.
2797  */
2798 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2799                         int do_repl_check, struct CtdlMessage *supplied_msg)
2800 {
2801         return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2802 }
2803
2804
2805
2806
2807 /*
2808  * Message base operation to save a new message to the message store
2809  * (returns new message number)
2810  *
2811  * This is the back end for CtdlSubmitMsg() and should not be directly
2812  * called by server-side modules.
2813  *
2814  */
2815 long send_message(struct CtdlMessage *msg) {
2816         long newmsgid;
2817         long retval;
2818         char msgidbuf[256];
2819         struct ser_ret smr;
2820         int is_bigmsg = 0;
2821         char *holdM = NULL;
2822
2823         /* Get a new message number */
2824         newmsgid = get_new_message_number();
2825         snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2826
2827         /* Generate an ID if we don't have one already */
2828         if (msg->cm_fields['I']==NULL) {
2829                 msg->cm_fields['I'] = strdup(msgidbuf);
2830         }
2831
2832         /* If the message is big, set its body aside for storage elsewhere */
2833         if (msg->cm_fields['M'] != NULL) {
2834                 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2835                         is_bigmsg = 1;
2836                         holdM = msg->cm_fields['M'];
2837                         msg->cm_fields['M'] = NULL;
2838                 }
2839         }
2840
2841         /* Serialize our data structure for storage in the database */  
2842         serialize_message(&smr, msg);
2843
2844         if (is_bigmsg) {
2845                 msg->cm_fields['M'] = holdM;
2846         }
2847
2848         if (smr.len == 0) {
2849                 cprintf("%d Unable to serialize message\n",
2850                         ERROR + INTERNAL_ERROR);
2851                 return (-1L);
2852         }
2853
2854         /* Write our little bundle of joy into the message base */
2855         if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2856                       smr.ser, smr.len) < 0) {
2857                 syslog(LOG_ERR, "Can't store message\n");
2858                 retval = 0L;
2859         } else {
2860                 if (is_bigmsg) {
2861                         cdb_store(CDB_BIGMSGS,
2862                                 &newmsgid,
2863                                 (int)sizeof(long),
2864                                 holdM,
2865                                 (strlen(holdM) + 1)
2866                         );
2867                 }
2868                 retval = newmsgid;
2869         }
2870
2871         /* Free the memory we used for the serialized message */
2872         free(smr.ser);
2873
2874         /* Return the *local* message ID to the caller
2875          * (even if we're storing an incoming network message)
2876          */
2877         return(retval);
2878 }
2879
2880
2881
2882 /*
2883  * Serialize a struct CtdlMessage into the format used on disk and network.
2884  * 
2885  * This function loads up a "struct ser_ret" (defined in server.h) which
2886  * contains the length of the serialized message and a pointer to the
2887  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
2888  */
2889 void serialize_message(struct ser_ret *ret,             /* return values */
2890                         struct CtdlMessage *msg)        /* unserialized msg */
2891 {
2892         size_t wlen, fieldlen;
2893         int i;
2894         static char *forder = FORDER;
2895
2896         /*
2897          * Check for valid message format
2898          */
2899         if (is_valid_message(msg) == 0) {
2900                 syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
2901                 ret->len = 0;
2902                 ret->ser = NULL;
2903                 return;
2904         }
2905
2906         ret->len = 3;
2907         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2908                 ret->len = ret->len +
2909                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
2910
2911         ret->ser = malloc(ret->len);
2912         if (ret->ser == NULL) {
2913                 syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2914                         (long)ret->len, strerror(errno));
2915                 ret->len = 0;
2916                 ret->ser = NULL;
2917                 return;
2918         }
2919
2920         ret->ser[0] = 0xFF;
2921         ret->ser[1] = msg->cm_anon_type;
2922         ret->ser[2] = msg->cm_format_type;
2923         wlen = 3;
2924
2925         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2926                 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2927                 ret->ser[wlen++] = (char)forder[i];
2928                 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2929                 wlen = wlen + fieldlen + 1;
2930         }
2931         if (ret->len != wlen) syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
2932                 (long)ret->len, (long)wlen);
2933
2934         return;
2935 }
2936
2937
2938 /*
2939  * Serialize a struct CtdlMessage into the format used on disk and network.
2940  * 
2941  * This function loads up a "struct ser_ret" (defined in server.h) which
2942  * contains the length of the serialized message and a pointer to the
2943  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
2944  */
2945 void dump_message(struct CtdlMessage *msg,      /* unserialized msg */
2946                   long Siz)                     /* how many chars ? */
2947 {
2948         size_t wlen;
2949         int i;
2950         static char *forder = FORDER;
2951         char *buf;
2952
2953         /*
2954          * Check for valid message format
2955          */
2956         if (is_valid_message(msg) == 0) {
2957                 syslog(LOG_ERR, "dump_message() aborting due to invalid message\n");
2958                 return;
2959         }
2960
2961         buf = (char*) malloc (Siz + 1);
2962
2963         wlen = 3;
2964         
2965         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2966                         snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i], 
2967                                    msg->cm_fields[(int)forder[i]]);
2968                         client_write (buf, strlen(buf));
2969                 }
2970
2971         return;
2972 }
2973
2974
2975
2976 /*
2977  * Check to see if any messages already exist in the current room which
2978  * carry the same Exclusive ID as this one.  If any are found, delete them.
2979  */
2980 void ReplicationChecks(struct CtdlMessage *msg) {
2981         long old_msgnum = (-1L);
2982
2983         if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2984
2985         syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
2986                 CC->room.QRname);
2987
2988         /* No exclusive id?  Don't do anything. */
2989         if (msg == NULL) return;
2990         if (msg->cm_fields['E'] == NULL) return;
2991         if (IsEmptyStr(msg->cm_fields['E'])) return;
2992         /*syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2993                 msg->cm_fields['E'], CC->room.QRname);*/
2994
2995         old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2996         if (old_msgnum > 0L) {
2997                 syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2998                 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2999         }
3000 }
3001
3002
3003
3004 /*
3005  * Save a message to disk and submit it into the delivery system.
3006  */
3007 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
3008                    struct recptypes *recps,     /* recipients (if mail) */
3009                    char *force,                 /* force a particular room? */
3010                    int flags                    /* should the message be exported clean? */
3011 ) {
3012         char submit_filename[128];
3013         char generated_timestamp[32];
3014         char hold_rm[ROOMNAMELEN];
3015         char actual_rm[ROOMNAMELEN];
3016         char force_room[ROOMNAMELEN];
3017         char content_type[SIZ];                 /* We have to learn this */
3018         char recipient[SIZ];
3019         long newmsgid;
3020         const char *mptr = NULL;
3021         struct ctdluser userbuf;
3022         int a, i;
3023         struct MetaData smi;
3024         FILE *network_fp = NULL;
3025         static int seqnum = 1;
3026         struct CtdlMessage *imsg = NULL;
3027         char *instr = NULL;
3028         size_t instr_alloc = 0;
3029         struct ser_ret smr;
3030         char *hold_R, *hold_D;
3031         char *collected_addresses = NULL;
3032         struct addresses_to_be_filed *aptr = NULL;
3033         StrBuf *saved_rfc822_version = NULL;
3034         int qualified_for_journaling = 0;
3035         CitContext *CCC = MyContext();
3036         char bounce_to[1024] = "";
3037         size_t tmp = 0;
3038         int rv = 0;
3039
3040         syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3041         if (is_valid_message(msg) == 0) return(-1);     /* self check */
3042
3043         /* If this message has no timestamp, we take the liberty of
3044          * giving it one, right now.
3045          */
3046         if (msg->cm_fields['T'] == NULL) {
3047                 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
3048                 msg->cm_fields['T'] = strdup(generated_timestamp);
3049         }
3050
3051         /* If this message has no path, we generate one.
3052          */
3053         if (msg->cm_fields['P'] == NULL) {
3054                 if (msg->cm_fields['A'] != NULL) {
3055                         msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
3056                         for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
3057                                 if (isspace(msg->cm_fields['P'][a])) {
3058                                         msg->cm_fields['P'][a] = ' ';
3059                                 }
3060                         }
3061                 }
3062                 else {
3063                         msg->cm_fields['P'] = strdup("unknown");
3064                 }
3065         }
3066
3067         if (force == NULL) {
3068                 strcpy(force_room, "");
3069         }
3070         else {
3071                 strcpy(force_room, force);
3072         }
3073
3074         /* Learn about what's inside, because it's what's inside that counts */
3075         if (msg->cm_fields['M'] == NULL) {
3076                 syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3077                 return(-2);
3078         }
3079
3080         switch (msg->cm_format_type) {
3081         case 0:
3082                 strcpy(content_type, "text/x-citadel-variformat");
3083                 break;
3084         case 1:
3085                 strcpy(content_type, "text/plain");
3086                 break;
3087         case 4:
3088                 strcpy(content_type, "text/plain");
3089                 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
3090                 if (mptr != NULL) {
3091                         char *aptr;
3092                         safestrncpy(content_type, &mptr[13], sizeof content_type);
3093                         striplt(content_type);
3094                         aptr = content_type;
3095                         while (!IsEmptyStr(aptr)) {
3096                                 if ((*aptr == ';')
3097                                     || (*aptr == ' ')
3098                                     || (*aptr == 13)
3099                                     || (*aptr == 10)) {
3100                                         *aptr = 0;
3101                                 }
3102                                 else aptr++;
3103                         }
3104                 }
3105         }
3106
3107         /* Goto the correct room */
3108         syslog(LOG_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
3109         strcpy(hold_rm, CCC->room.QRname);
3110         strcpy(actual_rm, CCC->room.QRname);
3111         if (recps != NULL) {
3112                 strcpy(actual_rm, SENTITEMS);
3113         }
3114
3115         /* If the user is a twit, move to the twit room for posting */
3116         if (TWITDETECT) {
3117                 if (CCC->user.axlevel == AxProbU) {
3118                         strcpy(hold_rm, actual_rm);
3119                         strcpy(actual_rm, config.c_twitroom);
3120                         syslog(LOG_DEBUG, "Diverting to twit room\n");
3121                 }
3122         }
3123
3124         /* ...or if this message is destined for Aide> then go there. */
3125         if (!IsEmptyStr(force_room)) {
3126                 strcpy(actual_rm, force_room);
3127         }
3128
3129         syslog(LOG_DEBUG, "Final selection: %s\n", actual_rm);
3130         if (strcasecmp(actual_rm, CCC->room.QRname)) {
3131                 /* CtdlGetRoom(&CCC->room, actual_rm); */
3132                 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3133         }
3134
3135         /*
3136          * If this message has no O (room) field, generate one.
3137          */
3138         if (msg->cm_fields['O'] == NULL) {
3139                 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3140         }
3141
3142         /* Perform "before save" hooks (aborting if any return nonzero) */
3143         syslog(LOG_DEBUG, "Performing before-save hooks\n");
3144         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3145
3146         /*
3147          * If this message has an Exclusive ID, and the room is replication
3148          * checking enabled, then do replication checks.
3149          */
3150         if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3151                 ReplicationChecks(msg);
3152         }
3153
3154         /* Save it to disk */
3155         syslog(LOG_DEBUG, "Saving to disk\n");
3156         newmsgid = send_message(msg);
3157         if (newmsgid <= 0L) return(-5);
3158
3159         /* Write a supplemental message info record.  This doesn't have to
3160          * be a critical section because nobody else knows about this message
3161          * yet.
3162          */
3163         syslog(LOG_DEBUG, "Creating MetaData record\n");
3164         memset(&smi, 0, sizeof(struct MetaData));
3165         smi.meta_msgnum = newmsgid;
3166         smi.meta_refcount = 0;
3167         safestrncpy(smi.meta_content_type, content_type,
3168                         sizeof smi.meta_content_type);
3169
3170         /*
3171          * Measure how big this message will be when rendered as RFC822.
3172          * We do this for two reasons:
3173          * 1. We need the RFC822 length for the new metadata record, so the
3174          *    POP and IMAP services don't have to calculate message lengths
3175          *    while the user is waiting (multiplied by potentially hundreds
3176          *    or thousands of messages).
3177          * 2. If journaling is enabled, we will need an RFC822 version of the
3178          *    message to attach to the journalized copy.
3179          */
3180         if (CCC->redirect_buffer != NULL) {
3181                 syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3182                 abort();
3183         }
3184         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3185         CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3186         smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3187         saved_rfc822_version = CCC->redirect_buffer;
3188         CCC->redirect_buffer = NULL;
3189
3190         PutMetaData(&smi);
3191
3192         /* Now figure out where to store the pointers */
3193         syslog(LOG_DEBUG, "Storing pointers\n");
3194
3195         /* If this is being done by the networker delivering a private
3196          * message, we want to BYPASS saving the sender's copy (because there
3197          * is no local sender; it would otherwise go to the Trashcan).
3198          */
3199         if ((!CCC->internal_pgm) || (recps == NULL)) {
3200                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3201                         syslog(LOG_ERR, "ERROR saving message pointer!\n");
3202                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3203                 }
3204         }
3205
3206         /* For internet mail, drop a copy in the outbound queue room */
3207         if ((recps != NULL) && (recps->num_internet > 0)) {
3208                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3209         }
3210
3211         /* If other rooms are specified, drop them there too. */
3212         if ((recps != NULL) && (recps->num_room > 0))
3213           for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3214                 extract_token(recipient, recps->recp_room, i,
3215                                         '|', sizeof recipient);
3216                 syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);
3217                 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3218         }
3219
3220         /* Bump this user's messages posted counter. */
3221         syslog(LOG_DEBUG, "Updating user\n");
3222         CtdlGetUserLock(&CCC->user, CCC->curr_user);
3223         CCC->user.posted = CCC->user.posted + 1;
3224         CtdlPutUserLock(&CCC->user);
3225
3226         /* Decide where bounces need to be delivered */
3227         if ((recps != NULL) && (recps->bounce_to != NULL)) {
3228                 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3229         }
3230         else if (CCC->logged_in) {
3231                 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3232         }
3233         else {
3234                 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3235         }
3236
3237         /* If this is private, local mail, make a copy in the
3238          * recipient's mailbox and bump the reference count.
3239          */
3240         if ((recps != NULL) && (recps->num_local > 0))
3241           for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3242                 extract_token(recipient, recps->recp_local, i,
3243                                         '|', sizeof recipient);
3244                 syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3245                         recipient);
3246                 if (CtdlGetUser(&userbuf, recipient) == 0) {
3247                         // Add a flag so the Funambol module knows its mail
3248                         msg->cm_fields['W'] = strdup(recipient);
3249                         CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3250                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3251                         CtdlBumpNewMailCounter(userbuf.usernum);
3252                         if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3253                         /* Generate a instruction message for the Funambol notification
3254                          * server, in the same style as the SMTP queue
3255                          */
3256                            instr_alloc = 1024;
3257                            instr = malloc(instr_alloc);
3258                            snprintf(instr, instr_alloc,
3259                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3260                         "bounceto|%s\n",
3261                         SPOOLMIME, newmsgid, (long)time(NULL),
3262                         bounce_to
3263                         );
3264
3265                            imsg = malloc(sizeof(struct CtdlMessage));
3266                            memset(imsg, 0, sizeof(struct CtdlMessage));
3267                            imsg->cm_magic = CTDLMESSAGE_MAGIC;
3268                            imsg->cm_anon_type = MES_NORMAL;
3269                            imsg->cm_format_type = FMT_RFC822;
3270                            imsg->cm_fields['A'] = strdup("Citadel");
3271                            imsg->cm_fields['J'] = strdup("do not journal");
3272                            imsg->cm_fields['M'] = instr;        /* imsg owns this memory now */
3273                            imsg->cm_fields['W'] = strdup(recipient);
3274                            CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3275                            CtdlFreeMessage(imsg);
3276                         }
3277                 }
3278                 else {
3279                         syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3280                         CtdlSaveMsgPointerInRoom(config.c_aideroom,
3281                                 newmsgid, 0, msg);
3282                 }
3283         }
3284
3285         /* Perform "after save" hooks */
3286         syslog(LOG_DEBUG, "Performing after-save hooks\n");
3287         PerformMessageHooks(msg, EVT_AFTERSAVE);
3288
3289         /* For IGnet mail, we have to save a new copy into the spooler for
3290          * each recipient, with the R and D fields set to the recipient and
3291          * destination-node.  This has two ugly side effects: all other
3292          * recipients end up being unlisted in this recipient's copy of the
3293          * message, and it has to deliver multiple messages to the same
3294          * node.  We'll revisit this again in a year or so when everyone has
3295          * a network spool receiver that can handle the new style messages.
3296          */
3297         if ((recps != NULL) && (recps->num_ignet > 0))
3298           for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3299                 extract_token(recipient, recps->recp_ignet, i,
3300                                 '|', sizeof recipient);
3301
3302                 hold_R = msg->cm_fields['R'];
3303                 hold_D = msg->cm_fields['D'];
3304                 msg->cm_fields['R'] = malloc(SIZ);
3305                 msg->cm_fields['D'] = malloc(128);
3306                 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3307                 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3308                 
3309                 serialize_message(&smr, msg);
3310                 if (smr.len > 0) {
3311                         snprintf(submit_filename, sizeof submit_filename,
3312                                          "%s/netmail.%04lx.%04x.%04x",
3313                                          ctdl_netin_dir,
3314                                          (long) getpid(), CCC->cs_pid, ++seqnum);
3315                         network_fp = fopen(submit_filename, "wb+");
3316                         if (network_fp != NULL) {
3317                                 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3318                                 fclose(network_fp);
3319                         }
3320                         free(smr.ser);
3321                 }
3322
3323                 free(msg->cm_fields['R']);
3324                 free(msg->cm_fields['D']);
3325                 msg->cm_fields['R'] = hold_R;
3326                 msg->cm_fields['D'] = hold_D;
3327         }
3328
3329         /* Go back to the room we started from */
3330         syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3331         if (strcasecmp(hold_rm, CCC->room.QRname))
3332                 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3333
3334         /* For internet mail, generate delivery instructions.
3335          * Yes, this is recursive.  Deal with it.  Infinite recursion does
3336          * not happen because the delivery instructions message does not
3337          * contain a recipient.
3338          */
3339         if ((recps != NULL) && (recps->num_internet > 0)) {
3340                 syslog(LOG_DEBUG, "Generating delivery instructions\n");
3341                 instr_alloc = 1024;
3342                 instr = malloc(instr_alloc);
3343                 snprintf(instr, instr_alloc,
3344                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3345                         "bounceto|%s\n",
3346                         SPOOLMIME, newmsgid, (long)time(NULL),
3347                         bounce_to
3348                 );
3349
3350                 if (recps->envelope_from != NULL) {
3351                         tmp = strlen(instr);
3352                         snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3353                 }
3354
3355                 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3356                         tmp = strlen(instr);
3357                         extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3358                         if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3359                                 instr_alloc = instr_alloc * 2;
3360                                 instr = realloc(instr, instr_alloc);
3361                         }
3362                         snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3363                 }
3364
3365                 imsg = malloc(sizeof(struct CtdlMessage));
3366                 memset(imsg, 0, sizeof(struct CtdlMessage));
3367                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3368                 imsg->cm_anon_type = MES_NORMAL;
3369                 imsg->cm_format_type = FMT_RFC822;
3370                 imsg->cm_fields['A'] = strdup("Citadel");
3371                 imsg->cm_fields['J'] = strdup("do not journal");
3372                 imsg->cm_fields['M'] = instr;   /* imsg owns this memory now */
3373                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3374                 CtdlFreeMessage(imsg);
3375         }
3376
3377         /*
3378          * Any addresses to harvest for someone's address book?
3379          */
3380         if ( (CCC->logged_in) && (recps != NULL) ) {
3381                 collected_addresses = harvest_collected_addresses(msg);
3382         }
3383
3384         if (collected_addresses != NULL) {
3385                 aptr = (struct addresses_to_be_filed *)
3386                         malloc(sizeof(struct addresses_to_be_filed));
3387                 CtdlMailboxName(actual_rm, sizeof actual_rm,
3388                         &CCC->user, USERCONTACTSROOM);
3389                 aptr->roomname = strdup(actual_rm);
3390                 aptr->collected_addresses = collected_addresses;
3391                 begin_critical_section(S_ATBF);
3392                 aptr->next = atbf;
3393                 atbf = aptr;
3394                 end_critical_section(S_ATBF);
3395         }
3396
3397         /*
3398          * Determine whether this message qualifies for journaling.
3399          */
3400         if (msg->cm_fields['J'] != NULL) {
3401                 qualified_for_journaling = 0;
3402         }
3403         else {
3404                 if (recps == NULL) {
3405                         qualified_for_journaling = config.c_journal_pubmsgs;
3406                 }
3407                 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3408                         qualified_for_journaling = config.c_journal_email;
3409                 }
3410                 else {
3411                         qualified_for_journaling = config.c_journal_pubmsgs;
3412                 }
3413         }
3414
3415         /*
3416          * Do we have to perform journaling?  If so, hand off the saved
3417          * RFC822 version will be handed off to the journaler for background
3418          * submit.  Otherwise, we have to free the memory ourselves.
3419          */
3420         if (saved_rfc822_version != NULL) {
3421                 if (qualified_for_journaling) {
3422                         JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3423                 }
3424                 else {
3425                         FreeStrBuf(&saved_rfc822_version);
3426                 }
3427         }
3428
3429         /* Done. */
3430         return(newmsgid);
3431 }
3432
3433
3434
3435 void aide_message (char *text, char *subject)
3436 {
3437         quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3438 }
3439
3440
3441 /*
3442  * Convenience function for generating small administrative messages.
3443  */
3444 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text, 
3445                         int format_type, const char *subject)
3446 {
3447         struct CtdlMessage *msg;
3448         struct recptypes *recp = NULL;
3449
3450         msg = malloc(sizeof(struct CtdlMessage));
3451         memset(msg, 0, sizeof(struct CtdlMessage));
3452         msg->cm_magic = CTDLMESSAGE_MAGIC;
3453         msg->cm_anon_type = MES_NORMAL;
3454         msg->cm_format_type = format_type;
3455
3456         if (from != NULL) {
3457                 msg->cm_fields['A'] = strdup(from);
3458         }
3459         else if (fromaddr != NULL) {
3460                 msg->cm_fields['A'] = strdup(fromaddr);
3461                 if (strchr(msg->cm_fields['A'], '@')) {
3462                         *strchr(msg->cm_fields['A'], '@') = 0;
3463                 }
3464         }
3465         else {
3466                 msg->cm_fields['A'] = strdup("Citadel");
3467         }
3468
3469         if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3470         if (room != NULL) msg->cm_fields['O'] = strdup(room);
3471         msg->cm_fields['N'] = strdup(NODENAME);
3472         if (to != NULL) {
3473                 msg->cm_fields['R'] = strdup(to);
3474                 recp = validate_recipients(to, NULL, 0);
3475         }
3476         if (subject != NULL) {
3477                 msg->cm_fields['U'] = strdup(subject);
3478         }
3479         msg->cm_fields['M'] = strdup(text);
3480
3481         CtdlSubmitMsg(msg, recp, room, 0);
3482         CtdlFreeMessage(msg);
3483         if (recp != NULL) free_recipients(recp);
3484 }
3485
3486
3487
3488 /*
3489  * Back end function used by CtdlMakeMessage() and similar functions
3490  */
3491 StrBuf *CtdlReadMessageBodyBuf(char *terminator,        /* token signalling EOT */
3492                                long tlen,
3493                                size_t maxlen,           /* maximum message length */
3494                                char *exist,             /* if non-null, append to it;
3495                                                            exist is ALWAYS freed  */
3496                                int crlf,                /* CRLF newlines instead of LF */
3497                                int *sock                /* socket handle or 0 for this session's client socket */
3498                         ) 
3499 {
3500         StrBuf *Message;
3501         StrBuf *LineBuf;
3502         int flushing = 0;
3503         int finished = 0;
3504         int dotdot = 0;
3505
3506         LineBuf = NewStrBufPlain(NULL, SIZ);
3507         if (exist == NULL) {
3508                 Message = NewStrBufPlain(NULL, 4 * SIZ);
3509         }
3510         else {
3511                 Message = NewStrBufPlain(exist, -1);
3512                 free(exist);
3513         }
3514
3515         /* Do we need to change leading ".." to "." for SMTP escaping? */
3516         if ((tlen == 1) && (*terminator == '.')) {
3517                 dotdot = 1;
3518         }
3519
3520         /* read in the lines of message text one by one */
3521         do {
3522                 if (sock != NULL) {
3523                         if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3524                             (*sock == -1))
3525                                 finished = 1;
3526                 }
3527                 else {
3528                         if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3529                 }
3530                 if ((StrLength(LineBuf) == tlen) && 
3531                     (!strcmp(ChrPtr(LineBuf), terminator)))
3532                         finished = 1;
3533
3534                 if ( (!flushing) && (!finished) ) {
3535                         if (crlf) {
3536                                 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3537                         }
3538                         else {
3539                                 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3540                         }
3541                         
3542                         /* Unescape SMTP-style input of two dots at the beginning of the line */
3543                         if ((dotdot) &&
3544                             (StrLength(LineBuf) == 2) && 
3545                             (!strcmp(ChrPtr(LineBuf), "..")))
3546                         {
3547                                 StrBufCutLeft(LineBuf, 1);
3548                         }
3549                         
3550                         StrBufAppendBuf(Message, LineBuf, 0);
3551                 }
3552
3553                 /* if we've hit the max msg length, flush the rest */
3554                 if (StrLength(Message) >= maxlen) flushing = 1;
3555
3556         } while (!finished);
3557         FreeStrBuf(&LineBuf);
3558         return Message;
3559 }
3560
3561
3562 /*
3563  * Back end function used by CtdlMakeMessage() and similar functions
3564  */
3565 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
3566                           long tlen,
3567                           size_t maxlen,                /* maximum message length */
3568                           char *exist,          /* if non-null, append to it;
3569                                                    exist is ALWAYS freed  */
3570                           int crlf,             /* CRLF newlines instead of LF */
3571                           int *sock             /* socket handle or 0 for this session's client socket */
3572         ) 
3573 {
3574         StrBuf *Message;
3575
3576         Message = CtdlReadMessageBodyBuf(terminator,
3577                                          tlen,
3578                                          maxlen,
3579                                          exist,
3580                                          crlf,
3581                                          sock);
3582         if (Message == NULL)
3583                 return NULL;
3584         else
3585                 return SmashStrBuf(&Message);
3586 }
3587
3588
3589 /*
3590  * Build a binary message to be saved on disk.
3591  * (NOTE: if you supply 'preformatted_text', the buffer you give it
3592  * will become part of the message.  This means you are no longer
3593  * responsible for managing that memory -- it will be freed along with
3594  * the rest of the fields when CtdlFreeMessage() is called.)
3595  */
3596
3597 struct CtdlMessage *CtdlMakeMessage(
3598         struct ctdluser *author,        /* author's user structure */
3599         char *recipient,                /* NULL if it's not mail */
3600         char *recp_cc,                  /* NULL if it's not mail */
3601         char *room,                     /* room where it's going */
3602         int type,                       /* see MES_ types in header file */
3603         int format_type,                /* variformat, plain text, MIME... */
3604         char *fake_name,                /* who we're masquerading as */
3605         char *my_email,                 /* which of my email addresses to use (empty is ok) */
3606         char *subject,                  /* Subject (optional) */
3607         char *supplied_euid,            /* ...or NULL if this is irrelevant */
3608         char *preformatted_text,        /* ...or NULL to read text from client */
3609         char *references                /* Thread references */
3610 ) {
3611         char dest_node[256];
3612         char buf[1024];
3613         struct CtdlMessage *msg;
3614         StrBuf *FakeAuthor;
3615         StrBuf *FakeEncAuthor = NULL;
3616
3617         msg = malloc(sizeof(struct CtdlMessage));
3618         memset(msg, 0, sizeof(struct CtdlMessage));
3619         msg->cm_magic = CTDLMESSAGE_MAGIC;
3620         msg->cm_anon_type = type;
3621         msg->cm_format_type = format_type;
3622
3623         /* Don't confuse the poor folks if it's not routed mail. */
3624         strcpy(dest_node, "");
3625
3626         if (recipient != NULL) striplt(recipient);
3627         if (recp_cc != NULL) striplt(recp_cc);
3628
3629         /* Path or Return-Path */
3630         if (my_email == NULL) my_email = "";
3631
3632         if (!IsEmptyStr(my_email)) {
3633                 msg->cm_fields['P'] = strdup(my_email);
3634         }
3635         else {
3636                 snprintf(buf, sizeof buf, "%s", author->fullname);
3637                 msg->cm_fields['P'] = strdup(buf);
3638         }
3639         convert_spaces_to_underscores(msg->cm_fields['P']);
3640
3641         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
3642         msg->cm_fields['T'] = strdup(buf);
3643
3644         if ((fake_name != NULL) && (fake_name[0])) {            /* author */
3645                 FakeAuthor = NewStrBufPlain (fake_name, -1);
3646         }
3647         else {
3648                 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3649         }
3650         StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3651         msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3652         FreeStrBuf(&FakeAuthor);
3653
3654         if (CC->room.QRflags & QR_MAILBOX) {            /* room */
3655                 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3656         }
3657         else {
3658                 msg->cm_fields['O'] = strdup(CC->room.QRname);
3659         }
3660
3661         msg->cm_fields['N'] = strdup(NODENAME);         /* nodename */
3662         msg->cm_fields['H'] = strdup(HUMANNODE);                /* hnodename */
3663
3664         if ((recipient != NULL) && (recipient[0] != 0)) {
3665                 msg->cm_fields['R'] = strdup(recipient);
3666         }
3667         if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3668                 msg->cm_fields['Y'] = strdup(recp_cc);
3669         }
3670         if (dest_node[0] != 0) {
3671                 msg->cm_fields['D'] = strdup(dest_node);
3672         }
3673
3674         if (!IsEmptyStr(my_email)) {
3675                 msg->cm_fields['F'] = strdup(my_email);
3676         }
3677         else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3678                 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3679         }
3680
3681         if (subject != NULL) {
3682                 long length;
3683                 striplt(subject);
3684                 length = strlen(subject);
3685                 if (length > 0) {
3686                         long i;
3687                         long IsAscii;
3688                         IsAscii = -1;
3689                         i = 0;
3690                         while ((subject[i] != '\0') &&
3691                                (IsAscii = isascii(subject[i]) != 0 ))
3692                                 i++;
3693                         if (IsAscii != 0)
3694                                 msg->cm_fields['U'] = strdup(subject);
3695                         else /* ok, we've got utf8 in the string. */
3696                         {
3697                                 msg->cm_fields['U'] = rfc2047encode(subject, length);
3698                         }
3699
3700                 }
3701         }
3702
3703         if (supplied_euid != NULL) {
3704                 msg->cm_fields['E'] = strdup(supplied_euid);
3705         }
3706
3707         if (references != NULL) {
3708                 if (!IsEmptyStr(references)) {
3709                         msg->cm_fields['W'] = strdup(references);
3710                 }
3711         }
3712
3713         if (preformatted_text != NULL) {
3714                 msg->cm_fields['M'] = preformatted_text;
3715         }
3716         else {
3717                 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3718         }
3719
3720         return(msg);
3721 }
3722
3723
3724 /*
3725  * Check to see whether we have permission to post a message in the current
3726  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3727  * returns 0 on success.
3728  */
3729 int CtdlDoIHavePermissionToPostInThisRoom(
3730         char *errmsgbuf, 
3731         size_t n, 
3732         const char* RemoteIdentifier,
3733         int PostPublic,
3734         int is_reply
3735 ) {
3736         int ra;
3737
3738         if (!(CC->logged_in) && 
3739             (PostPublic == POST_LOGGED_IN)) {
3740                 snprintf(errmsgbuf, n, "Not logged in.");
3741                 return (ERROR + NOT_LOGGED_IN);
3742         }
3743         else if (PostPublic == CHECK_EXISTANCE) {
3744                 return (0); // We're Evaling whether a recipient exists
3745         }
3746         else if (!(CC->logged_in)) {
3747                 
3748                 if ((CC->room.QRflags & QR_READONLY)) {
3749                         snprintf(errmsgbuf, n, "Not logged in.");
3750                         return (ERROR + NOT_LOGGED_IN);
3751                 }
3752                 if (CC->room.QRflags2 & QR2_MODERATED) {
3753                         snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3754                         return (ERROR + NOT_LOGGED_IN);
3755                 }
3756                 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3757                         SpoolControl *sc;
3758                         char filename[SIZ];
3759                         int found;
3760
3761                         if (RemoteIdentifier == NULL)
3762                         {
3763                                 snprintf(errmsgbuf, n, "Need sender to permit access.");
3764                                 return (ERROR + USERNAME_REQUIRED);
3765                         }
3766
3767                         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3768                         begin_critical_section(S_NETCONFIGS);
3769                         if (!read_spoolcontrol_file(&sc, filename))
3770                         {
3771                                 end_critical_section(S_NETCONFIGS);
3772                                 snprintf(errmsgbuf, n,
3773                                         "This mailing list only accepts posts from subscribers.");
3774                                 return (ERROR + NO_SUCH_USER);
3775                         }
3776                         end_critical_section(S_NETCONFIGS);
3777                         found = is_recipient (sc, RemoteIdentifier);
3778                         free_spoolcontrol_struct(&sc);
3779                         if (found) {
3780                                 return (0);
3781                         }
3782                         else {
3783                                 snprintf(errmsgbuf, n,
3784                                         "This mailing list only accepts posts from subscribers.");
3785                                 return (ERROR + NO_SUCH_USER);
3786                         }
3787                 }
3788                 return (0);
3789
3790         }
3791
3792         if ((CC->user.axlevel < AxProbU)
3793             && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3794                 snprintf(errmsgbuf, n, "Need to be validated to enter "
3795                                 "(except in %s> to sysop)", MAILROOM);
3796                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3797         }
3798
3799         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3800
3801         if ( (!(ra & UA_POSTALLOWED)) && (ra & UA_REPLYALLOWED) && (!is_reply) ) {
3802                 /*
3803                  * To be thorough, we ought to check to see if the message they are
3804                  * replying to is actually a valid one in this room, but unless this
3805                  * actually becomes a problem we'll go with high performance instead.
3806                  */
3807                 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
3808                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3809         }
3810
3811         else if (!(ra & UA_POSTALLOWED)) {
3812                 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3813                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3814         }
3815
3816         strcpy(errmsgbuf, "Ok");
3817         return(0);
3818 }
3819
3820
3821 /*
3822  * Check to see if the specified user has Internet mail permission
3823  * (returns nonzero if permission is granted)
3824  */
3825 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3826
3827         /* Do not allow twits to send Internet mail */
3828         if (who->axlevel <= AxProbU) return(0);
3829
3830         /* Globally enabled? */
3831         if (config.c_restrict == 0) return(1);
3832
3833         /* User flagged ok? */
3834         if (who->flags & US_INTERNET) return(2);
3835
3836         /* Aide level access? */
3837         if (who->axlevel >= AxAideU) return(3);
3838
3839         /* No mail for you! */
3840         return(0);
3841 }
3842
3843
3844 /*
3845  * Validate recipients, count delivery types and errors, and handle aliasing
3846  * FIXME check for dupes!!!!!
3847  *
3848  * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
3849  * were specified, or the number of addresses found invalid.
3850  *
3851  * Caller needs to free the result using free_recipients()
3852  */
3853 struct recptypes *validate_recipients(const char *supplied_recipients, 
3854                                       const char *RemoteIdentifier, 
3855                                       int Flags) {
3856         struct recptypes *ret;
3857         char *recipients = NULL;
3858         char this_recp[256];
3859         char this_recp_cooked[256];
3860         char append[SIZ];
3861         int num_recps = 0;
3862         int i, j;
3863         int mailtype;
3864         int invalid;
3865         struct ctdluser tempUS;
3866         struct ctdlroom tempQR;
3867         struct ctdlroom tempQR2;
3868         int err = 0;
3869         char errmsg[SIZ];
3870         int in_quotes = 0;
3871
3872         /* Initialize */
3873         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3874         if (ret == NULL) return(NULL);
3875
3876         /* Set all strings to null and numeric values to zero */
3877         memset(ret, 0, sizeof(struct recptypes));
3878
3879         if (supplied_recipients == NULL) {
3880                 recipients = strdup("");
3881         }
3882         else {
3883                 recipients = strdup(supplied_recipients);
3884         }
3885
3886         /* Allocate some memory.  Yes, this allocates 500% more memory than we will
3887          * actually need, but it's healthier for the heap than doing lots of tiny
3888          * realloc() calls instead.
3889          */
3890
3891         ret->errormsg = malloc(strlen(recipients) + 1024);
3892         ret->recp_local = malloc(strlen(recipients) + 1024);
3893         ret->recp_internet = malloc(strlen(recipients) + 1024);
3894         ret->recp_ignet = malloc(strlen(recipients) + 1024);
3895         ret->recp_room = malloc(strlen(recipients) + 1024);
3896         ret->display_recp = malloc(strlen(recipients) + 1024);
3897
3898         ret->errormsg[0] = 0;
3899         ret->recp_local[0] = 0;
3900         ret->recp_internet[0] = 0;
3901         ret->recp_ignet[0] = 0;
3902         ret->recp_room[0] = 0;
3903         ret->display_recp[0] = 0;
3904
3905         ret->recptypes_magic = RECPTYPES_MAGIC;
3906
3907         /* Change all valid separator characters to commas */
3908         for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3909                 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3910                         recipients[i] = ',';
3911                 }
3912         }
3913
3914         /* Now start extracting recipients... */
3915
3916         while (!IsEmptyStr(recipients)) {
3917
3918                 for (i=0; i<=strlen(recipients); ++i) {
3919                         if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3920                         if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3921                                 safestrncpy(this_recp, recipients, i+1);
3922                                 this_recp[i] = 0;
3923                                 if (recipients[i] == ',') {
3924                                         strcpy(recipients, &recipients[i+1]);
3925                                 }
3926                                 else {
3927                                         strcpy(recipients, "");
3928                                 }
3929                                 break;
3930                         }
3931                 }
3932
3933                 striplt(this_recp);
3934                 if (IsEmptyStr(this_recp))
3935                         break;
3936                 syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3937                 ++num_recps;
3938                 mailtype = alias(this_recp);
3939                 mailtype = alias(this_recp);
3940                 mailtype = alias(this_recp);
3941                 j = 0;
3942                 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3943                         if (this_recp[j]=='_') {
3944                                 this_recp_cooked[j] = ' ';
3945                         }
3946                         else {
3947                                 this_recp_cooked[j] = this_recp[j];
3948                         }
3949                 }
3950                 this_recp_cooked[j] = '\0';
3951                 invalid = 0;
3952                 errmsg[0] = 0;
3953                 switch(mailtype) {
3954                         case MES_LOCAL:
3955                                 if (!strcasecmp(this_recp, "sysop")) {
3956                                         ++ret->num_room;
3957                                         strcpy(this_recp, config.c_aideroom);
3958                                         if (!IsEmptyStr(ret->recp_room)) {
3959                                                 strcat(ret->recp_room, "|");
3960                                         }
3961                                         strcat(ret->recp_room, this_recp);
3962                                 }
3963                                 else if ( (!strncasecmp(this_recp, "room_", 5))
3964                                       && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3965
3966                                         /* Save room so we can restore it later */
3967                                         tempQR2 = CC->room;
3968                                         CC->room = tempQR;
3969                                         
3970                                         /* Check permissions to send mail to this room */
3971                                         err = CtdlDoIHavePermissionToPostInThisRoom(
3972                                                 errmsg, 
3973                                                 sizeof errmsg, 
3974                                                 RemoteIdentifier,
3975                                                 Flags,
3976                                                 0                       /* 0 = not a reply */
3977                                         );
3978                                         if (err)
3979                                         {
3980                                                 ++ret->num_error;
3981                                                 invalid = 1;
3982                                         } 
3983                                         else {
3984                                                 ++ret->num_room;
3985                                                 if (!IsEmptyStr(ret->recp_room)) {
3986                                                         strcat(ret->recp_room, "|");
3987                                                 }
3988                                                 strcat(ret->recp_room, &this_recp_cooked[5]);
3989                                         }
3990                                         
3991                                         /* Restore room in case something needs it */
3992                                         CC->room = tempQR2;
3993
3994                                 }
3995                                 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3996                                         ++ret->num_local;
3997                                         strcpy(this_recp, tempUS.fullname);
3998                                         if (!IsEmptyStr(ret->recp_local)) {
3999                                                 strcat(ret->recp_local, "|");
4000                                         }
4001                                         strcat(ret->recp_local, this_recp);
4002                                 }
4003                                 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4004                                         ++ret->num_local;
4005                                         strcpy(this_recp, tempUS.fullname);
4006                                         if (!IsEmptyStr(ret->recp_local)) {
4007                                                 strcat(ret->recp_local, "|");
4008                                         }
4009                                         strcat(ret->recp_local, this_recp);
4010                                 }
4011                                 else {
4012                                         ++ret->num_error;
4013                                         invalid = 1;
4014                                 }
4015                                 break;
4016                         case MES_INTERNET:
4017                                 /* Yes, you're reading this correctly: if the target
4018                                  * domain points back to the local system or an attached
4019                                  * Citadel directory, the address is invalid.  That's
4020                                  * because if the address were valid, we would have
4021                                  * already translated it to a local address by now.
4022                                  */
4023                                 if (IsDirectory(this_recp, 0)) {
4024                                         ++ret->num_error;
4025                                         invalid = 1;
4026                                 }
4027                                 else {
4028                                         ++ret->num_internet;
4029                                         if (!IsEmptyStr(ret->recp_internet)) {
4030                                                 strcat(ret->recp_internet, "|");
4031                                         }
4032                                         strcat(ret->recp_internet, this_recp);
4033                                 }
4034                                 break;
4035                         case MES_IGNET:
4036                                 ++ret->num_ignet;
4037                                 if (!IsEmptyStr(ret->recp_ignet)) {
4038                                         strcat(ret->recp_ignet, "|");
4039                                 }
4040                                 strcat(ret->recp_ignet, this_recp);
4041                                 break;
4042                         case MES_ERROR:
4043                                 ++ret->num_error;
4044                                 invalid = 1;
4045                                 break;
4046                 }
4047                 if (invalid) {
4048                         if (IsEmptyStr(errmsg)) {
4049                                 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4050                         }
4051                         else {
4052                                 snprintf(append, sizeof append, "%s", errmsg);
4053                         }
4054                         if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4055                                 if (!IsEmptyStr(ret->errormsg)) {
4056                                         strcat(ret->errormsg, "; ");
4057                                 }
4058                                 strcat(ret->errormsg, append);
4059                         }
4060                 }
4061                 else {
4062                         if (IsEmptyStr(ret->display_recp)) {
4063                                 strcpy(append, this_recp);
4064                         }
4065                         else {
4066                                 snprintf(append, sizeof append, ", %s", this_recp);
4067                         }
4068                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4069                                 strcat(ret->display_recp, append);
4070                         }
4071                 }
4072         }
4073
4074         if ((ret->num_local + ret->num_internet + ret->num_ignet +
4075            ret->num_room + ret->num_error) == 0) {
4076                 ret->num_error = (-1);
4077                 strcpy(ret->errormsg, "No recipients specified.");
4078         }
4079
4080         syslog(LOG_DEBUG, "validate_recipients()\n");
4081         syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4082         syslog(LOG_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
4083         syslog(LOG_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4084         syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4085         syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4086
4087         free(recipients);
4088         return(ret);
4089 }
4090
4091
4092 /*
4093  * Destructor for struct recptypes
4094  */
4095 void free_recipients(struct recptypes *valid) {
4096
4097         if (valid == NULL) {
4098                 return;
4099         }
4100
4101         if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4102                 syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4103                 abort();
4104         }
4105
4106         if (valid->errormsg != NULL)            free(valid->errormsg);
4107         if (valid->recp_local != NULL)          free(valid->recp_local);
4108         if (valid->recp_internet != NULL)       free(valid->recp_internet);
4109         if (valid->recp_ignet != NULL)          free(valid->recp_ignet);
4110         if (valid->recp_room != NULL)           free(valid->recp_room);
4111         if (valid->display_recp != NULL)        free(valid->display_recp);
4112         if (valid->bounce_to != NULL)           free(valid->bounce_to);
4113         if (valid->envelope_from != NULL)       free(valid->envelope_from);
4114         free(valid);
4115 }
4116
4117
4118
4119 /*
4120  * message entry  -  mode 0 (normal)
4121  */
4122 void cmd_ent0(char *entargs)
4123 {
4124         int post = 0;
4125         char recp[SIZ];
4126         char cc[SIZ];
4127         char bcc[SIZ];
4128         char supplied_euid[128];
4129         int anon_flag = 0;
4130         int format_type = 0;
4131         char newusername[256];
4132         char newuseremail[256];
4133         struct CtdlMessage *msg;
4134         int anonymous = 0;
4135         char errmsg[SIZ];
4136         int err = 0;
4137         struct recptypes *valid = NULL;
4138         struct recptypes *valid_to = NULL;
4139         struct recptypes *valid_cc = NULL;
4140         struct recptypes *valid_bcc = NULL;
4141         char subject[SIZ];
4142         int subject_required = 0;
4143         int do_confirm = 0;
4144         long msgnum;
4145         int i, j;
4146         char buf[256];
4147         int newuseremail_ok = 0;
4148         char references[SIZ];
4149         char *ptr;
4150
4151         unbuffer_output();
4152
4153         post = extract_int(entargs, 0);
4154         extract_token(recp, entargs, 1, '|', sizeof recp);
4155         anon_flag = extract_int(entargs, 2);
4156         format_type = extract_int(entargs, 3);
4157         extract_token(subject, entargs, 4, '|', sizeof subject);
4158         extract_token(newusername, entargs, 5, '|', sizeof newusername);
4159         do_confirm = extract_int(entargs, 6);
4160         extract_token(cc, entargs, 7, '|', sizeof cc);
4161         extract_token(bcc, entargs, 8, '|', sizeof bcc);
4162         switch(CC->room.QRdefaultview) {
4163                 case VIEW_NOTES:
4164                 case VIEW_WIKI:
4165                         extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4166                         break;
4167                 default:
4168                         supplied_euid[0] = 0;
4169                         break;
4170         }
4171         extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4172         extract_token(references, entargs, 11, '|', sizeof references);
4173         for (ptr=references; *ptr != 0; ++ptr) {
4174                 if (*ptr == '!') *ptr = '|';
4175         }
4176
4177         /* first check to make sure the request is valid. */
4178
4179         err = CtdlDoIHavePermissionToPostInThisRoom(
4180                 errmsg,
4181                 sizeof errmsg,
4182                 NULL,
4183                 POST_LOGGED_IN,
4184                 (!IsEmptyStr(references))               /* is this a reply?  or a top-level post? */
4185         );
4186         if (err)
4187         {
4188                 cprintf("%d %s\n", err, errmsg);
4189                 return;
4190         }
4191
4192         /* Check some other permission type things. */
4193
4194         if (IsEmptyStr(newusername)) {
4195                 strcpy(newusername, CC->user.fullname);
4196         }
4197         if (  (CC->user.axlevel < AxAideU)
4198            && (strcasecmp(newusername, CC->user.fullname))
4199            && (strcasecmp(newusername, CC->cs_inet_fn))
4200         ) {     
4201                 cprintf("%d You don't have permission to author messages as '%s'.\n",
4202                         ERROR + HIGHER_ACCESS_REQUIRED,
4203                         newusername
4204                 );
4205                 return;
4206         }
4207
4208
4209         if (IsEmptyStr(newuseremail)) {
4210                 newuseremail_ok = 1;
4211         }
4212
4213         if (!IsEmptyStr(newuseremail)) {
4214                 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4215                         newuseremail_ok = 1;
4216                 }
4217                 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4218                         j = num_tokens(CC->cs_inet_other_emails, '|');
4219                         for (i=0; i<j; ++i) {
4220                                 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4221                                 if (!strcasecmp(newuseremail, buf)) {
4222                                         newuseremail_ok = 1;
4223                                 }
4224                         }
4225                 }
4226         }
4227
4228         if (!newuseremail_ok) {
4229                 cprintf("%d You don't have permission to author messages as '%s'.\n",
4230                         ERROR + HIGHER_ACCESS_REQUIRED,
4231                         newuseremail
4232                 );
4233                 return;
4234         }
4235
4236         CC->cs_flags |= CS_POSTING;
4237
4238         /* In mailbox rooms we have to behave a little differently --
4239          * make sure the user has specified at least one recipient.  Then
4240          * validate the recipient(s).  We do this for the Mail> room, as
4241          * well as any room which has the "Mailbox" view set - unless it
4242          * is the DRAFTS room which does not require recipients
4243          */
4244
4245         if ( (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4246              || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4247         ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4248                 if (CC->user.axlevel < AxProbU) {
4249                         strcpy(recp, "sysop");
4250                         strcpy(cc, "");
4251                         strcpy(bcc, "");
4252                 }
4253
4254                 valid_to = validate_recipients(recp, NULL, 0);
4255                 if (valid_to->num_error > 0) {
4256                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4257                         free_recipients(valid_to);
4258                         return;
4259                 }
4260
4261                 valid_cc = validate_recipients(cc, NULL, 0);
4262                 if (valid_cc->num_error > 0) {
4263                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4264                         free_recipients(valid_to);
4265                         free_recipients(valid_cc);
4266                         return;
4267                 }
4268
4269                 valid_bcc = validate_recipients(bcc, NULL, 0);
4270                 if (valid_bcc->num_error > 0) {
4271                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4272                         free_recipients(valid_to);
4273                         free_recipients(valid_cc);
4274                         free_recipients(valid_bcc);
4275                         return;
4276                 }
4277
4278                 /* Recipient required, but none were specified */
4279                 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4280                         free_recipients(valid_to);
4281                         free_recipients(valid_cc);
4282                         free_recipients(valid_bcc);
4283                         cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4284                         return;
4285                 }
4286
4287                 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4288                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4289                                 cprintf("%d You do not have permission "
4290                                         "to send Internet mail.\n",
4291                                         ERROR + HIGHER_ACCESS_REQUIRED);
4292                                 free_recipients(valid_to);
4293                                 free_recipients(valid_cc);
4294                                 free_recipients(valid_bcc);
4295                                 return;
4296                         }
4297                 }
4298
4299                 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)
4300                    && (CC->user.axlevel < AxNetU) ) {
4301                         cprintf("%d Higher access required for network mail.\n",
4302                                 ERROR + HIGHER_ACCESS_REQUIRED);
4303                         free_recipients(valid_to);
4304                         free_recipients(valid_cc);
4305                         free_recipients(valid_bcc);
4306                         return;
4307                 }
4308         
4309                 if ((RESTRICT_INTERNET == 1)
4310                     && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4311                     && ((CC->user.flags & US_INTERNET) == 0)
4312                     && (!CC->internal_pgm)) {
4313                         cprintf("%d You don't have access to Internet mail.\n",
4314                                 ERROR + HIGHER_ACCESS_REQUIRED);
4315                         free_recipients(valid_to);
4316                         free_recipients(valid_cc);
4317                         free_recipients(valid_bcc);
4318                         return;
4319                 }
4320
4321         }
4322
4323         /* Is this a room which has anonymous-only or anonymous-option? */
4324         anonymous = MES_NORMAL;
4325         if (CC->room.QRflags & QR_ANONONLY) {
4326                 anonymous = MES_ANONONLY;
4327         }
4328         if (CC->room.QRflags & QR_ANONOPT) {
4329                 if (anon_flag == 1) {   /* only if the user requested it */
4330                         anonymous = MES_ANONOPT;
4331                 }
4332         }
4333
4334         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4335                 recp[0] = 0;
4336         }
4337
4338         /* Recommend to the client that the use of a message subject is
4339          * strongly recommended in this room, if either the SUBJECTREQ flag
4340          * is set, or if there is one or more Internet email recipients.
4341          */
4342         if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4343         if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
4344         if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
4345         if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
4346
4347         /* If we're only checking the validity of the request, return
4348          * success without creating the message.
4349          */
4350         if (post == 0) {
4351                 cprintf("%d %s|%d\n", CIT_OK,
4352                         ((valid_to != NULL) ? valid_to->display_recp : ""), 
4353                         subject_required);
4354                 free_recipients(valid_to);
4355                 free_recipients(valid_cc);
4356                 free_recipients(valid_bcc);
4357                 return;
4358         }
4359
4360         /* We don't need these anymore because we'll do it differently below */
4361         free_recipients(valid_to);
4362         free_recipients(valid_cc);
4363         free_recipients(valid_bcc);
4364
4365         /* Read in the message from the client. */
4366         if (do_confirm) {
4367                 cprintf("%d send message\n", START_CHAT_MODE);
4368         } else {
4369                 cprintf("%d send message\n", SEND_LISTING);
4370         }
4371
4372         msg = CtdlMakeMessage(&CC->user, recp, cc,
4373                 CC->room.QRname, anonymous, format_type,
4374                 newusername, newuseremail, subject,
4375                 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4376                 NULL, references);
4377
4378         /* Put together one big recipients struct containing to/cc/bcc all in
4379          * one.  This is for the envelope.
4380          */
4381         char *all_recps = malloc(SIZ * 3);
4382         strcpy(all_recps, recp);
4383         if (!IsEmptyStr(cc)) {
4384                 if (!IsEmptyStr(all_recps)) {
4385                         strcat(all_recps, ",");
4386                 }
4387                 strcat(all_recps, cc);
4388         }
4389         if (!IsEmptyStr(bcc)) {
4390                 if (!IsEmptyStr(all_recps)) {
4391                         strcat(all_recps, ",");
4392                 }
4393                 strcat(all_recps, bcc);
4394         }
4395         if (!IsEmptyStr(all_recps)) {
4396                 valid = validate_recipients(all_recps, NULL, 0);
4397         }
4398         else {
4399                 valid = NULL;
4400         }
4401         free(all_recps);
4402
4403         if (msg != NULL) {
4404                 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4405
4406                 if (do_confirm) {
4407                         cprintf("%ld\n", msgnum);
4408                         if (msgnum >= 0L) {
4409                                 cprintf("Message accepted.\n");
4410                         }
4411                         else {
4412                                 cprintf("Internal error.\n");
4413                         }
4414                         if (msg->cm_fields['E'] != NULL) {
4415                                 cprintf("%s\n", msg->cm_fields['E']);
4416                         } else {
4417                                 cprintf("\n");
4418                         }
4419                         cprintf("000\n");
4420                 }
4421
4422                 CtdlFreeMessage(msg);
4423         }
4424         if (valid != NULL) {
4425                 free_recipients(valid);
4426         }
4427         return;
4428 }
4429
4430
4431
4432 /*
4433  * API function to delete messages which match a set of criteria
4434  * (returns the actual number of messages deleted)
4435  */
4436 int CtdlDeleteMessages(char *room_name,         /* which room */
4437                         long *dmsgnums,         /* array of msg numbers to be deleted */
4438                         int num_dmsgnums,       /* number of msgs to be deleted, or 0 for "any" */
4439                         char *content_type      /* or "" for any.  regular expressions expected. */
4440 )
4441 {
4442         struct ctdlroom qrbuf;
4443         struct cdbdata *cdbfr;
4444         long *msglist = NULL;
4445         long *dellist = NULL;
4446         int num_msgs = 0;
4447         int i, j;
4448         int num_deleted = 0;
4449         int delete_this;
4450         struct MetaData smi;
4451         regex_t re;
4452         regmatch_t pm;
4453         int need_to_free_re = 0;
4454
4455         if (content_type) if (!IsEmptyStr(content_type)) {
4456                 regcomp(&re, content_type, 0);
4457                 need_to_free_re = 1;
4458         }
4459         syslog(LOG_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4460                 room_name, num_dmsgnums, content_type);
4461
4462         /* get room record, obtaining a lock... */
4463         if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4464                 syslog(LOG_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4465                         room_name);
4466                 if (need_to_free_re) regfree(&re);
4467                 return (0);     /* room not found */
4468         }
4469         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4470
4471         if (cdbfr != NULL) {
4472                 dellist = malloc(cdbfr->len);
4473                 msglist = (long *) cdbfr->ptr;
4474                 cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
4475                 num_msgs = cdbfr->len / sizeof(long);
4476                 cdb_free(cdbfr);
4477         }
4478         if (num_msgs > 0) {
4479                 for (i = 0; i < num_msgs; ++i) {
4480                         delete_this = 0x00;
4481
4482                         /* Set/clear a bit for each criterion */
4483
4484                         /* 0 messages in the list or a null list means that we are
4485                          * interested in deleting any messages which meet the other criteria.
4486                          */
4487                         if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4488                                 delete_this |= 0x01;
4489                         }
4490                         else {
4491                                 for (j=0; j<num_dmsgnums; ++j) {
4492                                         if (msglist[i] == dmsgnums[j]) {
4493                                                 delete_this |= 0x01;
4494                                         }
4495                                 }
4496                         }
4497
4498                         if (IsEmptyStr(content_type)) {
4499                                 delete_this |= 0x02;
4500                         } else {
4501                                 GetMetaData(&smi, msglist[i]);
4502                                 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4503                                         delete_this |= 0x02;
4504                                 }
4505                         }
4506
4507                         /* Delete message only if all bits are set */
4508                         if (delete_this == 0x03) {
4509                                 dellist[num_deleted++] = msglist[i];
4510                                 msglist[i] = 0L;
4511                         }
4512                 }
4513
4514                 num_msgs = sort_msglist(msglist, num_msgs);
4515                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4516                           msglist, (int)(num_msgs * sizeof(long)));
4517
4518                 if (num_msgs > 0)
4519                         qrbuf.QRhighest = msglist[num_msgs - 1];
4520                 else
4521                         qrbuf.QRhighest = 0;
4522         }
4523         CtdlPutRoomLock(&qrbuf);
4524
4525         /* Go through the messages we pulled out of the index, and decrement
4526          * their reference counts by 1.  If this is the only room the message
4527          * was in, the reference count will reach zero and the message will
4528          * automatically be deleted from the database.  We do this in a
4529          * separate pass because there might be plug-in hooks getting called,
4530          * and we don't want that happening during an S_ROOMS critical
4531          * section.
4532          */
4533         if (num_deleted) for (i=0; i<num_deleted; ++i) {
4534                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4535                 AdjRefCount(dellist[i], -1);
4536         }
4537
4538         /* Now free the memory we used, and go away. */
4539         if (msglist != NULL) free(msglist);
4540         if (dellist != NULL) free(dellist);
4541         syslog(LOG_DEBUG, "%d message(s) deleted.\n", num_deleted);
4542         if (need_to_free_re) regfree(&re);
4543         return (num_deleted);
4544 }
4545
4546
4547
4548 /*
4549  * Check whether the current user has permission to delete messages from
4550  * the current room (returns 1 for yes, 0 for no)
4551  */
4552 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4553         int ra;
4554         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4555         if (ra & UA_DELETEALLOWED) return(1);
4556         return(0);
4557 }
4558
4559
4560
4561
4562 /*
4563  * Delete message from current room
4564  */
4565 void cmd_dele(char *args)
4566 {
4567         int num_deleted;
4568         int i;
4569         char msgset[SIZ];
4570         char msgtok[32];
4571         long *msgs;
4572         int num_msgs = 0;
4573
4574         extract_token(msgset, args, 0, '|', sizeof msgset);
4575         num_msgs = num_tokens(msgset, ',');
4576         if (num_msgs < 1) {
4577                 cprintf("%d Nothing to do.\n", CIT_OK);
4578                 return;
4579         }
4580
4581         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4582                 cprintf("%d Higher access required.\n",
4583                         ERROR + HIGHER_ACCESS_REQUIRED);
4584                 return;
4585         }
4586
4587         /*
4588          * Build our message set to be moved/copied
4589          */
4590         msgs = malloc(num_msgs * sizeof(long));
4591         for (i=0; i<num_msgs; ++i) {
4592                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4593                 msgs[i] = atol(msgtok);
4594         }
4595
4596         num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4597         free(msgs);
4598
4599         if (num_deleted) {
4600                 cprintf("%d %d message%s deleted.\n", CIT_OK,
4601                         num_deleted, ((num_deleted != 1) ? "s" : ""));
4602         } else {
4603                 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4604         }
4605 }
4606
4607
4608
4609
4610 /*
4611  * move or copy a message to another room
4612  */
4613 void cmd_move(char *args)
4614 {
4615         char msgset[SIZ];
4616         char msgtok[32];
4617         long *msgs;
4618         int num_msgs = 0;
4619
4620         char targ[ROOMNAMELEN];
4621         struct ctdlroom qtemp;
4622         int err;
4623         int is_copy = 0;
4624         int ra;
4625         int permit = 0;
4626         int i;
4627
4628         extract_token(msgset, args, 0, '|', sizeof msgset);
4629         num_msgs = num_tokens(msgset, ',');
4630         if (num_msgs < 1) {
4631                 cprintf("%d Nothing to do.\n", CIT_OK);
4632                 return;
4633         }
4634
4635         extract_token(targ, args, 1, '|', sizeof targ);
4636         convert_room_name_macros(targ, sizeof targ);
4637         targ[ROOMNAMELEN - 1] = 0;
4638         is_copy = extract_int(args, 2);
4639
4640         if (CtdlGetRoom(&qtemp, targ) != 0) {
4641                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4642                 return;
4643         }
4644
4645         if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4646                 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4647                 return;
4648         }
4649
4650         CtdlGetUser(&CC->user, CC->curr_user);
4651         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4652
4653         /* Check for permission to perform this operation.
4654          * Remember: "CC->room" is source, "qtemp" is target.
4655          */
4656         permit = 0;
4657
4658         /* Aides can move/copy */
4659         if (CC->user.axlevel >= AxAideU) permit = 1;
4660
4661         /* Room aides can move/copy */
4662         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4663
4664         /* Permit move/copy from personal rooms */
4665         if ((CC->room.QRflags & QR_MAILBOX)
4666            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4667
4668         /* Permit only copy from public to personal room */
4669         if ( (is_copy)
4670            && (!(CC->room.QRflags & QR_MAILBOX))
4671            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4672
4673         /* Permit message removal from collaborative delete rooms */
4674         if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4675
4676         /* Users allowed to post into the target room may move into it too. */
4677         if ((CC->room.QRflags & QR_MAILBOX) && 
4678             (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
4679
4680         /* User must have access to target room */
4681         if (!(ra & UA_KNOWN))  permit = 0;
4682
4683         if (!permit) {
4684                 cprintf("%d Higher access required.\n",
4685                         ERROR + HIGHER_ACCESS_REQUIRED);
4686                 return;
4687         }
4688
4689         /*
4690          * Build our message set to be moved/copied
4691          */
4692         msgs = malloc(num_msgs * sizeof(long));
4693         for (i=0; i<num_msgs; ++i) {
4694                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4695                 msgs[i] = atol(msgtok);
4696         }
4697
4698         /*
4699          * Do the copy
4700          */
4701         err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4702         if (err != 0) {
4703                 cprintf("%d Cannot store message(s) in %s: error %d\n",
4704                         err, targ, err);
4705                 free(msgs);
4706                 return;
4707         }
4708
4709         /* Now delete the message from the source room,
4710          * if this is a 'move' rather than a 'copy' operation.
4711          */
4712         if (is_copy == 0) {
4713                 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4714         }
4715         free(msgs);
4716
4717         cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4718 }
4719
4720
4721
4722 /*
4723  * GetMetaData()  -  Get the supplementary record for a message
4724  */
4725 void GetMetaData(struct MetaData *smibuf, long msgnum)
4726 {
4727
4728         struct cdbdata *cdbsmi;
4729         long TheIndex;
4730
4731         memset(smibuf, 0, sizeof(struct MetaData));
4732         smibuf->meta_msgnum = msgnum;
4733         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
4734
4735         /* Use the negative of the message number for its supp record index */
4736         TheIndex = (0L - msgnum);
4737
4738         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4739         if (cdbsmi == NULL) {
4740                 return;         /* record not found; go with defaults */
4741         }
4742         memcpy(smibuf, cdbsmi->ptr,
4743                ((cdbsmi->len > sizeof(struct MetaData)) ?
4744                 sizeof(struct MetaData) : cdbsmi->len));
4745         cdb_free(cdbsmi);
4746         return;
4747 }
4748
4749
4750 /*
4751  * PutMetaData()  -  (re)write supplementary record for a message
4752  */
4753 void PutMetaData(struct MetaData *smibuf)
4754 {
4755         long TheIndex;
4756
4757         /* Use the negative of the message number for the metadata db index */
4758         TheIndex = (0L - smibuf->meta_msgnum);
4759
4760         cdb_store(CDB_MSGMAIN,
4761                   &TheIndex, (int)sizeof(long),
4762                   smibuf, (int)sizeof(struct MetaData));
4763
4764 }
4765
4766 /*
4767  * AdjRefCount  -  submit an adjustment to the reference count for a message.
4768  *                 (These are just queued -- we actually process them later.)
4769  */
4770 void AdjRefCount(long msgnum, int incr)
4771 {
4772         struct arcq new_arcq;
4773         int rv = 0;
4774
4775         syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4776                 msgnum, incr
4777         );
4778
4779         begin_critical_section(S_SUPPMSGMAIN);
4780         if (arcfp == NULL) {
4781                 arcfp = fopen(file_arcq, "ab+");
4782         }
4783         end_critical_section(S_SUPPMSGMAIN);
4784
4785         /* msgnum < 0 means that we're trying to close the file */
4786         if (msgnum < 0) {
4787                 syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
4788                 begin_critical_section(S_SUPPMSGMAIN);
4789                 if (arcfp != NULL) {
4790                         fclose(arcfp);
4791                         arcfp = NULL;
4792                 }
4793                 end_critical_section(S_SUPPMSGMAIN);
4794                 return;
4795         }
4796
4797         /*
4798          * If we can't open the queue, perform the operation synchronously.
4799          */
4800         if (arcfp == NULL) {
4801                 TDAP_AdjRefCount(msgnum, incr);
4802                 return;
4803         }
4804
4805         new_arcq.arcq_msgnum = msgnum;
4806         new_arcq.arcq_delta = incr;
4807         rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4808         fflush(arcfp);
4809
4810         return;
4811 }
4812
4813
4814 /*
4815  * TDAP_ProcessAdjRefCountQueue()
4816  *
4817  * Process the queue of message count adjustments that was created by calls
4818  * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4819  * for each one.  This should be an "off hours" operation.
4820  */
4821 int TDAP_ProcessAdjRefCountQueue(void)
4822 {
4823         char file_arcq_temp[PATH_MAX];
4824         int r;
4825         FILE *fp;
4826         struct arcq arcq_rec;
4827         int num_records_processed = 0;
4828
4829         snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4830
4831         begin_critical_section(S_SUPPMSGMAIN);
4832         if (arcfp != NULL) {
4833                 fclose(arcfp);
4834                 arcfp = NULL;
4835         }
4836
4837         r = link(file_arcq, file_arcq_temp);
4838         if (r != 0) {
4839                 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4840                 end_critical_section(S_SUPPMSGMAIN);
4841                 return(num_records_processed);
4842         }
4843
4844         unlink(file_arcq);
4845         end_critical_section(S_SUPPMSGMAIN);
4846
4847         fp = fopen(file_arcq_temp, "rb");
4848         if (fp == NULL) {
4849                 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4850                 return(num_records_processed);
4851         }
4852
4853         while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4854                 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4855                 ++num_records_processed;
4856         }
4857
4858         fclose(fp);
4859         r = unlink(file_arcq_temp);
4860         if (r != 0) {
4861                 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4862         }
4863
4864         return(num_records_processed);
4865 }
4866
4867
4868
4869 /*
4870  * TDAP_AdjRefCount  -  adjust the reference count for a message.
4871  *                      This one does it "for real" because it's called by
4872  *                      the autopurger function that processes the queue
4873  *                      created by AdjRefCount().   If a message's reference
4874  *                      count becomes zero, we also delete the message from
4875  *                      disk and de-index it.
4876  */
4877 void TDAP_AdjRefCount(long msgnum, int incr)
4878 {
4879
4880         struct MetaData smi;
4881         long delnum;
4882
4883         /* This is a *tight* critical section; please keep it that way, as
4884          * it may get called while nested in other critical sections.  
4885          * Complicating this any further will surely cause deadlock!
4886          */
4887         begin_critical_section(S_SUPPMSGMAIN);
4888         GetMetaData(&smi, msgnum);
4889         smi.meta_refcount += incr;
4890         PutMetaData(&smi);
4891         end_critical_section(S_SUPPMSGMAIN);
4892         syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
4893                 msgnum, incr, smi.meta_refcount
4894         );
4895
4896         /* If the reference count is now zero, delete the message
4897          * (and its supplementary record as well).
4898          */
4899         if (smi.meta_refcount == 0) {
4900                 syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
4901                 
4902                 /* Call delete hooks with NULL room to show it has gone altogether */
4903                 PerformDeleteHooks(NULL, msgnum);
4904
4905                 /* Remove from message base */
4906                 delnum = msgnum;
4907                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4908                 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4909
4910                 /* Remove metadata record */
4911                 delnum = (0L - msgnum);
4912                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4913         }
4914
4915 }
4916
4917 /*
4918  * Write a generic object to this room
4919  *
4920  * Note: this could be much more efficient.  Right now we use two temporary
4921  * files, and still pull the message into memory as with all others.
4922  */
4923 void CtdlWriteObject(char *req_room,                    /* Room to stuff it in */
4924                         char *content_type,             /* MIME type of this object */
4925                         char *raw_message,              /* Data to be written */
4926                         off_t raw_length,               /* Size of raw_message */
4927                         struct ctdluser *is_mailbox,    /* Mailbox room? */
4928                         int is_binary,                  /* Is encoding necessary? */
4929                         int is_unique,                  /* Del others of this type? */
4930                         unsigned int flags              /* Internal save flags */
4931                         )
4932 {
4933
4934         struct ctdlroom qrbuf;
4935         char roomname[ROOMNAMELEN];
4936         struct CtdlMessage *msg;
4937         char *encoded_message = NULL;
4938
4939         if (is_mailbox != NULL) {
4940                 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4941         }
4942         else {
4943                 safestrncpy(roomname, req_room, sizeof(roomname));
4944         }
4945
4946         syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
4947
4948         if (is_binary) {
4949                 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4950         }
4951         else {
4952                 encoded_message = malloc((size_t)(raw_length + 4096));
4953         }
4954
4955         sprintf(encoded_message, "Content-type: %s\n", content_type);
4956
4957         if (is_binary) {
4958                 sprintf(&encoded_message[strlen(encoded_message)],
4959                         "Content-transfer-encoding: base64\n\n"
4960                 );
4961         }
4962         else {
4963                 sprintf(&encoded_message[strlen(encoded_message)],
4964                         "Content-transfer-encoding: 7bit\n\n"
4965                 );
4966         }
4967
4968         if (is_binary) {
4969                 CtdlEncodeBase64(
4970                         &encoded_message[strlen(encoded_message)],
4971                         raw_message,
4972                         (int)raw_length,
4973                         0
4974                 );
4975         }
4976         else {
4977                 memcpy(
4978                         &encoded_message[strlen(encoded_message)],
4979                         raw_message,
4980                         (int)(raw_length+1)
4981                 );
4982         }
4983
4984         syslog(LOG_DEBUG, "Allocating\n");
4985         msg = malloc(sizeof(struct CtdlMessage));
4986         memset(msg, 0, sizeof(struct CtdlMessage));
4987         msg->cm_magic = CTDLMESSAGE_MAGIC;
4988         msg->cm_anon_type = MES_NORMAL;
4989         msg->cm_format_type = 4;
4990         msg->cm_fields['A'] = strdup(CC->user.fullname);
4991         msg->cm_fields['O'] = strdup(req_room);
4992         msg->cm_fields['N'] = strdup(config.c_nodename);
4993         msg->cm_fields['H'] = strdup(config.c_humannode);
4994         msg->cm_flags = flags;
4995         
4996         msg->cm_fields['M'] = encoded_message;
4997
4998         /* Create the requested room if we have to. */
4999         if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5000                 CtdlCreateRoom(roomname, 
5001                         ( (is_mailbox != NULL) ? 5 : 3 ),
5002                         "", 0, 1, 0, VIEW_BBS);
5003         }
5004         /* If the caller specified this object as unique, delete all
5005          * other objects of this type that are currently in the room.
5006          */
5007         if (is_unique) {
5008                 syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5009                         CtdlDeleteMessages(roomname, NULL, 0, content_type)
5010                 );
5011         }
5012         /* Now write the data */
5013         CtdlSubmitMsg(msg, NULL, roomname, 0);
5014         CtdlFreeMessage(msg);
5015 }
5016
5017
5018
5019
5020
5021
5022 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5023         config_msgnum = msgnum;
5024 }
5025
5026
5027 char *CtdlGetSysConfig(char *sysconfname) {
5028         char hold_rm[ROOMNAMELEN];
5029         long msgnum;
5030         char *conf;
5031         struct CtdlMessage *msg;
5032         char buf[SIZ];
5033         
5034         strcpy(hold_rm, CC->room.QRname);
5035         if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5036                 CtdlGetRoom(&CC->room, hold_rm);
5037                 return NULL;
5038         }
5039
5040
5041         /* We want the last (and probably only) config in this room */
5042         begin_critical_section(S_CONFIG);
5043         config_msgnum = (-1L);
5044         CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5045                 CtdlGetSysConfigBackend, NULL);
5046         msgnum = config_msgnum;
5047         end_critical_section(S_CONFIG);
5048
5049         if (msgnum < 0L) {
5050                 conf = NULL;
5051         }
5052         else {
5053                 msg = CtdlFetchMessage(msgnum, 1);
5054                 if (msg != NULL) {
5055                         conf = strdup(msg->cm_fields['M']);
5056                         CtdlFreeMessage(msg);
5057                 }
5058                 else {
5059                         conf = NULL;
5060                 }
5061         }
5062
5063         CtdlGetRoom(&CC->room, hold_rm);
5064
5065         if (conf != NULL) do {
5066                 extract_token(buf, conf, 0, '\n', sizeof buf);
5067                 strcpy(conf, &conf[strlen(buf)+1]);
5068         } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5069
5070         return(conf);
5071 }
5072
5073
5074 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5075         CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5076 }
5077
5078
5079 /*
5080  * Determine whether a given Internet address belongs to the current user
5081  */
5082 int CtdlIsMe(char *addr, int addr_buf_len)
5083 {
5084         struct recptypes *recp;
5085         int i;
5086
5087         recp = validate_recipients(addr, NULL, 0);
5088         if (recp == NULL) return(0);
5089
5090         if (recp->num_local == 0) {
5091                 free_recipients(recp);
5092                 return(0);
5093         }
5094
5095         for (i=0; i<recp->num_local; ++i) {
5096                 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5097                 if (!strcasecmp(addr, CC->user.fullname)) {
5098                         free_recipients(recp);
5099                         return(1);
5100                 }
5101         }
5102
5103         free_recipients(recp);
5104         return(0);
5105 }
5106
5107
5108 /*
5109  * Citadel protocol command to do the same
5110  */
5111 void cmd_isme(char *argbuf) {
5112         char addr[256];
5113
5114         if (CtdlAccessCheck(ac_logged_in)) return;
5115         extract_token(addr, argbuf, 0, '|', sizeof addr);
5116
5117         if (CtdlIsMe(addr, sizeof addr)) {
5118                 cprintf("%d %s\n", CIT_OK, addr);
5119         }
5120         else {
5121                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5122         }
5123
5124 }
5125
5126
5127 /*****************************************************************************/
5128 /*                      MODULE INITIALIZATION STUFF                          */
5129 /*****************************************************************************/
5130
5131 CTDL_MODULE_INIT(msgbase)
5132 {
5133         if (!threading) {
5134                 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5135                 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5136                 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5137                 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5138                 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5139                 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5140                 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5141                 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5142                 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5143                 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5144                 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5145                 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5146         }
5147
5148         /* return our Subversion id for the Log */
5149         return "msgbase";
5150 }