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