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