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