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