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