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