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