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