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