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