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