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