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