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