Fixed a bug in the 'only replies are allowed in this room' logic in ENT0 command.
[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 (except in %s> to sysop)", MAILROOM);
3943                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3944         }
3945
3946         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3947
3948         if (ra & UA_POSTALLOWED) {
3949                 strcpy(errmsgbuf, "OK to post or reply here");
3950                 return(0);
3951         }
3952
3953         if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
3954                 /*
3955                  * To be thorough, we ought to check to see if the message they are
3956                  * replying to is actually a valid one in this room, but unless this
3957                  * actually becomes a problem we'll go with high performance instead.
3958                  */
3959                 strcpy(errmsgbuf, "OK to reply here");
3960                 return(0);
3961         }
3962
3963         if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
3964                 /* Clarify what happened with a better error message */
3965                 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
3966                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3967         }
3968
3969         snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3970         return (ERROR + HIGHER_ACCESS_REQUIRED);
3971
3972 }
3973
3974
3975 /*
3976  * Check to see if the specified user has Internet mail permission
3977  * (returns nonzero if permission is granted)
3978  */
3979 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3980
3981         /* Do not allow twits to send Internet mail */
3982         if (who->axlevel <= AxProbU) return(0);
3983
3984         /* Globally enabled? */
3985         if (config.c_restrict == 0) return(1);
3986
3987         /* User flagged ok? */
3988         if (who->flags & US_INTERNET) return(2);
3989
3990         /* Aide level access? */
3991         if (who->axlevel >= AxAideU) return(3);
3992
3993         /* No mail for you! */
3994         return(0);
3995 }
3996
3997
3998 /*
3999  * Validate recipients, count delivery types and errors, and handle aliasing
4000  * FIXME check for dupes!!!!!
4001  *
4002  * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
4003  * were specified, or the number of addresses found invalid.
4004  *
4005  * Caller needs to free the result using free_recipients()
4006  */
4007 struct recptypes *validate_recipients(const char *supplied_recipients, 
4008                                       const char *RemoteIdentifier, 
4009                                       int Flags) {
4010         struct recptypes *ret;
4011         char *recipients = NULL;
4012         char this_recp[256];
4013         char this_recp_cooked[256];
4014         char append[SIZ];
4015         int num_recps = 0;
4016         int i, j;
4017         int mailtype;
4018         int invalid;
4019         struct ctdluser tempUS;
4020         struct ctdlroom tempQR;
4021         struct ctdlroom tempQR2;
4022         int err = 0;
4023         char errmsg[SIZ];
4024         int in_quotes = 0;
4025
4026         /* Initialize */
4027         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4028         if (ret == NULL) return(NULL);
4029
4030         /* Set all strings to null and numeric values to zero */
4031         memset(ret, 0, sizeof(struct recptypes));
4032
4033         if (supplied_recipients == NULL) {
4034                 recipients = strdup("");
4035         }
4036         else {
4037                 recipients = strdup(supplied_recipients);
4038         }
4039
4040         /* Allocate some memory.  Yes, this allocates 500% more memory than we will
4041          * actually need, but it's healthier for the heap than doing lots of tiny
4042          * realloc() calls instead.
4043          */
4044
4045         ret->errormsg = malloc(strlen(recipients) + 1024);
4046         ret->recp_local = malloc(strlen(recipients) + 1024);
4047         ret->recp_internet = malloc(strlen(recipients) + 1024);
4048         ret->recp_ignet = malloc(strlen(recipients) + 1024);
4049         ret->recp_room = malloc(strlen(recipients) + 1024);
4050         ret->display_recp = malloc(strlen(recipients) + 1024);
4051
4052         ret->errormsg[0] = 0;
4053         ret->recp_local[0] = 0;
4054         ret->recp_internet[0] = 0;
4055         ret->recp_ignet[0] = 0;
4056         ret->recp_room[0] = 0;
4057         ret->display_recp[0] = 0;
4058
4059         ret->recptypes_magic = RECPTYPES_MAGIC;
4060
4061         /* Change all valid separator characters to commas */
4062         for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4063                 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4064                         recipients[i] = ',';
4065                 }
4066         }
4067
4068         /* Now start extracting recipients... */
4069
4070         while (!IsEmptyStr(recipients)) {
4071
4072                 for (i=0; i<=strlen(recipients); ++i) {
4073                         if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4074                         if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4075                                 safestrncpy(this_recp, recipients, i+1);
4076                                 this_recp[i] = 0;
4077                                 if (recipients[i] == ',') {
4078                                         strcpy(recipients, &recipients[i+1]);
4079                                 }
4080                                 else {
4081                                         strcpy(recipients, "");
4082                                 }
4083                                 break;
4084                         }
4085                 }
4086
4087                 striplt(this_recp);
4088                 if (IsEmptyStr(this_recp))
4089                         break;
4090                 syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4091                 ++num_recps;
4092                 mailtype = alias(this_recp);
4093                 mailtype = alias(this_recp);
4094                 mailtype = alias(this_recp);
4095                 j = 0;
4096                 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
4097                         if (this_recp[j]=='_') {
4098                                 this_recp_cooked[j] = ' ';
4099                         }
4100                         else {
4101                                 this_recp_cooked[j] = this_recp[j];
4102                         }
4103                 }
4104                 this_recp_cooked[j] = '\0';
4105                 invalid = 0;
4106                 errmsg[0] = 0;
4107                 switch(mailtype) {
4108                         case MES_LOCAL:
4109                                 if (!strcasecmp(this_recp, "sysop")) {
4110                                         ++ret->num_room;
4111                                         strcpy(this_recp, config.c_aideroom);
4112                                         if (!IsEmptyStr(ret->recp_room)) {
4113                                                 strcat(ret->recp_room, "|");
4114                                         }
4115                                         strcat(ret->recp_room, this_recp);
4116                                 }
4117                                 else if ( (!strncasecmp(this_recp, "room_", 5))
4118                                       && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4119
4120                                         /* Save room so we can restore it later */
4121                                         tempQR2 = CC->room;
4122                                         CC->room = tempQR;
4123                                         
4124                                         /* Check permissions to send mail to this room */
4125                                         err = CtdlDoIHavePermissionToPostInThisRoom(
4126                                                 errmsg, 
4127                                                 sizeof errmsg, 
4128                                                 RemoteIdentifier,
4129                                                 Flags,
4130                                                 0                       /* 0 = not a reply */
4131                                         );
4132                                         if (err)
4133                                         {
4134                                                 ++ret->num_error;
4135                                                 invalid = 1;
4136                                         } 
4137                                         else {
4138                                                 ++ret->num_room;
4139                                                 if (!IsEmptyStr(ret->recp_room)) {
4140                                                         strcat(ret->recp_room, "|");
4141                                                 }
4142                                                 strcat(ret->recp_room, &this_recp_cooked[5]);
4143                                         }
4144                                         
4145                                         /* Restore room in case something needs it */
4146                                         CC->room = tempQR2;
4147
4148                                 }
4149                                 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4150                                         ++ret->num_local;
4151                                         strcpy(this_recp, tempUS.fullname);
4152                                         if (!IsEmptyStr(ret->recp_local)) {
4153                                                 strcat(ret->recp_local, "|");
4154                                         }
4155                                         strcat(ret->recp_local, this_recp);
4156                                 }
4157                                 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4158                                         ++ret->num_local;
4159                                         strcpy(this_recp, tempUS.fullname);
4160                                         if (!IsEmptyStr(ret->recp_local)) {
4161                                                 strcat(ret->recp_local, "|");
4162                                         }
4163                                         strcat(ret->recp_local, this_recp);
4164                                 }
4165                                 else {
4166                                         ++ret->num_error;
4167                                         invalid = 1;
4168                                 }
4169                                 break;
4170                         case MES_INTERNET:
4171                                 /* Yes, you're reading this correctly: if the target
4172                                  * domain points back to the local system or an attached
4173                                  * Citadel directory, the address is invalid.  That's
4174                                  * because if the address were valid, we would have
4175                                  * already translated it to a local address by now.
4176                                  */
4177                                 if (IsDirectory(this_recp, 0)) {
4178                                         ++ret->num_error;
4179                                         invalid = 1;
4180                                 }
4181                                 else {
4182                                         ++ret->num_internet;
4183                                         if (!IsEmptyStr(ret->recp_internet)) {
4184                                                 strcat(ret->recp_internet, "|");
4185                                         }
4186                                         strcat(ret->recp_internet, this_recp);
4187                                 }
4188                                 break;
4189                         case MES_IGNET:
4190                                 ++ret->num_ignet;
4191                                 if (!IsEmptyStr(ret->recp_ignet)) {
4192                                         strcat(ret->recp_ignet, "|");
4193                                 }
4194                                 strcat(ret->recp_ignet, this_recp);
4195                                 break;
4196                         case MES_ERROR:
4197                                 ++ret->num_error;
4198                                 invalid = 1;
4199                                 break;
4200                 }
4201                 if (invalid) {
4202                         if (IsEmptyStr(errmsg)) {
4203                                 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4204                         }
4205                         else {
4206                                 snprintf(append, sizeof append, "%s", errmsg);
4207                         }
4208                         if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4209                                 if (!IsEmptyStr(ret->errormsg)) {
4210                                         strcat(ret->errormsg, "; ");
4211                                 }
4212                                 strcat(ret->errormsg, append);
4213                         }
4214                 }
4215                 else {
4216                         if (IsEmptyStr(ret->display_recp)) {
4217                                 strcpy(append, this_recp);
4218                         }
4219                         else {
4220                                 snprintf(append, sizeof append, ", %s", this_recp);
4221                         }
4222                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4223                                 strcat(ret->display_recp, append);
4224                         }
4225                 }
4226         }
4227
4228         if ((ret->num_local + ret->num_internet + ret->num_ignet +
4229            ret->num_room + ret->num_error) == 0) {
4230                 ret->num_error = (-1);
4231                 strcpy(ret->errormsg, "No recipients specified.");
4232         }
4233
4234         syslog(LOG_DEBUG, "validate_recipients()\n");
4235         syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4236         syslog(LOG_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
4237         syslog(LOG_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4238         syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4239         syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4240
4241         free(recipients);
4242         return(ret);
4243 }
4244
4245
4246 /*
4247  * Destructor for struct recptypes
4248  */
4249 void free_recipients(struct recptypes *valid) {
4250
4251         if (valid == NULL) {
4252                 return;
4253         }
4254
4255         if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4256                 syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4257                 abort();
4258         }
4259
4260         if (valid->errormsg != NULL)            free(valid->errormsg);
4261         if (valid->recp_local != NULL)          free(valid->recp_local);
4262         if (valid->recp_internet != NULL)       free(valid->recp_internet);
4263         if (valid->recp_ignet != NULL)          free(valid->recp_ignet);
4264         if (valid->recp_room != NULL)           free(valid->recp_room);
4265         if (valid->display_recp != NULL)        free(valid->display_recp);
4266         if (valid->bounce_to != NULL)           free(valid->bounce_to);
4267         if (valid->envelope_from != NULL)       free(valid->envelope_from);
4268         free(valid);
4269 }
4270
4271
4272
4273 /*
4274  * message entry  -  mode 0 (normal)
4275  */
4276 void cmd_ent0(char *entargs)
4277 {
4278         int post = 0;
4279         char recp[SIZ];
4280         char cc[SIZ];
4281         char bcc[SIZ];
4282         char supplied_euid[128];
4283         int anon_flag = 0;
4284         int format_type = 0;
4285         char newusername[256];
4286         char newuseremail[256];
4287         struct CtdlMessage *msg;
4288         int anonymous = 0;
4289         char errmsg[SIZ];
4290         int err = 0;
4291         struct recptypes *valid = NULL;
4292         struct recptypes *valid_to = NULL;
4293         struct recptypes *valid_cc = NULL;
4294         struct recptypes *valid_bcc = NULL;
4295         char subject[SIZ];
4296         int subject_required = 0;
4297         int do_confirm = 0;
4298         long msgnum;
4299         int i, j;
4300         char buf[256];
4301         int newuseremail_ok = 0;
4302         char references[SIZ];
4303         char *ptr;
4304
4305         unbuffer_output();
4306
4307         post = extract_int(entargs, 0);
4308         extract_token(recp, entargs, 1, '|', sizeof recp);
4309         anon_flag = extract_int(entargs, 2);
4310         format_type = extract_int(entargs, 3);
4311         extract_token(subject, entargs, 4, '|', sizeof subject);
4312         extract_token(newusername, entargs, 5, '|', sizeof newusername);
4313         do_confirm = extract_int(entargs, 6);
4314         extract_token(cc, entargs, 7, '|', sizeof cc);
4315         extract_token(bcc, entargs, 8, '|', sizeof bcc);
4316         switch(CC->room.QRdefaultview) {
4317                 case VIEW_NOTES:
4318                 case VIEW_WIKI:
4319                         extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4320                         break;
4321                 default:
4322                         supplied_euid[0] = 0;
4323                         break;
4324         }
4325         extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4326         extract_token(references, entargs, 11, '|', sizeof references);
4327         for (ptr=references; *ptr != 0; ++ptr) {
4328                 if (*ptr == '!') *ptr = '|';
4329         }
4330
4331         /* first check to make sure the request is valid. */
4332
4333         err = CtdlDoIHavePermissionToPostInThisRoom(
4334                 errmsg,
4335                 sizeof errmsg,
4336                 NULL,
4337                 POST_LOGGED_IN,
4338                 (!IsEmptyStr(references))               /* is this a reply?  or a top-level post? */
4339         );
4340         if (err)
4341         {
4342                 cprintf("%d %s\n", err, errmsg);
4343                 return;
4344         }
4345
4346         /* Check some other permission type things. */
4347
4348         if (IsEmptyStr(newusername)) {
4349                 strcpy(newusername, CC->user.fullname);
4350         }
4351         if (  (CC->user.axlevel < AxAideU)
4352            && (strcasecmp(newusername, CC->user.fullname))
4353            && (strcasecmp(newusername, CC->cs_inet_fn))
4354         ) {     
4355                 cprintf("%d You don't have permission to author messages as '%s'.\n",
4356                         ERROR + HIGHER_ACCESS_REQUIRED,
4357                         newusername
4358                 );
4359                 return;
4360         }
4361
4362
4363         if (IsEmptyStr(newuseremail)) {
4364                 newuseremail_ok = 1;
4365         }
4366
4367         if (!IsEmptyStr(newuseremail)) {
4368                 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4369                         newuseremail_ok = 1;
4370                 }
4371                 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4372                         j = num_tokens(CC->cs_inet_other_emails, '|');
4373                         for (i=0; i<j; ++i) {
4374                                 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4375                                 if (!strcasecmp(newuseremail, buf)) {
4376                                         newuseremail_ok = 1;
4377                                 }
4378                         }
4379                 }
4380         }
4381
4382         if (!newuseremail_ok) {
4383                 cprintf("%d You don't have permission to author messages as '%s'.\n",
4384                         ERROR + HIGHER_ACCESS_REQUIRED,
4385                         newuseremail
4386                 );
4387                 return;
4388         }
4389
4390         CC->cs_flags |= CS_POSTING;
4391
4392         /* In mailbox rooms we have to behave a little differently --
4393          * make sure the user has specified at least one recipient.  Then
4394          * validate the recipient(s).  We do this for the Mail> room, as
4395          * well as any room which has the "Mailbox" view set - unless it
4396          * is the DRAFTS room which does not require recipients
4397          */
4398
4399         if ( (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4400              || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4401         ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4402                 if (CC->user.axlevel < AxProbU) {
4403                         strcpy(recp, "sysop");
4404                         strcpy(cc, "");
4405                         strcpy(bcc, "");
4406                 }
4407
4408                 valid_to = validate_recipients(recp, NULL, 0);
4409                 if (valid_to->num_error > 0) {
4410                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4411                         free_recipients(valid_to);
4412                         return;
4413                 }
4414
4415                 valid_cc = validate_recipients(cc, NULL, 0);
4416                 if (valid_cc->num_error > 0) {
4417                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4418                         free_recipients(valid_to);
4419                         free_recipients(valid_cc);
4420                         return;
4421                 }
4422
4423                 valid_bcc = validate_recipients(bcc, NULL, 0);
4424                 if (valid_bcc->num_error > 0) {
4425                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4426                         free_recipients(valid_to);
4427                         free_recipients(valid_cc);
4428                         free_recipients(valid_bcc);
4429                         return;
4430                 }
4431
4432                 /* Recipient required, but none were specified */
4433                 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4434                         free_recipients(valid_to);
4435                         free_recipients(valid_cc);
4436                         free_recipients(valid_bcc);
4437                         cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4438                         return;
4439                 }
4440
4441                 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4442                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4443                                 cprintf("%d You do not have permission "
4444                                         "to send Internet mail.\n",
4445                                         ERROR + HIGHER_ACCESS_REQUIRED);
4446                                 free_recipients(valid_to);
4447                                 free_recipients(valid_cc);
4448                                 free_recipients(valid_bcc);
4449                                 return;
4450                         }
4451                 }
4452
4453                 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)
4454                    && (CC->user.axlevel < AxNetU) ) {
4455                         cprintf("%d Higher access required for network mail.\n",
4456                                 ERROR + HIGHER_ACCESS_REQUIRED);
4457                         free_recipients(valid_to);
4458                         free_recipients(valid_cc);
4459                         free_recipients(valid_bcc);
4460                         return;
4461                 }
4462         
4463                 if ((RESTRICT_INTERNET == 1)
4464                     && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4465                     && ((CC->user.flags & US_INTERNET) == 0)
4466                     && (!CC->internal_pgm)) {
4467                         cprintf("%d You don't have access to Internet mail.\n",
4468                                 ERROR + HIGHER_ACCESS_REQUIRED);
4469                         free_recipients(valid_to);
4470                         free_recipients(valid_cc);
4471                         free_recipients(valid_bcc);
4472                         return;
4473                 }
4474
4475         }
4476
4477         /* Is this a room which has anonymous-only or anonymous-option? */
4478         anonymous = MES_NORMAL;
4479         if (CC->room.QRflags & QR_ANONONLY) {
4480                 anonymous = MES_ANONONLY;
4481         }
4482         if (CC->room.QRflags & QR_ANONOPT) {
4483                 if (anon_flag == 1) {   /* only if the user requested it */
4484                         anonymous = MES_ANONOPT;
4485                 }
4486         }
4487
4488         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4489                 recp[0] = 0;
4490         }
4491
4492         /* Recommend to the client that the use of a message subject is
4493          * strongly recommended in this room, if either the SUBJECTREQ flag
4494          * is set, or if there is one or more Internet email recipients.
4495          */
4496         if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4497         if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
4498         if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
4499         if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
4500
4501         /* If we're only checking the validity of the request, return
4502          * success without creating the message.
4503          */
4504         if (post == 0) {
4505                 cprintf("%d %s|%d\n", CIT_OK,
4506                         ((valid_to != NULL) ? valid_to->display_recp : ""), 
4507                         subject_required);
4508                 free_recipients(valid_to);
4509                 free_recipients(valid_cc);
4510                 free_recipients(valid_bcc);
4511                 return;
4512         }
4513
4514         /* We don't need these anymore because we'll do it differently below */
4515         free_recipients(valid_to);
4516         free_recipients(valid_cc);
4517         free_recipients(valid_bcc);
4518
4519         /* Read in the message from the client. */
4520         if (do_confirm) {
4521                 cprintf("%d send message\n", START_CHAT_MODE);
4522         } else {
4523                 cprintf("%d send message\n", SEND_LISTING);
4524         }
4525
4526         msg = CtdlMakeMessage(&CC->user, recp, cc,
4527                 CC->room.QRname, anonymous, format_type,
4528                 newusername, newuseremail, subject,
4529                 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4530                 NULL, references);
4531
4532         /* Put together one big recipients struct containing to/cc/bcc all in
4533          * one.  This is for the envelope.
4534          */
4535         char *all_recps = malloc(SIZ * 3);
4536         strcpy(all_recps, recp);
4537         if (!IsEmptyStr(cc)) {
4538                 if (!IsEmptyStr(all_recps)) {
4539                         strcat(all_recps, ",");
4540                 }
4541                 strcat(all_recps, cc);
4542         }
4543         if (!IsEmptyStr(bcc)) {
4544                 if (!IsEmptyStr(all_recps)) {
4545                         strcat(all_recps, ",");
4546                 }
4547                 strcat(all_recps, bcc);
4548         }
4549         if (!IsEmptyStr(all_recps)) {
4550                 valid = validate_recipients(all_recps, NULL, 0);
4551         }
4552         else {
4553                 valid = NULL;
4554         }
4555         free(all_recps);
4556
4557         if (msg != NULL) {
4558                 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4559
4560                 if (do_confirm) {
4561                         cprintf("%ld\n", msgnum);
4562                         if (msgnum >= 0L) {
4563                                 cprintf("Message accepted.\n");
4564                         }
4565                         else {
4566                                 cprintf("Internal error.\n");
4567                         }
4568                         if (msg->cm_fields['E'] != NULL) {
4569                                 cprintf("%s\n", msg->cm_fields['E']);
4570                         } else {
4571                                 cprintf("\n");
4572                         }
4573                         cprintf("000\n");
4574                 }
4575
4576                 CtdlFreeMessage(msg);
4577         }
4578         if (valid != NULL) {
4579                 free_recipients(valid);
4580         }
4581         return;
4582 }
4583
4584
4585
4586 /*
4587  * API function to delete messages which match a set of criteria
4588  * (returns the actual number of messages deleted)
4589  */
4590 int CtdlDeleteMessages(char *room_name,         /* which room */
4591                         long *dmsgnums,         /* array of msg numbers to be deleted */
4592                         int num_dmsgnums,       /* number of msgs to be deleted, or 0 for "any" */
4593                         char *content_type      /* or "" for any.  regular expressions expected. */
4594 )
4595 {
4596         struct ctdlroom qrbuf;
4597         struct cdbdata *cdbfr;
4598         long *msglist = NULL;
4599         long *dellist = NULL;
4600         int num_msgs = 0;
4601         int i, j;
4602         int num_deleted = 0;
4603         int delete_this;
4604         struct MetaData smi;
4605         regex_t re;
4606         regmatch_t pm;
4607         int need_to_free_re = 0;
4608
4609         if (content_type) if (!IsEmptyStr(content_type)) {
4610                 regcomp(&re, content_type, 0);
4611                 need_to_free_re = 1;
4612         }
4613         syslog(LOG_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4614                 room_name, num_dmsgnums, content_type);
4615
4616         /* get room record, obtaining a lock... */
4617         if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4618                 syslog(LOG_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4619                         room_name);
4620                 if (need_to_free_re) regfree(&re);
4621                 return (0);     /* room not found */
4622         }
4623         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4624
4625         if (cdbfr != NULL) {
4626                 dellist = malloc(cdbfr->len);
4627                 msglist = (long *) cdbfr->ptr;
4628                 cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
4629                 num_msgs = cdbfr->len / sizeof(long);
4630                 cdb_free(cdbfr);
4631         }
4632         if (num_msgs > 0) {
4633                 for (i = 0; i < num_msgs; ++i) {
4634                         delete_this = 0x00;
4635
4636                         /* Set/clear a bit for each criterion */
4637
4638                         /* 0 messages in the list or a null list means that we are
4639                          * interested in deleting any messages which meet the other criteria.
4640                          */
4641                         if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4642                                 delete_this |= 0x01;
4643                         }
4644                         else {
4645                                 for (j=0; j<num_dmsgnums; ++j) {
4646                                         if (msglist[i] == dmsgnums[j]) {
4647                                                 delete_this |= 0x01;
4648                                         }
4649                                 }
4650                         }
4651
4652                         if (IsEmptyStr(content_type)) {
4653                                 delete_this |= 0x02;
4654                         } else {
4655                                 GetMetaData(&smi, msglist[i]);
4656                                 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4657                                         delete_this |= 0x02;
4658                                 }
4659                         }
4660
4661                         /* Delete message only if all bits are set */
4662                         if (delete_this == 0x03) {
4663                                 dellist[num_deleted++] = msglist[i];
4664                                 msglist[i] = 0L;
4665                         }
4666                 }
4667
4668                 num_msgs = sort_msglist(msglist, num_msgs);
4669                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4670                           msglist, (int)(num_msgs * sizeof(long)));
4671
4672                 if (num_msgs > 0)
4673                         qrbuf.QRhighest = msglist[num_msgs - 1];
4674                 else
4675                         qrbuf.QRhighest = 0;
4676         }
4677         CtdlPutRoomLock(&qrbuf);
4678
4679         /* Go through the messages we pulled out of the index, and decrement
4680          * their reference counts by 1.  If this is the only room the message
4681          * was in, the reference count will reach zero and the message will
4682          * automatically be deleted from the database.  We do this in a
4683          * separate pass because there might be plug-in hooks getting called,
4684          * and we don't want that happening during an S_ROOMS critical
4685          * section.
4686          */
4687         if (num_deleted) for (i=0; i<num_deleted; ++i) {
4688                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4689                 AdjRefCount(dellist[i], -1);
4690         }
4691
4692         /* Now free the memory we used, and go away. */
4693         if (msglist != NULL) free(msglist);
4694         if (dellist != NULL) free(dellist);
4695         syslog(LOG_DEBUG, "%d message(s) deleted.\n", num_deleted);
4696         if (need_to_free_re) regfree(&re);
4697         return (num_deleted);
4698 }
4699
4700
4701
4702 /*
4703  * Check whether the current user has permission to delete messages from
4704  * the current room (returns 1 for yes, 0 for no)
4705  */
4706 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4707         int ra;
4708         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4709         if (ra & UA_DELETEALLOWED) return(1);
4710         return(0);
4711 }
4712
4713
4714
4715
4716 /*
4717  * Delete message from current room
4718  */
4719 void cmd_dele(char *args)
4720 {
4721         int num_deleted;
4722         int i;
4723         char msgset[SIZ];
4724         char msgtok[32];
4725         long *msgs;
4726         int num_msgs = 0;
4727
4728         extract_token(msgset, args, 0, '|', sizeof msgset);
4729         num_msgs = num_tokens(msgset, ',');
4730         if (num_msgs < 1) {
4731                 cprintf("%d Nothing to do.\n", CIT_OK);
4732                 return;
4733         }
4734
4735         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4736                 cprintf("%d Higher access required.\n",
4737                         ERROR + HIGHER_ACCESS_REQUIRED);
4738                 return;
4739         }
4740
4741         /*
4742          * Build our message set to be moved/copied
4743          */
4744         msgs = malloc(num_msgs * sizeof(long));
4745         for (i=0; i<num_msgs; ++i) {
4746                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4747                 msgs[i] = atol(msgtok);
4748         }
4749
4750         num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4751         free(msgs);
4752
4753         if (num_deleted) {
4754                 cprintf("%d %d message%s deleted.\n", CIT_OK,
4755                         num_deleted, ((num_deleted != 1) ? "s" : ""));
4756         } else {
4757                 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4758         }
4759 }
4760
4761
4762
4763
4764 /*
4765  * move or copy a message to another room
4766  */
4767 void cmd_move(char *args)
4768 {
4769         char msgset[SIZ];
4770         char msgtok[32];
4771         long *msgs;
4772         int num_msgs = 0;
4773
4774         char targ[ROOMNAMELEN];
4775         struct ctdlroom qtemp;
4776         int err;
4777         int is_copy = 0;
4778         int ra;
4779         int permit = 0;
4780         int i;
4781
4782         extract_token(msgset, args, 0, '|', sizeof msgset);
4783         num_msgs = num_tokens(msgset, ',');
4784         if (num_msgs < 1) {
4785                 cprintf("%d Nothing to do.\n", CIT_OK);
4786                 return;
4787         }
4788
4789         extract_token(targ, args, 1, '|', sizeof targ);
4790         convert_room_name_macros(targ, sizeof targ);
4791         targ[ROOMNAMELEN - 1] = 0;
4792         is_copy = extract_int(args, 2);
4793
4794         if (CtdlGetRoom(&qtemp, targ) != 0) {
4795                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4796                 return;
4797         }
4798
4799         if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4800                 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4801                 return;
4802         }
4803
4804         CtdlGetUser(&CC->user, CC->curr_user);
4805         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4806
4807         /* Check for permission to perform this operation.
4808          * Remember: "CC->room" is source, "qtemp" is target.
4809          */
4810         permit = 0;
4811
4812         /* Aides can move/copy */
4813         if (CC->user.axlevel >= AxAideU) permit = 1;
4814
4815         /* Room aides can move/copy */
4816         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4817
4818         /* Permit move/copy from personal rooms */
4819         if ((CC->room.QRflags & QR_MAILBOX)
4820            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4821
4822         /* Permit only copy from public to personal room */
4823         if ( (is_copy)
4824            && (!(CC->room.QRflags & QR_MAILBOX))
4825            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4826
4827         /* Permit message removal from collaborative delete rooms */
4828         if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4829
4830         /* Users allowed to post into the target room may move into it too. */
4831         if ((CC->room.QRflags & QR_MAILBOX) && 
4832             (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
4833
4834         /* User must have access to target room */
4835         if (!(ra & UA_KNOWN))  permit = 0;
4836
4837         if (!permit) {
4838                 cprintf("%d Higher access required.\n",
4839                         ERROR + HIGHER_ACCESS_REQUIRED);
4840                 return;
4841         }
4842
4843         /*
4844          * Build our message set to be moved/copied
4845          */
4846         msgs = malloc(num_msgs * sizeof(long));
4847         for (i=0; i<num_msgs; ++i) {
4848                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4849                 msgs[i] = atol(msgtok);
4850         }
4851
4852         /*
4853          * Do the copy
4854          */
4855         err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4856         if (err != 0) {
4857                 cprintf("%d Cannot store message(s) in %s: error %d\n",
4858                         err, targ, err);
4859                 free(msgs);
4860                 return;
4861         }
4862
4863         /* Now delete the message from the source room,
4864          * if this is a 'move' rather than a 'copy' operation.
4865          */
4866         if (is_copy == 0) {
4867                 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4868         }
4869         free(msgs);
4870
4871         cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4872 }
4873
4874
4875
4876 /*
4877  * GetMetaData()  -  Get the supplementary record for a message
4878  */
4879 void GetMetaData(struct MetaData *smibuf, long msgnum)
4880 {
4881
4882         struct cdbdata *cdbsmi;
4883         long TheIndex;
4884
4885         memset(smibuf, 0, sizeof(struct MetaData));
4886         smibuf->meta_msgnum = msgnum;
4887         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
4888
4889         /* Use the negative of the message number for its supp record index */
4890         TheIndex = (0L - msgnum);
4891
4892         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4893         if (cdbsmi == NULL) {
4894                 return;         /* record not found; go with defaults */
4895         }
4896         memcpy(smibuf, cdbsmi->ptr,
4897                ((cdbsmi->len > sizeof(struct MetaData)) ?
4898                 sizeof(struct MetaData) : cdbsmi->len));
4899         cdb_free(cdbsmi);
4900         return;
4901 }
4902
4903
4904 /*
4905  * PutMetaData()  -  (re)write supplementary record for a message
4906  */
4907 void PutMetaData(struct MetaData *smibuf)
4908 {
4909         long TheIndex;
4910
4911         /* Use the negative of the message number for the metadata db index */
4912         TheIndex = (0L - smibuf->meta_msgnum);
4913
4914         cdb_store(CDB_MSGMAIN,
4915                   &TheIndex, (int)sizeof(long),
4916                   smibuf, (int)sizeof(struct MetaData));
4917
4918 }
4919
4920 /*
4921  * AdjRefCount  -  submit an adjustment to the reference count for a message.
4922  *                 (These are just queued -- we actually process them later.)
4923  */
4924 void AdjRefCount(long msgnum, int incr)
4925 {
4926         struct arcq new_arcq;
4927         int rv = 0;
4928
4929         syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4930                 msgnum, incr
4931         );
4932
4933         begin_critical_section(S_SUPPMSGMAIN);
4934         if (arcfp == NULL) {
4935                 arcfp = fopen(file_arcq, "ab+");
4936         }
4937         end_critical_section(S_SUPPMSGMAIN);
4938
4939         /* msgnum < 0 means that we're trying to close the file */
4940         if (msgnum < 0) {
4941                 syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
4942                 begin_critical_section(S_SUPPMSGMAIN);
4943                 if (arcfp != NULL) {
4944                         fclose(arcfp);
4945                         arcfp = NULL;
4946                 }
4947                 end_critical_section(S_SUPPMSGMAIN);
4948                 return;
4949         }
4950
4951         /*
4952          * If we can't open the queue, perform the operation synchronously.
4953          */
4954         if (arcfp == NULL) {
4955                 TDAP_AdjRefCount(msgnum, incr);
4956                 return;
4957         }
4958
4959         new_arcq.arcq_msgnum = msgnum;
4960         new_arcq.arcq_delta = incr;
4961         rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4962         fflush(arcfp);
4963
4964         return;
4965 }
4966
4967
4968 /*
4969  * TDAP_ProcessAdjRefCountQueue()
4970  *
4971  * Process the queue of message count adjustments that was created by calls
4972  * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4973  * for each one.  This should be an "off hours" operation.
4974  */
4975 int TDAP_ProcessAdjRefCountQueue(void)
4976 {
4977         char file_arcq_temp[PATH_MAX];
4978         int r;
4979         FILE *fp;
4980         struct arcq arcq_rec;
4981         int num_records_processed = 0;
4982
4983         snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4984
4985         begin_critical_section(S_SUPPMSGMAIN);
4986         if (arcfp != NULL) {
4987                 fclose(arcfp);
4988                 arcfp = NULL;
4989         }
4990
4991         r = link(file_arcq, file_arcq_temp);
4992         if (r != 0) {
4993                 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4994                 end_critical_section(S_SUPPMSGMAIN);
4995                 return(num_records_processed);
4996         }
4997
4998         unlink(file_arcq);
4999         end_critical_section(S_SUPPMSGMAIN);
5000
5001         fp = fopen(file_arcq_temp, "rb");
5002         if (fp == NULL) {
5003                 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5004                 return(num_records_processed);
5005         }
5006
5007         while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5008                 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5009                 ++num_records_processed;
5010         }
5011
5012         fclose(fp);
5013         r = unlink(file_arcq_temp);
5014         if (r != 0) {
5015                 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5016         }
5017
5018         return(num_records_processed);
5019 }
5020
5021
5022
5023 /*
5024  * TDAP_AdjRefCount  -  adjust the reference count for a message.
5025  *                      This one does it "for real" because it's called by
5026  *                      the autopurger function that processes the queue
5027  *                      created by AdjRefCount().   If a message's reference
5028  *                      count becomes zero, we also delete the message from
5029  *                      disk and de-index it.
5030  */
5031 void TDAP_AdjRefCount(long msgnum, int incr)
5032 {
5033
5034         struct MetaData smi;
5035         long delnum;
5036
5037         /* This is a *tight* critical section; please keep it that way, as
5038          * it may get called while nested in other critical sections.  
5039          * Complicating this any further will surely cause deadlock!
5040          */
5041         begin_critical_section(S_SUPPMSGMAIN);
5042         GetMetaData(&smi, msgnum);
5043         smi.meta_refcount += incr;
5044         PutMetaData(&smi);
5045         end_critical_section(S_SUPPMSGMAIN);
5046         syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5047                 msgnum, incr, smi.meta_refcount
5048         );
5049
5050         /* If the reference count is now zero, delete the message
5051          * (and its supplementary record as well).
5052          */
5053         if (smi.meta_refcount == 0) {
5054                 syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5055                 
5056                 /* Call delete hooks with NULL room to show it has gone altogether */
5057                 PerformDeleteHooks(NULL, msgnum);
5058
5059                 /* Remove from message base */
5060                 delnum = msgnum;
5061                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5062                 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5063
5064                 /* Remove metadata record */
5065                 delnum = (0L - msgnum);
5066                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5067         }
5068
5069 }
5070
5071 /*
5072  * Write a generic object to this room
5073  *
5074  * Note: this could be much more efficient.  Right now we use two temporary
5075  * files, and still pull the message into memory as with all others.
5076  */
5077 void CtdlWriteObject(char *req_room,                    /* Room to stuff it in */
5078                         char *content_type,             /* MIME type of this object */
5079                         char *raw_message,              /* Data to be written */
5080                         off_t raw_length,               /* Size of raw_message */
5081                         struct ctdluser *is_mailbox,    /* Mailbox room? */
5082                         int is_binary,                  /* Is encoding necessary? */
5083                         int is_unique,                  /* Del others of this type? */
5084                         unsigned int flags              /* Internal save flags */
5085                         )
5086 {
5087
5088         struct ctdlroom qrbuf;
5089         char roomname[ROOMNAMELEN];
5090         struct CtdlMessage *msg;
5091         char *encoded_message = NULL;
5092
5093         if (is_mailbox != NULL) {
5094                 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5095         }
5096         else {
5097                 safestrncpy(roomname, req_room, sizeof(roomname));
5098         }
5099
5100         syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5101
5102         if (is_binary) {
5103                 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5104         }
5105         else {
5106                 encoded_message = malloc((size_t)(raw_length + 4096));
5107         }
5108
5109         sprintf(encoded_message, "Content-type: %s\n", content_type);
5110
5111         if (is_binary) {
5112                 sprintf(&encoded_message[strlen(encoded_message)],
5113                         "Content-transfer-encoding: base64\n\n"
5114                 );
5115         }
5116         else {
5117                 sprintf(&encoded_message[strlen(encoded_message)],
5118                         "Content-transfer-encoding: 7bit\n\n"
5119                 );
5120         }
5121
5122         if (is_binary) {
5123                 CtdlEncodeBase64(
5124                         &encoded_message[strlen(encoded_message)],
5125                         raw_message,
5126                         (int)raw_length,
5127                         0
5128                 );
5129         }
5130         else {
5131                 memcpy(
5132                         &encoded_message[strlen(encoded_message)],
5133                         raw_message,
5134                         (int)(raw_length+1)
5135                 );
5136         }
5137
5138         syslog(LOG_DEBUG, "Allocating\n");
5139         msg = malloc(sizeof(struct CtdlMessage));
5140         memset(msg, 0, sizeof(struct CtdlMessage));
5141         msg->cm_magic = CTDLMESSAGE_MAGIC;
5142         msg->cm_anon_type = MES_NORMAL;
5143         msg->cm_format_type = 4;
5144         msg->cm_fields['A'] = strdup(CC->user.fullname);
5145         msg->cm_fields['O'] = strdup(req_room);
5146         msg->cm_fields['N'] = strdup(config.c_nodename);
5147         msg->cm_fields['H'] = strdup(config.c_humannode);
5148         msg->cm_flags = flags;
5149         
5150         msg->cm_fields['M'] = encoded_message;
5151
5152         /* Create the requested room if we have to. */
5153         if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5154                 CtdlCreateRoom(roomname, 
5155                         ( (is_mailbox != NULL) ? 5 : 3 ),
5156                         "", 0, 1, 0, VIEW_BBS);
5157         }
5158         /* If the caller specified this object as unique, delete all
5159          * other objects of this type that are currently in the room.
5160          */
5161         if (is_unique) {
5162                 syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5163                         CtdlDeleteMessages(roomname, NULL, 0, content_type)
5164                 );
5165         }
5166         /* Now write the data */
5167         CtdlSubmitMsg(msg, NULL, roomname, 0);
5168         CtdlFreeMessage(msg);
5169 }
5170
5171
5172
5173
5174
5175
5176 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5177         config_msgnum = msgnum;
5178 }
5179
5180
5181 char *CtdlGetSysConfig(char *sysconfname) {
5182         char hold_rm[ROOMNAMELEN];
5183         long msgnum;
5184         char *conf;
5185         struct CtdlMessage *msg;
5186         char buf[SIZ];
5187         
5188         strcpy(hold_rm, CC->room.QRname);
5189         if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5190                 CtdlGetRoom(&CC->room, hold_rm);
5191                 return NULL;
5192         }
5193
5194
5195         /* We want the last (and probably only) config in this room */
5196         begin_critical_section(S_CONFIG);
5197         config_msgnum = (-1L);
5198         CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5199                 CtdlGetSysConfigBackend, NULL);
5200         msgnum = config_msgnum;
5201         end_critical_section(S_CONFIG);
5202
5203         if (msgnum < 0L) {
5204                 conf = NULL;
5205         }
5206         else {
5207                 msg = CtdlFetchMessage(msgnum, 1);
5208                 if (msg != NULL) {
5209                         conf = strdup(msg->cm_fields['M']);
5210                         CtdlFreeMessage(msg);
5211                 }
5212                 else {
5213                         conf = NULL;
5214                 }
5215         }
5216
5217         CtdlGetRoom(&CC->room, hold_rm);
5218
5219         if (conf != NULL) do {
5220                 extract_token(buf, conf, 0, '\n', sizeof buf);
5221                 strcpy(conf, &conf[strlen(buf)+1]);
5222         } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5223
5224         return(conf);
5225 }
5226
5227
5228 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5229         CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5230 }
5231
5232
5233 /*
5234  * Determine whether a given Internet address belongs to the current user
5235  */
5236 int CtdlIsMe(char *addr, int addr_buf_len)
5237 {
5238         struct recptypes *recp;
5239         int i;
5240
5241         recp = validate_recipients(addr, NULL, 0);
5242         if (recp == NULL) return(0);
5243
5244         if (recp->num_local == 0) {
5245                 free_recipients(recp);
5246                 return(0);
5247         }
5248
5249         for (i=0; i<recp->num_local; ++i) {
5250                 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5251                 if (!strcasecmp(addr, CC->user.fullname)) {
5252                         free_recipients(recp);
5253                         return(1);
5254                 }
5255         }
5256
5257         free_recipients(recp);
5258         return(0);
5259 }
5260
5261
5262 /*
5263  * Citadel protocol command to do the same
5264  */
5265 void cmd_isme(char *argbuf) {
5266         char addr[256];
5267
5268         if (CtdlAccessCheck(ac_logged_in)) return;
5269         extract_token(addr, argbuf, 0, '|', sizeof addr);
5270
5271         if (CtdlIsMe(addr, sizeof addr)) {
5272                 cprintf("%d %s\n", CIT_OK, addr);
5273         }
5274         else {
5275                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5276         }
5277
5278 }
5279
5280
5281 /*****************************************************************************/
5282 /*                      MODULE INITIALIZATION STUFF                          */
5283 /*****************************************************************************/
5284
5285 CTDL_MODULE_INIT(msgbase)
5286 {
5287         if (!threading) {
5288                 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5289                 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5290                 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5291                 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5292                 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5293                 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5294                 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5295                 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5296                 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5297                 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5298                 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5299                 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5300         }
5301
5302         /* return our Subversion id for the Log */
5303         return "msgbase";
5304 }