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