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