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