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