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