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