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