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