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