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