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