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