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