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