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