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