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