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