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