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