added some temporary logging
[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         if (num_newmsgs > 0) for (i=0; i<num_newmsgs; ++i) syslog(LOG_DEBUG, "\033[33mmerge %ld\033[0m", newmsgidlist[i]);
2639
2640         strcpy(hold_rm, CC->room.QRname);
2641
2642         /* Sanity checks */
2643         if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2644         if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2645         if (num_newmsgs > 1) supplied_msg = NULL;
2646
2647         /* Now the regular stuff */
2648         if (CtdlGetRoomLock(&CC->room,
2649            ((roomname != NULL) ? roomname : CC->room.QRname) )
2650            != 0) {
2651                 syslog(LOG_ERR, "No such room <%s>\n", roomname);
2652                 return(ERROR + ROOM_NOT_FOUND);
2653         }
2654
2655
2656         msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2657         num_msgs_to_be_merged = 0;
2658
2659
2660         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2661         if (cdbfr == NULL) {
2662                 msglist = NULL;
2663                 num_msgs = 0;
2664         } else {
2665                 msglist = (long *) cdbfr->ptr;
2666                 cdbfr->ptr = NULL;      /* CtdlSaveMsgPointerInRoom() now owns this memory */
2667                 num_msgs = cdbfr->len / sizeof(long);
2668                 cdb_free(cdbfr);
2669         }
2670
2671
2672         /* Create a list of msgid's which were supplied by the caller, but do
2673          * not already exist in the target room.  It is absolutely taboo to
2674          * have more than one reference to the same message in a room.
2675          */
2676         for (i=0; i<num_newmsgs; ++i) {
2677                 unique = 1;
2678                 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2679                         if (msglist[j] == newmsgidlist[i]) {
2680                                 unique = 0;
2681                         }
2682                 }
2683                 if (unique) {
2684                         msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2685                         syslog(LOG_DEBUG, "\033[32mmsg %ld is being merged\033[0m", newmsgidlist[i]);
2686                 }
2687                 else {
2688                         syslog(LOG_DEBUG, "\033[31mmsg %ld is not unique\033[0m", newmsgidlist[i]);
2689                 }
2690         }
2691
2692         syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2693
2694         /*
2695          * Now merge the new messages
2696          */
2697         msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2698         if (msglist == NULL) {
2699                 syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2700         }
2701         memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2702         num_msgs += num_msgs_to_be_merged;
2703
2704         /* Sort the message list, so all the msgid's are in order */
2705         num_msgs = sort_msglist(msglist, num_msgs);
2706
2707         /* Determine the highest message number */
2708         highest_msg = msglist[num_msgs - 1];
2709
2710         /* Write it back to disk. */
2711         cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2712                   msglist, (int)(num_msgs * sizeof(long)));
2713
2714         /* Free up the memory we used. */
2715         free(msglist);
2716
2717         /* Update the highest-message pointer and unlock the room. */
2718         CC->room.QRhighest = highest_msg;
2719         CtdlPutRoomLock(&CC->room);
2720
2721         /* Perform replication checks if necessary */
2722         if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2723                 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2724
2725                 for (i=0; i<num_msgs_to_be_merged; ++i) {
2726                         msgid = msgs_to_be_merged[i];
2727         
2728                         if (supplied_msg != NULL) {
2729                                 msg = supplied_msg;
2730                         }
2731                         else {
2732                                 msg = CtdlFetchMessage(msgid, 0);
2733                         }
2734         
2735                         if (msg != NULL) {
2736                                 ReplicationChecks(msg);
2737                 
2738                                 /* If the message has an Exclusive ID, index that... */
2739                                 if (msg->cm_fields['E'] != NULL) {
2740                                         index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2741                                 }
2742
2743                                 /* Free up the memory we may have allocated */
2744                                 if (msg != supplied_msg) {
2745                                         CtdlFreeMessage(msg);
2746                                 }
2747                         }
2748         
2749                 }
2750         }
2751
2752         else {
2753                 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2754         }
2755
2756         /* Submit this room for processing by hooks */
2757         PerformRoomHooks(&CC->room);
2758
2759         /* Go back to the room we were in before we wandered here... */
2760         CtdlGetRoom(&CC->room, hold_rm);
2761
2762         /* Bump the reference count for all messages which were merged */
2763         if (!suppress_refcount_adj) {
2764                 for (i=0; i<num_msgs_to_be_merged; ++i) {
2765                         AdjRefCount(msgs_to_be_merged[i], +1);
2766                 }
2767         }
2768
2769         /* Free up memory... */
2770         if (msgs_to_be_merged != NULL) {
2771                 free(msgs_to_be_merged);
2772         }
2773
2774         /* Return success. */
2775         return (0);
2776 }
2777
2778
2779 /*
2780  * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2781  * a single message.
2782  */
2783 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2784                              int do_repl_check, struct CtdlMessage *supplied_msg)
2785 {
2786         return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2787 }
2788
2789
2790
2791
2792 /*
2793  * Message base operation to save a new message to the message store
2794  * (returns new message number)
2795  *
2796  * This is the back end for CtdlSubmitMsg() and should not be directly
2797  * called by server-side modules.
2798  *
2799  */
2800 long send_message(struct CtdlMessage *msg) {
2801         long newmsgid;
2802         long retval;
2803         char msgidbuf[256];
2804         struct ser_ret smr;
2805         int is_bigmsg = 0;
2806         char *holdM = NULL;
2807
2808         /* Get a new message number */
2809         newmsgid = get_new_message_number();
2810         snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2811                  (long unsigned int) time(NULL),
2812                  (long unsigned int) newmsgid,
2813                  config.c_fqdn
2814                 );
2815
2816         /* Generate an ID if we don't have one already */
2817         if (msg->cm_fields['I']==NULL) {
2818                 msg->cm_fields['I'] = strdup(msgidbuf);
2819         }
2820
2821         /* If the message is big, set its body aside for storage elsewhere */
2822         if (msg->cm_fields['M'] != NULL) {
2823                 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2824                         is_bigmsg = 1;
2825                         holdM = msg->cm_fields['M'];
2826                         msg->cm_fields['M'] = NULL;
2827                 }
2828         }
2829
2830         /* Serialize our data structure for storage in the database */  
2831         serialize_message(&smr, msg);
2832
2833         if (is_bigmsg) {
2834                 msg->cm_fields['M'] = holdM;
2835         }
2836
2837         if (smr.len == 0) {
2838                 cprintf("%d Unable to serialize message\n",
2839                         ERROR + INTERNAL_ERROR);
2840                 return (-1L);
2841         }
2842
2843         /* Write our little bundle of joy into the message base */
2844         if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2845                       smr.ser, smr.len) < 0) {
2846                 syslog(LOG_ERR, "Can't store message\n");
2847                 retval = 0L;
2848         } else {
2849                 if (is_bigmsg) {
2850                         cdb_store(CDB_BIGMSGS,
2851                                   &newmsgid,
2852                                   (int)sizeof(long),
2853                                   holdM,
2854                                   (strlen(holdM) + 1)
2855                                 );
2856                 }
2857                 retval = newmsgid;
2858         }
2859
2860         /* Free the memory we used for the serialized message */
2861         free(smr.ser);
2862
2863         /* Return the *local* message ID to the caller
2864          * (even if we're storing an incoming network message)
2865          */
2866         return(retval);
2867 }
2868
2869
2870
2871 /*
2872  * Serialize a struct CtdlMessage into the format used on disk and network.
2873  * 
2874  * This function loads up a "struct ser_ret" (defined in server.h) which
2875  * contains the length of the serialized message and a pointer to the
2876  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
2877  */
2878 void serialize_message(struct ser_ret *ret,             /* return values */
2879                        struct CtdlMessage *msg) /* unserialized msg */
2880 {
2881         size_t wlen, fieldlen;
2882         int i;
2883         static char *forder = FORDER;
2884
2885         /*
2886          * Check for valid message format
2887          */
2888         if (is_valid_message(msg) == 0) {
2889                 syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
2890                 ret->len = 0;
2891                 ret->ser = NULL;
2892                 return;
2893         }
2894
2895         ret->len = 3;
2896         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2897                                      ret->len = ret->len +
2898                                              strlen(msg->cm_fields[(int)forder[i]]) + 2;
2899
2900         ret->ser = malloc(ret->len);
2901         if (ret->ser == NULL) {
2902                 syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2903                        (long)ret->len, strerror(errno));
2904                 ret->len = 0;
2905                 ret->ser = NULL;
2906                 return;
2907         }
2908
2909         ret->ser[0] = 0xFF;
2910         ret->ser[1] = msg->cm_anon_type;
2911         ret->ser[2] = msg->cm_format_type;
2912         wlen = 3;
2913
2914         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2915                         fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2916                         ret->ser[wlen++] = (char)forder[i];
2917                         safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2918                         wlen = wlen + fieldlen + 1;
2919                 }
2920         if (ret->len != wlen) syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
2921                                      (long)ret->len, (long)wlen);
2922
2923         return;
2924 }
2925
2926
2927 /*
2928  * Serialize a struct CtdlMessage into the format used on disk and network.
2929  * 
2930  * This function loads up a "struct ser_ret" (defined in server.h) which
2931  * contains the length of the serialized message and a pointer to the
2932  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
2933  */
2934 void dump_message(struct CtdlMessage *msg,      /* unserialized msg */
2935                   long Siz)                     /* how many chars ? */
2936 {
2937         int i;
2938         static char *forder = FORDER;
2939         char *buf;
2940
2941         /*
2942          * Check for valid message format
2943          */
2944         if (is_valid_message(msg) == 0) {
2945                 syslog(LOG_ERR, "dump_message() aborting due to invalid message\n");
2946                 return;
2947         }
2948
2949         buf = (char*) malloc (Siz + 1);
2950
2951         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2952                         snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i], 
2953                                    msg->cm_fields[(int)forder[i]]);
2954                         if (client_write (buf, strlen(buf)) == -1)
2955                         {
2956                                 syslog(LOG_ERR, "dump_message(): aborting due to write failure.\n");
2957                                 return;
2958                         }
2959                 }
2960
2961         return;
2962 }
2963
2964
2965
2966 /*
2967  * Check to see if any messages already exist in the current room which
2968  * carry the same Exclusive ID as this one.  If any are found, delete them.
2969  */
2970 void ReplicationChecks(struct CtdlMessage *msg) {
2971         long old_msgnum = (-1L);
2972
2973         if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2974
2975         syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
2976                CC->room.QRname);
2977
2978         /* No exclusive id?  Don't do anything. */
2979         if (msg == NULL) return;
2980         if (msg->cm_fields['E'] == NULL) return;
2981         if (IsEmptyStr(msg->cm_fields['E'])) return;
2982         /*syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2983           msg->cm_fields['E'], CC->room.QRname);*/
2984
2985         old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2986         if (old_msgnum > 0L) {
2987                 syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2988                 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2989         }
2990 }
2991
2992
2993
2994 /*
2995  * Save a message to disk and submit it into the delivery system.
2996  */
2997 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
2998                    struct recptypes *recps,     /* recipients (if mail) */
2999                    const char *force,           /* force a particular room? */
3000                    int flags                    /* should the message be exported clean? */
3001         )
3002 {
3003         char submit_filename[128];
3004         char generated_timestamp[32];
3005         char hold_rm[ROOMNAMELEN];
3006         char actual_rm[ROOMNAMELEN];
3007         char force_room[ROOMNAMELEN];
3008         char content_type[SIZ];                 /* We have to learn this */
3009         char recipient[SIZ];
3010         long newmsgid;
3011         const char *mptr = NULL;
3012         struct ctdluser userbuf;
3013         int a, i;
3014         struct MetaData smi;
3015         FILE *network_fp = NULL;
3016         static int seqnum = 1;
3017         struct CtdlMessage *imsg = NULL;
3018         char *instr = NULL;
3019         size_t instr_alloc = 0;
3020         struct ser_ret smr;
3021         char *hold_R, *hold_D;
3022         char *collected_addresses = NULL;
3023         struct addresses_to_be_filed *aptr = NULL;
3024         StrBuf *saved_rfc822_version = NULL;
3025         int qualified_for_journaling = 0;
3026         CitContext *CCC = MyContext();
3027         char bounce_to[1024] = "";
3028         size_t tmp = 0;
3029         int rv = 0;
3030
3031         syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3032         if (is_valid_message(msg) == 0) return(-1);     /* self check */
3033
3034         /* If this message has no timestamp, we take the liberty of
3035          * giving it one, right now.
3036          */
3037         if (msg->cm_fields['T'] == NULL) {
3038                 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
3039                 msg->cm_fields['T'] = strdup(generated_timestamp);
3040         }
3041
3042         /* If this message has no path, we generate one.
3043          */
3044         if (msg->cm_fields['P'] == NULL) {
3045                 if (msg->cm_fields['A'] != NULL) {
3046                         msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
3047                         for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
3048                                 if (isspace(msg->cm_fields['P'][a])) {
3049                                         msg->cm_fields['P'][a] = ' ';
3050                                 }
3051                         }
3052                 }
3053                 else {
3054                         msg->cm_fields['P'] = strdup("unknown");
3055                 }
3056         }
3057
3058         if (force == NULL) {
3059                 strcpy(force_room, "");
3060         }
3061         else {
3062                 strcpy(force_room, force);
3063         }
3064
3065         /* Learn about what's inside, because it's what's inside that counts */
3066         if (msg->cm_fields['M'] == NULL) {
3067                 syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3068                 return(-2);
3069         }
3070
3071         switch (msg->cm_format_type) {
3072         case 0:
3073                 strcpy(content_type, "text/x-citadel-variformat");
3074                 break;
3075         case 1:
3076                 strcpy(content_type, "text/plain");
3077                 break;
3078         case 4:
3079                 strcpy(content_type, "text/plain");
3080                 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
3081                 if (mptr != NULL) {
3082                         char *aptr;
3083                         safestrncpy(content_type, &mptr[13], sizeof content_type);
3084                         striplt(content_type);
3085                         aptr = content_type;
3086                         while (!IsEmptyStr(aptr)) {
3087                                 if ((*aptr == ';')
3088                                     || (*aptr == ' ')
3089                                     || (*aptr == 13)
3090                                     || (*aptr == 10)) {
3091                                         *aptr = 0;
3092                                 }
3093                                 else aptr++;
3094                         }
3095                 }
3096         }
3097
3098         /* Goto the correct room */
3099         syslog(LOG_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
3100         strcpy(hold_rm, CCC->room.QRname);
3101         strcpy(actual_rm, CCC->room.QRname);
3102         if (recps != NULL) {
3103                 strcpy(actual_rm, SENTITEMS);
3104         }
3105
3106         /* If the user is a twit, move to the twit room for posting */
3107         if (TWITDETECT) {
3108                 if (CCC->user.axlevel == AxProbU) {
3109                         strcpy(hold_rm, actual_rm);
3110                         strcpy(actual_rm, config.c_twitroom);
3111                         syslog(LOG_DEBUG, "Diverting to twit room\n");
3112                 }
3113         }
3114
3115         /* ...or if this message is destined for Aide> then go there. */
3116         if (!IsEmptyStr(force_room)) {
3117                 strcpy(actual_rm, force_room);
3118         }
3119
3120         syslog(LOG_DEBUG, "Final selection: %s\n", actual_rm);
3121         if (strcasecmp(actual_rm, CCC->room.QRname)) {
3122                 /* CtdlGetRoom(&CCC->room, actual_rm); */
3123                 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3124         }
3125
3126         /*
3127          * If this message has no O (room) field, generate one.
3128          */
3129         if (msg->cm_fields['O'] == NULL) {
3130                 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3131         }
3132
3133         /* Perform "before save" hooks (aborting if any return nonzero) */
3134         syslog(LOG_DEBUG, "Performing before-save hooks\n");
3135         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3136
3137         /*
3138          * If this message has an Exclusive ID, and the room is replication
3139          * checking enabled, then do replication checks.
3140          */
3141         if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3142                 ReplicationChecks(msg);
3143         }
3144
3145         /* Save it to disk */
3146         syslog(LOG_DEBUG, "Saving to disk\n");
3147         newmsgid = send_message(msg);
3148         if (newmsgid <= 0L) return(-5);
3149
3150         /* Write a supplemental message info record.  This doesn't have to
3151          * be a critical section because nobody else knows about this message
3152          * yet.
3153          */
3154         syslog(LOG_DEBUG, "Creating MetaData record\n");
3155         memset(&smi, 0, sizeof(struct MetaData));
3156         smi.meta_msgnum = newmsgid;
3157         smi.meta_refcount = 0;
3158         safestrncpy(smi.meta_content_type, content_type,
3159                     sizeof smi.meta_content_type);
3160
3161         /*
3162          * Measure how big this message will be when rendered as RFC822.
3163          * We do this for two reasons:
3164          * 1. We need the RFC822 length for the new metadata record, so the
3165          *    POP and IMAP services don't have to calculate message lengths
3166          *    while the user is waiting (multiplied by potentially hundreds
3167          *    or thousands of messages).
3168          * 2. If journaling is enabled, we will need an RFC822 version of the
3169          *    message to attach to the journalized copy.
3170          */
3171         if (CCC->redirect_buffer != NULL) {
3172                 syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3173                 abort();
3174         }
3175         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3176         CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3177         smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3178         saved_rfc822_version = CCC->redirect_buffer;
3179         CCC->redirect_buffer = NULL;
3180
3181         PutMetaData(&smi);
3182
3183         /* Now figure out where to store the pointers */
3184         syslog(LOG_DEBUG, "Storing pointers\n");
3185
3186         /* If this is being done by the networker delivering a private
3187          * message, we want to BYPASS saving the sender's copy (because there
3188          * is no local sender; it would otherwise go to the Trashcan).
3189          */
3190         if ((!CCC->internal_pgm) || (recps == NULL)) {
3191                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3192                         syslog(LOG_ERR, "ERROR saving message pointer!\n");
3193                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3194                 }
3195         }
3196
3197         /* For internet mail, drop a copy in the outbound queue room */
3198         if ((recps != NULL) && (recps->num_internet > 0)) {
3199                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3200         }
3201
3202         /* If other rooms are specified, drop them there too. */
3203         if ((recps != NULL) && (recps->num_room > 0))
3204                 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3205                         extract_token(recipient, recps->recp_room, i,
3206                                       '|', sizeof recipient);
3207                         syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);
3208                         CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3209                 }
3210
3211         /* Bump this user's messages posted counter. */
3212         syslog(LOG_DEBUG, "Updating user\n");
3213         CtdlGetUserLock(&CCC->user, CCC->curr_user);
3214         CCC->user.posted = CCC->user.posted + 1;
3215         CtdlPutUserLock(&CCC->user);
3216
3217         /* Decide where bounces need to be delivered */
3218         if ((recps != NULL) && (recps->bounce_to != NULL)) {
3219                 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3220         }
3221         else if (CCC->logged_in) {
3222                 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3223         }
3224         else {
3225                 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3226         }
3227
3228         /* If this is private, local mail, make a copy in the
3229          * recipient's mailbox and bump the reference count.
3230          */
3231         if ((recps != NULL) && (recps->num_local > 0))
3232                 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3233                         extract_token(recipient, recps->recp_local, i,
3234                                       '|', sizeof recipient);
3235                         syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3236                                recipient);
3237                         if (CtdlGetUser(&userbuf, recipient) == 0) {
3238                                 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3239                                 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3240                                 CtdlBumpNewMailCounter(userbuf.usernum);
3241                                 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3242                                         /* Generate a instruction message for the Funambol notification
3243                                          * server, in the same style as the SMTP queue
3244                                          */
3245                                         instr_alloc = 1024;
3246                                         instr = malloc(instr_alloc);
3247                                         snprintf(instr, instr_alloc,
3248                                                  "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3249                                                  "bounceto|%s\n",
3250                                                  SPOOLMIME, newmsgid, (long)time(NULL),
3251                                                  bounce_to
3252                                                 );
3253                                 
3254                                         imsg = malloc(sizeof(struct CtdlMessage));
3255                                         memset(imsg, 0, sizeof(struct CtdlMessage));
3256                                         imsg->cm_magic = CTDLMESSAGE_MAGIC;
3257                                         imsg->cm_anon_type = MES_NORMAL;
3258                                         imsg->cm_format_type = FMT_RFC822;
3259                                         imsg->cm_fields['A'] = strdup("Citadel");
3260                                         imsg->cm_fields['J'] = strdup("do not journal");
3261                                         imsg->cm_fields['M'] = instr;   /* imsg owns this memory now */
3262                                         imsg->cm_fields['2'] = strdup(recipient);
3263                                         CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3264                                         CtdlFreeMessage(imsg);
3265                                 }
3266                         }
3267                         else {
3268                                 syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3269                                 CtdlSaveMsgPointerInRoom(config.c_aideroom,
3270                                                          newmsgid, 0, msg);
3271                         }
3272                 }
3273
3274         /* Perform "after save" hooks */
3275         syslog(LOG_DEBUG, "Performing after-save hooks\n");
3276         PerformMessageHooks(msg, EVT_AFTERSAVE);
3277
3278         /* For IGnet mail, we have to save a new copy into the spooler for
3279          * each recipient, with the R and D fields set to the recipient and
3280          * destination-node.  This has two ugly side effects: all other
3281          * recipients end up being unlisted in this recipient's copy of the
3282          * message, and it has to deliver multiple messages to the same
3283          * node.  We'll revisit this again in a year or so when everyone has
3284          * a network spool receiver that can handle the new style messages.
3285          */
3286         if ((recps != NULL) && (recps->num_ignet > 0))
3287                 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3288                         extract_token(recipient, recps->recp_ignet, i,
3289                                       '|', sizeof recipient);
3290
3291                         hold_R = msg->cm_fields['R'];
3292                         hold_D = msg->cm_fields['D'];
3293                         msg->cm_fields['R'] = malloc(SIZ);
3294                         msg->cm_fields['D'] = malloc(128);
3295                         extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3296                         extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3297                 
3298                         serialize_message(&smr, msg);
3299                         if (smr.len > 0) {
3300                                 snprintf(submit_filename, sizeof submit_filename,
3301                                          "%s/netmail.%04lx.%04x.%04x",
3302                                          ctdl_netin_dir,
3303                                          (long) getpid(), CCC->cs_pid, ++seqnum);
3304                                 network_fp = fopen(submit_filename, "wb+");
3305                                 if (network_fp != NULL) {
3306                                         rv = fwrite(smr.ser, smr.len, 1, network_fp);
3307                                         if (rv == -1) {
3308                                                 syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
3309                                                        strerror(errno));
3310                                         }
3311                                         fclose(network_fp);
3312                                 }
3313                                 free(smr.ser);
3314                         }
3315
3316                         free(msg->cm_fields['R']);
3317                         free(msg->cm_fields['D']);
3318                         msg->cm_fields['R'] = hold_R;
3319                         msg->cm_fields['D'] = hold_D;
3320                 }
3321
3322         /* Go back to the room we started from */
3323         syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3324         if (strcasecmp(hold_rm, CCC->room.QRname))
3325                 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3326
3327         /* For internet mail, generate delivery instructions.
3328          * Yes, this is recursive.  Deal with it.  Infinite recursion does
3329          * not happen because the delivery instructions message does not
3330          * contain a recipient.
3331          */
3332         if ((recps != NULL) && (recps->num_internet > 0)) {
3333                 syslog(LOG_DEBUG, "Generating delivery instructions\n");
3334                 instr_alloc = 1024;
3335                 instr = malloc(instr_alloc);
3336                 snprintf(instr, instr_alloc,
3337                          "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3338                          "bounceto|%s\n",
3339                          SPOOLMIME, newmsgid, (long)time(NULL),
3340                          bounce_to
3341                         );
3342
3343                 if (recps->envelope_from != NULL) {
3344                         tmp = strlen(instr);
3345                         snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3346                 }
3347
3348                 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3349                         tmp = strlen(instr);
3350                         extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3351                         if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3352                                 instr_alloc = instr_alloc * 2;
3353                                 instr = realloc(instr, instr_alloc);
3354                         }
3355                         snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3356                 }
3357
3358                 imsg = malloc(sizeof(struct CtdlMessage));
3359                 memset(imsg, 0, sizeof(struct CtdlMessage));
3360                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3361                 imsg->cm_anon_type = MES_NORMAL;
3362                 imsg->cm_format_type = FMT_RFC822;
3363                 imsg->cm_fields['A'] = strdup("Citadel");
3364                 imsg->cm_fields['J'] = strdup("do not journal");
3365                 imsg->cm_fields['M'] = instr;   /* imsg owns this memory now */
3366                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3367                 CtdlFreeMessage(imsg);
3368         }
3369
3370         /*
3371          * Any addresses to harvest for someone's address book?
3372          */
3373         if ( (CCC->logged_in) && (recps != NULL) ) {
3374                 collected_addresses = harvest_collected_addresses(msg);
3375         }
3376
3377         if (collected_addresses != NULL) {
3378                 aptr = (struct addresses_to_be_filed *)
3379                         malloc(sizeof(struct addresses_to_be_filed));
3380                 CtdlMailboxName(actual_rm, sizeof actual_rm,
3381                                 &CCC->user, USERCONTACTSROOM);
3382                 aptr->roomname = strdup(actual_rm);
3383                 aptr->collected_addresses = collected_addresses;
3384                 begin_critical_section(S_ATBF);
3385                 aptr->next = atbf;
3386                 atbf = aptr;
3387                 end_critical_section(S_ATBF);
3388         }
3389
3390         /*
3391          * Determine whether this message qualifies for journaling.
3392          */
3393         if (msg->cm_fields['J'] != NULL) {
3394                 qualified_for_journaling = 0;
3395         }
3396         else {
3397                 if (recps == NULL) {
3398                         qualified_for_journaling = config.c_journal_pubmsgs;
3399                 }
3400                 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3401                         qualified_for_journaling = config.c_journal_email;
3402                 }
3403                 else {
3404                         qualified_for_journaling = config.c_journal_pubmsgs;
3405                 }
3406         }
3407
3408         /*
3409          * Do we have to perform journaling?  If so, hand off the saved
3410          * RFC822 version will be handed off to the journaler for background
3411          * submit.  Otherwise, we have to free the memory ourselves.
3412          */
3413         if (saved_rfc822_version != NULL) {
3414                 if (qualified_for_journaling) {
3415                         JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3416                 }
3417                 else {
3418                         FreeStrBuf(&saved_rfc822_version);
3419                 }
3420         }
3421
3422         /* Done. */
3423         return(newmsgid);
3424 }
3425
3426
3427
3428 void aide_message (char *text, char *subject)
3429 {
3430         quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3431 }
3432
3433
3434 /*
3435  * Convenience function for generating small administrative messages.
3436  */
3437 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text, 
3438                      int format_type, const char *subject)
3439 {
3440         struct CtdlMessage *msg;
3441         struct recptypes *recp = NULL;
3442
3443         msg = malloc(sizeof(struct CtdlMessage));
3444         memset(msg, 0, sizeof(struct CtdlMessage));
3445         msg->cm_magic = CTDLMESSAGE_MAGIC;
3446         msg->cm_anon_type = MES_NORMAL;
3447         msg->cm_format_type = format_type;
3448
3449         if (from != NULL) {
3450                 msg->cm_fields['A'] = strdup(from);
3451         }
3452         else if (fromaddr != NULL) {
3453                 msg->cm_fields['A'] = strdup(fromaddr);
3454                 if (strchr(msg->cm_fields['A'], '@')) {
3455                         *strchr(msg->cm_fields['A'], '@') = 0;
3456                 }
3457         }
3458         else {
3459                 msg->cm_fields['A'] = strdup("Citadel");
3460         }
3461
3462         if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3463         if (room != NULL) msg->cm_fields['O'] = strdup(room);
3464         msg->cm_fields['N'] = strdup(NODENAME);
3465         if (to != NULL) {
3466                 msg->cm_fields['R'] = strdup(to);
3467                 recp = validate_recipients(to, NULL, 0);
3468         }
3469         if (subject != NULL) {
3470                 msg->cm_fields['U'] = strdup(subject);
3471         }
3472         msg->cm_fields['M'] = strdup(text);
3473
3474         CtdlSubmitMsg(msg, recp, room, 0);
3475         CtdlFreeMessage(msg);
3476         if (recp != NULL) free_recipients(recp);
3477 }
3478
3479
3480
3481 /*
3482  * Back end function used by CtdlMakeMessage() and similar functions
3483  */
3484 StrBuf *CtdlReadMessageBodyBuf(char *terminator,        /* token signalling EOT */
3485                                long tlen,
3486                                size_t maxlen,           /* maximum message length */
3487                                char *exist,             /* if non-null, append to it;
3488                                                            exist is ALWAYS freed  */
3489                                int crlf,                /* CRLF newlines instead of LF */
3490                                int *sock                /* socket handle or 0 for this session's client socket */
3491         ) 
3492 {
3493         StrBuf *Message;
3494         StrBuf *LineBuf;
3495         int flushing = 0;
3496         int finished = 0;
3497         int dotdot = 0;
3498
3499         LineBuf = NewStrBufPlain(NULL, SIZ);
3500         if (exist == NULL) {
3501                 Message = NewStrBufPlain(NULL, 4 * SIZ);
3502         }
3503         else {
3504                 Message = NewStrBufPlain(exist, -1);
3505                 free(exist);
3506         }
3507
3508         /* Do we need to change leading ".." to "." for SMTP escaping? */
3509         if ((tlen == 1) && (*terminator == '.')) {
3510                 dotdot = 1;
3511         }
3512
3513         /* read in the lines of message text one by one */
3514         do {
3515                 if (sock != NULL) {
3516                         if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3517                             (*sock == -1))
3518                                 finished = 1;
3519                 }
3520                 else {
3521                         if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3522                 }
3523                 if ((StrLength(LineBuf) == tlen) && 
3524                     (!strcmp(ChrPtr(LineBuf), terminator)))
3525                         finished = 1;
3526
3527                 if ( (!flushing) && (!finished) ) {
3528                         if (crlf) {
3529                                 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3530                         }
3531                         else {
3532                                 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3533                         }
3534                         
3535                         /* Unescape SMTP-style input of two dots at the beginning of the line */
3536                         if ((dotdot) &&
3537                             (StrLength(LineBuf) == 2) && 
3538                             (!strcmp(ChrPtr(LineBuf), "..")))
3539                         {
3540                                 StrBufCutLeft(LineBuf, 1);
3541                         }
3542                         
3543                         StrBufAppendBuf(Message, LineBuf, 0);
3544                 }
3545
3546                 /* if we've hit the max msg length, flush the rest */
3547                 if (StrLength(Message) >= maxlen) flushing = 1;
3548
3549         } while (!finished);
3550         FreeStrBuf(&LineBuf);
3551         return Message;
3552 }
3553
3554 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3555 {
3556         if (*Msg == NULL)
3557                 return;
3558         FreeStrBuf(&(*Msg)->MsgBuf);
3559
3560         free(*Msg);
3561         *Msg = NULL;
3562 }
3563
3564 ReadAsyncMsg *NewAsyncMsg(const char *terminator,       /* token signalling EOT */
3565                           long tlen,
3566                           size_t maxlen,                /* maximum message length */
3567                           size_t expectlen,             /* if we expect a message, how long should it be? */
3568                           char *exist,                  /* if non-null, append to it;
3569                                                            exist is ALWAYS freed  */
3570                           long eLen,                    /* length of exist */
3571                           int crlf                      /* CRLF newlines instead of LF */
3572         )
3573 {
3574         ReadAsyncMsg *NewMsg;
3575
3576         NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3577         memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3578
3579         if (exist == NULL) {
3580                 long len;
3581
3582                 if (expectlen == 0) {
3583                         len = 4 * SIZ;
3584                 }
3585                 else {
3586                         len = expectlen + 10;
3587                 }
3588                 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3589         }
3590         else {
3591                 NewMsg->MsgBuf = NewStrBufPlain(exist, eLen);
3592                 free(exist);
3593         }
3594         /* Do we need to change leading ".." to "." for SMTP escaping? */
3595         if ((tlen == 1) && (*terminator == '.')) {
3596                 NewMsg->dodot = 1;
3597         }
3598
3599         NewMsg->terminator = terminator;
3600         NewMsg->tlen = tlen;
3601
3602         NewMsg->maxlen = maxlen;
3603
3604         NewMsg->crlf = crlf;
3605
3606         return NewMsg;
3607 }
3608
3609 /*
3610  * Back end function used by CtdlMakeMessage() and similar functions
3611  */
3612 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3613 {
3614         ReadAsyncMsg *ReadMsg;
3615         int MsgFinished = 0;
3616         eReadState Finished = eMustReadMore;
3617
3618 #ifdef BIGBAD_IODBG
3619         char fn [SIZ];
3620         FILE *fd;
3621         const char *pch = ChrPtr(IO->SendBuf.Buf);
3622         const char *pchh = IO->SendBuf.ReadWritePointer;
3623         long nbytes;
3624         
3625         if (pchh == NULL)
3626                 pchh = pch;
3627         
3628         nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3629         snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3630                  ((CitContext*)(IO->CitContext))->ServiceName,
3631                  IO->SendBuf.fd);
3632         
3633         fd = fopen(fn, "a+");
3634 #endif
3635
3636         ReadMsg = IO->ReadMsg;
3637
3638         /* read in the lines of message text one by one */
3639         do {
3640                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3641                 
3642                 switch (Finished) {
3643                 case eMustReadMore: /// read new from socket... 
3644 #ifdef BIGBAD_IODBG
3645                         if (IO->RecvBuf.ReadWritePointer != NULL) {
3646                                 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3647                                 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3648                                 
3649                                 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3650                         
3651                                 fprintf(fd, "]\n");
3652                         } else {
3653                                 fprintf(fd, "BufferEmpty! \n");
3654                         }
3655                         fclose(fd);
3656 #endif
3657                         return Finished;
3658                     break;
3659                 case eBufferNotEmpty: /* shouldn't happen... */
3660                 case eReadSuccess: /// done for now...
3661                     break;
3662                 case eReadFail: /// WHUT?
3663                     ///todo: shut down! 
3664                         break;
3665                 }
3666             
3667
3668                 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) && 
3669                     (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3670                         MsgFinished = 1;
3671 #ifdef BIGBAD_IODBG
3672                         fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3673 #endif
3674                 }
3675                 else if (!ReadMsg->flushing) {
3676
3677 #ifdef BIGBAD_IODBG
3678                         fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3679 #endif
3680
3681                         /* Unescape SMTP-style input of two dots at the beginning of the line */
3682                         if ((ReadMsg->dodot) &&
3683                             (StrLength(IO->IOBuf) == 2) &&  /* TODO: do we just unescape lines with two dots or any line? */
3684                             (!strcmp(ChrPtr(IO->IOBuf), "..")))
3685                         {
3686 #ifdef BIGBAD_IODBG
3687                                 fprintf(fd, "UnEscaped!\n");
3688 #endif
3689                                 StrBufCutLeft(IO->IOBuf, 1);
3690                         }
3691
3692                         if (ReadMsg->crlf) {
3693                                 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3694                         }
3695                         else {
3696                                 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3697                         }
3698
3699                         StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3700                 }
3701
3702                 /* if we've hit the max msg length, flush the rest */
3703                 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3704
3705         } while (!MsgFinished);
3706
3707 #ifdef BIGBAD_IODBG
3708         fprintf(fd, "Done with reading; %s.\n, ",
3709                 (MsgFinished)?"Message Finished": "FAILED");
3710         fclose(fd);
3711 #endif
3712         if (MsgFinished)
3713                 return eReadSuccess;
3714         else 
3715                 return eAbort;
3716 }
3717
3718
3719 /*
3720  * Back end function used by CtdlMakeMessage() and similar functions
3721  */
3722 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
3723                           long tlen,
3724                           size_t maxlen,                /* maximum message length */
3725                           char *exist,          /* if non-null, append to it;
3726                                                    exist is ALWAYS freed  */
3727                           int crlf,             /* CRLF newlines instead of LF */
3728                           int *sock             /* socket handle or 0 for this session's client socket */
3729         ) 
3730 {
3731         StrBuf *Message;
3732
3733         Message = CtdlReadMessageBodyBuf(terminator,
3734                                          tlen,
3735                                          maxlen,
3736                                          exist,
3737                                          crlf,
3738                                          sock);
3739         if (Message == NULL)
3740                 return NULL;
3741         else
3742                 return SmashStrBuf(&Message);
3743 }
3744
3745
3746 /*
3747  * Build a binary message to be saved on disk.
3748  * (NOTE: if you supply 'preformatted_text', the buffer you give it
3749  * will become part of the message.  This means you are no longer
3750  * responsible for managing that memory -- it will be freed along with
3751  * the rest of the fields when CtdlFreeMessage() is called.)
3752  */
3753
3754 struct CtdlMessage *CtdlMakeMessage(
3755         struct ctdluser *author,        /* author's user structure */
3756         char *recipient,                /* NULL if it's not mail */
3757         char *recp_cc,                  /* NULL if it's not mail */
3758         char *room,                     /* room where it's going */
3759         int type,                       /* see MES_ types in header file */
3760         int format_type,                /* variformat, plain text, MIME... */
3761         char *fake_name,                /* who we're masquerading as */
3762         char *my_email,                 /* which of my email addresses to use (empty is ok) */
3763         char *subject,                  /* Subject (optional) */
3764         char *supplied_euid,            /* ...or NULL if this is irrelevant */
3765         char *preformatted_text,        /* ...or NULL to read text from client */
3766         char *references                /* Thread references */
3767         ) {
3768         char dest_node[256];
3769         char buf[1024];
3770         struct CtdlMessage *msg;
3771         StrBuf *FakeAuthor;
3772         StrBuf *FakeEncAuthor = NULL;
3773
3774         msg = malloc(sizeof(struct CtdlMessage));
3775         memset(msg, 0, sizeof(struct CtdlMessage));
3776         msg->cm_magic = CTDLMESSAGE_MAGIC;
3777         msg->cm_anon_type = type;
3778         msg->cm_format_type = format_type;
3779
3780         /* Don't confuse the poor folks if it's not routed mail. */
3781         strcpy(dest_node, "");
3782
3783         if (recipient != NULL) striplt(recipient);
3784         if (recp_cc != NULL) striplt(recp_cc);
3785
3786         /* Path or Return-Path */
3787         if (my_email == NULL) my_email = "";
3788
3789         if (!IsEmptyStr(my_email)) {
3790                 msg->cm_fields['P'] = strdup(my_email);
3791         }
3792         else {
3793                 snprintf(buf, sizeof buf, "%s", author->fullname);
3794                 msg->cm_fields['P'] = strdup(buf);
3795         }
3796         convert_spaces_to_underscores(msg->cm_fields['P']);
3797
3798         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
3799         msg->cm_fields['T'] = strdup(buf);
3800
3801         if ((fake_name != NULL) && (fake_name[0])) {            /* author */
3802                 FakeAuthor = NewStrBufPlain (fake_name, -1);
3803         }
3804         else {
3805                 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3806         }
3807         StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3808         msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3809         FreeStrBuf(&FakeAuthor);
3810
3811         if (CC->room.QRflags & QR_MAILBOX) {            /* room */
3812                 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3813         }
3814         else {
3815                 msg->cm_fields['O'] = strdup(CC->room.QRname);
3816         }
3817
3818         msg->cm_fields['N'] = strdup(NODENAME);         /* nodename */
3819         msg->cm_fields['H'] = strdup(HUMANNODE);                /* hnodename */
3820
3821         if ((recipient != NULL) && (recipient[0] != 0)) {
3822                 msg->cm_fields['R'] = strdup(recipient);
3823         }
3824         if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3825                 msg->cm_fields['Y'] = strdup(recp_cc);
3826         }
3827         if (dest_node[0] != 0) {
3828                 msg->cm_fields['D'] = strdup(dest_node);
3829         }
3830
3831         if (!IsEmptyStr(my_email)) {
3832                 msg->cm_fields['F'] = strdup(my_email);
3833         }
3834         else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3835                 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3836         }
3837
3838         if (subject != NULL) {
3839                 long length;
3840                 striplt(subject);
3841                 length = strlen(subject);
3842                 if (length > 0) {
3843                         long i;
3844                         long IsAscii;
3845                         IsAscii = -1;
3846                         i = 0;
3847                         while ((subject[i] != '\0') &&
3848                                (IsAscii = isascii(subject[i]) != 0 ))
3849                                 i++;
3850                         if (IsAscii != 0)
3851                                 msg->cm_fields['U'] = strdup(subject);
3852                         else /* ok, we've got utf8 in the string. */
3853                         {
3854                                 msg->cm_fields['U'] = rfc2047encode(subject, length);
3855                         }
3856
3857                 }
3858         }
3859
3860         if (supplied_euid != NULL) {
3861                 msg->cm_fields['E'] = strdup(supplied_euid);
3862         }
3863
3864         if ((references != NULL) && (!IsEmptyStr(references))) {
3865                 if (msg->cm_fields['W'] != NULL)
3866                         free(msg->cm_fields['W']);
3867                 msg->cm_fields['W'] = strdup(references);
3868         }
3869
3870         if (preformatted_text != NULL) {
3871                 msg->cm_fields['M'] = preformatted_text;
3872         }
3873         else {
3874                 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3875         }
3876
3877         return(msg);
3878 }
3879
3880 extern int netconfig_check_roomaccess(
3881         char *errmsgbuf, 
3882         size_t n,
3883         const char* RemoteIdentifier); /* TODO: find a smarter way */
3884
3885 /*
3886  * Check to see whether we have permission to post a message in the current
3887  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3888  * returns 0 on success.
3889  */
3890 int CtdlDoIHavePermissionToPostInThisRoom(
3891         char *errmsgbuf, 
3892         size_t n, 
3893         const char* RemoteIdentifier,
3894         int PostPublic,
3895         int is_reply
3896         ) {
3897         int ra;
3898
3899         if (!(CC->logged_in) && 
3900             (PostPublic == POST_LOGGED_IN)) {
3901                 snprintf(errmsgbuf, n, "Not logged in.");
3902                 return (ERROR + NOT_LOGGED_IN);
3903         }
3904         else if (PostPublic == CHECK_EXISTANCE) {
3905                 return (0); // We're Evaling whether a recipient exists
3906         }
3907         else if (!(CC->logged_in)) {
3908                 
3909                 if ((CC->room.QRflags & QR_READONLY)) {
3910                         snprintf(errmsgbuf, n, "Not logged in.");
3911                         return (ERROR + NOT_LOGGED_IN);
3912                 }
3913                 if (CC->room.QRflags2 & QR2_MODERATED) {
3914                         snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3915                         return (ERROR + NOT_LOGGED_IN);
3916                 }
3917                 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3918
3919                         return netconfig_check_roomaccess(errmsgbuf, n, RemoteIdentifier);
3920                 }
3921                 return (0);
3922
3923         }
3924
3925         if ((CC->user.axlevel < AxProbU)
3926             && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3927                 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
3928                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3929         }
3930
3931         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3932
3933         if (ra & UA_POSTALLOWED) {
3934                 strcpy(errmsgbuf, "OK to post or reply here");
3935                 return(0);
3936         }
3937
3938         if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
3939                 /*
3940                  * To be thorough, we ought to check to see if the message they are
3941                  * replying to is actually a valid one in this room, but unless this
3942                  * actually becomes a problem we'll go with high performance instead.
3943                  */
3944                 strcpy(errmsgbuf, "OK to reply here");
3945                 return(0);
3946         }
3947
3948         if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
3949                 /* Clarify what happened with a better error message */
3950                 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
3951                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3952         }
3953
3954         snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3955         return (ERROR + HIGHER_ACCESS_REQUIRED);
3956
3957 }
3958
3959
3960 /*
3961  * Check to see if the specified user has Internet mail permission
3962  * (returns nonzero if permission is granted)
3963  */
3964 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3965
3966         /* Do not allow twits to send Internet mail */
3967         if (who->axlevel <= AxProbU) return(0);
3968
3969         /* Globally enabled? */
3970         if (config.c_restrict == 0) return(1);
3971
3972         /* User flagged ok? */
3973         if (who->flags & US_INTERNET) return(2);
3974
3975         /* Aide level access? */
3976         if (who->axlevel >= AxAideU) return(3);
3977
3978         /* No mail for you! */
3979         return(0);
3980 }
3981
3982
3983 /*
3984  * Validate recipients, count delivery types and errors, and handle aliasing
3985  * FIXME check for dupes!!!!!
3986  *
3987  * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
3988  * were specified, or the number of addresses found invalid.
3989  *
3990  * Caller needs to free the result using free_recipients()
3991  */
3992 struct recptypes *validate_recipients(const char *supplied_recipients, 
3993                                       const char *RemoteIdentifier, 
3994                                       int Flags) {
3995         struct recptypes *ret;
3996         char *recipients = NULL;
3997         char this_recp[256];
3998         char this_recp_cooked[256];
3999         char append[SIZ];
4000         int num_recps = 0;
4001         int i, j;
4002         int mailtype;
4003         int invalid;
4004         struct ctdluser tempUS;
4005         struct ctdlroom tempQR;
4006         struct ctdlroom tempQR2;
4007         int err = 0;
4008         char errmsg[SIZ];
4009         int in_quotes = 0;
4010
4011         /* Initialize */
4012         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4013         if (ret == NULL) return(NULL);
4014
4015         /* Set all strings to null and numeric values to zero */
4016         memset(ret, 0, sizeof(struct recptypes));
4017
4018         if (supplied_recipients == NULL) {
4019                 recipients = strdup("");
4020         }
4021         else {
4022                 recipients = strdup(supplied_recipients);
4023         }
4024
4025         /* Allocate some memory.  Yes, this allocates 500% more memory than we will
4026          * actually need, but it's healthier for the heap than doing lots of tiny
4027          * realloc() calls instead.
4028          */
4029
4030         ret->errormsg = malloc(strlen(recipients) + 1024);
4031         ret->recp_local = malloc(strlen(recipients) + 1024);
4032         ret->recp_internet = malloc(strlen(recipients) + 1024);
4033         ret->recp_ignet = malloc(strlen(recipients) + 1024);
4034         ret->recp_room = malloc(strlen(recipients) + 1024);
4035         ret->display_recp = malloc(strlen(recipients) + 1024);
4036
4037         ret->errormsg[0] = 0;
4038         ret->recp_local[0] = 0;
4039         ret->recp_internet[0] = 0;
4040         ret->recp_ignet[0] = 0;
4041         ret->recp_room[0] = 0;
4042         ret->display_recp[0] = 0;
4043
4044         ret->recptypes_magic = RECPTYPES_MAGIC;
4045
4046         /* Change all valid separator characters to commas */
4047         for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4048                 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4049                         recipients[i] = ',';
4050                 }
4051         }
4052
4053         /* Now start extracting recipients... */
4054
4055         while (!IsEmptyStr(recipients)) {
4056
4057                 for (i=0; i<=strlen(recipients); ++i) {
4058                         if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4059                         if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4060                                 safestrncpy(this_recp, recipients, i+1);
4061                                 this_recp[i] = 0;
4062                                 if (recipients[i] == ',') {
4063                                         strcpy(recipients, &recipients[i+1]);
4064                                 }
4065                                 else {
4066                                         strcpy(recipients, "");
4067                                 }
4068                                 break;
4069                         }
4070                 }
4071
4072                 striplt(this_recp);
4073                 if (IsEmptyStr(this_recp))
4074                         break;
4075                 syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4076                 ++num_recps;
4077                 mailtype = alias(this_recp);
4078                 mailtype = alias(this_recp);
4079                 mailtype = alias(this_recp);
4080                 j = 0;
4081                 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
4082                         if (this_recp[j]=='_') {
4083                                 this_recp_cooked[j] = ' ';
4084                         }
4085                         else {
4086                                 this_recp_cooked[j] = this_recp[j];
4087                         }
4088                 }
4089                 this_recp_cooked[j] = '\0';
4090                 invalid = 0;
4091                 errmsg[0] = 0;
4092                 switch(mailtype) {
4093                 case MES_LOCAL:
4094                         if (!strcasecmp(this_recp, "sysop")) {
4095                                 ++ret->num_room;
4096                                 strcpy(this_recp, config.c_aideroom);
4097                                 if (!IsEmptyStr(ret->recp_room)) {
4098                                         strcat(ret->recp_room, "|");
4099                                 }
4100                                 strcat(ret->recp_room, this_recp);
4101                         }
4102                         else if ( (!strncasecmp(this_recp, "room_", 5))
4103                                   && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4104
4105                                 /* Save room so we can restore it later */
4106                                 tempQR2 = CC->room;
4107                                 CC->room = tempQR;
4108                                         
4109                                 /* Check permissions to send mail to this room */
4110                                 err = CtdlDoIHavePermissionToPostInThisRoom(
4111                                         errmsg, 
4112                                         sizeof errmsg, 
4113                                         RemoteIdentifier,
4114                                         Flags,
4115                                         0                       /* 0 = not a reply */
4116                                         );
4117                                 if (err)
4118                                 {
4119                                         ++ret->num_error;
4120                                         invalid = 1;
4121                                 } 
4122                                 else {
4123                                         ++ret->num_room;
4124                                         if (!IsEmptyStr(ret->recp_room)) {
4125                                                 strcat(ret->recp_room, "|");
4126                                         }
4127                                         strcat(ret->recp_room, &this_recp_cooked[5]);
4128                                 }
4129                                         
4130                                 /* Restore room in case something needs it */
4131                                 CC->room = tempQR2;
4132
4133                         }
4134                         else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4135                                 ++ret->num_local;
4136                                 strcpy(this_recp, tempUS.fullname);
4137                                 if (!IsEmptyStr(ret->recp_local)) {
4138                                         strcat(ret->recp_local, "|");
4139                                 }
4140                                 strcat(ret->recp_local, this_recp);
4141                         }
4142                         else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4143                                 ++ret->num_local;
4144                                 strcpy(this_recp, tempUS.fullname);
4145                                 if (!IsEmptyStr(ret->recp_local)) {
4146                                         strcat(ret->recp_local, "|");
4147                                 }
4148                                 strcat(ret->recp_local, this_recp);
4149                         }
4150                         else {
4151                                 ++ret->num_error;
4152                                 invalid = 1;
4153                         }
4154                         break;
4155                 case MES_INTERNET:
4156                         /* Yes, you're reading this correctly: if the target
4157                          * domain points back to the local system or an attached
4158                          * Citadel directory, the address is invalid.  That's
4159                          * because if the address were valid, we would have
4160                          * already translated it to a local address by now.
4161                          */
4162                         if (IsDirectory(this_recp, 0)) {
4163                                 ++ret->num_error;
4164                                 invalid = 1;
4165                         }
4166                         else {
4167                                 ++ret->num_internet;
4168                                 if (!IsEmptyStr(ret->recp_internet)) {
4169                                         strcat(ret->recp_internet, "|");
4170                                 }
4171                                 strcat(ret->recp_internet, this_recp);
4172                         }
4173                         break;
4174                 case MES_IGNET:
4175                         ++ret->num_ignet;
4176                         if (!IsEmptyStr(ret->recp_ignet)) {
4177                                 strcat(ret->recp_ignet, "|");
4178                         }
4179                         strcat(ret->recp_ignet, this_recp);
4180                         break;
4181                 case MES_ERROR:
4182                         ++ret->num_error;
4183                         invalid = 1;
4184                         break;
4185                 }
4186                 if (invalid) {
4187                         if (IsEmptyStr(errmsg)) {
4188                                 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4189                         }
4190                         else {
4191                                 snprintf(append, sizeof append, "%s", errmsg);
4192                         }
4193                         if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4194                                 if (!IsEmptyStr(ret->errormsg)) {
4195                                         strcat(ret->errormsg, "; ");
4196                                 }
4197                                 strcat(ret->errormsg, append);
4198                         }
4199                 }
4200                 else {
4201                         if (IsEmptyStr(ret->display_recp)) {
4202                                 strcpy(append, this_recp);
4203                         }
4204                         else {
4205                                 snprintf(append, sizeof append, ", %s", this_recp);
4206                         }
4207                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4208                                 strcat(ret->display_recp, append);
4209                         }
4210                 }
4211         }
4212
4213         if ((ret->num_local + ret->num_internet + ret->num_ignet +
4214              ret->num_room + ret->num_error) == 0) {
4215                 ret->num_error = (-1);
4216                 strcpy(ret->errormsg, "No recipients specified.");
4217         }
4218
4219         syslog(LOG_DEBUG, "validate_recipients()\n");
4220         syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4221         syslog(LOG_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
4222         syslog(LOG_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4223         syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4224         syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4225
4226         free(recipients);
4227         return(ret);
4228 }
4229
4230
4231 /*
4232  * Destructor for struct recptypes
4233  */
4234 void free_recipients(struct recptypes *valid) {
4235
4236         if (valid == NULL) {
4237                 return;
4238         }
4239
4240         if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4241                 syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4242                 abort();
4243         }
4244
4245         if (valid->errormsg != NULL)            free(valid->errormsg);
4246         if (valid->recp_local != NULL)          free(valid->recp_local);
4247         if (valid->recp_internet != NULL)       free(valid->recp_internet);
4248         if (valid->recp_ignet != NULL)          free(valid->recp_ignet);
4249         if (valid->recp_room != NULL)           free(valid->recp_room);
4250         if (valid->display_recp != NULL)        free(valid->display_recp);
4251         if (valid->bounce_to != NULL)           free(valid->bounce_to);
4252         if (valid->envelope_from != NULL)       free(valid->envelope_from);
4253         free(valid);
4254 }
4255
4256
4257
4258 /*
4259  * message entry  -  mode 0 (normal)
4260  */
4261 void cmd_ent0(char *entargs)
4262 {
4263         struct CitContext *CCC = CC;
4264         int post = 0;
4265         char recp[SIZ];
4266         char cc[SIZ];
4267         char bcc[SIZ];
4268         char supplied_euid[128];
4269         int anon_flag = 0;
4270         int format_type = 0;
4271         char newusername[256];
4272         char newuseremail[256];
4273         struct CtdlMessage *msg;
4274         int anonymous = 0;
4275         char errmsg[SIZ];
4276         int err = 0;
4277         struct recptypes *valid = NULL;
4278         struct recptypes *valid_to = NULL;
4279         struct recptypes *valid_cc = NULL;
4280         struct recptypes *valid_bcc = NULL;
4281         char subject[SIZ];
4282         int subject_required = 0;
4283         int do_confirm = 0;
4284         int verbose_reply = 0;
4285         long msgnum;
4286         int i, j;
4287         char buf[256];
4288         int newuseremail_ok = 0;
4289         char references[SIZ];
4290         char *ptr;
4291
4292         unbuffer_output();
4293
4294         post = extract_int(entargs, 0);
4295         extract_token(recp, entargs, 1, '|', sizeof recp);
4296         anon_flag = extract_int(entargs, 2);
4297         format_type = extract_int(entargs, 3);
4298         extract_token(subject, entargs, 4, '|', sizeof subject);
4299         extract_token(newusername, entargs, 5, '|', sizeof newusername);
4300         do_confirm = extract_int(entargs, 6);
4301         extract_token(cc, entargs, 7, '|', sizeof cc);
4302         extract_token(bcc, entargs, 8, '|', sizeof bcc);
4303         verbose_reply = extract_int(entargs, 9);
4304         switch(CC->room.QRdefaultview) {
4305         case VIEW_NOTES:
4306         case VIEW_WIKI:
4307                 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4308                 break;
4309         default:
4310                 supplied_euid[0] = 0;
4311                 break;
4312         }
4313         extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4314         extract_token(references, entargs, 11, '|', sizeof references);
4315         for (ptr=references; *ptr != 0; ++ptr) {
4316                 if (*ptr == '!') *ptr = '|';
4317         }
4318
4319         /* first check to make sure the request is valid. */
4320
4321         err = CtdlDoIHavePermissionToPostInThisRoom(
4322                 errmsg,
4323                 sizeof errmsg,
4324                 NULL,
4325                 POST_LOGGED_IN,
4326                 (!IsEmptyStr(references))               /* is this a reply?  or a top-level post? */
4327                 );
4328         if (err)
4329         {
4330                 cprintf("%d %s\n", err, errmsg);
4331                 return;
4332         }
4333
4334         /* Check some other permission type things. */
4335
4336         if (IsEmptyStr(newusername)) {
4337                 strcpy(newusername, CCC->user.fullname);
4338         }
4339         if (  (CCC->user.axlevel < AxAideU)
4340               && (strcasecmp(newusername, CCC->user.fullname))
4341               && (strcasecmp(newusername, CCC->cs_inet_fn))
4342                 ) {     
4343                 cprintf("%d You don't have permission to author messages as '%s'.\n",
4344                         ERROR + HIGHER_ACCESS_REQUIRED,
4345                         newusername
4346                         );
4347                 return;
4348         }
4349
4350
4351         if (IsEmptyStr(newuseremail)) {
4352                 newuseremail_ok = 1;
4353         }
4354
4355         if (!IsEmptyStr(newuseremail)) {
4356                 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
4357                         newuseremail_ok = 1;
4358                 }
4359                 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
4360                         j = num_tokens(CCC->cs_inet_other_emails, '|');
4361                         for (i=0; i<j; ++i) {
4362                                 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
4363                                 if (!strcasecmp(newuseremail, buf)) {
4364                                         newuseremail_ok = 1;
4365                                 }
4366                         }
4367                 }
4368         }
4369
4370         if (!newuseremail_ok) {
4371                 cprintf("%d You don't have permission to author messages as '%s'.\n",
4372                         ERROR + HIGHER_ACCESS_REQUIRED,
4373                         newuseremail
4374                         );
4375                 return;
4376         }
4377
4378         CCC->cs_flags |= CS_POSTING;
4379
4380         /* In mailbox rooms we have to behave a little differently --
4381          * make sure the user has specified at least one recipient.  Then
4382          * validate the recipient(s).  We do this for the Mail> room, as
4383          * well as any room which has the "Mailbox" view set - unless it
4384          * is the DRAFTS room which does not require recipients
4385          */
4386
4387         if ( (  ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4388                 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4389                      ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4390                 if (CCC->user.axlevel < AxProbU) {
4391                         strcpy(recp, "sysop");
4392                         strcpy(cc, "");
4393                         strcpy(bcc, "");
4394                 }
4395
4396                 valid_to = validate_recipients(recp, NULL, 0);
4397                 if (valid_to->num_error > 0) {
4398                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4399                         free_recipients(valid_to);
4400                         return;
4401                 }
4402
4403                 valid_cc = validate_recipients(cc, NULL, 0);
4404                 if (valid_cc->num_error > 0) {
4405                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4406                         free_recipients(valid_to);
4407                         free_recipients(valid_cc);
4408                         return;
4409                 }
4410
4411                 valid_bcc = validate_recipients(bcc, NULL, 0);
4412                 if (valid_bcc->num_error > 0) {
4413                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4414                         free_recipients(valid_to);
4415                         free_recipients(valid_cc);
4416                         free_recipients(valid_bcc);
4417                         return;
4418                 }
4419
4420                 /* Recipient required, but none were specified */
4421                 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4422                         free_recipients(valid_to);
4423                         free_recipients(valid_cc);
4424                         free_recipients(valid_bcc);
4425                         cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4426                         return;
4427                 }
4428
4429                 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4430                         if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4431                                 cprintf("%d You do not have permission "
4432                                         "to send Internet mail.\n",
4433                                         ERROR + HIGHER_ACCESS_REQUIRED);
4434                                 free_recipients(valid_to);
4435                                 free_recipients(valid_cc);
4436                                 free_recipients(valid_bcc);
4437                                 return;
4438                         }
4439                 }
4440
4441                 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)
4442                      && (CCC->user.axlevel < AxNetU) ) {
4443                         cprintf("%d Higher access required for network mail.\n",
4444                                 ERROR + HIGHER_ACCESS_REQUIRED);
4445                         free_recipients(valid_to);
4446                         free_recipients(valid_cc);
4447                         free_recipients(valid_bcc);
4448                         return;
4449                 }
4450         
4451                 if ((RESTRICT_INTERNET == 1)
4452                     && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4453                     && ((CCC->user.flags & US_INTERNET) == 0)
4454                     && (!CCC->internal_pgm)) {
4455                         cprintf("%d You don't have access to Internet mail.\n",
4456                                 ERROR + HIGHER_ACCESS_REQUIRED);
4457                         free_recipients(valid_to);
4458                         free_recipients(valid_cc);
4459                         free_recipients(valid_bcc);
4460                         return;
4461                 }
4462
4463         }
4464
4465         /* Is this a room which has anonymous-only or anonymous-option? */
4466         anonymous = MES_NORMAL;
4467         if (CCC->room.QRflags & QR_ANONONLY) {
4468                 anonymous = MES_ANONONLY;
4469         }
4470         if (CCC->room.QRflags & QR_ANONOPT) {
4471                 if (anon_flag == 1) {   /* only if the user requested it */
4472                         anonymous = MES_ANONOPT;
4473                 }
4474         }
4475
4476         if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4477                 recp[0] = 0;
4478         }
4479
4480         /* Recommend to the client that the use of a message subject is
4481          * strongly recommended in this room, if either the SUBJECTREQ flag
4482          * is set, or if there is one or more Internet email recipients.
4483          */
4484         if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4485         if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
4486         if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
4487         if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
4488
4489         /* If we're only checking the validity of the request, return
4490          * success without creating the message.
4491          */
4492         if (post == 0) {
4493                 cprintf("%d %s|%d\n", CIT_OK,
4494                         ((valid_to != NULL) ? valid_to->display_recp : ""), 
4495                         subject_required);
4496                 free_recipients(valid_to);
4497                 free_recipients(valid_cc);
4498                 free_recipients(valid_bcc);
4499                 return;
4500         }
4501
4502         /* We don't need these anymore because we'll do it differently below */
4503         free_recipients(valid_to);
4504         free_recipients(valid_cc);
4505         free_recipients(valid_bcc);
4506
4507         /* Read in the message from the client. */
4508         if (do_confirm) {
4509                 cprintf("%d send message\n", START_CHAT_MODE);
4510         } else {
4511                 cprintf("%d send message\n", SEND_LISTING);
4512         }
4513
4514         msg = CtdlMakeMessage(&CCC->user, recp, cc,
4515                               CCC->room.QRname, anonymous, format_type,
4516                               newusername, newuseremail, subject,
4517                               ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4518                               NULL, references);
4519
4520         /* Put together one big recipients struct containing to/cc/bcc all in
4521          * one.  This is for the envelope.
4522          */
4523         char *all_recps = malloc(SIZ * 3);
4524         strcpy(all_recps, recp);
4525         if (!IsEmptyStr(cc)) {
4526                 if (!IsEmptyStr(all_recps)) {
4527                         strcat(all_recps, ",");
4528                 }
4529                 strcat(all_recps, cc);
4530         }
4531         if (!IsEmptyStr(bcc)) {
4532                 if (!IsEmptyStr(all_recps)) {
4533                         strcat(all_recps, ",");
4534                 }
4535                 strcat(all_recps, bcc);
4536         }
4537         if (!IsEmptyStr(all_recps)) {
4538                 valid = validate_recipients(all_recps, NULL, 0);
4539         }
4540         else {
4541                 valid = NULL;
4542         }
4543         free(all_recps);
4544
4545         if (msg != NULL) {
4546                 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4547                 if (verbose_reply)
4548                 {
4549                         if (StrLength(CCC->StatusMessage)>0)
4550                         {
4551                                 StrBufAppendBufPlain(CCC->StatusMessage, HKEY("\n000\n"), 0);
4552                                 cputbuf(CCC->StatusMessage);
4553                         }
4554                         else
4555                                 client_write(HKEY("\n000\n"));
4556                 }
4557
4558                 if (do_confirm) {
4559                         cprintf("%ld\n", msgnum);
4560                         if (msgnum >= 0L) {
4561                                 client_write(HKEY("Message accepted.\n"));
4562                         }
4563                         else {
4564                                 client_write(HKEY("Internal error.\n"));
4565                         }
4566                         if (msg->cm_fields['E'] != NULL) {
4567                                 cprintf("%s\n", msg->cm_fields['E']);
4568                         } else {
4569                                 cprintf("\n");
4570                         }
4571                         cprintf("000\n");
4572                 }
4573
4574                 CtdlFreeMessage(msg);
4575         }
4576         if (valid != NULL) {
4577                 free_recipients(valid);
4578         }
4579         return;
4580 }
4581
4582
4583
4584 /*
4585  * API function to delete messages which match a set of criteria
4586  * (returns the actual number of messages deleted)
4587  */
4588 int CtdlDeleteMessages(char *room_name,         /* which room */
4589                        long *dmsgnums,          /* array of msg numbers to be deleted */
4590                        int num_dmsgnums,        /* number of msgs to be deleted, or 0 for "any" */
4591                        char *content_type       /* or "" for any.  regular expressions expected. */
4592         )
4593 {
4594         struct ctdlroom qrbuf;
4595         struct cdbdata *cdbfr;
4596         long *msglist = NULL;
4597         long *dellist = NULL;
4598         int num_msgs = 0;
4599         int i, j;
4600         int num_deleted = 0;
4601         int delete_this;
4602         struct MetaData smi;
4603         regex_t re;
4604         regmatch_t pm;
4605         int need_to_free_re = 0;
4606
4607         if (content_type) if (!IsEmptyStr(content_type)) {
4608                         regcomp(&re, content_type, 0);
4609                         need_to_free_re = 1;
4610                 }
4611         syslog(LOG_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4612                room_name, num_dmsgnums, content_type);
4613
4614         /* get room record, obtaining a lock... */
4615         if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4616                 syslog(LOG_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4617                        room_name);
4618                 if (need_to_free_re) regfree(&re);
4619                 return (0);     /* room not found */
4620         }
4621         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4622
4623         if (cdbfr != NULL) {
4624                 dellist = malloc(cdbfr->len);
4625                 msglist = (long *) cdbfr->ptr;
4626                 cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
4627                 num_msgs = cdbfr->len / sizeof(long);
4628                 cdb_free(cdbfr);
4629         }
4630         if (num_msgs > 0) {
4631                 for (i = 0; i < num_msgs; ++i) {
4632                         delete_this = 0x00;
4633
4634                         /* Set/clear a bit for each criterion */
4635
4636                         /* 0 messages in the list or a null list means that we are
4637                          * interested in deleting any messages which meet the other criteria.
4638                          */
4639                         if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4640                                 delete_this |= 0x01;
4641                         }
4642                         else {
4643                                 for (j=0; j<num_dmsgnums; ++j) {
4644                                         if (msglist[i] == dmsgnums[j]) {
4645                                                 delete_this |= 0x01;
4646                                         }
4647                                 }
4648                         }
4649
4650                         if (IsEmptyStr(content_type)) {
4651                                 delete_this |= 0x02;
4652                         } else {
4653                                 GetMetaData(&smi, msglist[i]);
4654                                 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4655                                         delete_this |= 0x02;
4656                                 }
4657                         }
4658
4659                         /* Delete message only if all bits are set */
4660                         if (delete_this == 0x03) {
4661                                 dellist[num_deleted++] = msglist[i];
4662                                 msglist[i] = 0L;
4663                         }
4664                 }
4665
4666                 num_msgs = sort_msglist(msglist, num_msgs);
4667                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4668                           msglist, (int)(num_msgs * sizeof(long)));
4669
4670                 if (num_msgs > 0)
4671                         qrbuf.QRhighest = msglist[num_msgs - 1];
4672                 else
4673                         qrbuf.QRhighest = 0;
4674         }
4675         CtdlPutRoomLock(&qrbuf);
4676
4677         /* Go through the messages we pulled out of the index, and decrement
4678          * their reference counts by 1.  If this is the only room the message
4679          * was in, the reference count will reach zero and the message will
4680          * automatically be deleted from the database.  We do this in a
4681          * separate pass because there might be plug-in hooks getting called,
4682          * and we don't want that happening during an S_ROOMS critical
4683          * section.
4684          */
4685         if (num_deleted) for (i=0; i<num_deleted; ++i) {
4686                         PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4687                         AdjRefCount(dellist[i], -1);
4688                 }
4689
4690         /* Now free the memory we used, and go away. */
4691         if (msglist != NULL) free(msglist);
4692         if (dellist != NULL) free(dellist);
4693         syslog(LOG_DEBUG, "%d message(s) deleted.\n", num_deleted);
4694         if (need_to_free_re) regfree(&re);
4695         return (num_deleted);
4696 }
4697
4698
4699
4700 /*
4701  * Check whether the current user has permission to delete messages from
4702  * the current room (returns 1 for yes, 0 for no)
4703  */
4704 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4705         int ra;
4706         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4707         if (ra & UA_DELETEALLOWED) return(1);
4708         return(0);
4709 }
4710
4711
4712
4713
4714 /*
4715  * Delete message from current room
4716  */
4717 void cmd_dele(char *args)
4718 {
4719         int num_deleted;
4720         int i;
4721         char msgset[SIZ];
4722         char msgtok[32];
4723         long *msgs;
4724         int num_msgs = 0;
4725
4726         extract_token(msgset, args, 0, '|', sizeof msgset);
4727         num_msgs = num_tokens(msgset, ',');
4728         if (num_msgs < 1) {
4729                 cprintf("%d Nothing to do.\n", CIT_OK);
4730                 return;
4731         }
4732
4733         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4734                 cprintf("%d Higher access required.\n",
4735                         ERROR + HIGHER_ACCESS_REQUIRED);
4736                 return;
4737         }
4738
4739         /*
4740          * Build our message set to be moved/copied
4741          */
4742         msgs = malloc(num_msgs * sizeof(long));
4743         for (i=0; i<num_msgs; ++i) {
4744                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4745                 msgs[i] = atol(msgtok);
4746         }
4747
4748         num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4749         free(msgs);
4750
4751         if (num_deleted) {
4752                 cprintf("%d %d message%s deleted.\n", CIT_OK,
4753                         num_deleted, ((num_deleted != 1) ? "s" : ""));
4754         } else {
4755                 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4756         }
4757 }
4758
4759
4760
4761
4762 /*
4763  * move or copy a message to another room
4764  */
4765 void cmd_move(char *args)
4766 {
4767         char msgset[SIZ];
4768         char msgtok[32];
4769         long *msgs;
4770         int num_msgs = 0;
4771
4772         char targ[ROOMNAMELEN];
4773         struct ctdlroom qtemp;
4774         int err;
4775         int is_copy = 0;
4776         int ra;
4777         int permit = 0;
4778         int i;
4779
4780         extract_token(msgset, args, 0, '|', sizeof msgset);
4781         num_msgs = num_tokens(msgset, ',');
4782         if (num_msgs < 1) {
4783                 cprintf("%d Nothing to do.\n", CIT_OK);
4784                 return;
4785         }
4786
4787         extract_token(targ, args, 1, '|', sizeof targ);
4788         convert_room_name_macros(targ, sizeof targ);
4789         targ[ROOMNAMELEN - 1] = 0;
4790         is_copy = extract_int(args, 2);
4791
4792         if (CtdlGetRoom(&qtemp, targ) != 0) {
4793                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4794                 return;
4795         }
4796
4797         if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4798                 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4799                 return;
4800         }
4801
4802         CtdlGetUser(&CC->user, CC->curr_user);
4803         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4804
4805         /* Check for permission to perform this operation.
4806          * Remember: "CC->room" is source, "qtemp" is target.
4807          */
4808         permit = 0;
4809
4810         /* Aides can move/copy */
4811         if (CC->user.axlevel >= AxAideU) permit = 1;
4812
4813         /* Room aides can move/copy */
4814         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4815
4816         /* Permit move/copy from personal rooms */
4817         if ((CC->room.QRflags & QR_MAILBOX)
4818             && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4819
4820         /* Permit only copy from public to personal room */
4821         if ( (is_copy)
4822              && (!(CC->room.QRflags & QR_MAILBOX))
4823              && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4824
4825         /* Permit message removal from collaborative delete rooms */
4826         if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4827
4828         /* Users allowed to post into the target room may move into it too. */
4829         if ((CC->room.QRflags & QR_MAILBOX) && 
4830             (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
4831
4832         /* User must have access to target room */
4833         if (!(ra & UA_KNOWN))  permit = 0;
4834
4835         if (!permit) {
4836                 cprintf("%d Higher access required.\n",
4837                         ERROR + HIGHER_ACCESS_REQUIRED);
4838                 return;
4839         }
4840
4841         /*
4842          * Build our message set to be moved/copied
4843          */
4844         msgs = malloc(num_msgs * sizeof(long));
4845         for (i=0; i<num_msgs; ++i) {
4846                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4847                 msgs[i] = atol(msgtok);
4848         }
4849
4850         /*
4851          * Do the copy
4852          */
4853         err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4854         if (err != 0) {
4855                 cprintf("%d Cannot store message(s) in %s: error %d\n",
4856                         err, targ, err);
4857                 free(msgs);
4858                 return;
4859         }
4860
4861         /* Now delete the message from the source room,
4862          * if this is a 'move' rather than a 'copy' operation.
4863          */
4864         if (is_copy == 0) {
4865                 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4866         }
4867         free(msgs);
4868
4869         cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4870 }
4871
4872
4873
4874 /*
4875  * GetMetaData()  -  Get the supplementary record for a message
4876  */
4877 void GetMetaData(struct MetaData *smibuf, long msgnum)
4878 {
4879
4880         struct cdbdata *cdbsmi;
4881         long TheIndex;
4882
4883         memset(smibuf, 0, sizeof(struct MetaData));
4884         smibuf->meta_msgnum = msgnum;
4885         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
4886
4887         /* Use the negative of the message number for its supp record index */
4888         TheIndex = (0L - msgnum);
4889
4890         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4891         if (cdbsmi == NULL) {
4892                 return;         /* record not found; go with defaults */
4893         }
4894         memcpy(smibuf, cdbsmi->ptr,
4895                ((cdbsmi->len > sizeof(struct MetaData)) ?
4896                 sizeof(struct MetaData) : cdbsmi->len));
4897         cdb_free(cdbsmi);
4898         return;
4899 }
4900
4901
4902 /*
4903  * PutMetaData()  -  (re)write supplementary record for a message
4904  */
4905 void PutMetaData(struct MetaData *smibuf)
4906 {
4907         long TheIndex;
4908
4909         /* Use the negative of the message number for the metadata db index */
4910         TheIndex = (0L - smibuf->meta_msgnum);
4911
4912         cdb_store(CDB_MSGMAIN,
4913                   &TheIndex, (int)sizeof(long),
4914                   smibuf, (int)sizeof(struct MetaData));
4915
4916 }
4917
4918 /*
4919  * AdjRefCount  -  submit an adjustment to the reference count for a message.
4920  *                 (These are just queued -- we actually process them later.)
4921  */
4922 void AdjRefCount(long msgnum, int incr)
4923 {
4924         struct arcq new_arcq;
4925         int rv = 0;
4926
4927         syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4928                msgnum, incr
4929                 );
4930
4931         begin_critical_section(S_SUPPMSGMAIN);
4932         if (arcfp == NULL) {
4933                 arcfp = fopen(file_arcq, "ab+");
4934         }
4935         end_critical_section(S_SUPPMSGMAIN);
4936
4937         /* msgnum < 0 means that we're trying to close the file */
4938         if (msgnum < 0) {
4939                 syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
4940                 begin_critical_section(S_SUPPMSGMAIN);
4941                 if (arcfp != NULL) {
4942                         fclose(arcfp);
4943                         arcfp = NULL;
4944                 }
4945                 end_critical_section(S_SUPPMSGMAIN);
4946                 return;
4947         }
4948
4949         /*
4950          * If we can't open the queue, perform the operation synchronously.
4951          */
4952         if (arcfp == NULL) {
4953                 TDAP_AdjRefCount(msgnum, incr);
4954                 return;
4955         }
4956
4957         new_arcq.arcq_msgnum = msgnum;
4958         new_arcq.arcq_delta = incr;
4959         rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4960         if (rv == -1) {
4961                 syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
4962                        file_arcq,
4963                        strerror(errno));
4964         }
4965         fflush(arcfp);
4966
4967         return;
4968 }
4969
4970
4971 /*
4972  * TDAP_ProcessAdjRefCountQueue()
4973  *
4974  * Process the queue of message count adjustments that was created by calls
4975  * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4976  * for each one.  This should be an "off hours" operation.
4977  */
4978 int TDAP_ProcessAdjRefCountQueue(void)
4979 {
4980         char file_arcq_temp[PATH_MAX];
4981         int r;
4982         FILE *fp;
4983         struct arcq arcq_rec;
4984         int num_records_processed = 0;
4985
4986         snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4987
4988         begin_critical_section(S_SUPPMSGMAIN);
4989         if (arcfp != NULL) {
4990                 fclose(arcfp);
4991                 arcfp = NULL;
4992         }
4993
4994         r = link(file_arcq, file_arcq_temp);
4995         if (r != 0) {
4996                 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4997                 end_critical_section(S_SUPPMSGMAIN);
4998                 return(num_records_processed);
4999         }
5000
5001         unlink(file_arcq);
5002         end_critical_section(S_SUPPMSGMAIN);
5003
5004         fp = fopen(file_arcq_temp, "rb");
5005         if (fp == NULL) {
5006                 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5007                 return(num_records_processed);
5008         }
5009
5010         while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5011                 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5012                 ++num_records_processed;
5013         }
5014
5015         fclose(fp);
5016         r = unlink(file_arcq_temp);
5017         if (r != 0) {
5018                 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5019         }
5020
5021         return(num_records_processed);
5022 }
5023
5024
5025
5026 /*
5027  * TDAP_AdjRefCount  -  adjust the reference count for a message.
5028  *                      This one does it "for real" because it's called by
5029  *                      the autopurger function that processes the queue
5030  *                      created by AdjRefCount().   If a message's reference
5031  *                      count becomes zero, we also delete the message from
5032  *                      disk and de-index it.
5033  */
5034 void TDAP_AdjRefCount(long msgnum, int incr)
5035 {
5036
5037         struct MetaData smi;
5038         long delnum;
5039
5040         /* This is a *tight* critical section; please keep it that way, as
5041          * it may get called while nested in other critical sections.  
5042          * Complicating this any further will surely cause deadlock!
5043          */
5044         begin_critical_section(S_SUPPMSGMAIN);
5045         GetMetaData(&smi, msgnum);
5046         smi.meta_refcount += incr;
5047         PutMetaData(&smi);
5048         end_critical_section(S_SUPPMSGMAIN);
5049         syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5050                msgnum, incr, smi.meta_refcount
5051                 );
5052
5053         /* If the reference count is now zero, delete the message
5054          * (and its supplementary record as well).
5055          */
5056         if (smi.meta_refcount == 0) {
5057                 syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5058                 
5059                 /* Call delete hooks with NULL room to show it has gone altogether */
5060                 PerformDeleteHooks(NULL, msgnum);
5061
5062                 /* Remove from message base */
5063                 delnum = msgnum;
5064                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5065                 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5066
5067                 /* Remove metadata record */
5068                 delnum = (0L - msgnum);
5069                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5070         }
5071
5072 }
5073
5074 /*
5075  * Write a generic object to this room
5076  *
5077  * Note: this could be much more efficient.  Right now we use two temporary
5078  * files, and still pull the message into memory as with all others.
5079  */
5080 void CtdlWriteObject(char *req_room,                    /* Room to stuff it in */
5081                      char *content_type,                /* MIME type of this object */
5082                      char *raw_message,         /* Data to be written */
5083                      off_t raw_length,          /* Size of raw_message */
5084                      struct ctdluser *is_mailbox,       /* Mailbox room? */
5085                      int is_binary,                     /* Is encoding necessary? */
5086                      int is_unique,                     /* Del others of this type? */
5087                      unsigned int flags         /* Internal save flags */
5088         )
5089 {
5090
5091         struct ctdlroom qrbuf;
5092         char roomname[ROOMNAMELEN];
5093         struct CtdlMessage *msg;
5094         char *encoded_message = NULL;
5095
5096         if (is_mailbox != NULL) {
5097                 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5098         }
5099         else {
5100                 safestrncpy(roomname, req_room, sizeof(roomname));
5101         }
5102
5103         syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5104
5105         if (is_binary) {
5106                 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5107         }
5108         else {
5109                 encoded_message = malloc((size_t)(raw_length + 4096));
5110         }
5111
5112         sprintf(encoded_message, "Content-type: %s\n", content_type);
5113
5114         if (is_binary) {
5115                 sprintf(&encoded_message[strlen(encoded_message)],
5116                         "Content-transfer-encoding: base64\n\n"
5117                         );
5118         }
5119         else {
5120                 sprintf(&encoded_message[strlen(encoded_message)],
5121                         "Content-transfer-encoding: 7bit\n\n"
5122                         );
5123         }
5124
5125         if (is_binary) {
5126                 CtdlEncodeBase64(
5127                         &encoded_message[strlen(encoded_message)],
5128                         raw_message,
5129                         (int)raw_length,
5130                         0
5131                         );
5132         }
5133         else {
5134                 memcpy(
5135                         &encoded_message[strlen(encoded_message)],
5136                         raw_message,
5137                         (int)(raw_length+1)
5138                         );
5139         }
5140
5141         syslog(LOG_DEBUG, "Allocating\n");
5142         msg = malloc(sizeof(struct CtdlMessage));
5143         memset(msg, 0, sizeof(struct CtdlMessage));
5144         msg->cm_magic = CTDLMESSAGE_MAGIC;
5145         msg->cm_anon_type = MES_NORMAL;
5146         msg->cm_format_type = 4;
5147         msg->cm_fields['A'] = strdup(CC->user.fullname);
5148         msg->cm_fields['O'] = strdup(req_room);
5149         msg->cm_fields['N'] = strdup(config.c_nodename);
5150         msg->cm_fields['H'] = strdup(config.c_humannode);
5151         msg->cm_flags = flags;
5152         
5153         msg->cm_fields['M'] = encoded_message;
5154
5155         /* Create the requested room if we have to. */
5156         if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5157                 CtdlCreateRoom(roomname, 
5158                                ( (is_mailbox != NULL) ? 5 : 3 ),
5159                                "", 0, 1, 0, VIEW_BBS);
5160         }
5161         /* If the caller specified this object as unique, delete all
5162          * other objects of this type that are currently in the room.
5163          */
5164         if (is_unique) {
5165                 syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5166                        CtdlDeleteMessages(roomname, NULL, 0, content_type)
5167                         );
5168         }
5169         /* Now write the data */
5170         CtdlSubmitMsg(msg, NULL, roomname, 0);
5171         CtdlFreeMessage(msg);
5172 }
5173
5174
5175
5176
5177
5178
5179 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5180         config_msgnum = msgnum;
5181 }
5182
5183
5184 char *CtdlGetSysConfig(char *sysconfname) {
5185         char hold_rm[ROOMNAMELEN];
5186         long msgnum;
5187         char *conf;
5188         struct CtdlMessage *msg;
5189         char buf[SIZ];
5190         
5191         strcpy(hold_rm, CC->room.QRname);
5192         if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5193                 CtdlGetRoom(&CC->room, hold_rm);
5194                 return NULL;
5195         }
5196
5197
5198         /* We want the last (and probably only) config in this room */
5199         begin_critical_section(S_CONFIG);
5200         config_msgnum = (-1L);
5201         CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5202                            CtdlGetSysConfigBackend, NULL);
5203         msgnum = config_msgnum;
5204         end_critical_section(S_CONFIG);
5205
5206         if (msgnum < 0L) {
5207                 conf = NULL;
5208         }
5209         else {
5210                 msg = CtdlFetchMessage(msgnum, 1);
5211                 if (msg != NULL) {
5212                         conf = strdup(msg->cm_fields['M']);
5213                         CtdlFreeMessage(msg);
5214                 }
5215                 else {
5216                         conf = NULL;
5217                 }
5218         }
5219
5220         CtdlGetRoom(&CC->room, hold_rm);
5221
5222         if (conf != NULL) do {
5223                         extract_token(buf, conf, 0, '\n', sizeof buf);
5224                         strcpy(conf, &conf[strlen(buf)+1]);
5225                 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5226
5227         return(conf);
5228 }
5229
5230
5231 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5232         CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5233 }
5234
5235
5236 /*
5237  * Determine whether a given Internet address belongs to the current user
5238  */
5239 int CtdlIsMe(char *addr, int addr_buf_len)
5240 {
5241         struct recptypes *recp;
5242         int i;
5243
5244         recp = validate_recipients(addr, NULL, 0);
5245         if (recp == NULL) return(0);
5246
5247         if (recp->num_local == 0) {
5248                 free_recipients(recp);
5249                 return(0);
5250         }
5251
5252         for (i=0; i<recp->num_local; ++i) {
5253                 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5254                 if (!strcasecmp(addr, CC->user.fullname)) {
5255                         free_recipients(recp);
5256                         return(1);
5257                 }
5258         }
5259
5260         free_recipients(recp);
5261         return(0);
5262 }
5263
5264
5265 /*
5266  * Citadel protocol command to do the same
5267  */
5268 void cmd_isme(char *argbuf) {
5269         char addr[256];
5270
5271         if (CtdlAccessCheck(ac_logged_in)) return;
5272         extract_token(addr, argbuf, 0, '|', sizeof addr);
5273
5274         if (CtdlIsMe(addr, sizeof addr)) {
5275                 cprintf("%d %s\n", CIT_OK, addr);
5276         }
5277         else {
5278                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5279         }
5280
5281 }
5282
5283
5284 /*****************************************************************************/
5285 /*                      MODULE INITIALIZATION STUFF                          */
5286 /*****************************************************************************/
5287
5288 CTDL_MODULE_INIT(msgbase)
5289 {
5290         if (!threading) {
5291                 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5292                 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5293                 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5294                 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5295                 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5296                 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5297                 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5298                 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5299                 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5300                 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5301                 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5302                 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5303         }
5304
5305         /* return our Subversion id for the Log */
5306         return "msgbase";
5307 }