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