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