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