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