Add aide message functionality that uses the UT table to protect the user from an...
[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         if (cdbut != NULL) return;
3602         /* no, this message isn't sent recently; go ahead. */
3603         quickie_message(from,
3604                         fromaddr,
3605                         to,
3606                         room,
3607                         text, 
3608                         format_type,
3609                         subject);
3610 }
3611
3612
3613 /*
3614  * Back end function used by CtdlMakeMessage() and similar functions
3615  */
3616 StrBuf *CtdlReadMessageBodyBuf(char *terminator,        /* token signalling EOT */
3617                                long tlen,
3618                                size_t maxlen,           /* maximum message length */
3619                                char *exist,             /* if non-null, append to it;
3620                                                            exist is ALWAYS freed  */
3621                                int crlf,                /* CRLF newlines instead of LF */
3622                                int *sock                /* socket handle or 0 for this session's client socket */
3623         ) 
3624 {
3625         StrBuf *Message;
3626         StrBuf *LineBuf;
3627         int flushing = 0;
3628         int finished = 0;
3629         int dotdot = 0;
3630
3631         LineBuf = NewStrBufPlain(NULL, SIZ);
3632         if (exist == NULL) {
3633                 Message = NewStrBufPlain(NULL, 4 * SIZ);
3634         }
3635         else {
3636                 Message = NewStrBufPlain(exist, -1);
3637                 free(exist);
3638         }
3639
3640         /* Do we need to change leading ".." to "." for SMTP escaping? */
3641         if ((tlen == 1) && (*terminator == '.')) {
3642                 dotdot = 1;
3643         }
3644
3645         /* read in the lines of message text one by one */
3646         do {
3647                 if (sock != NULL) {
3648                         if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3649                             (*sock == -1))
3650                                 finished = 1;
3651                 }
3652                 else {
3653                         if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3654                 }
3655                 if ((StrLength(LineBuf) == tlen) && 
3656                     (!strcmp(ChrPtr(LineBuf), terminator)))
3657                         finished = 1;
3658
3659                 if ( (!flushing) && (!finished) ) {
3660                         if (crlf) {
3661                                 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3662                         }
3663                         else {
3664                                 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3665                         }
3666                         
3667                         /* Unescape SMTP-style input of two dots at the beginning of the line */
3668                         if ((dotdot) &&
3669                             (StrLength(LineBuf) == 2) && 
3670                             (!strcmp(ChrPtr(LineBuf), "..")))
3671                         {
3672                                 StrBufCutLeft(LineBuf, 1);
3673                         }
3674                         
3675                         StrBufAppendBuf(Message, LineBuf, 0);
3676                 }
3677
3678                 /* if we've hit the max msg length, flush the rest */
3679                 if (StrLength(Message) >= maxlen) flushing = 1;
3680
3681         } while (!finished);
3682         FreeStrBuf(&LineBuf);
3683         return Message;
3684 }
3685
3686 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3687 {
3688         if (*Msg == NULL)
3689                 return;
3690         FreeStrBuf(&(*Msg)->MsgBuf);
3691
3692         free(*Msg);
3693         *Msg = NULL;
3694 }
3695
3696 ReadAsyncMsg *NewAsyncMsg(const char *terminator,       /* token signalling EOT */
3697                           long tlen,
3698                           size_t maxlen,                /* maximum message length */
3699                           size_t expectlen,             /* if we expect a message, how long should it be? */
3700                           char *exist,                  /* if non-null, append to it;
3701                                                            exist is ALWAYS freed  */
3702                           long eLen,                    /* length of exist */
3703                           int crlf                      /* CRLF newlines instead of LF */
3704         )
3705 {
3706         ReadAsyncMsg *NewMsg;
3707
3708         NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3709         memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3710
3711         if (exist == NULL) {
3712                 long len;
3713
3714                 if (expectlen == 0) {
3715                         len = 4 * SIZ;
3716                 }
3717                 else {
3718                         len = expectlen + 10;
3719                 }
3720                 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3721         }
3722         else {
3723                 NewMsg->MsgBuf = NewStrBufPlain(exist, eLen);
3724                 free(exist);
3725         }
3726         /* Do we need to change leading ".." to "." for SMTP escaping? */
3727         if ((tlen == 1) && (*terminator == '.')) {
3728                 NewMsg->dodot = 1;
3729         }
3730
3731         NewMsg->terminator = terminator;
3732         NewMsg->tlen = tlen;
3733
3734         NewMsg->maxlen = maxlen;
3735
3736         NewMsg->crlf = crlf;
3737
3738         return NewMsg;
3739 }
3740
3741 /*
3742  * Back end function used by CtdlMakeMessage() and similar functions
3743  */
3744 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3745 {
3746         ReadAsyncMsg *ReadMsg;
3747         int MsgFinished = 0;
3748         eReadState Finished = eMustReadMore;
3749
3750 #ifdef BIGBAD_IODBG
3751         char fn [SIZ];
3752         FILE *fd;
3753         const char *pch = ChrPtr(IO->SendBuf.Buf);
3754         const char *pchh = IO->SendBuf.ReadWritePointer;
3755         long nbytes;
3756         
3757         if (pchh == NULL)
3758                 pchh = pch;
3759         
3760         nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3761         snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3762                  ((CitContext*)(IO->CitContext))->ServiceName,
3763                  IO->SendBuf.fd);
3764         
3765         fd = fopen(fn, "a+");
3766 #endif
3767
3768         ReadMsg = IO->ReadMsg;
3769
3770         /* read in the lines of message text one by one */
3771         do {
3772                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3773                 
3774                 switch (Finished) {
3775                 case eMustReadMore: /// read new from socket... 
3776 #ifdef BIGBAD_IODBG
3777                         if (IO->RecvBuf.ReadWritePointer != NULL) {
3778                                 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3779                                 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3780                                 
3781                                 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3782                         
3783                                 fprintf(fd, "]\n");
3784                         } else {
3785                                 fprintf(fd, "BufferEmpty! \n");
3786                         }
3787                         fclose(fd);
3788 #endif
3789                         return Finished;
3790                     break;
3791                 case eBufferNotEmpty: /* shouldn't happen... */
3792                 case eReadSuccess: /// done for now...
3793                     break;
3794                 case eReadFail: /// WHUT?
3795                     ///todo: shut down! 
3796                         break;
3797                 }
3798             
3799
3800                 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) && 
3801                     (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3802                         MsgFinished = 1;
3803 #ifdef BIGBAD_IODBG
3804                         fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3805 #endif
3806                 }
3807                 else if (!ReadMsg->flushing) {
3808
3809 #ifdef BIGBAD_IODBG
3810                         fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3811 #endif
3812
3813                         /* Unescape SMTP-style input of two dots at the beginning of the line */
3814                         if ((ReadMsg->dodot) &&
3815                             (StrLength(IO->IOBuf) == 2) &&  /* TODO: do we just unescape lines with two dots or any line? */
3816                             (!strcmp(ChrPtr(IO->IOBuf), "..")))
3817                         {
3818 #ifdef BIGBAD_IODBG
3819                                 fprintf(fd, "UnEscaped!\n");
3820 #endif
3821                                 StrBufCutLeft(IO->IOBuf, 1);
3822                         }
3823
3824                         if (ReadMsg->crlf) {
3825                                 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3826                         }
3827                         else {
3828                                 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3829                         }
3830
3831                         StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3832                 }
3833
3834                 /* if we've hit the max msg length, flush the rest */
3835                 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3836
3837         } while (!MsgFinished);
3838
3839 #ifdef BIGBAD_IODBG
3840         fprintf(fd, "Done with reading; %s.\n, ",
3841                 (MsgFinished)?"Message Finished": "FAILED");
3842         fclose(fd);
3843 #endif
3844         if (MsgFinished)
3845                 return eReadSuccess;
3846         else 
3847                 return eAbort;
3848 }
3849
3850
3851 /*
3852  * Back end function used by CtdlMakeMessage() and similar functions
3853  */
3854 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
3855                           long tlen,
3856                           size_t maxlen,                /* maximum message length */
3857                           char *exist,          /* if non-null, append to it;
3858                                                    exist is ALWAYS freed  */
3859                           int crlf,             /* CRLF newlines instead of LF */
3860                           int *sock             /* socket handle or 0 for this session's client socket */
3861         ) 
3862 {
3863         StrBuf *Message;
3864
3865         Message = CtdlReadMessageBodyBuf(terminator,
3866                                          tlen,
3867                                          maxlen,
3868                                          exist,
3869                                          crlf,
3870                                          sock);
3871         if (Message == NULL)
3872                 return NULL;
3873         else
3874                 return SmashStrBuf(&Message);
3875 }
3876
3877
3878 /*
3879  * Build a binary message to be saved on disk.
3880  * (NOTE: if you supply 'preformatted_text', the buffer you give it
3881  * will become part of the message.  This means you are no longer
3882  * responsible for managing that memory -- it will be freed along with
3883  * the rest of the fields when CtdlFreeMessage() is called.)
3884  */
3885
3886 struct CtdlMessage *CtdlMakeMessage(
3887         struct ctdluser *author,        /* author's user structure */
3888         char *recipient,                /* NULL if it's not mail */
3889         char *recp_cc,                  /* NULL if it's not mail */
3890         char *room,                     /* room where it's going */
3891         int type,                       /* see MES_ types in header file */
3892         int format_type,                /* variformat, plain text, MIME... */
3893         char *fake_name,                /* who we're masquerading as */
3894         char *my_email,                 /* which of my email addresses to use (empty is ok) */
3895         char *subject,                  /* Subject (optional) */
3896         char *supplied_euid,            /* ...or NULL if this is irrelevant */
3897         char *preformatted_text,        /* ...or NULL to read text from client */
3898         char *references                /* Thread references */
3899         ) {
3900         char dest_node[256];
3901         char buf[1024];
3902         struct CtdlMessage *msg;
3903         StrBuf *FakeAuthor;
3904         StrBuf *FakeEncAuthor = NULL;
3905
3906         msg = malloc(sizeof(struct CtdlMessage));
3907         memset(msg, 0, sizeof(struct CtdlMessage));
3908         msg->cm_magic = CTDLMESSAGE_MAGIC;
3909         msg->cm_anon_type = type;
3910         msg->cm_format_type = format_type;
3911
3912         /* Don't confuse the poor folks if it's not routed mail. */
3913         strcpy(dest_node, "");
3914
3915         if (recipient != NULL) striplt(recipient);
3916         if (recp_cc != NULL) striplt(recp_cc);
3917
3918         /* Path or Return-Path */
3919         if (my_email == NULL) my_email = "";
3920
3921         if (!IsEmptyStr(my_email)) {
3922                 msg->cm_fields['P'] = strdup(my_email);
3923         }
3924         else {
3925                 snprintf(buf, sizeof buf, "%s", author->fullname);
3926                 msg->cm_fields['P'] = strdup(buf);
3927         }
3928         convert_spaces_to_underscores(msg->cm_fields['P']);
3929
3930         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
3931         msg->cm_fields['T'] = strdup(buf);
3932
3933         if ((fake_name != NULL) && (fake_name[0])) {            /* author */
3934                 FakeAuthor = NewStrBufPlain (fake_name, -1);
3935         }
3936         else {
3937                 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3938         }
3939         StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3940         msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3941         FreeStrBuf(&FakeAuthor);
3942
3943         if (CC->room.QRflags & QR_MAILBOX) {            /* room */
3944                 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3945         }
3946         else {
3947                 msg->cm_fields['O'] = strdup(CC->room.QRname);
3948         }
3949
3950         msg->cm_fields['N'] = strdup(NODENAME);         /* nodename */
3951         msg->cm_fields['H'] = strdup(HUMANNODE);                /* hnodename */
3952
3953         if ((recipient != NULL) && (recipient[0] != 0)) {
3954                 msg->cm_fields['R'] = strdup(recipient);
3955         }
3956         if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3957                 msg->cm_fields['Y'] = strdup(recp_cc);
3958         }
3959         if (dest_node[0] != 0) {
3960                 msg->cm_fields['D'] = strdup(dest_node);
3961         }
3962
3963         if (!IsEmptyStr(my_email)) {
3964                 msg->cm_fields['F'] = strdup(my_email);
3965         }
3966         else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3967                 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3968         }
3969
3970         if (subject != NULL) {
3971                 long length;
3972                 striplt(subject);
3973                 length = strlen(subject);
3974                 if (length > 0) {
3975                         long i;
3976                         long IsAscii;
3977                         IsAscii = -1;
3978                         i = 0;
3979                         while ((subject[i] != '\0') &&
3980                                (IsAscii = isascii(subject[i]) != 0 ))
3981                                 i++;
3982                         if (IsAscii != 0)
3983                                 msg->cm_fields['U'] = strdup(subject);
3984                         else /* ok, we've got utf8 in the string. */
3985                         {
3986                                 msg->cm_fields['U'] = rfc2047encode(subject, length);
3987                         }
3988
3989                 }
3990         }
3991
3992         if (supplied_euid != NULL) {
3993                 msg->cm_fields['E'] = strdup(supplied_euid);
3994         }
3995
3996         if ((references != NULL) && (!IsEmptyStr(references))) {
3997                 if (msg->cm_fields['W'] != NULL)
3998                         free(msg->cm_fields['W']);
3999                 msg->cm_fields['W'] = strdup(references);
4000         }
4001
4002         if (preformatted_text != NULL) {
4003                 msg->cm_fields['M'] = preformatted_text;
4004         }
4005         else {
4006                 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
4007         }
4008
4009         return(msg);
4010 }
4011
4012 extern int netconfig_check_roomaccess(
4013         char *errmsgbuf, 
4014         size_t n,
4015         const char* RemoteIdentifier); /* TODO: find a smarter way */
4016
4017 /*
4018  * Check to see whether we have permission to post a message in the current
4019  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
4020  * returns 0 on success.
4021  */
4022 int CtdlDoIHavePermissionToPostInThisRoom(
4023         char *errmsgbuf, 
4024         size_t n, 
4025         const char* RemoteIdentifier,
4026         int PostPublic,
4027         int is_reply
4028         ) {
4029         int ra;
4030
4031         if (!(CC->logged_in) && 
4032             (PostPublic == POST_LOGGED_IN)) {
4033                 snprintf(errmsgbuf, n, "Not logged in.");
4034                 return (ERROR + NOT_LOGGED_IN);
4035         }
4036         else if (PostPublic == CHECK_EXISTANCE) {
4037                 return (0); // We're Evaling whether a recipient exists
4038         }
4039         else if (!(CC->logged_in)) {
4040                 
4041                 if ((CC->room.QRflags & QR_READONLY)) {
4042                         snprintf(errmsgbuf, n, "Not logged in.");
4043                         return (ERROR + NOT_LOGGED_IN);
4044                 }
4045                 if (CC->room.QRflags2 & QR2_MODERATED) {
4046                         snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
4047                         return (ERROR + NOT_LOGGED_IN);
4048                 }
4049                 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
4050
4051                         return netconfig_check_roomaccess(errmsgbuf, n, RemoteIdentifier);
4052                 }
4053                 return (0);
4054
4055         }
4056
4057         if ((CC->user.axlevel < AxProbU)
4058             && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
4059                 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
4060                 return (ERROR + HIGHER_ACCESS_REQUIRED);
4061         }
4062
4063         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4064
4065         if (ra & UA_POSTALLOWED) {
4066                 strcpy(errmsgbuf, "OK to post or reply here");
4067                 return(0);
4068         }
4069
4070         if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
4071                 /*
4072                  * To be thorough, we ought to check to see if the message they are
4073                  * replying to is actually a valid one in this room, but unless this
4074                  * actually becomes a problem we'll go with high performance instead.
4075                  */
4076                 strcpy(errmsgbuf, "OK to reply here");
4077                 return(0);
4078         }
4079
4080         if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
4081                 /* Clarify what happened with a better error message */
4082                 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
4083                 return (ERROR + HIGHER_ACCESS_REQUIRED);
4084         }
4085
4086         snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
4087         return (ERROR + HIGHER_ACCESS_REQUIRED);
4088
4089 }
4090
4091
4092 /*
4093  * Check to see if the specified user has Internet mail permission
4094  * (returns nonzero if permission is granted)
4095  */
4096 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
4097
4098         /* Do not allow twits to send Internet mail */
4099         if (who->axlevel <= AxProbU) return(0);
4100
4101         /* Globally enabled? */
4102         if (config.c_restrict == 0) return(1);
4103
4104         /* User flagged ok? */
4105         if (who->flags & US_INTERNET) return(2);
4106
4107         /* Admin level access? */
4108         if (who->axlevel >= AxAideU) return(3);
4109
4110         /* No mail for you! */
4111         return(0);
4112 }
4113
4114
4115 /*
4116  * Validate recipients, count delivery types and errors, and handle aliasing
4117  * FIXME check for dupes!!!!!
4118  *
4119  * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
4120  * were specified, or the number of addresses found invalid.
4121  *
4122  * Caller needs to free the result using free_recipients()
4123  */
4124 struct recptypes *validate_recipients(const char *supplied_recipients, 
4125                                       const char *RemoteIdentifier, 
4126                                       int Flags) {
4127         struct CitContext *CCC = CC;
4128         struct recptypes *ret;
4129         char *recipients = NULL;
4130         char *org_recp;
4131         char this_recp[256];
4132         char this_recp_cooked[256];
4133         char append[SIZ];
4134         long len;
4135         int num_recps = 0;
4136         int i, j;
4137         int mailtype;
4138         int invalid;
4139         struct ctdluser tempUS;
4140         struct ctdlroom tempQR;
4141         struct ctdlroom tempQR2;
4142         int err = 0;
4143         char errmsg[SIZ];
4144         int in_quotes = 0;
4145
4146         /* Initialize */
4147         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4148         if (ret == NULL) return(NULL);
4149
4150         /* Set all strings to null and numeric values to zero */
4151         memset(ret, 0, sizeof(struct recptypes));
4152
4153         if (supplied_recipients == NULL) {
4154                 recipients = strdup("");
4155         }
4156         else {
4157                 recipients = strdup(supplied_recipients);
4158         }
4159
4160         /* Allocate some memory.  Yes, this allocates 500% more memory than we will
4161          * actually need, but it's healthier for the heap than doing lots of tiny
4162          * realloc() calls instead.
4163          */
4164         len = strlen(recipients) + 1024;
4165         ret->errormsg = malloc(len);
4166         ret->recp_local = malloc(len);
4167         ret->recp_internet = malloc(len);
4168         ret->recp_ignet = malloc(len);
4169         ret->recp_room = malloc(len);
4170         ret->display_recp = malloc(len);
4171         ret->recp_orgroom = malloc(len);
4172         org_recp = malloc(len);
4173
4174         ret->errormsg[0] = 0;
4175         ret->recp_local[0] = 0;
4176         ret->recp_internet[0] = 0;
4177         ret->recp_ignet[0] = 0;
4178         ret->recp_room[0] = 0;
4179         ret->recp_orgroom[0] = 0;
4180         ret->display_recp[0] = 0;
4181
4182         ret->recptypes_magic = RECPTYPES_MAGIC;
4183
4184         /* Change all valid separator characters to commas */
4185         for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4186                 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4187                         recipients[i] = ',';
4188                 }
4189         }
4190
4191         /* Now start extracting recipients... */
4192
4193         while (!IsEmptyStr(recipients)) {
4194                 for (i=0; i<=strlen(recipients); ++i) {
4195                         if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4196                         if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4197                                 safestrncpy(this_recp, recipients, i+1);
4198                                 this_recp[i] = 0;
4199                                 if (recipients[i] == ',') {
4200                                         strcpy(recipients, &recipients[i+1]);
4201                                 }
4202                                 else {
4203                                         strcpy(recipients, "");
4204                                 }
4205                                 break;
4206                         }
4207                 }
4208
4209                 striplt(this_recp);
4210                 if (IsEmptyStr(this_recp))
4211                         break;
4212                 MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4213                 ++num_recps;
4214
4215                 strcpy(org_recp, this_recp);
4216                 alias(this_recp);
4217                 alias(this_recp);
4218                 mailtype = alias(this_recp);
4219
4220                 for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
4221                         if (this_recp[j]=='_') {
4222                                 this_recp_cooked[j] = ' ';
4223                         }
4224                         else {
4225                                 this_recp_cooked[j] = this_recp[j];
4226                         }
4227                 }
4228                 this_recp_cooked[j] = '\0';
4229                 invalid = 0;
4230                 errmsg[0] = 0;
4231                 switch(mailtype) {
4232                 case MES_LOCAL:
4233                         if (!strcasecmp(this_recp, "sysop")) {
4234                                 ++ret->num_room;
4235                                 strcpy(this_recp, config.c_aideroom);
4236                                 if (!IsEmptyStr(ret->recp_room)) {
4237                                         strcat(ret->recp_room, "|");
4238                                 }
4239                                 strcat(ret->recp_room, this_recp);
4240                         }
4241                         else if ( (!strncasecmp(this_recp, "room_", 5))
4242                                   && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4243
4244                                 /* Save room so we can restore it later */
4245                                 tempQR2 = CCC->room;
4246                                 CCC->room = tempQR;
4247                                         
4248                                 /* Check permissions to send mail to this room */
4249                                 err = CtdlDoIHavePermissionToPostInThisRoom(
4250                                         errmsg, 
4251                                         sizeof errmsg, 
4252                                         RemoteIdentifier,
4253                                         Flags,
4254                                         0                       /* 0 = not a reply */
4255                                         );
4256                                 if (err)
4257                                 {
4258                                         ++ret->num_error;
4259                                         invalid = 1;
4260                                 } 
4261                                 else {
4262                                         ++ret->num_room;
4263                                         if (!IsEmptyStr(ret->recp_room)) {
4264                                                 strcat(ret->recp_room, "|");
4265                                         }
4266                                         strcat(ret->recp_room, &this_recp_cooked[5]);
4267
4268                                         if (!IsEmptyStr(ret->recp_orgroom)) {
4269                                                 strcat(ret->recp_orgroom, "|");
4270                                         }
4271                                         strcat(ret->recp_orgroom, org_recp);
4272
4273                                 }
4274                                         
4275                                 /* Restore room in case something needs it */
4276                                 CCC->room = tempQR2;
4277
4278                         }
4279                         else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4280                                 ++ret->num_local;
4281                                 strcpy(this_recp, tempUS.fullname);
4282                                 if (!IsEmptyStr(ret->recp_local)) {
4283                                         strcat(ret->recp_local, "|");
4284                                 }
4285                                 strcat(ret->recp_local, this_recp);
4286                         }
4287                         else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4288                                 ++ret->num_local;
4289                                 strcpy(this_recp, tempUS.fullname);
4290                                 if (!IsEmptyStr(ret->recp_local)) {
4291                                         strcat(ret->recp_local, "|");
4292                                 }
4293                                 strcat(ret->recp_local, this_recp);
4294                         }
4295                         else {
4296                                 ++ret->num_error;
4297                                 invalid = 1;
4298                         }
4299                         break;
4300                 case MES_INTERNET:
4301                         /* Yes, you're reading this correctly: if the target
4302                          * domain points back to the local system or an attached
4303                          * Citadel directory, the address is invalid.  That's
4304                          * because if the address were valid, we would have
4305                          * already translated it to a local address by now.
4306                          */
4307                         if (IsDirectory(this_recp, 0)) {
4308                                 ++ret->num_error;
4309                                 invalid = 1;
4310                         }
4311                         else {
4312                                 ++ret->num_internet;
4313                                 if (!IsEmptyStr(ret->recp_internet)) {
4314                                         strcat(ret->recp_internet, "|");
4315                                 }
4316                                 strcat(ret->recp_internet, this_recp);
4317                         }
4318                         break;
4319                 case MES_IGNET:
4320                         ++ret->num_ignet;
4321                         if (!IsEmptyStr(ret->recp_ignet)) {
4322                                 strcat(ret->recp_ignet, "|");
4323                         }
4324                         strcat(ret->recp_ignet, this_recp);
4325                         break;
4326                 case MES_ERROR:
4327                         ++ret->num_error;
4328                         invalid = 1;
4329                         break;
4330                 }
4331                 if (invalid) {
4332                         if (IsEmptyStr(errmsg)) {
4333                                 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4334                         }
4335                         else {
4336                                 snprintf(append, sizeof append, "%s", errmsg);
4337                         }
4338                         if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4339                                 if (!IsEmptyStr(ret->errormsg)) {
4340                                         strcat(ret->errormsg, "; ");
4341                                 }
4342                                 strcat(ret->errormsg, append);
4343                         }
4344                 }
4345                 else {
4346                         if (IsEmptyStr(ret->display_recp)) {
4347                                 strcpy(append, this_recp);
4348                         }
4349                         else {
4350                                 snprintf(append, sizeof append, ", %s", this_recp);
4351                         }
4352                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4353                                 strcat(ret->display_recp, append);
4354                         }
4355                 }
4356         }
4357         free(org_recp);
4358
4359         if ((ret->num_local + ret->num_internet + ret->num_ignet +
4360              ret->num_room + ret->num_error) == 0) {
4361                 ret->num_error = (-1);
4362                 strcpy(ret->errormsg, "No recipients specified.");
4363         }
4364
4365         MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
4366         MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4367         MSG_syslog(LOG_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
4368         MSG_syslog(LOG_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4369         MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4370         MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4371
4372         free(recipients);
4373         return(ret);
4374 }
4375
4376
4377 /*
4378  * Destructor for struct recptypes
4379  */
4380 void free_recipients(struct recptypes *valid) {
4381
4382         if (valid == NULL) {
4383                 return;
4384         }
4385
4386         if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4387                 struct CitContext *CCC = CC;
4388                 MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4389                 abort();
4390         }
4391
4392         if (valid->errormsg != NULL)            free(valid->errormsg);
4393         if (valid->recp_local != NULL)          free(valid->recp_local);
4394         if (valid->recp_internet != NULL)       free(valid->recp_internet);
4395         if (valid->recp_ignet != NULL)          free(valid->recp_ignet);
4396         if (valid->recp_room != NULL)           free(valid->recp_room);
4397         if (valid->recp_orgroom != NULL)        free(valid->recp_orgroom);
4398         if (valid->display_recp != NULL)        free(valid->display_recp);
4399         if (valid->bounce_to != NULL)           free(valid->bounce_to);
4400         if (valid->envelope_from != NULL)       free(valid->envelope_from);
4401         if (valid->sending_room != NULL)        free(valid->sending_room);
4402         free(valid);
4403 }
4404
4405
4406
4407 /*
4408  * message entry  -  mode 0 (normal)
4409  */
4410 void cmd_ent0(char *entargs)
4411 {
4412         struct CitContext *CCC = CC;
4413         int post = 0;
4414         char recp[SIZ];
4415         char cc[SIZ];
4416         char bcc[SIZ];
4417         char supplied_euid[128];
4418         int anon_flag = 0;
4419         int format_type = 0;
4420         char newusername[256];
4421         char newuseremail[256];
4422         struct CtdlMessage *msg;
4423         int anonymous = 0;
4424         char errmsg[SIZ];
4425         int err = 0;
4426         struct recptypes *valid = NULL;
4427         struct recptypes *valid_to = NULL;
4428         struct recptypes *valid_cc = NULL;
4429         struct recptypes *valid_bcc = NULL;
4430         char subject[SIZ];
4431         int subject_required = 0;
4432         int do_confirm = 0;
4433         long msgnum;
4434         int i, j;
4435         char buf[256];
4436         int newuseremail_ok = 0;
4437         char references[SIZ];
4438         char *ptr;
4439
4440         unbuffer_output();
4441
4442         post = extract_int(entargs, 0);
4443         extract_token(recp, entargs, 1, '|', sizeof recp);
4444         anon_flag = extract_int(entargs, 2);
4445         format_type = extract_int(entargs, 3);
4446         extract_token(subject, entargs, 4, '|', sizeof subject);
4447         extract_token(newusername, entargs, 5, '|', sizeof newusername);
4448         do_confirm = extract_int(entargs, 6);
4449         extract_token(cc, entargs, 7, '|', sizeof cc);
4450         extract_token(bcc, entargs, 8, '|', sizeof bcc);
4451         switch(CC->room.QRdefaultview) {
4452         case VIEW_NOTES:
4453         case VIEW_WIKI:
4454                 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4455                 break;
4456         default:
4457                 supplied_euid[0] = 0;
4458                 break;
4459         }
4460         extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4461         extract_token(references, entargs, 11, '|', sizeof references);
4462         for (ptr=references; *ptr != 0; ++ptr) {
4463                 if (*ptr == '!') *ptr = '|';
4464         }
4465
4466         /* first check to make sure the request is valid. */
4467
4468         err = CtdlDoIHavePermissionToPostInThisRoom(
4469                 errmsg,
4470                 sizeof errmsg,
4471                 NULL,
4472                 POST_LOGGED_IN,
4473                 (!IsEmptyStr(references))               /* is this a reply?  or a top-level post? */
4474                 );
4475         if (err)
4476         {
4477                 cprintf("%d %s\n", err, errmsg);
4478                 return;
4479         }
4480
4481         /* Check some other permission type things. */
4482
4483         if (IsEmptyStr(newusername)) {
4484                 strcpy(newusername, CCC->user.fullname);
4485         }
4486         if (  (CCC->user.axlevel < AxAideU)
4487               && (strcasecmp(newusername, CCC->user.fullname))
4488               && (strcasecmp(newusername, CCC->cs_inet_fn))
4489                 ) {     
4490                 cprintf("%d You don't have permission to author messages as '%s'.\n",
4491                         ERROR + HIGHER_ACCESS_REQUIRED,
4492                         newusername
4493                         );
4494                 return;
4495         }
4496
4497
4498         if (IsEmptyStr(newuseremail)) {
4499                 newuseremail_ok = 1;
4500         }
4501
4502         if (!IsEmptyStr(newuseremail)) {
4503                 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
4504                         newuseremail_ok = 1;
4505                 }
4506                 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
4507                         j = num_tokens(CCC->cs_inet_other_emails, '|');
4508                         for (i=0; i<j; ++i) {
4509                                 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
4510                                 if (!strcasecmp(newuseremail, buf)) {
4511                                         newuseremail_ok = 1;
4512                                 }
4513                         }
4514                 }
4515         }
4516
4517         if (!newuseremail_ok) {
4518                 cprintf("%d You don't have permission to author messages as '%s'.\n",
4519                         ERROR + HIGHER_ACCESS_REQUIRED,
4520                         newuseremail
4521                         );
4522                 return;
4523         }
4524
4525         CCC->cs_flags |= CS_POSTING;
4526
4527         /* In mailbox rooms we have to behave a little differently --
4528          * make sure the user has specified at least one recipient.  Then
4529          * validate the recipient(s).  We do this for the Mail> room, as
4530          * well as any room which has the "Mailbox" view set - unless it
4531          * is the DRAFTS room which does not require recipients
4532          */
4533
4534         if ( (  ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4535                 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4536                      ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4537                 if (CCC->user.axlevel < AxProbU) {
4538                         strcpy(recp, "sysop");
4539                         strcpy(cc, "");
4540                         strcpy(bcc, "");
4541                 }
4542
4543                 valid_to = validate_recipients(recp, NULL, 0);
4544                 if (valid_to->num_error > 0) {
4545                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4546                         free_recipients(valid_to);
4547                         return;
4548                 }
4549
4550                 valid_cc = validate_recipients(cc, NULL, 0);
4551                 if (valid_cc->num_error > 0) {
4552                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4553                         free_recipients(valid_to);
4554                         free_recipients(valid_cc);
4555                         return;
4556                 }
4557
4558                 valid_bcc = validate_recipients(bcc, NULL, 0);
4559                 if (valid_bcc->num_error > 0) {
4560                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4561                         free_recipients(valid_to);
4562                         free_recipients(valid_cc);
4563                         free_recipients(valid_bcc);
4564                         return;
4565                 }
4566
4567                 /* Recipient required, but none were specified */
4568                 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4569                         free_recipients(valid_to);
4570                         free_recipients(valid_cc);
4571                         free_recipients(valid_bcc);
4572                         cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4573                         return;
4574                 }
4575
4576                 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4577                         if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4578                                 cprintf("%d You do not have permission "
4579                                         "to send Internet mail.\n",
4580                                         ERROR + HIGHER_ACCESS_REQUIRED);
4581                                 free_recipients(valid_to);
4582                                 free_recipients(valid_cc);
4583                                 free_recipients(valid_bcc);
4584                                 return;
4585                         }
4586                 }
4587
4588                 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)
4589                      && (CCC->user.axlevel < AxNetU) ) {
4590                         cprintf("%d Higher access required for network mail.\n",
4591                                 ERROR + HIGHER_ACCESS_REQUIRED);
4592                         free_recipients(valid_to);
4593                         free_recipients(valid_cc);
4594                         free_recipients(valid_bcc);
4595                         return;
4596                 }
4597         
4598                 if ((RESTRICT_INTERNET == 1)
4599                     && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4600                     && ((CCC->user.flags & US_INTERNET) == 0)
4601                     && (!CCC->internal_pgm)) {
4602                         cprintf("%d You don't have access to Internet mail.\n",
4603                                 ERROR + HIGHER_ACCESS_REQUIRED);
4604                         free_recipients(valid_to);
4605                         free_recipients(valid_cc);
4606                         free_recipients(valid_bcc);
4607                         return;
4608                 }
4609
4610         }
4611
4612         /* Is this a room which has anonymous-only or anonymous-option? */
4613         anonymous = MES_NORMAL;
4614         if (CCC->room.QRflags & QR_ANONONLY) {
4615                 anonymous = MES_ANONONLY;
4616         }
4617         if (CCC->room.QRflags & QR_ANONOPT) {
4618                 if (anon_flag == 1) {   /* only if the user requested it */
4619                         anonymous = MES_ANONOPT;
4620                 }
4621         }
4622
4623         if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4624                 recp[0] = 0;
4625         }
4626
4627         /* Recommend to the client that the use of a message subject is
4628          * strongly recommended in this room, if either the SUBJECTREQ flag
4629          * is set, or if there is one or more Internet email recipients.
4630          */
4631         if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4632         if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
4633         if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
4634         if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
4635
4636         /* If we're only checking the validity of the request, return
4637          * success without creating the message.
4638          */
4639         if (post == 0) {
4640                 cprintf("%d %s|%d\n", CIT_OK,
4641                         ((valid_to != NULL) ? valid_to->display_recp : ""), 
4642                         subject_required);
4643                 free_recipients(valid_to);
4644                 free_recipients(valid_cc);
4645                 free_recipients(valid_bcc);
4646                 return;
4647         }
4648
4649         /* We don't need these anymore because we'll do it differently below */
4650         free_recipients(valid_to);
4651         free_recipients(valid_cc);
4652         free_recipients(valid_bcc);
4653
4654         /* Read in the message from the client. */
4655         if (do_confirm) {
4656                 cprintf("%d send message\n", START_CHAT_MODE);
4657         } else {
4658                 cprintf("%d send message\n", SEND_LISTING);
4659         }
4660
4661         msg = CtdlMakeMessage(&CCC->user, recp, cc,
4662                               CCC->room.QRname, anonymous, format_type,
4663                               newusername, newuseremail, subject,
4664                               ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4665                               NULL, references);
4666
4667         /* Put together one big recipients struct containing to/cc/bcc all in
4668          * one.  This is for the envelope.
4669          */
4670         char *all_recps = malloc(SIZ * 3);
4671         strcpy(all_recps, recp);
4672         if (!IsEmptyStr(cc)) {
4673                 if (!IsEmptyStr(all_recps)) {
4674                         strcat(all_recps, ",");
4675                 }
4676                 strcat(all_recps, cc);
4677         }
4678         if (!IsEmptyStr(bcc)) {
4679                 if (!IsEmptyStr(all_recps)) {
4680                         strcat(all_recps, ",");
4681                 }
4682                 strcat(all_recps, bcc);
4683         }
4684         if (!IsEmptyStr(all_recps)) {
4685                 valid = validate_recipients(all_recps, NULL, 0);
4686         }
4687         else {
4688                 valid = NULL;
4689         }
4690         free(all_recps);
4691
4692         if ((valid != NULL) && (valid->num_room == 1))
4693         {
4694                 /* posting into an ML room? set the envelope from 
4695                  * to the actual mail address so others get a valid
4696                  * reply-to-header.
4697                  */
4698                 msg->cm_fields['V'] = strdup(valid->recp_orgroom);
4699         }
4700
4701         if (msg != NULL) {
4702                 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4703                 if (do_confirm) {
4704                         cprintf("%ld\n", msgnum);
4705
4706                         if (StrLength(CCC->StatusMessage) > 0) {
4707                                 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
4708                         }
4709                         else if (msgnum >= 0L) {
4710                                 client_write(HKEY("Message accepted.\n"));
4711                         }
4712                         else {
4713                                 client_write(HKEY("Internal error.\n"));
4714                         }
4715
4716                         if (msg->cm_fields['E'] != NULL) {
4717                                 cprintf("%s\n", msg->cm_fields['E']);
4718                         } else {
4719                                 cprintf("\n");
4720                         }
4721                         cprintf("000\n");
4722                 }
4723
4724                 CtdlFreeMessage(msg);
4725         }
4726         if (valid != NULL) {
4727                 free_recipients(valid);
4728         }
4729         return;
4730 }
4731
4732
4733
4734 /*
4735  * API function to delete messages which match a set of criteria
4736  * (returns the actual number of messages deleted)
4737  */
4738 int CtdlDeleteMessages(char *room_name,         /* which room */
4739                        long *dmsgnums,          /* array of msg numbers to be deleted */
4740                        int num_dmsgnums,        /* number of msgs to be deleted, or 0 for "any" */
4741                        char *content_type       /* or "" for any.  regular expressions expected. */
4742         )
4743 {
4744         struct CitContext *CCC = CC;
4745         struct ctdlroom qrbuf;
4746         struct cdbdata *cdbfr;
4747         long *msglist = NULL;
4748         long *dellist = NULL;
4749         int num_msgs = 0;
4750         int i, j;
4751         int num_deleted = 0;
4752         int delete_this;
4753         struct MetaData smi;
4754         regex_t re;
4755         regmatch_t pm;
4756         int need_to_free_re = 0;
4757
4758         if (content_type) if (!IsEmptyStr(content_type)) {
4759                         regcomp(&re, content_type, 0);
4760                         need_to_free_re = 1;
4761                 }
4762         MSG_syslog(LOG_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4763                    room_name, num_dmsgnums, content_type);
4764
4765         /* get room record, obtaining a lock... */
4766         if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4767                 MSG_syslog(LOG_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4768                            room_name);
4769                 if (need_to_free_re) regfree(&re);
4770                 return (0);     /* room not found */
4771         }
4772         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4773
4774         if (cdbfr != NULL) {
4775                 dellist = malloc(cdbfr->len);
4776                 msglist = (long *) cdbfr->ptr;
4777                 cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
4778                 num_msgs = cdbfr->len / sizeof(long);
4779                 cdb_free(cdbfr);
4780         }
4781         if (num_msgs > 0) {
4782                 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
4783                 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
4784                 int have_more_del = 1;
4785
4786                 num_msgs = sort_msglist(msglist, num_msgs);
4787                 if (num_dmsgnums > 1)
4788                         num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
4789 /*
4790                 {
4791                         StrBuf *dbg = NewStrBuf();
4792                         for (i = 0; i < num_dmsgnums; i++)
4793                                 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
4794                         MSG_syslog(LOG_DEBUG, "Deleting before: %s", ChrPtr(dbg));
4795                         FreeStrBuf(&dbg);
4796                 }
4797 */
4798                 i = 0; j = 0;
4799                 while ((i < num_msgs) && (have_more_del)) {
4800                         delete_this = 0x00;
4801
4802
4803                         /* Set/clear a bit for each criterion */
4804
4805                         /* 0 messages in the list or a null list means that we are
4806                          * interested in deleting any messages which meet the other criteria.
4807                          */
4808                         if (have_delmsgs) {
4809                                 delete_this |= 0x01;
4810                         }
4811                         else {
4812                                 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
4813                                 if (msglist[i] == dmsgnums[j]) {
4814                                         delete_this |= 0x01;
4815                                 }
4816                                 j++;
4817                                 have_more_del = (j < num_dmsgnums);
4818                         }
4819
4820                         if (have_contenttype) {
4821                                 GetMetaData(&smi, msglist[i]);
4822                                 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4823                                         delete_this |= 0x02;
4824                                 }
4825                         } else {
4826                                 delete_this |= 0x02;
4827                         }
4828
4829                         /* Delete message only if all bits are set */
4830                         if (delete_this == 0x03) {
4831                                 dellist[num_deleted++] = msglist[i];
4832                                 msglist[i] = 0L;
4833                         }
4834                         i++;
4835                 }
4836 /*
4837                 {
4838                         StrBuf *dbg = NewStrBuf();
4839                         for (i = 0; i < num_deleted; i++)
4840                                 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
4841                         MSG_syslog(LOG_DEBUG, "Deleting: %s", ChrPtr(dbg));
4842                         FreeStrBuf(&dbg);
4843                 }
4844 */
4845                 num_msgs = sort_msglist(msglist, num_msgs);
4846                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4847                           msglist, (int)(num_msgs * sizeof(long)));
4848
4849                 if (num_msgs > 0)
4850                         qrbuf.QRhighest = msglist[num_msgs - 1];
4851                 else
4852                         qrbuf.QRhighest = 0;
4853         }
4854         CtdlPutRoomLock(&qrbuf);
4855
4856         /* Go through the messages we pulled out of the index, and decrement
4857          * their reference counts by 1.  If this is the only room the message
4858          * was in, the reference count will reach zero and the message will
4859          * automatically be deleted from the database.  We do this in a
4860          * separate pass because there might be plug-in hooks getting called,
4861          * and we don't want that happening during an S_ROOMS critical
4862          * section.
4863          */
4864         if (num_deleted) {
4865                 for (i=0; i<num_deleted; ++i) {
4866                         PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4867                 }
4868                 AdjRefCountList(dellist, num_deleted, -1);
4869         }
4870         /* Now free the memory we used, and go away. */
4871         if (msglist != NULL) free(msglist);
4872         if (dellist != NULL) free(dellist);
4873         MSG_syslog(LOG_DEBUG, "%d message(s) deleted.\n", num_deleted);
4874         if (need_to_free_re) regfree(&re);
4875         return (num_deleted);
4876 }
4877
4878
4879
4880 /*
4881  * Check whether the current user has permission to delete messages from
4882  * the current room (returns 1 for yes, 0 for no)
4883  */
4884 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4885         int ra;
4886         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4887         if (ra & UA_DELETEALLOWED) return(1);
4888         return(0);
4889 }
4890
4891
4892
4893
4894 /*
4895  * Delete message from current room
4896  */
4897 void cmd_dele(char *args)
4898 {
4899         int num_deleted;
4900         int i;
4901         char msgset[SIZ];
4902         char msgtok[32];
4903         long *msgs;
4904         int num_msgs = 0;
4905
4906         extract_token(msgset, args, 0, '|', sizeof msgset);
4907         num_msgs = num_tokens(msgset, ',');
4908         if (num_msgs < 1) {
4909                 cprintf("%d Nothing to do.\n", CIT_OK);
4910                 return;
4911         }
4912
4913         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4914                 cprintf("%d Higher access required.\n",
4915                         ERROR + HIGHER_ACCESS_REQUIRED);
4916                 return;
4917         }
4918
4919         /*
4920          * Build our message set to be moved/copied
4921          */
4922         msgs = malloc(num_msgs * sizeof(long));
4923         for (i=0; i<num_msgs; ++i) {
4924                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4925                 msgs[i] = atol(msgtok);
4926         }
4927
4928         num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4929         free(msgs);
4930
4931         if (num_deleted) {
4932                 cprintf("%d %d message%s deleted.\n", CIT_OK,
4933                         num_deleted, ((num_deleted != 1) ? "s" : ""));
4934         } else {
4935                 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4936         }
4937 }
4938
4939
4940
4941
4942 /*
4943  * move or copy a message to another room
4944  */
4945 void cmd_move(char *args)
4946 {
4947         char msgset[SIZ];
4948         char msgtok[32];
4949         long *msgs;
4950         int num_msgs = 0;
4951
4952         char targ[ROOMNAMELEN];
4953         struct ctdlroom qtemp;
4954         int err;
4955         int is_copy = 0;
4956         int ra;
4957         int permit = 0;
4958         int i;
4959
4960         extract_token(msgset, args, 0, '|', sizeof msgset);
4961         num_msgs = num_tokens(msgset, ',');
4962         if (num_msgs < 1) {
4963                 cprintf("%d Nothing to do.\n", CIT_OK);
4964                 return;
4965         }
4966
4967         extract_token(targ, args, 1, '|', sizeof targ);
4968         convert_room_name_macros(targ, sizeof targ);
4969         targ[ROOMNAMELEN - 1] = 0;
4970         is_copy = extract_int(args, 2);
4971
4972         if (CtdlGetRoom(&qtemp, targ) != 0) {
4973                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4974                 return;
4975         }
4976
4977         if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4978                 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4979                 return;
4980         }
4981
4982         CtdlGetUser(&CC->user, CC->curr_user);
4983         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4984
4985         /* Check for permission to perform this operation.
4986          * Remember: "CC->room" is source, "qtemp" is target.
4987          */
4988         permit = 0;
4989
4990         /* Admins can move/copy */
4991         if (CC->user.axlevel >= AxAideU) permit = 1;
4992
4993         /* Room aides can move/copy */
4994         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4995
4996         /* Permit move/copy from personal rooms */
4997         if ((CC->room.QRflags & QR_MAILBOX)
4998             && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4999
5000         /* Permit only copy from public to personal room */
5001         if ( (is_copy)
5002              && (!(CC->room.QRflags & QR_MAILBOX))
5003              && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5004
5005         /* Permit message removal from collaborative delete rooms */
5006         if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
5007
5008         /* Users allowed to post into the target room may move into it too. */
5009         if ((CC->room.QRflags & QR_MAILBOX) && 
5010             (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
5011
5012         /* User must have access to target room */
5013         if (!(ra & UA_KNOWN))  permit = 0;
5014
5015         if (!permit) {
5016                 cprintf("%d Higher access required.\n",
5017                         ERROR + HIGHER_ACCESS_REQUIRED);
5018                 return;
5019         }
5020
5021         /*
5022          * Build our message set to be moved/copied
5023          */
5024         msgs = malloc(num_msgs * sizeof(long));
5025         for (i=0; i<num_msgs; ++i) {
5026                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5027                 msgs[i] = atol(msgtok);
5028         }
5029
5030         /*
5031          * Do the copy
5032          */
5033         err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
5034         if (err != 0) {
5035                 cprintf("%d Cannot store message(s) in %s: error %d\n",
5036                         err, targ, err);
5037                 free(msgs);
5038                 return;
5039         }
5040
5041         /* Now delete the message from the source room,
5042          * if this is a 'move' rather than a 'copy' operation.
5043          */
5044         if (is_copy == 0) {
5045                 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5046         }
5047         free(msgs);
5048
5049         cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
5050 }
5051
5052
5053
5054 /*
5055  * GetMetaData()  -  Get the supplementary record for a message
5056  */
5057 void GetMetaData(struct MetaData *smibuf, long msgnum)
5058 {
5059
5060         struct cdbdata *cdbsmi;
5061         long TheIndex;
5062
5063         memset(smibuf, 0, sizeof(struct MetaData));
5064         smibuf->meta_msgnum = msgnum;
5065         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
5066
5067         /* Use the negative of the message number for its supp record index */
5068         TheIndex = (0L - msgnum);
5069
5070         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
5071         if (cdbsmi == NULL) {
5072                 return;         /* record not found; go with defaults */
5073         }
5074         memcpy(smibuf, cdbsmi->ptr,
5075                ((cdbsmi->len > sizeof(struct MetaData)) ?
5076                 sizeof(struct MetaData) : cdbsmi->len));
5077         cdb_free(cdbsmi);
5078         return;
5079 }
5080
5081
5082 /*
5083  * PutMetaData()  -  (re)write supplementary record for a message
5084  */
5085 void PutMetaData(struct MetaData *smibuf)
5086 {
5087         long TheIndex;
5088
5089         /* Use the negative of the message number for the metadata db index */
5090         TheIndex = (0L - smibuf->meta_msgnum);
5091
5092         cdb_store(CDB_MSGMAIN,
5093                   &TheIndex, (int)sizeof(long),
5094                   smibuf, (int)sizeof(struct MetaData));
5095
5096 }
5097
5098 /*
5099  * AdjRefCount  -  submit an adjustment to the reference count for a message.
5100  *                 (These are just queued -- we actually process them later.)
5101  */
5102 void AdjRefCount(long msgnum, int incr)
5103 {
5104         struct CitContext *CCC = CC;
5105         struct arcq new_arcq;
5106         int rv = 0;
5107
5108         MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
5109
5110         begin_critical_section(S_SUPPMSGMAIN);
5111         if (arcfp == NULL) {
5112                 arcfp = fopen(file_arcq, "ab+");
5113                 chown(file_arcq, CTDLUID, (-1));
5114                 chmod(file_arcq, 0600);
5115         }
5116         end_critical_section(S_SUPPMSGMAIN);
5117
5118         /* msgnum < 0 means that we're trying to close the file */
5119         if (msgnum < 0) {
5120                 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
5121                 begin_critical_section(S_SUPPMSGMAIN);
5122                 if (arcfp != NULL) {
5123                         fclose(arcfp);
5124                         arcfp = NULL;
5125                 }
5126                 end_critical_section(S_SUPPMSGMAIN);
5127                 return;
5128         }
5129
5130         /*
5131          * If we can't open the queue, perform the operation synchronously.
5132          */
5133         if (arcfp == NULL) {
5134                 TDAP_AdjRefCount(msgnum, incr);
5135                 return;
5136         }
5137
5138         new_arcq.arcq_msgnum = msgnum;
5139         new_arcq.arcq_delta = incr;
5140         rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
5141         if (rv == -1) {
5142                 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5143                            file_arcq,
5144                            strerror(errno));
5145         }
5146         fflush(arcfp);
5147
5148         return;
5149 }
5150
5151 void AdjRefCountList(long *msgnum, long nmsg, int incr)
5152 {
5153         struct CitContext *CCC = CC;
5154         long i, the_size, offset;
5155         struct arcq *new_arcq;
5156         int rv = 0;
5157
5158         MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
5159
5160         begin_critical_section(S_SUPPMSGMAIN);
5161         if (arcfp == NULL) {
5162                 arcfp = fopen(file_arcq, "ab+");
5163                 chown(file_arcq, CTDLUID, (-1));
5164                 chmod(file_arcq, 0600);
5165         }
5166         end_critical_section(S_SUPPMSGMAIN);
5167
5168         /*
5169          * If we can't open the queue, perform the operation synchronously.
5170          */
5171         if (arcfp == NULL) {
5172                 for (i = 0; i < nmsg; i++)
5173                         TDAP_AdjRefCount(msgnum[i], incr);
5174                 return;
5175         }
5176
5177         the_size = sizeof(struct arcq) * nmsg;
5178         new_arcq = malloc(the_size);
5179         for (i = 0; i < nmsg; i++) {
5180                 new_arcq[i].arcq_msgnum = msgnum[i];
5181                 new_arcq[i].arcq_delta = incr;
5182         }
5183         rv = 0;
5184         offset = 0;
5185         while ((rv >= 0) && (offset < the_size))
5186         {
5187                 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
5188                 if (rv == -1) {
5189                         MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5190                                    file_arcq,
5191                                    strerror(errno));
5192                 }
5193                 else {
5194                         offset += rv;
5195                 }
5196         }
5197         free(new_arcq);
5198         fflush(arcfp);
5199
5200         return;
5201 }
5202
5203
5204 /*
5205  * TDAP_ProcessAdjRefCountQueue()
5206  *
5207  * Process the queue of message count adjustments that was created by calls
5208  * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
5209  * for each one.  This should be an "off hours" operation.
5210  */
5211 int TDAP_ProcessAdjRefCountQueue(void)
5212 {
5213         struct CitContext *CCC = CC;
5214         char file_arcq_temp[PATH_MAX];
5215         int r;
5216         FILE *fp;
5217         struct arcq arcq_rec;
5218         int num_records_processed = 0;
5219
5220         snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
5221
5222         begin_critical_section(S_SUPPMSGMAIN);
5223         if (arcfp != NULL) {
5224                 fclose(arcfp);
5225                 arcfp = NULL;
5226         }
5227
5228         r = link(file_arcq, file_arcq_temp);
5229         if (r != 0) {
5230                 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5231                 end_critical_section(S_SUPPMSGMAIN);
5232                 return(num_records_processed);
5233         }
5234
5235         unlink(file_arcq);
5236         end_critical_section(S_SUPPMSGMAIN);
5237
5238         fp = fopen(file_arcq_temp, "rb");
5239         if (fp == NULL) {
5240                 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5241                 return(num_records_processed);
5242         }
5243
5244         while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5245                 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5246                 ++num_records_processed;
5247         }
5248
5249         fclose(fp);
5250         r = unlink(file_arcq_temp);
5251         if (r != 0) {
5252                 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5253         }
5254
5255         return(num_records_processed);
5256 }
5257
5258
5259
5260 /*
5261  * TDAP_AdjRefCount  -  adjust the reference count for a message.
5262  *                      This one does it "for real" because it's called by
5263  *                      the autopurger function that processes the queue
5264  *                      created by AdjRefCount().   If a message's reference
5265  *                      count becomes zero, we also delete the message from
5266  *                      disk and de-index it.
5267  */
5268 void TDAP_AdjRefCount(long msgnum, int incr)
5269 {
5270         struct CitContext *CCC = CC;
5271
5272         struct MetaData smi;
5273         long delnum;
5274
5275         /* This is a *tight* critical section; please keep it that way, as
5276          * it may get called while nested in other critical sections.  
5277          * Complicating this any further will surely cause deadlock!
5278          */
5279         begin_critical_section(S_SUPPMSGMAIN);
5280         GetMetaData(&smi, msgnum);
5281         smi.meta_refcount += incr;
5282         PutMetaData(&smi);
5283         end_critical_section(S_SUPPMSGMAIN);
5284         MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5285                    msgnum, incr, smi.meta_refcount
5286                 );
5287
5288         /* If the reference count is now zero, delete the message
5289          * (and its supplementary record as well).
5290          */
5291         if (smi.meta_refcount == 0) {
5292                 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5293                 
5294                 /* Call delete hooks with NULL room to show it has gone altogether */
5295                 PerformDeleteHooks(NULL, msgnum);
5296
5297                 /* Remove from message base */
5298                 delnum = msgnum;
5299                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5300                 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5301
5302                 /* Remove metadata record */
5303                 delnum = (0L - msgnum);
5304                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5305         }
5306
5307 }
5308
5309 /*
5310  * Write a generic object to this room
5311  *
5312  * Note: this could be much more efficient.  Right now we use two temporary
5313  * files, and still pull the message into memory as with all others.
5314  */
5315 void CtdlWriteObject(char *req_room,                    /* Room to stuff it in */
5316                      char *content_type,                /* MIME type of this object */
5317                      char *raw_message,         /* Data to be written */
5318                      off_t raw_length,          /* Size of raw_message */
5319                      struct ctdluser *is_mailbox,       /* Mailbox room? */
5320                      int is_binary,                     /* Is encoding necessary? */
5321                      int is_unique,                     /* Del others of this type? */
5322                      unsigned int flags         /* Internal save flags */
5323         )
5324 {
5325         struct CitContext *CCC = CC;
5326         struct ctdlroom qrbuf;
5327         char roomname[ROOMNAMELEN];
5328         struct CtdlMessage *msg;
5329         char *encoded_message = NULL;
5330
5331         if (is_mailbox != NULL) {
5332                 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5333         }
5334         else {
5335                 safestrncpy(roomname, req_room, sizeof(roomname));
5336         }
5337
5338         MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5339
5340         if (is_binary) {
5341                 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5342         }
5343         else {
5344                 encoded_message = malloc((size_t)(raw_length + 4096));
5345         }
5346
5347         sprintf(encoded_message, "Content-type: %s\n", content_type);
5348
5349         if (is_binary) {
5350                 sprintf(&encoded_message[strlen(encoded_message)],
5351                         "Content-transfer-encoding: base64\n\n"
5352                         );
5353         }
5354         else {
5355                 sprintf(&encoded_message[strlen(encoded_message)],
5356                         "Content-transfer-encoding: 7bit\n\n"
5357                         );
5358         }
5359
5360         if (is_binary) {
5361                 CtdlEncodeBase64(
5362                         &encoded_message[strlen(encoded_message)],
5363                         raw_message,
5364                         (int)raw_length,
5365                         0
5366                         );
5367         }
5368         else {
5369                 memcpy(
5370                         &encoded_message[strlen(encoded_message)],
5371                         raw_message,
5372                         (int)(raw_length+1)
5373                         );
5374         }
5375
5376         MSGM_syslog(LOG_DEBUG, "Allocating\n");
5377         msg = malloc(sizeof(struct CtdlMessage));
5378         memset(msg, 0, sizeof(struct CtdlMessage));
5379         msg->cm_magic = CTDLMESSAGE_MAGIC;
5380         msg->cm_anon_type = MES_NORMAL;
5381         msg->cm_format_type = 4;
5382         msg->cm_fields['A'] = strdup(CCC->user.fullname);
5383         msg->cm_fields['O'] = strdup(req_room);
5384         msg->cm_fields['N'] = strdup(config.c_nodename);
5385         msg->cm_fields['H'] = strdup(config.c_humannode);
5386         msg->cm_flags = flags;
5387         
5388         msg->cm_fields['M'] = encoded_message;
5389
5390         /* Create the requested room if we have to. */
5391         if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5392                 CtdlCreateRoom(roomname, 
5393                                ( (is_mailbox != NULL) ? 5 : 3 ),
5394                                "", 0, 1, 0, VIEW_BBS);
5395         }
5396         /* If the caller specified this object as unique, delete all
5397          * other objects of this type that are currently in the room.
5398          */
5399         if (is_unique) {
5400                 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5401                            CtdlDeleteMessages(roomname, NULL, 0, content_type)
5402                         );
5403         }
5404         /* Now write the data */
5405         CtdlSubmitMsg(msg, NULL, roomname, 0);
5406         CtdlFreeMessage(msg);
5407 }
5408
5409
5410
5411
5412
5413
5414 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5415         config_msgnum = msgnum;
5416 }
5417
5418
5419 char *CtdlGetSysConfig(char *sysconfname) {
5420         char hold_rm[ROOMNAMELEN];
5421         long msgnum;
5422         char *conf;
5423         struct CtdlMessage *msg;
5424         char buf[SIZ];
5425         
5426         strcpy(hold_rm, CC->room.QRname);
5427         if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5428                 CtdlGetRoom(&CC->room, hold_rm);
5429                 return NULL;
5430         }
5431
5432
5433         /* We want the last (and probably only) config in this room */
5434         begin_critical_section(S_CONFIG);
5435         config_msgnum = (-1L);
5436         CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5437                            CtdlGetSysConfigBackend, NULL);
5438         msgnum = config_msgnum;
5439         end_critical_section(S_CONFIG);
5440
5441         if (msgnum < 0L) {
5442                 conf = NULL;
5443         }
5444         else {
5445                 msg = CtdlFetchMessage(msgnum, 1);
5446                 if (msg != NULL) {
5447                         conf = strdup(msg->cm_fields['M']);
5448                         CtdlFreeMessage(msg);
5449                 }
5450                 else {
5451                         conf = NULL;
5452                 }
5453         }
5454
5455         CtdlGetRoom(&CC->room, hold_rm);
5456
5457         if (conf != NULL) do {
5458                         extract_token(buf, conf, 0, '\n', sizeof buf);
5459                         strcpy(conf, &conf[strlen(buf)+1]);
5460                 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5461
5462         return(conf);
5463 }
5464
5465
5466 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5467         CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5468 }
5469
5470
5471 /*
5472  * Determine whether a given Internet address belongs to the current user
5473  */
5474 int CtdlIsMe(char *addr, int addr_buf_len)
5475 {
5476         struct recptypes *recp;
5477         int i;
5478
5479         recp = validate_recipients(addr, NULL, 0);
5480         if (recp == NULL) return(0);
5481
5482         if (recp->num_local == 0) {
5483                 free_recipients(recp);
5484                 return(0);
5485         }
5486
5487         for (i=0; i<recp->num_local; ++i) {
5488                 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5489                 if (!strcasecmp(addr, CC->user.fullname)) {
5490                         free_recipients(recp);
5491                         return(1);
5492                 }
5493         }
5494
5495         free_recipients(recp);
5496         return(0);
5497 }
5498
5499
5500 /*
5501  * Citadel protocol command to do the same
5502  */
5503 void cmd_isme(char *argbuf) {
5504         char addr[256];
5505
5506         if (CtdlAccessCheck(ac_logged_in)) return;
5507         extract_token(addr, argbuf, 0, '|', sizeof addr);
5508
5509         if (CtdlIsMe(addr, sizeof addr)) {
5510                 cprintf("%d %s\n", CIT_OK, addr);
5511         }
5512         else {
5513                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5514         }
5515
5516 }
5517
5518
5519 /*****************************************************************************/
5520 /*                      MODULE INITIALIZATION STUFF                          */
5521 /*****************************************************************************/
5522 void SetMessageDebugEnabled(const int n)
5523 {
5524         MessageDebugEnabled = n;
5525 }
5526 CTDL_MODULE_INIT(msgbase)
5527 {
5528         if (!threading) {
5529                 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
5530
5531                 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5532                 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5533                 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5534                 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5535                 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5536                 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5537                 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5538                 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5539                 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5540                 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5541                 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5542                 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5543         }
5544
5545         /* return our Subversion id for the Log */
5546         return "msgbase";
5547 }