2 * Implements the message store.
4 * Copyright (c) 1987-2010 by the citadel.org team
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
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.
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
27 #if TIME_WITH_SYS_TIME
28 # include <sys/time.h>
32 # include <sys/time.h>
45 #include <sys/types.h>
47 #include <libcitadel.h>
50 #include "serv_extensions.h"
54 #include "sysdep_decls.h"
55 #include "citserver.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"
70 #include "ctdl_module.h"
73 struct addresses_to_be_filed *atbf = NULL;
75 /* This temp file holds the queue of operations for AdjRefCount() */
76 static FILE *arcfp = NULL;
79 * This really belongs in serv_network.c, but I don't know how to export
80 * symbols between modules.
82 struct FilterList *filterlist = NULL;
86 * These are the four-character field headers we use when outputting
87 * messages in Citadel format (as opposed to RFC822 format).
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,
126 * This function is self explanatory.
127 * (What can I say, I'm in a weird mood today...)
129 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
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]);
139 while (isspace(name[i + 1])) {
140 strcpy(&name[i + 1], &name[i + 2]);
148 * Aliasing for network mail.
149 * (Error messages have been commented out, because this is a server.)
151 int alias(char *name)
152 { /* process alias and routing info for mail */
155 char aaa[SIZ], bbb[SIZ];
156 char *ignetcfg = NULL;
157 char *ignetmap = NULL;
163 char original_name[256];
164 safestrncpy(original_name, name, sizeof original_name);
167 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
168 stripallbut(name, '<', '>');
170 fp = fopen(file_mail_aliases, "r");
172 fp = fopen("/dev/null", "r");
179 while (fgets(aaa, sizeof aaa, fp) != NULL) {
180 while (isspace(name[0]))
181 strcpy(name, &name[1]);
182 aaa[strlen(aaa) - 1] = 0;
184 for (a = 0; a < strlen(aaa); ++a) {
186 strcpy(bbb, &aaa[a + 1]);
190 if (!strcasecmp(name, aaa))
195 /* Hit the Global Address Book */
196 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
200 if (strcasecmp(original_name, name)) {
201 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
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) {
209 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
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);
220 /* figure out the delivery mode */
221 extract_token(node, name, 1, '@', sizeof node);
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.
226 if (haschar(node, '.') > 0) {
227 return(MES_INTERNET);
230 /* Otherwise we look in the IGnet maps for a valid Citadel node.
231 * Try directly-connected nodes first...
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)) {
245 * Then try nodes that are two or more hops away.
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)) {
258 /* If we get to this point it's an invalid node name */
264 * Back end for the MSGS command: output message number only.
266 void simple_listing(long msgnum, void *userdata)
268 cprintf("%ld\n", msgnum);
274 * Back end for the MSGS command: output header summary.
276 void headers_listing(long msgnum, void *userdata)
278 struct CtdlMessage *msg;
280 msg = CtdlFetchMessage(msgnum, 0);
282 cprintf("%ld|0|||||\n", msgnum);
286 cprintf("%ld|%s|%s|%s|%s|%s|\n",
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'] : "")
294 CtdlFreeMessage(msg);
298 * Back end for the MSGS command: output EUID header.
300 void headers_euid(long msgnum, void *userdata)
302 struct CtdlMessage *msg;
304 msg = CtdlFetchMessage(msgnum, 0);
306 cprintf("%ld||\n", msgnum);
310 cprintf("%ld|%s|%s\n",
312 (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""),
313 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"));
314 CtdlFreeMessage(msg);
321 /* Determine if a given message matches the fields in a message template.
322 * Return 0 for a successful match.
324 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
327 /* If there aren't any fields in the template, all messages will
330 if (template == NULL) return(0);
332 /* Null messages are bogus. */
333 if (msg == NULL) return(1);
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;
342 if (strcasecmp(msg->cm_fields[i],
343 template->cm_fields[i])) return 1;
347 /* All compares succeeded: we have a match! */
354 * Retrieve the "seen" message list for the current room.
356 void CtdlGetSeen(char *buf, int which_set) {
359 /* Learn about the user and room in question */
360 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
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);
371 * Manipulate the "seen msgs" string (or other message set strings)
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;
390 char *is_set; /* actually an array of booleans */
392 /* Don't bother doing *anything* if we were passed a list of zero messages */
393 if (num_target_msgnums < 1) {
397 /* If no room was specified, we go with the current room. */
399 which_room = &CC->room;
402 /* If no user was specified, we go with the current user. */
404 which_user = &CC->user;
407 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
408 num_target_msgnums, target_msgnums[0],
409 (target_setting ? "SET" : "CLEAR"),
413 /* Learn about the user and room in question */
414 CtdlGetRelationship(&vbuf, which_user, which_room);
416 /* Load the message list */
417 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
419 msglist = (long *) cdbfr->ptr;
420 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
421 num_msgs = cdbfr->len / sizeof(long);
424 return; /* No messages at all? No further action. */
427 is_set = malloc(num_msgs * sizeof(char));
428 memset(is_set, 0, (num_msgs * sizeof(char)) );
430 /* Decide which message set we're manipulating */
432 case ctdlsetseen_seen:
433 vset = NewStrBufPlain(vbuf.v_seen, -1);
435 case ctdlsetseen_answered:
436 vset = NewStrBufPlain(vbuf.v_answered, -1);
443 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
444 CtdlLogPrintf(CTDL_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();
448 CtdlLogPrintf(CTDL_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();
454 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
456 /* Translate the existing sequence set into an array of booleans */
457 setstr = NewStrBuf();
461 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
463 StrBufExtract_token(lostr, setstr, 0, ':');
464 if (StrBufNum_tokens(setstr, ':') >= 2) {
465 StrBufExtract_token(histr, setstr, 1, ':');
469 StrBufAppendBuf(histr, lostr, 0);
472 if (!strcmp(ChrPtr(histr), "*")) {
479 for (i = 0; i < num_msgs; ++i) {
480 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
490 /* Now translate the array of booleans back into a sequence set */
496 for (i=0; i<num_msgs; ++i) {
500 for (k=0; k<num_target_msgnums; ++k) {
501 if (msglist[i] == target_msgnums[k]) {
502 is_seen = target_setting;
506 if ((was_seen == 0) && (is_seen == 1)) {
509 else if ((was_seen == 1) && (is_seen == 0)) {
512 if (StrLength(vset) > 0) {
513 StrBufAppendBufPlain(vset, HKEY(","), 0);
516 StrBufAppendPrintf(vset, "%ld", hi);
519 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
523 if ((is_seen) && (i == num_msgs - 1)) {
524 if (StrLength(vset) > 0) {
525 StrBufAppendBufPlain(vset, HKEY(","), 0);
527 if ((i==0) || (was_seen == 0)) {
528 StrBufAppendPrintf(vset, "%ld", msglist[i]);
531 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
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.
544 int number_of_truncations = 0;
545 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
546 StrBufRemove_token(vset, 0, ',');
547 ++number_of_truncations;
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'.
555 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
557 first_tok = NewStrBuf();
558 StrBufExtract_token(first_tok, vset, 0, ',');
559 StrBufRemove_token(vset, 0, ',');
561 if (StrBufNum_tokens(first_tok, ':') > 1) {
562 StrBufRemove_token(first_tok, 0, ':');
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);
573 FreeStrBuf(&first_tok);
577 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
579 /* Decide which message set we're manipulating */
581 case ctdlsetseen_seen:
582 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
584 case ctdlsetseen_answered:
585 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
591 CtdlSetRelationship(&vbuf, which_user, which_room);
597 * API function to perform an operation for each qualifying message in the
598 * current room. (Returns the number of messages processed.)
600 int CtdlForEachMessage(int mode, long ref, char *search_string,
602 struct CtdlMessage *compare,
603 ForEachMsgCallback CallBack,
609 struct cdbdata *cdbfr;
610 long *msglist = NULL;
612 int num_processed = 0;
615 struct CtdlMessage *msg = NULL;
618 int printed_lastold = 0;
619 int num_search_msgs = 0;
620 long *search_msgs = NULL;
622 int need_to_free_re = 0;
625 if ((content_type) && (!IsEmptyStr(content_type))) {
626 regcomp(&re, content_type, 0);
630 /* Learn about the user and room in question */
631 CtdlGetUser(&CC->user, CC->curr_user);
632 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
634 /* Load the message list */
635 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
637 msglist = (long *) cdbfr->ptr;
638 num_msgs = cdbfr->len / sizeof(long);
640 if (need_to_free_re) regfree(&re);
641 return 0; /* No messages at all? No further action. */
646 * Now begin the traversal.
648 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
650 /* If the caller is looking for a specific MIME type, filter
651 * out all messages which are not of the type requested.
653 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
655 /* This call to GetMetaData() sits inside this loop
656 * so that we only do the extra database read per msg
657 * if we need to. Doing the extra read all the time
658 * really kills the server. If we ever need to use
659 * metadata for another search criterion, we need to
660 * move the read somewhere else -- but still be smart
661 * enough to only do the read if the caller has
662 * specified something that will need it.
664 GetMetaData(&smi, msglist[a]);
666 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
667 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
673 num_msgs = sort_msglist(msglist, num_msgs);
675 /* If a template was supplied, filter out the messages which
676 * don't match. (This could induce some delays!)
679 if (compare != NULL) {
680 for (a = 0; a < num_msgs; ++a) {
681 msg = CtdlFetchMessage(msglist[a], 1);
683 if (CtdlMsgCmp(msg, compare)) {
686 CtdlFreeMessage(msg);
692 /* If a search string was specified, get a message list from
693 * the full text index and remove messages which aren't on both
697 * Since the lists are sorted and strictly ascending, and the
698 * output list is guaranteed to be shorter than or equal to the
699 * input list, we overwrite the bottom of the input list. This
700 * eliminates the need to memmove big chunks of the list over and
703 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
705 /* Call search module via hook mechanism.
706 * NULL means use any search function available.
707 * otherwise replace with a char * to name of search routine
709 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
711 if (num_search_msgs > 0) {
715 orig_num_msgs = num_msgs;
717 for (i=0; i<orig_num_msgs; ++i) {
718 for (j=0; j<num_search_msgs; ++j) {
719 if (msglist[i] == search_msgs[j]) {
720 msglist[num_msgs++] = msglist[i];
726 num_msgs = 0; /* No messages qualify */
728 if (search_msgs != NULL) free(search_msgs);
730 /* Now that we've purged messages which don't contain the search
731 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
738 * Now iterate through the message list, according to the
739 * criteria supplied by the caller.
742 for (a = 0; a < num_msgs; ++a) {
743 thismsg = msglist[a];
744 if (mode == MSGS_ALL) {
748 is_seen = is_msg_in_sequence_set(
749 vbuf.v_seen, thismsg);
750 if (is_seen) lastold = thismsg;
756 || ((mode == MSGS_OLD) && (is_seen))
757 || ((mode == MSGS_NEW) && (!is_seen))
758 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
759 || ((mode == MSGS_FIRST) && (a < ref))
760 || ((mode == MSGS_GT) && (thismsg > ref))
761 || ((mode == MSGS_LT) && (thismsg < ref))
762 || ((mode == MSGS_EQ) && (thismsg == ref))
765 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
767 CallBack(lastold, userdata);
771 if (CallBack) CallBack(thismsg, userdata);
775 cdb_free(cdbfr); /* Clean up */
776 if (need_to_free_re) regfree(&re);
777 return num_processed;
783 * cmd_msgs() - get list of message #'s in this room
784 * implements the MSGS server command using CtdlForEachMessage()
786 void cmd_msgs(char *cmdbuf)
795 int with_template = 0;
796 struct CtdlMessage *template = NULL;
797 char search_string[1024];
798 ForEachMsgCallback CallBack;
800 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
802 extract_token(which, cmdbuf, 0, '|', sizeof which);
803 cm_ref = extract_int(cmdbuf, 1);
804 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
805 with_template = extract_int(cmdbuf, 2);
806 switch (extract_int(cmdbuf, 3))
810 CallBack = simple_listing;
813 CallBack = headers_listing;
816 CallBack = headers_euid;
821 if (!strncasecmp(which, "OLD", 3))
823 else if (!strncasecmp(which, "NEW", 3))
825 else if (!strncasecmp(which, "FIRST", 5))
827 else if (!strncasecmp(which, "LAST", 4))
829 else if (!strncasecmp(which, "GT", 2))
831 else if (!strncasecmp(which, "LT", 2))
833 else if (!strncasecmp(which, "SEARCH", 6))
838 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
839 cprintf("%d Full text index is not enabled on this server.\n",
840 ERROR + CMD_NOT_SUPPORTED);
846 cprintf("%d Send template then receive message list\n",
848 template = (struct CtdlMessage *)
849 malloc(sizeof(struct CtdlMessage));
850 memset(template, 0, sizeof(struct CtdlMessage));
851 template->cm_magic = CTDLMESSAGE_MAGIC;
852 template->cm_anon_type = MES_NORMAL;
854 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
855 extract_token(tfield, buf, 0, '|', sizeof tfield);
856 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
857 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
858 if (!strcasecmp(tfield, msgkeys[i])) {
859 template->cm_fields[i] =
867 cprintf("%d \n", LISTING_FOLLOWS);
870 CtdlForEachMessage(mode,
871 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
872 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
877 if (template != NULL) CtdlFreeMessage(template);
885 * help_subst() - support routine for help file viewer
887 void help_subst(char *strbuf, char *source, char *dest)
892 while (p = pattern2(strbuf, source), (p >= 0)) {
893 strcpy(workbuf, &strbuf[p + strlen(source)]);
894 strcpy(&strbuf[p], dest);
895 strcat(strbuf, workbuf);
900 void do_help_subst(char *buffer)
904 help_subst(buffer, "^nodename", config.c_nodename);
905 help_subst(buffer, "^humannode", config.c_humannode);
906 help_subst(buffer, "^fqdn", config.c_fqdn);
907 help_subst(buffer, "^username", CC->user.fullname);
908 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
909 help_subst(buffer, "^usernum", buf2);
910 help_subst(buffer, "^sysadm", config.c_sysadm);
911 help_subst(buffer, "^variantname", CITADEL);
912 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
913 help_subst(buffer, "^maxsessions", buf2);
914 help_subst(buffer, "^bbsdir", ctdl_message_dir);
920 * memfmout() - Citadel text formatter and paginator.
921 * Although the original purpose of this routine was to format
922 * text to the reader's screen width, all we're really using it
923 * for here is to format text out to 80 columns before sending it
924 * to the client. The client software may reformat it again.
927 char *mptr, /* where are we going to get our text from? */
928 const char *nl /* string to terminate lines with */
931 unsigned char ch = 0;
938 while (ch=*(mptr++), ch != 0) {
941 client_write(outbuf, len);
943 client_write(nl, nllen);
946 else if (ch == '\r') {
947 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
949 else if (isspace(ch)) {
950 if (column > 72) { /* Beyond 72 columns, break on the next space */
951 client_write(outbuf, len);
953 client_write(nl, nllen);
964 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
965 client_write(outbuf, len);
967 client_write(nl, nllen);
973 client_write(outbuf, len);
975 client_write(nl, nllen);
983 * Callback function for mime parser that simply lists the part
985 void list_this_part(char *name, char *filename, char *partnum, char *disp,
986 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
987 char *cbid, void *cbuserdata)
991 ma = (struct ma_info *)cbuserdata;
992 if (ma->is_ma == 0) {
993 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1006 * Callback function for multipart prefix
1008 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1009 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1010 char *cbid, void *cbuserdata)
1014 ma = (struct ma_info *)cbuserdata;
1015 if (!strcasecmp(cbtype, "multipart/alternative")) {
1019 if (ma->is_ma == 0) {
1020 cprintf("pref=%s|%s\n", partnum, cbtype);
1025 * Callback function for multipart sufffix
1027 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1028 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1029 char *cbid, void *cbuserdata)
1033 ma = (struct ma_info *)cbuserdata;
1034 if (ma->is_ma == 0) {
1035 cprintf("suff=%s|%s\n", partnum, cbtype);
1037 if (!strcasecmp(cbtype, "multipart/alternative")) {
1044 * Callback function for mime parser that opens a section for downloading
1046 void mime_download(char *name, char *filename, char *partnum, char *disp,
1047 void *content, char *cbtype, char *cbcharset, size_t length,
1048 char *encoding, char *cbid, void *cbuserdata)
1052 /* Silently go away if there's already a download open. */
1053 if (CC->download_fp != NULL)
1057 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1058 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1060 CC->download_fp = tmpfile();
1061 if (CC->download_fp == NULL)
1064 rv = fwrite(content, length, 1, CC->download_fp);
1065 fflush(CC->download_fp);
1066 rewind(CC->download_fp);
1068 OpenCmdResult(filename, cbtype);
1075 * Callback function for mime parser that outputs a section all at once.
1076 * We can specify the desired section by part number *or* content-id.
1078 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1079 void *content, char *cbtype, char *cbcharset, size_t length,
1080 char *encoding, char *cbid, void *cbuserdata)
1082 int *found_it = (int *)cbuserdata;
1085 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1086 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1089 cprintf("%d %d|-1|%s|%s|%s\n",
1096 client_write(content, length);
1103 * Load a message from disk into memory.
1104 * This is used by CtdlOutputMsg() and other fetch functions.
1106 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1107 * using the CtdlMessageFree() function.
1109 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1111 struct cdbdata *dmsgtext;
1112 struct CtdlMessage *ret = NULL;
1116 cit_uint8_t field_header;
1118 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1120 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1121 if (dmsgtext == NULL) {
1124 mptr = dmsgtext->ptr;
1125 upper_bound = mptr + dmsgtext->len;
1127 /* Parse the three bytes that begin EVERY message on disk.
1128 * The first is always 0xFF, the on-disk magic number.
1129 * The second is the anonymous/public type byte.
1130 * The third is the format type byte (vari, fixed, or MIME).
1134 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1138 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1139 memset(ret, 0, sizeof(struct CtdlMessage));
1141 ret->cm_magic = CTDLMESSAGE_MAGIC;
1142 ret->cm_anon_type = *mptr++; /* Anon type byte */
1143 ret->cm_format_type = *mptr++; /* Format type byte */
1146 * The rest is zero or more arbitrary fields. Load them in.
1147 * We're done when we encounter either a zero-length field or
1148 * have just processed the 'M' (message text) field.
1151 if (mptr >= upper_bound) {
1154 field_header = *mptr++;
1155 ret->cm_fields[field_header] = strdup(mptr);
1157 while (*mptr++ != 0); /* advance to next field */
1159 } while ((mptr < upper_bound) && (field_header != 'M'));
1163 /* Always make sure there's something in the msg text field. If
1164 * it's NULL, the message text is most likely stored separately,
1165 * so go ahead and fetch that. Failing that, just set a dummy
1166 * body so other code doesn't barf.
1168 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1169 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1170 if (dmsgtext != NULL) {
1171 ret->cm_fields['M'] = dmsgtext->ptr;
1172 dmsgtext->ptr = NULL;
1176 if (ret->cm_fields['M'] == NULL) {
1177 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1180 /* Perform "before read" hooks (aborting if any return nonzero) */
1181 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1182 CtdlFreeMessage(ret);
1191 * Returns 1 if the supplied pointer points to a valid Citadel message.
1192 * If the pointer is NULL or the magic number check fails, returns 0.
1194 int is_valid_message(struct CtdlMessage *msg) {
1197 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1198 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1206 * 'Destructor' for struct CtdlMessage
1208 void CtdlFreeMessage(struct CtdlMessage *msg)
1212 if (is_valid_message(msg) == 0)
1214 if (msg != NULL) free (msg);
1218 for (i = 0; i < 256; ++i)
1219 if (msg->cm_fields[i] != NULL) {
1220 free(msg->cm_fields[i]);
1223 msg->cm_magic = 0; /* just in case */
1229 * Pre callback function for multipart/alternative
1231 * NOTE: this differs from the standard behavior for a reason. Normally when
1232 * displaying multipart/alternative you want to show the _last_ usable
1233 * format in the message. Here we show the _first_ one, because it's
1234 * usually text/plain. Since this set of functions is designed for text
1235 * output to non-MIME-aware clients, this is the desired behavior.
1238 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1239 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1240 char *cbid, void *cbuserdata)
1244 ma = (struct ma_info *)cbuserdata;
1245 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1246 if (!strcasecmp(cbtype, "multipart/alternative")) {
1250 if (!strcasecmp(cbtype, "message/rfc822")) {
1256 * Post callback function for multipart/alternative
1258 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1259 void *content, char *cbtype, char *cbcharset, size_t length,
1260 char *encoding, char *cbid, void *cbuserdata)
1264 ma = (struct ma_info *)cbuserdata;
1265 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1266 if (!strcasecmp(cbtype, "multipart/alternative")) {
1270 if (!strcasecmp(cbtype, "message/rfc822")) {
1276 * Inline callback function for mime parser that wants to display text
1278 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1279 void *content, char *cbtype, char *cbcharset, size_t length,
1280 char *encoding, char *cbid, void *cbuserdata)
1287 ma = (struct ma_info *)cbuserdata;
1289 CtdlLogPrintf(CTDL_DEBUG,
1290 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1291 partnum, filename, cbtype, (long)length);
1294 * If we're in the middle of a multipart/alternative scope and
1295 * we've already printed another section, skip this one.
1297 if ( (ma->is_ma) && (ma->did_print) ) {
1298 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1303 if ( (!strcasecmp(cbtype, "text/plain"))
1304 || (IsEmptyStr(cbtype)) ) {
1307 client_write(wptr, length);
1308 if (wptr[length-1] != '\n') {
1315 if (!strcasecmp(cbtype, "text/html")) {
1316 ptr = html_to_ascii(content, length, 80, 0);
1318 client_write(ptr, wlen);
1319 if (ptr[wlen-1] != '\n') {
1326 if (ma->use_fo_hooks) {
1327 if (PerformFixedOutputHooks(cbtype, content, length)) {
1328 /* above function returns nonzero if it handled the part */
1333 if (strncasecmp(cbtype, "multipart/", 10)) {
1334 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1335 partnum, filename, cbtype, (long)length);
1341 * The client is elegant and sophisticated and wants to be choosy about
1342 * MIME content types, so figure out which multipart/alternative part
1343 * we're going to send.
1345 * We use a system of weights. When we find a part that matches one of the
1346 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1347 * and then set ma->chosen_pref to that MIME type's position in our preference
1348 * list. If we then hit another match, we only replace the first match if
1349 * the preference value is lower.
1351 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1352 void *content, char *cbtype, char *cbcharset, size_t length,
1353 char *encoding, char *cbid, void *cbuserdata)
1359 ma = (struct ma_info *)cbuserdata;
1361 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1362 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1363 // I don't know if there are any side effects! Please TEST TEST TEST
1364 //if (ma->is_ma > 0) {
1366 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1367 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1368 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1369 if (i < ma->chosen_pref) {
1370 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1371 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1372 ma->chosen_pref = i;
1379 * Now that we've chosen our preferred part, output it.
1381 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1382 void *content, char *cbtype, char *cbcharset, size_t length,
1383 char *encoding, char *cbid, void *cbuserdata)
1387 int add_newline = 0;
1391 ma = (struct ma_info *)cbuserdata;
1393 /* This is not the MIME part you're looking for... */
1394 if (strcasecmp(partnum, ma->chosen_part)) return;
1396 /* If the content-type of this part is in our preferred formats
1397 * list, we can simply output it verbatim.
1399 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1400 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1401 if (!strcasecmp(buf, cbtype)) {
1402 /* Yeah! Go! W00t!! */
1404 text_content = (char *)content;
1405 if (text_content[length-1] != '\n') {
1408 cprintf("Content-type: %s", cbtype);
1409 if (!IsEmptyStr(cbcharset)) {
1410 cprintf("; charset=%s", cbcharset);
1412 cprintf("\nContent-length: %d\n",
1413 (int)(length + add_newline) );
1414 if (!IsEmptyStr(encoding)) {
1415 cprintf("Content-transfer-encoding: %s\n", encoding);
1418 cprintf("Content-transfer-encoding: 7bit\n");
1420 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1422 client_write(content, length);
1423 if (add_newline) cprintf("\n");
1428 /* No translations required or possible: output as text/plain */
1429 cprintf("Content-type: text/plain\n\n");
1430 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1431 length, encoding, cbid, cbuserdata);
1436 char desired_section[64];
1443 * Callback function for
1445 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1446 void *content, char *cbtype, char *cbcharset, size_t length,
1447 char *encoding, char *cbid, void *cbuserdata)
1449 struct encapmsg *encap;
1451 encap = (struct encapmsg *)cbuserdata;
1453 /* Only proceed if this is the desired section... */
1454 if (!strcasecmp(encap->desired_section, partnum)) {
1455 encap->msglen = length;
1456 encap->msg = malloc(length + 2);
1457 memcpy(encap->msg, content, length);
1468 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1469 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1470 return(om_not_logged_in);
1477 * Get a message off disk. (returns om_* values found in msgbase.h)
1480 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1481 int mode, /* how would you like that message? */
1482 int headers_only, /* eschew the message body? */
1483 int do_proto, /* do Citadel protocol responses? */
1484 int crlf, /* Use CRLF newlines instead of LF? */
1485 char *section, /* NULL or a message/rfc822 section */
1486 int flags /* various flags; see msgbase.h */
1488 struct CtdlMessage *TheMessage = NULL;
1489 int retcode = om_no_such_msg;
1490 struct encapmsg encap;
1493 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1495 (section ? section : "<>")
1498 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1501 if (r == om_not_logged_in) {
1502 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1505 cprintf("%d An unknown error has occurred.\n", ERROR);
1511 /* FIXME: check message id against msglist for this room */
1514 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1515 * request that we don't even bother loading the body into memory.
1517 if (headers_only == HEADERS_FAST) {
1518 TheMessage = CtdlFetchMessage(msg_num, 0);
1521 TheMessage = CtdlFetchMessage(msg_num, 1);
1524 if (TheMessage == NULL) {
1525 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1526 ERROR + MESSAGE_NOT_FOUND, msg_num);
1527 return(om_no_such_msg);
1530 /* Here is the weird form of this command, to process only an
1531 * encapsulated message/rfc822 section.
1533 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1534 memset(&encap, 0, sizeof encap);
1535 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1536 mime_parser(TheMessage->cm_fields['M'],
1538 *extract_encapsulated_message,
1539 NULL, NULL, (void *)&encap, 0
1541 CtdlFreeMessage(TheMessage);
1545 encap.msg[encap.msglen] = 0;
1546 TheMessage = convert_internet_message(encap.msg);
1547 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1549 /* Now we let it fall through to the bottom of this
1550 * function, because TheMessage now contains the
1551 * encapsulated message instead of the top-level
1552 * message. Isn't that neat?
1557 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1558 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1559 retcode = om_no_such_msg;
1564 /* Ok, output the message now */
1565 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1566 CtdlFreeMessage(TheMessage);
1572 char *qp_encode_email_addrs(char *source)
1574 char *user, *node, *name;
1575 const char headerStr[] = "=?UTF-8?Q?";
1579 int need_to_encode = 0;
1585 long nAddrPtrMax = 50;
1590 if (source == NULL) return source;
1591 if (IsEmptyStr(source)) return source;
1593 CtdlLogPrintf(CTDL_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1595 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1596 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1597 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1600 while (!IsEmptyStr (&source[i])) {
1601 if (nColons >= nAddrPtrMax){
1604 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1605 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1606 free (AddrPtr), AddrPtr = ptr;
1608 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1609 memset(&ptr[nAddrPtrMax], 0,
1610 sizeof (long) * nAddrPtrMax);
1612 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1613 free (AddrUtf8), AddrUtf8 = ptr;
1616 if (((unsigned char) source[i] < 32) ||
1617 ((unsigned char) source[i] > 126)) {
1619 AddrUtf8[nColons] = 1;
1621 if (source[i] == '"')
1622 InQuotes = !InQuotes;
1623 if (!InQuotes && source[i] == ',') {
1624 AddrPtr[nColons] = i;
1629 if (need_to_encode == 0) {
1636 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1637 Encoded = (char*) malloc (EncodedMaxLen);
1639 for (i = 0; i < nColons; i++)
1640 source[AddrPtr[i]++] = '\0';
1641 /* TODO: if libidn, this might get larger*/
1642 user = malloc(SourceLen + 1);
1643 node = malloc(SourceLen + 1);
1644 name = malloc(SourceLen + 1);
1648 for (i = 0; i < nColons && nPtr != NULL; i++) {
1649 nmax = EncodedMaxLen - (nPtr - Encoded);
1651 process_rfc822_addr(&source[AddrPtr[i]],
1655 /* TODO: libIDN here ! */
1656 if (IsEmptyStr(name)) {
1657 n = snprintf(nPtr, nmax,
1658 (i==0)?"%s@%s" : ",%s@%s",
1662 EncodedName = rfc2047encode(name, strlen(name));
1663 n = snprintf(nPtr, nmax,
1664 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1665 EncodedName, user, node);
1670 n = snprintf(nPtr, nmax,
1671 (i==0)?"%s" : ",%s",
1672 &source[AddrPtr[i]]);
1678 ptr = (char*) malloc(EncodedMaxLen * 2);
1679 memcpy(ptr, Encoded, EncodedMaxLen);
1680 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1681 free(Encoded), Encoded = ptr;
1683 i--; /* do it once more with properly lengthened buffer */
1686 for (i = 0; i < nColons; i++)
1687 source[--AddrPtr[i]] = ',';
1698 /* If the last item in a list of recipients was truncated to a partial address,
1699 * remove it completely in order to avoid choking libSieve
1701 void sanitize_truncated_recipient(char *str)
1704 if (num_tokens(str, ',') < 2) return;
1706 int len = strlen(str);
1707 if (len < 900) return;
1708 if (len > 998) str[998] = 0;
1710 char *cptr = strrchr(str, ',');
1713 char *lptr = strchr(cptr, '<');
1714 char *rptr = strchr(cptr, '>');
1716 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1722 void OutputCtdlMsgHeaders(
1723 struct CtdlMessage *TheMessage,
1724 int do_proto) /* do Citadel protocol responses? */
1730 char display_name[256];
1732 /* begin header processing loop for Citadel message format */
1733 safestrncpy(display_name, "<unknown>", sizeof display_name);
1734 if (TheMessage->cm_fields['A']) {
1735 strcpy(buf, TheMessage->cm_fields['A']);
1736 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1737 safestrncpy(display_name, "****", sizeof display_name);
1739 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1740 safestrncpy(display_name, "anonymous", sizeof display_name);
1743 safestrncpy(display_name, buf, sizeof display_name);
1745 if ((is_room_aide())
1746 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1747 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1748 size_t tmp = strlen(display_name);
1749 snprintf(&display_name[tmp],
1750 sizeof display_name - tmp,
1755 /* Don't show Internet address for users on the
1756 * local Citadel network.
1759 if (TheMessage->cm_fields['N'] != NULL)
1760 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1761 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1765 /* Now spew the header fields in the order we like them. */
1766 n = safestrncpy(allkeys, FORDER, sizeof allkeys);
1767 for (i=0; i<n; ++i) {
1768 k = (int) allkeys[i];
1770 if ( (TheMessage->cm_fields[k] != NULL)
1771 && (msgkeys[k] != NULL) ) {
1772 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1773 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1776 if (do_proto) cprintf("%s=%s\n",
1780 else if ((k == 'F') && (suppress_f)) {
1783 /* Masquerade display name if needed */
1785 if (do_proto) cprintf("%s=%s\n",
1787 TheMessage->cm_fields[k]
1796 void OutputRFC822MsgHeaders(
1797 struct CtdlMessage *TheMessage,
1798 int flags, /* should the bessage be exported clean */
1800 char *mid, long sizeof_mid,
1801 char *suser, long sizeof_suser,
1802 char *luser, long sizeof_luser,
1803 char *fuser, long sizeof_fuser,
1804 char *snode, long sizeof_snode)
1806 char datestamp[100];
1807 int subject_found = 0;
1813 for (i = 0; i < 256; ++i) {
1814 if (TheMessage->cm_fields[i]) {
1815 mptr = mpptr = TheMessage->cm_fields[i];
1818 safestrncpy(luser, mptr, sizeof_luser);
1819 safestrncpy(suser, mptr, sizeof_suser);
1821 else if (i == 'Y') {
1822 if ((flags & QP_EADDR) != 0) {
1823 mptr = qp_encode_email_addrs(mptr);
1825 sanitize_truncated_recipient(mptr);
1826 cprintf("CC: %s%s", mptr, nl);
1828 else if (i == 'P') {
1829 cprintf("Return-Path: %s%s", mptr, nl);
1831 else if (i == 'L') {
1832 cprintf("List-ID: %s%s", mptr, nl);
1834 else if (i == 'V') {
1835 if ((flags & QP_EADDR) != 0)
1836 mptr = qp_encode_email_addrs(mptr);
1837 cprintf("Envelope-To: %s%s", mptr, nl);
1839 else if (i == 'U') {
1840 cprintf("Subject: %s%s", mptr, nl);
1844 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1846 safestrncpy(fuser, mptr, sizeof_fuser);
1847 /* else if (i == 'O')
1848 cprintf("X-Citadel-Room: %s%s",
1851 safestrncpy(snode, mptr, sizeof_snode);
1854 if (haschar(mptr, '@') == 0)
1856 sanitize_truncated_recipient(mptr);
1857 cprintf("To: %s@%s", mptr, config.c_fqdn);
1862 if ((flags & QP_EADDR) != 0) {
1863 mptr = qp_encode_email_addrs(mptr);
1865 sanitize_truncated_recipient(mptr);
1866 cprintf("To: %s", mptr);
1870 else if (i == 'T') {
1871 datestring(datestamp, sizeof datestamp,
1872 atol(mptr), DATESTRING_RFC822);
1873 cprintf("Date: %s%s", datestamp, nl);
1875 else if (i == 'W') {
1876 cprintf("References: ");
1877 k = num_tokens(mptr, '|');
1878 for (j=0; j<k; ++j) {
1879 extract_token(buf, mptr, j, '|', sizeof buf);
1880 cprintf("<%s>", buf);
1889 else if (i == 'K') {
1890 cprintf("Reply-To: <%s>%s", mptr, nl);
1896 if (subject_found == 0) {
1897 cprintf("Subject: (no subject)%s", nl);
1902 void Dump_RFC822HeadersBody(
1903 struct CtdlMessage *TheMessage,
1904 int headers_only, /* eschew the message body? */
1905 int flags, /* should the bessage be exported clean? */
1909 cit_uint8_t prev_ch;
1911 const char *StartOfText = StrBufNOTNULL;
1914 int nllen = strlen(nl);
1917 mptr = TheMessage->cm_fields['M'];
1921 while (*mptr != '\0') {
1922 if (*mptr == '\r') {
1929 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1931 eoh = *(mptr+1) == '\n';
1935 StartOfText = strchr(StartOfText, '\n');
1936 StartOfText = strchr(StartOfText, '\n');
1939 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1940 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1941 ((headers_only != HEADERS_NONE) &&
1942 (headers_only != HEADERS_ONLY))
1944 if (*mptr == '\n') {
1945 memcpy(&outbuf[outlen], nl, nllen);
1947 outbuf[outlen] = '\0';
1950 outbuf[outlen++] = *mptr;
1954 if (flags & ESC_DOT)
1956 if ((prev_ch == '\n') &&
1958 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
1960 outbuf[outlen++] = '.';
1965 if (outlen > 1000) {
1966 client_write(outbuf, outlen);
1971 client_write(outbuf, outlen);
1978 /* If the format type on disk is 1 (fixed-format), then we want
1979 * everything to be output completely literally ... regardless of
1980 * what message transfer format is in use.
1982 void DumpFormatFixed(
1983 struct CtdlMessage *TheMessage,
1984 int mode, /* how would you like that message? */
1991 int nllen = strlen (nl);
1994 mptr = TheMessage->cm_fields['M'];
1996 if (mode == MT_MIME) {
1997 cprintf("Content-type: text/plain\n\n");
2001 while (ch = *mptr++, ch > 0) {
2005 if ((buflen > 250) && (!xlline)){
2009 while ((buflen > 0) &&
2010 (!isspace(buf[buflen])))
2016 mptr -= tbuflen - buflen;
2021 /* if we reach the outer bounds of our buffer,
2022 abort without respect what whe purge. */
2025 (buflen > SIZ - nllen - 2)))
2029 memcpy (&buf[buflen], nl, nllen);
2033 client_write(buf, buflen);
2043 if (!IsEmptyStr(buf))
2044 cprintf("%s%s", buf, nl);
2048 * Get a message off disk. (returns om_* values found in msgbase.h)
2050 int CtdlOutputPreLoadedMsg(
2051 struct CtdlMessage *TheMessage,
2052 int mode, /* how would you like that message? */
2053 int headers_only, /* eschew the message body? */
2054 int do_proto, /* do Citadel protocol responses? */
2055 int crlf, /* Use CRLF newlines instead of LF? */
2056 int flags /* should the bessage be exported clean? */
2060 const char *nl; /* newline string */
2063 /* Buffers needed for RFC822 translation. These are all filled
2064 * using functions that are bounds-checked, and therefore we can
2065 * make them substantially smaller than SIZ.
2073 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2074 ((TheMessage == NULL) ? "NULL" : "not null"),
2075 mode, headers_only, do_proto, crlf);
2077 strcpy(mid, "unknown");
2078 nl = (crlf ? "\r\n" : "\n");
2080 if (!is_valid_message(TheMessage)) {
2081 CtdlLogPrintf(CTDL_ERR,
2082 "ERROR: invalid preloaded message for output\n");
2084 return(om_no_such_msg);
2087 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2088 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2090 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2091 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2094 /* Are we downloading a MIME component? */
2095 if (mode == MT_DOWNLOAD) {
2096 if (TheMessage->cm_format_type != FMT_RFC822) {
2098 cprintf("%d This is not a MIME message.\n",
2099 ERROR + ILLEGAL_VALUE);
2100 } else if (CC->download_fp != NULL) {
2101 if (do_proto) cprintf(
2102 "%d You already have a download open.\n",
2103 ERROR + RESOURCE_BUSY);
2105 /* Parse the message text component */
2106 mptr = TheMessage->cm_fields['M'];
2107 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2108 /* If there's no file open by this time, the requested
2109 * section wasn't found, so print an error
2111 if (CC->download_fp == NULL) {
2112 if (do_proto) cprintf(
2113 "%d Section %s not found.\n",
2114 ERROR + FILE_NOT_FOUND,
2115 CC->download_desired_section);
2118 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2121 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2122 * in a single server operation instead of opening a download file.
2124 if (mode == MT_SPEW_SECTION) {
2125 if (TheMessage->cm_format_type != FMT_RFC822) {
2127 cprintf("%d This is not a MIME message.\n",
2128 ERROR + ILLEGAL_VALUE);
2130 /* Parse the message text component */
2133 mptr = TheMessage->cm_fields['M'];
2134 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2135 /* If section wasn't found, print an error
2138 if (do_proto) cprintf(
2139 "%d Section %s not found.\n",
2140 ERROR + FILE_NOT_FOUND,
2141 CC->download_desired_section);
2144 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2147 /* now for the user-mode message reading loops */
2148 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2150 /* Does the caller want to skip the headers? */
2151 if (headers_only == HEADERS_NONE) goto START_TEXT;
2153 /* Tell the client which format type we're using. */
2154 if ( (mode == MT_CITADEL) && (do_proto) ) {
2155 cprintf("type=%d\n", TheMessage->cm_format_type);
2158 /* nhdr=yes means that we're only displaying headers, no body */
2159 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2160 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2163 cprintf("nhdr=yes\n");
2166 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2167 OutputCtdlMsgHeaders(TheMessage, do_proto);
2170 /* begin header processing loop for RFC822 transfer format */
2174 strcpy(snode, NODENAME);
2175 if (mode == MT_RFC822)
2176 OutputRFC822MsgHeaders(
2181 suser, sizeof(suser),
2182 luser, sizeof(luser),
2183 fuser, sizeof(fuser),
2184 snode, sizeof(snode)
2188 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2189 suser[i] = tolower(suser[i]);
2190 if (!isalnum(suser[i])) suser[i]='_';
2193 if (mode == MT_RFC822) {
2194 if (!strcasecmp(snode, NODENAME)) {
2195 safestrncpy(snode, FQDN, sizeof snode);
2198 /* Construct a fun message id */
2199 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2200 if (strchr(mid, '@')==NULL) {
2201 cprintf("@%s", snode);
2205 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2206 cprintf("From: \"----\" <x@x.org>%s", nl);
2208 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2209 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2211 else if (!IsEmptyStr(fuser)) {
2212 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2215 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2218 /* Blank line signifying RFC822 end-of-headers */
2219 if (TheMessage->cm_format_type != FMT_RFC822) {
2224 /* end header processing loop ... at this point, we're in the text */
2226 if (headers_only == HEADERS_FAST) goto DONE;
2228 /* Tell the client about the MIME parts in this message */
2229 if (TheMessage->cm_format_type == FMT_RFC822) {
2230 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2231 mptr = TheMessage->cm_fields['M'];
2232 memset(&ma, 0, sizeof(struct ma_info));
2233 mime_parser(mptr, NULL,
2234 (do_proto ? *list_this_part : NULL),
2235 (do_proto ? *list_this_pref : NULL),
2236 (do_proto ? *list_this_suff : NULL),
2239 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2240 Dump_RFC822HeadersBody(
2249 if (headers_only == HEADERS_ONLY) {
2253 /* signify start of msg text */
2254 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2255 if (do_proto) cprintf("text\n");
2258 if (TheMessage->cm_format_type == FMT_FIXED)
2261 mode, /* how would you like that message? */
2264 /* If the message on disk is format 0 (Citadel vari-format), we
2265 * output using the formatter at 80 columns. This is the final output
2266 * form if the transfer format is RFC822, but if the transfer format
2267 * is Citadel proprietary, it'll still work, because the indentation
2268 * for new paragraphs is correct and the client will reformat the
2269 * message to the reader's screen width.
2271 if (TheMessage->cm_format_type == FMT_CITADEL) {
2272 mptr = TheMessage->cm_fields['M'];
2274 if (mode == MT_MIME) {
2275 cprintf("Content-type: text/x-citadel-variformat\n\n");
2280 /* If the message on disk is format 4 (MIME), we've gotta hand it
2281 * off to the MIME parser. The client has already been told that
2282 * this message is format 1 (fixed format), so the callback function
2283 * we use will display those parts as-is.
2285 if (TheMessage->cm_format_type == FMT_RFC822) {
2286 memset(&ma, 0, sizeof(struct ma_info));
2288 if (mode == MT_MIME) {
2289 ma.use_fo_hooks = 0;
2290 strcpy(ma.chosen_part, "1");
2291 ma.chosen_pref = 9999;
2292 mime_parser(mptr, NULL,
2293 *choose_preferred, *fixed_output_pre,
2294 *fixed_output_post, (void *)&ma, 0);
2295 mime_parser(mptr, NULL,
2296 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2299 ma.use_fo_hooks = 1;
2300 mime_parser(mptr, NULL,
2301 *fixed_output, *fixed_output_pre,
2302 *fixed_output_post, (void *)&ma, 0);
2307 DONE: /* now we're done */
2308 if (do_proto) cprintf("000\n");
2314 * display a message (mode 0 - Citadel proprietary)
2316 void cmd_msg0(char *cmdbuf)
2319 int headers_only = HEADERS_ALL;
2321 msgid = extract_long(cmdbuf, 0);
2322 headers_only = extract_int(cmdbuf, 1);
2324 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2330 * display a message (mode 2 - RFC822)
2332 void cmd_msg2(char *cmdbuf)
2335 int headers_only = HEADERS_ALL;
2337 msgid = extract_long(cmdbuf, 0);
2338 headers_only = extract_int(cmdbuf, 1);
2340 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2346 * display a message (mode 3 - IGnet raw format - internal programs only)
2348 void cmd_msg3(char *cmdbuf)
2351 struct CtdlMessage *msg = NULL;
2354 if (CC->internal_pgm == 0) {
2355 cprintf("%d This command is for internal programs only.\n",
2356 ERROR + HIGHER_ACCESS_REQUIRED);
2360 msgnum = extract_long(cmdbuf, 0);
2361 msg = CtdlFetchMessage(msgnum, 1);
2363 cprintf("%d Message %ld not found.\n",
2364 ERROR + MESSAGE_NOT_FOUND, msgnum);
2368 serialize_message(&smr, msg);
2369 CtdlFreeMessage(msg);
2372 cprintf("%d Unable to serialize message\n",
2373 ERROR + INTERNAL_ERROR);
2377 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2378 client_write((char *)smr.ser, (int)smr.len);
2385 * Display a message using MIME content types
2387 void cmd_msg4(char *cmdbuf)
2392 msgid = extract_long(cmdbuf, 0);
2393 extract_token(section, cmdbuf, 1, '|', sizeof section);
2394 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2400 * Client tells us its preferred message format(s)
2402 void cmd_msgp(char *cmdbuf)
2404 if (!strcasecmp(cmdbuf, "dont_decode")) {
2405 CC->msg4_dont_decode = 1;
2406 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2409 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2410 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2416 * Open a component of a MIME message as a download file
2418 void cmd_opna(char *cmdbuf)
2421 char desired_section[128];
2423 msgid = extract_long(cmdbuf, 0);
2424 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2425 safestrncpy(CC->download_desired_section, desired_section,
2426 sizeof CC->download_desired_section);
2427 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2432 * Open a component of a MIME message and transmit it all at once
2434 void cmd_dlat(char *cmdbuf)
2437 char desired_section[128];
2439 msgid = extract_long(cmdbuf, 0);
2440 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2441 safestrncpy(CC->download_desired_section, desired_section,
2442 sizeof CC->download_desired_section);
2443 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2448 * Save one or more message pointers into a specified room
2449 * (Returns 0 for success, nonzero for failure)
2450 * roomname may be NULL to use the current room
2452 * Note that the 'supplied_msg' field may be set to NULL, in which case
2453 * the message will be fetched from disk, by number, if we need to perform
2454 * replication checks. This adds an additional database read, so if the
2455 * caller already has the message in memory then it should be supplied. (Obviously
2456 * this mode of operation only works if we're saving a single message.)
2458 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2459 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2462 char hold_rm[ROOMNAMELEN];
2463 struct cdbdata *cdbfr;
2466 long highest_msg = 0L;
2469 struct CtdlMessage *msg = NULL;
2471 long *msgs_to_be_merged = NULL;
2472 int num_msgs_to_be_merged = 0;
2474 CtdlLogPrintf(CTDL_DEBUG,
2475 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2476 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2479 strcpy(hold_rm, CC->room.QRname);
2482 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2483 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2484 if (num_newmsgs > 1) supplied_msg = NULL;
2486 /* Now the regular stuff */
2487 if (CtdlGetRoomLock(&CC->room,
2488 ((roomname != NULL) ? roomname : CC->room.QRname) )
2490 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2491 return(ERROR + ROOM_NOT_FOUND);
2495 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2496 num_msgs_to_be_merged = 0;
2499 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2500 if (cdbfr == NULL) {
2504 msglist = (long *) cdbfr->ptr;
2505 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2506 num_msgs = cdbfr->len / sizeof(long);
2511 /* Create a list of msgid's which were supplied by the caller, but do
2512 * not already exist in the target room. It is absolutely taboo to
2513 * have more than one reference to the same message in a room.
2515 for (i=0; i<num_newmsgs; ++i) {
2517 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2518 if (msglist[j] == newmsgidlist[i]) {
2523 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2527 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2530 * Now merge the new messages
2532 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2533 if (msglist == NULL) {
2534 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2536 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2537 num_msgs += num_msgs_to_be_merged;
2539 /* Sort the message list, so all the msgid's are in order */
2540 num_msgs = sort_msglist(msglist, num_msgs);
2542 /* Determine the highest message number */
2543 highest_msg = msglist[num_msgs - 1];
2545 /* Write it back to disk. */
2546 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2547 msglist, (int)(num_msgs * sizeof(long)));
2549 /* Free up the memory we used. */
2552 /* Update the highest-message pointer and unlock the room. */
2553 CC->room.QRhighest = highest_msg;
2554 CtdlPutRoomLock(&CC->room);
2556 /* Perform replication checks if necessary */
2557 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2558 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2560 for (i=0; i<num_msgs_to_be_merged; ++i) {
2561 msgid = msgs_to_be_merged[i];
2563 if (supplied_msg != NULL) {
2567 msg = CtdlFetchMessage(msgid, 0);
2571 ReplicationChecks(msg);
2573 /* If the message has an Exclusive ID, index that... */
2574 if (msg->cm_fields['E'] != NULL) {
2575 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2578 /* Free up the memory we may have allocated */
2579 if (msg != supplied_msg) {
2580 CtdlFreeMessage(msg);
2588 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2591 /* Submit this room for processing by hooks */
2592 PerformRoomHooks(&CC->room);
2594 /* Go back to the room we were in before we wandered here... */
2595 CtdlGetRoom(&CC->room, hold_rm);
2597 /* Bump the reference count for all messages which were merged */
2598 if (!suppress_refcount_adj) {
2599 for (i=0; i<num_msgs_to_be_merged; ++i) {
2600 AdjRefCount(msgs_to_be_merged[i], +1);
2604 /* Free up memory... */
2605 if (msgs_to_be_merged != NULL) {
2606 free(msgs_to_be_merged);
2609 /* Return success. */
2615 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2618 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2619 int do_repl_check, struct CtdlMessage *supplied_msg)
2621 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2628 * Message base operation to save a new message to the message store
2629 * (returns new message number)
2631 * This is the back end for CtdlSubmitMsg() and should not be directly
2632 * called by server-side modules.
2635 long send_message(struct CtdlMessage *msg) {
2643 /* Get a new message number */
2644 newmsgid = get_new_message_number();
2645 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2647 /* Generate an ID if we don't have one already */
2648 if (msg->cm_fields['I']==NULL) {
2649 msg->cm_fields['I'] = strdup(msgidbuf);
2652 /* If the message is big, set its body aside for storage elsewhere */
2653 if (msg->cm_fields['M'] != NULL) {
2654 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2656 holdM = msg->cm_fields['M'];
2657 msg->cm_fields['M'] = NULL;
2661 /* Serialize our data structure for storage in the database */
2662 serialize_message(&smr, msg);
2665 msg->cm_fields['M'] = holdM;
2669 cprintf("%d Unable to serialize message\n",
2670 ERROR + INTERNAL_ERROR);
2674 /* Write our little bundle of joy into the message base */
2675 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2676 smr.ser, smr.len) < 0) {
2677 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2681 cdb_store(CDB_BIGMSGS,
2691 /* Free the memory we used for the serialized message */
2694 /* Return the *local* message ID to the caller
2695 * (even if we're storing an incoming network message)
2703 * Serialize a struct CtdlMessage into the format used on disk and network.
2705 * This function loads up a "struct ser_ret" (defined in server.h) which
2706 * contains the length of the serialized message and a pointer to the
2707 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2709 void serialize_message(struct ser_ret *ret, /* return values */
2710 struct CtdlMessage *msg) /* unserialized msg */
2712 size_t wlen, fieldlen;
2714 static char *forder = FORDER;
2717 * Check for valid message format
2719 if (is_valid_message(msg) == 0) {
2720 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2727 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2728 ret->len = ret->len +
2729 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2731 ret->ser = malloc(ret->len);
2732 if (ret->ser == NULL) {
2733 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2734 (long)ret->len, strerror(errno));
2741 ret->ser[1] = msg->cm_anon_type;
2742 ret->ser[2] = msg->cm_format_type;
2745 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2746 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2747 ret->ser[wlen++] = (char)forder[i];
2748 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2749 wlen = wlen + fieldlen + 1;
2751 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2752 (long)ret->len, (long)wlen);
2759 * Serialize a struct CtdlMessage into the format used on disk and network.
2761 * This function loads up a "struct ser_ret" (defined in server.h) which
2762 * contains the length of the serialized message and a pointer to the
2763 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2765 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2766 long Siz) /* how many chars ? */
2770 static char *forder = FORDER;
2774 * Check for valid message format
2776 if (is_valid_message(msg) == 0) {
2777 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2781 buf = (char*) malloc (Siz + 1);
2785 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2786 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2787 msg->cm_fields[(int)forder[i]]);
2788 client_write (buf, strlen(buf));
2797 * Check to see if any messages already exist in the current room which
2798 * carry the same Exclusive ID as this one. If any are found, delete them.
2800 void ReplicationChecks(struct CtdlMessage *msg) {
2801 long old_msgnum = (-1L);
2803 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2805 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2808 /* No exclusive id? Don't do anything. */
2809 if (msg == NULL) return;
2810 if (msg->cm_fields['E'] == NULL) return;
2811 if (IsEmptyStr(msg->cm_fields['E'])) return;
2812 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2813 msg->cm_fields['E'], CC->room.QRname);*/
2815 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2816 if (old_msgnum > 0L) {
2817 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2818 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2825 * Save a message to disk and submit it into the delivery system.
2827 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2828 struct recptypes *recps, /* recipients (if mail) */
2829 char *force, /* force a particular room? */
2830 int flags /* should the message be exported clean? */
2832 char submit_filename[128];
2833 char generated_timestamp[32];
2834 char hold_rm[ROOMNAMELEN];
2835 char actual_rm[ROOMNAMELEN];
2836 char force_room[ROOMNAMELEN];
2837 char content_type[SIZ]; /* We have to learn this */
2838 char recipient[SIZ];
2840 const char *mptr = NULL;
2841 struct ctdluser userbuf;
2843 struct MetaData smi;
2844 FILE *network_fp = NULL;
2845 static int seqnum = 1;
2846 struct CtdlMessage *imsg = NULL;
2848 size_t instr_alloc = 0;
2850 char *hold_R, *hold_D;
2851 char *collected_addresses = NULL;
2852 struct addresses_to_be_filed *aptr = NULL;
2853 StrBuf *saved_rfc822_version = NULL;
2854 int qualified_for_journaling = 0;
2855 CitContext *CCC = MyContext();
2856 char bounce_to[1024] = "";
2860 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2861 if (is_valid_message(msg) == 0) return(-1); /* self check */
2863 /* If this message has no timestamp, we take the liberty of
2864 * giving it one, right now.
2866 if (msg->cm_fields['T'] == NULL) {
2867 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2868 msg->cm_fields['T'] = strdup(generated_timestamp);
2871 /* If this message has no path, we generate one.
2873 if (msg->cm_fields['P'] == NULL) {
2874 if (msg->cm_fields['A'] != NULL) {
2875 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2876 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2877 if (isspace(msg->cm_fields['P'][a])) {
2878 msg->cm_fields['P'][a] = ' ';
2883 msg->cm_fields['P'] = strdup("unknown");
2887 if (force == NULL) {
2888 strcpy(force_room, "");
2891 strcpy(force_room, force);
2894 /* Learn about what's inside, because it's what's inside that counts */
2895 if (msg->cm_fields['M'] == NULL) {
2896 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2900 switch (msg->cm_format_type) {
2902 strcpy(content_type, "text/x-citadel-variformat");
2905 strcpy(content_type, "text/plain");
2908 strcpy(content_type, "text/plain");
2909 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2912 safestrncpy(content_type, &mptr[13], sizeof content_type);
2913 striplt(content_type);
2914 aptr = content_type;
2915 while (!IsEmptyStr(aptr)) {
2927 /* Goto the correct room */
2928 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2929 strcpy(hold_rm, CCC->room.QRname);
2930 strcpy(actual_rm, CCC->room.QRname);
2931 if (recps != NULL) {
2932 strcpy(actual_rm, SENTITEMS);
2935 /* If the user is a twit, move to the twit room for posting */
2937 if (CCC->user.axlevel == AxProbU) {
2938 strcpy(hold_rm, actual_rm);
2939 strcpy(actual_rm, config.c_twitroom);
2940 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2944 /* ...or if this message is destined for Aide> then go there. */
2945 if (!IsEmptyStr(force_room)) {
2946 strcpy(actual_rm, force_room);
2949 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2950 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2951 /* CtdlGetRoom(&CCC->room, actual_rm); */
2952 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
2956 * If this message has no O (room) field, generate one.
2958 if (msg->cm_fields['O'] == NULL) {
2959 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2962 /* Perform "before save" hooks (aborting if any return nonzero) */
2963 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2964 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2967 * If this message has an Exclusive ID, and the room is replication
2968 * checking enabled, then do replication checks.
2970 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2971 ReplicationChecks(msg);
2974 /* Save it to disk */
2975 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2976 newmsgid = send_message(msg);
2977 if (newmsgid <= 0L) return(-5);
2979 /* Write a supplemental message info record. This doesn't have to
2980 * be a critical section because nobody else knows about this message
2983 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2984 memset(&smi, 0, sizeof(struct MetaData));
2985 smi.meta_msgnum = newmsgid;
2986 smi.meta_refcount = 0;
2987 safestrncpy(smi.meta_content_type, content_type,
2988 sizeof smi.meta_content_type);
2991 * Measure how big this message will be when rendered as RFC822.
2992 * We do this for two reasons:
2993 * 1. We need the RFC822 length for the new metadata record, so the
2994 * POP and IMAP services don't have to calculate message lengths
2995 * while the user is waiting (multiplied by potentially hundreds
2996 * or thousands of messages).
2997 * 2. If journaling is enabled, we will need an RFC822 version of the
2998 * message to attach to the journalized copy.
3000 if (CCC->redirect_buffer != NULL) {
3001 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3004 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3005 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3006 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3007 saved_rfc822_version = CCC->redirect_buffer;
3008 CCC->redirect_buffer = NULL;
3012 /* Now figure out where to store the pointers */
3013 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
3015 /* If this is being done by the networker delivering a private
3016 * message, we want to BYPASS saving the sender's copy (because there
3017 * is no local sender; it would otherwise go to the Trashcan).
3019 if ((!CCC->internal_pgm) || (recps == NULL)) {
3020 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3021 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
3022 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3026 /* For internet mail, drop a copy in the outbound queue room */
3027 if ((recps != NULL) && (recps->num_internet > 0)) {
3028 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3031 /* If other rooms are specified, drop them there too. */
3032 if ((recps != NULL) && (recps->num_room > 0))
3033 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3034 extract_token(recipient, recps->recp_room, i,
3035 '|', sizeof recipient);
3036 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
3037 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3040 /* Bump this user's messages posted counter. */
3041 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
3042 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3043 CCC->user.posted = CCC->user.posted + 1;
3044 CtdlPutUserLock(&CCC->user);
3046 /* Decide where bounces need to be delivered */
3047 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3048 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3050 else if (CCC->logged_in) {
3051 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3054 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3057 /* If this is private, local mail, make a copy in the
3058 * recipient's mailbox and bump the reference count.
3060 if ((recps != NULL) && (recps->num_local > 0))
3061 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3062 extract_token(recipient, recps->recp_local, i,
3063 '|', sizeof recipient);
3064 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
3066 if (CtdlGetUser(&userbuf, recipient) == 0) {
3067 // Add a flag so the Funambol module knows its mail
3068 msg->cm_fields['W'] = strdup(recipient);
3069 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3070 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3071 CtdlBumpNewMailCounter(userbuf.usernum);
3072 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3073 /* Generate a instruction message for the Funambol notification
3074 * server, in the same style as the SMTP queue
3077 instr = malloc(instr_alloc);
3078 snprintf(instr, instr_alloc,
3079 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3081 SPOOLMIME, newmsgid, (long)time(NULL),
3085 imsg = malloc(sizeof(struct CtdlMessage));
3086 memset(imsg, 0, sizeof(struct CtdlMessage));
3087 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3088 imsg->cm_anon_type = MES_NORMAL;
3089 imsg->cm_format_type = FMT_RFC822;
3090 imsg->cm_fields['A'] = strdup("Citadel");
3091 imsg->cm_fields['J'] = strdup("do not journal");
3092 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3093 imsg->cm_fields['W'] = strdup(recipient);
3094 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3095 CtdlFreeMessage(imsg);
3099 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
3100 CtdlSaveMsgPointerInRoom(config.c_aideroom,
3105 /* Perform "after save" hooks */
3106 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
3107 PerformMessageHooks(msg, EVT_AFTERSAVE);
3109 /* For IGnet mail, we have to save a new copy into the spooler for
3110 * each recipient, with the R and D fields set to the recipient and
3111 * destination-node. This has two ugly side effects: all other
3112 * recipients end up being unlisted in this recipient's copy of the
3113 * message, and it has to deliver multiple messages to the same
3114 * node. We'll revisit this again in a year or so when everyone has
3115 * a network spool receiver that can handle the new style messages.
3117 if ((recps != NULL) && (recps->num_ignet > 0))
3118 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3119 extract_token(recipient, recps->recp_ignet, i,
3120 '|', sizeof recipient);
3122 hold_R = msg->cm_fields['R'];
3123 hold_D = msg->cm_fields['D'];
3124 msg->cm_fields['R'] = malloc(SIZ);
3125 msg->cm_fields['D'] = malloc(128);
3126 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3127 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3129 serialize_message(&smr, msg);
3131 snprintf(submit_filename, sizeof submit_filename,
3132 "%s/netmail.%04lx.%04x.%04x",
3134 (long) getpid(), CCC->cs_pid, ++seqnum);
3135 network_fp = fopen(submit_filename, "wb+");
3136 if (network_fp != NULL) {
3137 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3143 free(msg->cm_fields['R']);
3144 free(msg->cm_fields['D']);
3145 msg->cm_fields['R'] = hold_R;
3146 msg->cm_fields['D'] = hold_D;
3149 /* Go back to the room we started from */
3150 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3151 if (strcasecmp(hold_rm, CCC->room.QRname))
3152 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3154 /* For internet mail, generate delivery instructions.
3155 * Yes, this is recursive. Deal with it. Infinite recursion does
3156 * not happen because the delivery instructions message does not
3157 * contain a recipient.
3159 if ((recps != NULL) && (recps->num_internet > 0)) {
3160 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3162 instr = malloc(instr_alloc);
3163 snprintf(instr, instr_alloc,
3164 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3166 SPOOLMIME, newmsgid, (long)time(NULL),
3170 if (recps->envelope_from != NULL) {
3171 tmp = strlen(instr);
3172 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3175 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3176 tmp = strlen(instr);
3177 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3178 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3179 instr_alloc = instr_alloc * 2;
3180 instr = realloc(instr, instr_alloc);
3182 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3185 imsg = malloc(sizeof(struct CtdlMessage));
3186 memset(imsg, 0, sizeof(struct CtdlMessage));
3187 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3188 imsg->cm_anon_type = MES_NORMAL;
3189 imsg->cm_format_type = FMT_RFC822;
3190 imsg->cm_fields['A'] = strdup("Citadel");
3191 imsg->cm_fields['J'] = strdup("do not journal");
3192 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3193 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3194 CtdlFreeMessage(imsg);
3198 * Any addresses to harvest for someone's address book?
3200 if ( (CCC->logged_in) && (recps != NULL) ) {
3201 collected_addresses = harvest_collected_addresses(msg);
3204 if (collected_addresses != NULL) {
3205 aptr = (struct addresses_to_be_filed *)
3206 malloc(sizeof(struct addresses_to_be_filed));
3207 CtdlMailboxName(actual_rm, sizeof actual_rm,
3208 &CCC->user, USERCONTACTSROOM);
3209 aptr->roomname = strdup(actual_rm);
3210 aptr->collected_addresses = collected_addresses;
3211 begin_critical_section(S_ATBF);
3214 end_critical_section(S_ATBF);
3218 * Determine whether this message qualifies for journaling.
3220 if (msg->cm_fields['J'] != NULL) {
3221 qualified_for_journaling = 0;
3224 if (recps == NULL) {
3225 qualified_for_journaling = config.c_journal_pubmsgs;
3227 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3228 qualified_for_journaling = config.c_journal_email;
3231 qualified_for_journaling = config.c_journal_pubmsgs;
3236 * Do we have to perform journaling? If so, hand off the saved
3237 * RFC822 version will be handed off to the journaler for background
3238 * submit. Otherwise, we have to free the memory ourselves.
3240 if (saved_rfc822_version != NULL) {
3241 if (qualified_for_journaling) {
3242 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3245 FreeStrBuf(&saved_rfc822_version);
3255 void aide_message (char *text, char *subject)
3257 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3262 * Convenience function for generating small administrative messages.
3264 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3265 int format_type, const char *subject)
3267 struct CtdlMessage *msg;
3268 struct recptypes *recp = NULL;
3270 msg = malloc(sizeof(struct CtdlMessage));
3271 memset(msg, 0, sizeof(struct CtdlMessage));
3272 msg->cm_magic = CTDLMESSAGE_MAGIC;
3273 msg->cm_anon_type = MES_NORMAL;
3274 msg->cm_format_type = format_type;
3277 msg->cm_fields['A'] = strdup(from);
3279 else if (fromaddr != NULL) {
3280 msg->cm_fields['A'] = strdup(fromaddr);
3281 if (strchr(msg->cm_fields['A'], '@')) {
3282 *strchr(msg->cm_fields['A'], '@') = 0;
3286 msg->cm_fields['A'] = strdup("Citadel");
3289 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3290 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3291 msg->cm_fields['N'] = strdup(NODENAME);
3293 msg->cm_fields['R'] = strdup(to);
3294 recp = validate_recipients(to, NULL, 0);
3296 if (subject != NULL) {
3297 msg->cm_fields['U'] = strdup(subject);
3299 msg->cm_fields['M'] = strdup(text);
3301 CtdlSubmitMsg(msg, recp, room, 0);
3302 CtdlFreeMessage(msg);
3303 if (recp != NULL) free_recipients(recp);
3309 * Back end function used by CtdlMakeMessage() and similar functions
3311 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3313 size_t maxlen, /* maximum message length */
3314 char *exist, /* if non-null, append to it;
3315 exist is ALWAYS freed */
3316 int crlf, /* CRLF newlines instead of LF */
3317 int *sock /* socket handle or 0 for this session's client socket */
3326 LineBuf = NewStrBufPlain(NULL, SIZ);
3327 if (exist == NULL) {
3328 Message = NewStrBufPlain(NULL, 4 * SIZ);
3331 Message = NewStrBufPlain(exist, -1);
3335 /* Do we need to change leading ".." to "." for SMTP escaping? */
3336 if ((tlen == 1) && (*terminator == '.')) {
3340 /* read in the lines of message text one by one */
3343 if ((CtdlSockGetLine(sock, LineBuf) < 0) ||
3348 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3350 if ((StrLength(LineBuf) == tlen) &&
3351 (!strcmp(ChrPtr(LineBuf), terminator)))
3354 if ( (!flushing) && (!finished) ) {
3356 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3359 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3362 /* Unescape SMTP-style input of two dots at the beginning of the line */
3364 (StrLength(LineBuf) == 2) &&
3365 (!strcmp(ChrPtr(LineBuf), "..")))
3367 StrBufCutLeft(LineBuf, 1);
3370 StrBufAppendBuf(Message, LineBuf, 0);
3373 /* if we've hit the max msg length, flush the rest */
3374 if (StrLength(Message) >= maxlen) flushing = 1;
3376 } while (!finished);
3377 FreeStrBuf(&LineBuf);
3383 * Back end function used by CtdlMakeMessage() and similar functions
3385 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3387 size_t maxlen, /* maximum message length */
3388 char *exist, /* if non-null, append to it;
3389 exist is ALWAYS freed */
3390 int crlf, /* CRLF newlines instead of LF */
3391 int *sock /* socket handle or 0 for this session's client socket */
3396 Message = CtdlReadMessageBodyBuf(terminator,
3402 if (Message == NULL)
3405 return SmashStrBuf(&Message);
3410 * Build a binary message to be saved on disk.
3411 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3412 * will become part of the message. This means you are no longer
3413 * responsible for managing that memory -- it will be freed along with
3414 * the rest of the fields when CtdlFreeMessage() is called.)
3417 struct CtdlMessage *CtdlMakeMessage(
3418 struct ctdluser *author, /* author's user structure */
3419 char *recipient, /* NULL if it's not mail */
3420 char *recp_cc, /* NULL if it's not mail */
3421 char *room, /* room where it's going */
3422 int type, /* see MES_ types in header file */
3423 int format_type, /* variformat, plain text, MIME... */
3424 char *fake_name, /* who we're masquerading as */
3425 char *my_email, /* which of my email addresses to use (empty is ok) */
3426 char *subject, /* Subject (optional) */
3427 char *supplied_euid, /* ...or NULL if this is irrelevant */
3428 char *preformatted_text, /* ...or NULL to read text from client */
3429 char *references /* Thread references */
3431 char dest_node[256];
3433 struct CtdlMessage *msg;
3435 msg = malloc(sizeof(struct CtdlMessage));
3436 memset(msg, 0, sizeof(struct CtdlMessage));
3437 msg->cm_magic = CTDLMESSAGE_MAGIC;
3438 msg->cm_anon_type = type;
3439 msg->cm_format_type = format_type;
3441 /* Don't confuse the poor folks if it's not routed mail. */
3442 strcpy(dest_node, "");
3444 if (recipient != NULL) striplt(recipient);
3445 if (recp_cc != NULL) striplt(recp_cc);
3447 /* Path or Return-Path */
3448 if (my_email == NULL) my_email = "";
3450 if (!IsEmptyStr(my_email)) {
3451 msg->cm_fields['P'] = strdup(my_email);
3454 snprintf(buf, sizeof buf, "%s", author->fullname);
3455 msg->cm_fields['P'] = strdup(buf);
3457 convert_spaces_to_underscores(msg->cm_fields['P']);
3459 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3460 msg->cm_fields['T'] = strdup(buf);
3462 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3463 msg->cm_fields['A'] = strdup(fake_name);
3466 msg->cm_fields['A'] = strdup(author->fullname);
3469 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3470 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3473 msg->cm_fields['O'] = strdup(CC->room.QRname);
3476 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3477 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3479 if ((recipient != NULL) && (recipient[0] != 0)) {
3480 msg->cm_fields['R'] = strdup(recipient);
3482 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3483 msg->cm_fields['Y'] = strdup(recp_cc);
3485 if (dest_node[0] != 0) {
3486 msg->cm_fields['D'] = strdup(dest_node);
3489 if (!IsEmptyStr(my_email)) {
3490 msg->cm_fields['F'] = strdup(my_email);
3492 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3493 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3496 if (subject != NULL) {
3499 length = strlen(subject);
3505 while ((subject[i] != '\0') &&
3506 (IsAscii = isascii(subject[i]) != 0 ))
3509 msg->cm_fields['U'] = strdup(subject);
3510 else /* ok, we've got utf8 in the string. */
3512 msg->cm_fields['U'] = rfc2047encode(subject, length);
3518 if (supplied_euid != NULL) {
3519 msg->cm_fields['E'] = strdup(supplied_euid);
3522 if (references != NULL) {
3523 if (!IsEmptyStr(references)) {
3524 msg->cm_fields['W'] = strdup(references);
3528 if (preformatted_text != NULL) {
3529 msg->cm_fields['M'] = preformatted_text;
3532 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3540 * Check to see whether we have permission to post a message in the current
3541 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3542 * returns 0 on success.
3544 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3546 const char* RemoteIdentifier,
3550 if (!(CC->logged_in) &&
3551 (PostPublic == POST_LOGGED_IN)) {
3552 snprintf(errmsgbuf, n, "Not logged in.");
3553 return (ERROR + NOT_LOGGED_IN);
3555 else if (PostPublic == CHECK_EXISTANCE) {
3556 return (0); // We're Evaling whether a recipient exists
3558 else if (!(CC->logged_in)) {
3560 if ((CC->room.QRflags & QR_READONLY)) {
3561 snprintf(errmsgbuf, n, "Not logged in.");
3562 return (ERROR + NOT_LOGGED_IN);
3564 if (CC->room.QRflags2 & QR2_MODERATED) {
3565 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3566 return (ERROR + NOT_LOGGED_IN);
3568 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3573 if (RemoteIdentifier == NULL)
3575 snprintf(errmsgbuf, n, "Need sender to permit access.");
3576 return (ERROR + USERNAME_REQUIRED);
3579 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3580 begin_critical_section(S_NETCONFIGS);
3581 if (!read_spoolcontrol_file(&sc, filename))
3583 end_critical_section(S_NETCONFIGS);
3584 snprintf(errmsgbuf, n,
3585 "This mailing list only accepts posts from subscribers.");
3586 return (ERROR + NO_SUCH_USER);
3588 end_critical_section(S_NETCONFIGS);
3589 found = is_recipient (sc, RemoteIdentifier);
3590 free_spoolcontrol_struct(&sc);
3595 snprintf(errmsgbuf, n,
3596 "This mailing list only accepts posts from subscribers.");
3597 return (ERROR + NO_SUCH_USER);
3604 if ((CC->user.axlevel < AxProbU)
3605 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3606 snprintf(errmsgbuf, n, "Need to be validated to enter "
3607 "(except in %s> to sysop)", MAILROOM);
3608 return (ERROR + HIGHER_ACCESS_REQUIRED);
3611 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3612 if (!(ra & UA_POSTALLOWED)) {
3613 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3614 return (ERROR + HIGHER_ACCESS_REQUIRED);
3617 strcpy(errmsgbuf, "Ok");
3623 * Check to see if the specified user has Internet mail permission
3624 * (returns nonzero if permission is granted)
3626 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3628 /* Do not allow twits to send Internet mail */
3629 if (who->axlevel <= AxProbU) return(0);
3631 /* Globally enabled? */
3632 if (config.c_restrict == 0) return(1);
3634 /* User flagged ok? */
3635 if (who->flags & US_INTERNET) return(2);
3637 /* Aide level access? */
3638 if (who->axlevel >= AxAideU) return(3);
3640 /* No mail for you! */
3646 * Validate recipients, count delivery types and errors, and handle aliasing
3647 * FIXME check for dupes!!!!!
3649 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3650 * were specified, or the number of addresses found invalid.
3652 * Caller needs to free the result using free_recipients()
3654 struct recptypes *validate_recipients(const char *supplied_recipients,
3655 const char *RemoteIdentifier,
3657 struct recptypes *ret;
3658 char *recipients = NULL;
3659 char this_recp[256];
3660 char this_recp_cooked[256];
3666 struct ctdluser tempUS;
3667 struct ctdlroom tempQR;
3668 struct ctdlroom tempQR2;
3674 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3675 if (ret == NULL) return(NULL);
3677 /* Set all strings to null and numeric values to zero */
3678 memset(ret, 0, sizeof(struct recptypes));
3680 if (supplied_recipients == NULL) {
3681 recipients = strdup("");
3684 recipients = strdup(supplied_recipients);
3687 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3688 * actually need, but it's healthier for the heap than doing lots of tiny
3689 * realloc() calls instead.
3692 ret->errormsg = malloc(strlen(recipients) + 1024);
3693 ret->recp_local = malloc(strlen(recipients) + 1024);
3694 ret->recp_internet = malloc(strlen(recipients) + 1024);
3695 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3696 ret->recp_room = malloc(strlen(recipients) + 1024);
3697 ret->display_recp = malloc(strlen(recipients) + 1024);
3699 ret->errormsg[0] = 0;
3700 ret->recp_local[0] = 0;
3701 ret->recp_internet[0] = 0;
3702 ret->recp_ignet[0] = 0;
3703 ret->recp_room[0] = 0;
3704 ret->display_recp[0] = 0;
3706 ret->recptypes_magic = RECPTYPES_MAGIC;
3708 /* Change all valid separator characters to commas */
3709 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3710 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3711 recipients[i] = ',';
3715 /* Now start extracting recipients... */
3717 while (!IsEmptyStr(recipients)) {
3719 for (i=0; i<=strlen(recipients); ++i) {
3720 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3721 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3722 safestrncpy(this_recp, recipients, i+1);
3724 if (recipients[i] == ',') {
3725 strcpy(recipients, &recipients[i+1]);
3728 strcpy(recipients, "");
3735 if (IsEmptyStr(this_recp))
3737 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3739 mailtype = alias(this_recp);
3740 mailtype = alias(this_recp);
3741 mailtype = alias(this_recp);
3743 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3744 if (this_recp[j]=='_') {
3745 this_recp_cooked[j] = ' ';
3748 this_recp_cooked[j] = this_recp[j];
3751 this_recp_cooked[j] = '\0';
3756 if (!strcasecmp(this_recp, "sysop")) {
3758 strcpy(this_recp, config.c_aideroom);
3759 if (!IsEmptyStr(ret->recp_room)) {
3760 strcat(ret->recp_room, "|");
3762 strcat(ret->recp_room, this_recp);
3764 else if ( (!strncasecmp(this_recp, "room_", 5))
3765 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3767 /* Save room so we can restore it later */
3771 /* Check permissions to send mail to this room */
3772 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3784 if (!IsEmptyStr(ret->recp_room)) {
3785 strcat(ret->recp_room, "|");
3787 strcat(ret->recp_room, &this_recp_cooked[5]);
3790 /* Restore room in case something needs it */
3794 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3796 strcpy(this_recp, tempUS.fullname);
3797 if (!IsEmptyStr(ret->recp_local)) {
3798 strcat(ret->recp_local, "|");
3800 strcat(ret->recp_local, this_recp);
3802 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
3804 strcpy(this_recp, tempUS.fullname);
3805 if (!IsEmptyStr(ret->recp_local)) {
3806 strcat(ret->recp_local, "|");
3808 strcat(ret->recp_local, this_recp);
3816 /* Yes, you're reading this correctly: if the target
3817 * domain points back to the local system or an attached
3818 * Citadel directory, the address is invalid. That's
3819 * because if the address were valid, we would have
3820 * already translated it to a local address by now.
3822 if (IsDirectory(this_recp, 0)) {
3827 ++ret->num_internet;
3828 if (!IsEmptyStr(ret->recp_internet)) {
3829 strcat(ret->recp_internet, "|");
3831 strcat(ret->recp_internet, this_recp);
3836 if (!IsEmptyStr(ret->recp_ignet)) {
3837 strcat(ret->recp_ignet, "|");
3839 strcat(ret->recp_ignet, this_recp);
3847 if (IsEmptyStr(errmsg)) {
3848 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3851 snprintf(append, sizeof append, "%s", errmsg);
3853 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3854 if (!IsEmptyStr(ret->errormsg)) {
3855 strcat(ret->errormsg, "; ");
3857 strcat(ret->errormsg, append);
3861 if (IsEmptyStr(ret->display_recp)) {
3862 strcpy(append, this_recp);
3865 snprintf(append, sizeof append, ", %s", this_recp);
3867 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3868 strcat(ret->display_recp, append);
3873 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3874 ret->num_room + ret->num_error) == 0) {
3875 ret->num_error = (-1);
3876 strcpy(ret->errormsg, "No recipients specified.");
3879 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3880 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3881 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3882 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3883 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3884 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3892 * Destructor for struct recptypes
3894 void free_recipients(struct recptypes *valid) {
3896 if (valid == NULL) {
3900 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3901 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3905 if (valid->errormsg != NULL) free(valid->errormsg);
3906 if (valid->recp_local != NULL) free(valid->recp_local);
3907 if (valid->recp_internet != NULL) free(valid->recp_internet);
3908 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3909 if (valid->recp_room != NULL) free(valid->recp_room);
3910 if (valid->display_recp != NULL) free(valid->display_recp);
3911 if (valid->bounce_to != NULL) free(valid->bounce_to);
3912 if (valid->envelope_from != NULL) free(valid->envelope_from);
3919 * message entry - mode 0 (normal)
3921 void cmd_ent0(char *entargs)
3927 char supplied_euid[128];
3929 int format_type = 0;
3930 char newusername[256];
3931 char newuseremail[256];
3932 struct CtdlMessage *msg;
3936 struct recptypes *valid = NULL;
3937 struct recptypes *valid_to = NULL;
3938 struct recptypes *valid_cc = NULL;
3939 struct recptypes *valid_bcc = NULL;
3941 int subject_required = 0;
3946 int newuseremail_ok = 0;
3947 char references[SIZ];
3952 post = extract_int(entargs, 0);
3953 extract_token(recp, entargs, 1, '|', sizeof recp);
3954 anon_flag = extract_int(entargs, 2);
3955 format_type = extract_int(entargs, 3);
3956 extract_token(subject, entargs, 4, '|', sizeof subject);
3957 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3958 do_confirm = extract_int(entargs, 6);
3959 extract_token(cc, entargs, 7, '|', sizeof cc);
3960 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3961 switch(CC->room.QRdefaultview) {
3964 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3967 supplied_euid[0] = 0;
3970 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3971 extract_token(references, entargs, 11, '|', sizeof references);
3972 for (ptr=references; *ptr != 0; ++ptr) {
3973 if (*ptr == '!') *ptr = '|';
3976 /* first check to make sure the request is valid. */
3978 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3981 cprintf("%d %s\n", err, errmsg);
3985 /* Check some other permission type things. */
3987 if (IsEmptyStr(newusername)) {
3988 strcpy(newusername, CC->user.fullname);
3990 if ( (CC->user.axlevel < AxAideU)
3991 && (strcasecmp(newusername, CC->user.fullname))
3992 && (strcasecmp(newusername, CC->cs_inet_fn))
3994 cprintf("%d You don't have permission to author messages as '%s'.\n",
3995 ERROR + HIGHER_ACCESS_REQUIRED,
4002 if (IsEmptyStr(newuseremail)) {
4003 newuseremail_ok = 1;
4006 if (!IsEmptyStr(newuseremail)) {
4007 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4008 newuseremail_ok = 1;
4010 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4011 j = num_tokens(CC->cs_inet_other_emails, '|');
4012 for (i=0; i<j; ++i) {
4013 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4014 if (!strcasecmp(newuseremail, buf)) {
4015 newuseremail_ok = 1;
4021 if (!newuseremail_ok) {
4022 cprintf("%d You don't have permission to author messages as '%s'.\n",
4023 ERROR + HIGHER_ACCESS_REQUIRED,
4029 CC->cs_flags |= CS_POSTING;
4031 /* In mailbox rooms we have to behave a little differently --
4032 * make sure the user has specified at least one recipient. Then
4033 * validate the recipient(s). We do this for the Mail> room, as
4034 * well as any room which has the "Mailbox" view set.
4037 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4038 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4040 if (CC->user.axlevel < AxProbU) {
4041 strcpy(recp, "sysop");
4046 valid_to = validate_recipients(recp, NULL, 0);
4047 if (valid_to->num_error > 0) {
4048 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4049 free_recipients(valid_to);
4053 valid_cc = validate_recipients(cc, NULL, 0);
4054 if (valid_cc->num_error > 0) {
4055 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4056 free_recipients(valid_to);
4057 free_recipients(valid_cc);
4061 valid_bcc = validate_recipients(bcc, NULL, 0);
4062 if (valid_bcc->num_error > 0) {
4063 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4064 free_recipients(valid_to);
4065 free_recipients(valid_cc);
4066 free_recipients(valid_bcc);
4070 /* Recipient required, but none were specified */
4071 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4072 free_recipients(valid_to);
4073 free_recipients(valid_cc);
4074 free_recipients(valid_bcc);
4075 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4079 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4080 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4081 cprintf("%d You do not have permission "
4082 "to send Internet mail.\n",
4083 ERROR + HIGHER_ACCESS_REQUIRED);
4084 free_recipients(valid_to);
4085 free_recipients(valid_cc);
4086 free_recipients(valid_bcc);
4091 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)
4092 && (CC->user.axlevel < AxNetU) ) {
4093 cprintf("%d Higher access required for network mail.\n",
4094 ERROR + HIGHER_ACCESS_REQUIRED);
4095 free_recipients(valid_to);
4096 free_recipients(valid_cc);
4097 free_recipients(valid_bcc);
4101 if ((RESTRICT_INTERNET == 1)
4102 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4103 && ((CC->user.flags & US_INTERNET) == 0)
4104 && (!CC->internal_pgm)) {
4105 cprintf("%d You don't have access to Internet mail.\n",
4106 ERROR + HIGHER_ACCESS_REQUIRED);
4107 free_recipients(valid_to);
4108 free_recipients(valid_cc);
4109 free_recipients(valid_bcc);
4115 /* Is this a room which has anonymous-only or anonymous-option? */
4116 anonymous = MES_NORMAL;
4117 if (CC->room.QRflags & QR_ANONONLY) {
4118 anonymous = MES_ANONONLY;
4120 if (CC->room.QRflags & QR_ANONOPT) {
4121 if (anon_flag == 1) { /* only if the user requested it */
4122 anonymous = MES_ANONOPT;
4126 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4130 /* Recommend to the client that the use of a message subject is
4131 * strongly recommended in this room, if either the SUBJECTREQ flag
4132 * is set, or if there is one or more Internet email recipients.
4134 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4135 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4136 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4137 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4139 /* If we're only checking the validity of the request, return
4140 * success without creating the message.
4143 cprintf("%d %s|%d\n", CIT_OK,
4144 ((valid_to != NULL) ? valid_to->display_recp : ""),
4146 free_recipients(valid_to);
4147 free_recipients(valid_cc);
4148 free_recipients(valid_bcc);
4152 /* We don't need these anymore because we'll do it differently below */
4153 free_recipients(valid_to);
4154 free_recipients(valid_cc);
4155 free_recipients(valid_bcc);
4157 /* Read in the message from the client. */
4159 cprintf("%d send message\n", START_CHAT_MODE);
4161 cprintf("%d send message\n", SEND_LISTING);
4164 msg = CtdlMakeMessage(&CC->user, recp, cc,
4165 CC->room.QRname, anonymous, format_type,
4166 newusername, newuseremail, subject,
4167 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4170 /* Put together one big recipients struct containing to/cc/bcc all in
4171 * one. This is for the envelope.
4173 char *all_recps = malloc(SIZ * 3);
4174 strcpy(all_recps, recp);
4175 if (!IsEmptyStr(cc)) {
4176 if (!IsEmptyStr(all_recps)) {
4177 strcat(all_recps, ",");
4179 strcat(all_recps, cc);
4181 if (!IsEmptyStr(bcc)) {
4182 if (!IsEmptyStr(all_recps)) {
4183 strcat(all_recps, ",");
4185 strcat(all_recps, bcc);
4187 if (!IsEmptyStr(all_recps)) {
4188 valid = validate_recipients(all_recps, NULL, 0);
4196 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4199 cprintf("%ld\n", msgnum);
4201 cprintf("Message accepted.\n");
4204 cprintf("Internal error.\n");
4206 if (msg->cm_fields['E'] != NULL) {
4207 cprintf("%s\n", msg->cm_fields['E']);
4214 CtdlFreeMessage(msg);
4216 if (valid != NULL) {
4217 free_recipients(valid);
4225 * API function to delete messages which match a set of criteria
4226 * (returns the actual number of messages deleted)
4228 int CtdlDeleteMessages(char *room_name, /* which room */
4229 long *dmsgnums, /* array of msg numbers to be deleted */
4230 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4231 char *content_type /* or "" for any. regular expressions expected. */
4234 struct ctdlroom qrbuf;
4235 struct cdbdata *cdbfr;
4236 long *msglist = NULL;
4237 long *dellist = NULL;
4240 int num_deleted = 0;
4242 struct MetaData smi;
4245 int need_to_free_re = 0;
4247 if (content_type) if (!IsEmptyStr(content_type)) {
4248 regcomp(&re, content_type, 0);
4249 need_to_free_re = 1;
4251 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4252 room_name, num_dmsgnums, content_type);
4254 /* get room record, obtaining a lock... */
4255 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4256 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4258 if (need_to_free_re) regfree(&re);
4259 return (0); /* room not found */
4261 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4263 if (cdbfr != NULL) {
4264 dellist = malloc(cdbfr->len);
4265 msglist = (long *) cdbfr->ptr;
4266 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4267 num_msgs = cdbfr->len / sizeof(long);
4271 for (i = 0; i < num_msgs; ++i) {
4274 /* Set/clear a bit for each criterion */
4276 /* 0 messages in the list or a null list means that we are
4277 * interested in deleting any messages which meet the other criteria.
4279 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4280 delete_this |= 0x01;
4283 for (j=0; j<num_dmsgnums; ++j) {
4284 if (msglist[i] == dmsgnums[j]) {
4285 delete_this |= 0x01;
4290 if (IsEmptyStr(content_type)) {
4291 delete_this |= 0x02;
4293 GetMetaData(&smi, msglist[i]);
4294 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4295 delete_this |= 0x02;
4299 /* Delete message only if all bits are set */
4300 if (delete_this == 0x03) {
4301 dellist[num_deleted++] = msglist[i];
4306 num_msgs = sort_msglist(msglist, num_msgs);
4307 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4308 msglist, (int)(num_msgs * sizeof(long)));
4311 qrbuf.QRhighest = msglist[num_msgs - 1];
4313 qrbuf.QRhighest = 0;
4315 CtdlPutRoomLock(&qrbuf);
4317 /* Go through the messages we pulled out of the index, and decrement
4318 * their reference counts by 1. If this is the only room the message
4319 * was in, the reference count will reach zero and the message will
4320 * automatically be deleted from the database. We do this in a
4321 * separate pass because there might be plug-in hooks getting called,
4322 * and we don't want that happening during an S_ROOMS critical
4325 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4326 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4327 AdjRefCount(dellist[i], -1);
4330 /* Now free the memory we used, and go away. */
4331 if (msglist != NULL) free(msglist);
4332 if (dellist != NULL) free(dellist);
4333 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4334 if (need_to_free_re) regfree(&re);
4335 return (num_deleted);
4341 * Check whether the current user has permission to delete messages from
4342 * the current room (returns 1 for yes, 0 for no)
4344 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4346 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4347 if (ra & UA_DELETEALLOWED) return(1);
4355 * Delete message from current room
4357 void cmd_dele(char *args)
4366 extract_token(msgset, args, 0, '|', sizeof msgset);
4367 num_msgs = num_tokens(msgset, ',');
4369 cprintf("%d Nothing to do.\n", CIT_OK);
4373 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4374 cprintf("%d Higher access required.\n",
4375 ERROR + HIGHER_ACCESS_REQUIRED);
4380 * Build our message set to be moved/copied
4382 msgs = malloc(num_msgs * sizeof(long));
4383 for (i=0; i<num_msgs; ++i) {
4384 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4385 msgs[i] = atol(msgtok);
4388 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4392 cprintf("%d %d message%s deleted.\n", CIT_OK,
4393 num_deleted, ((num_deleted != 1) ? "s" : ""));
4395 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4403 * move or copy a message to another room
4405 void cmd_move(char *args)
4412 char targ[ROOMNAMELEN];
4413 struct ctdlroom qtemp;
4420 extract_token(msgset, args, 0, '|', sizeof msgset);
4421 num_msgs = num_tokens(msgset, ',');
4423 cprintf("%d Nothing to do.\n", CIT_OK);
4427 extract_token(targ, args, 1, '|', sizeof targ);
4428 convert_room_name_macros(targ, sizeof targ);
4429 targ[ROOMNAMELEN - 1] = 0;
4430 is_copy = extract_int(args, 2);
4432 if (CtdlGetRoom(&qtemp, targ) != 0) {
4433 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4437 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4438 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4442 CtdlGetUser(&CC->user, CC->curr_user);
4443 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4445 /* Check for permission to perform this operation.
4446 * Remember: "CC->room" is source, "qtemp" is target.
4450 /* Aides can move/copy */
4451 if (CC->user.axlevel >= AxAideU) permit = 1;
4453 /* Room aides can move/copy */
4454 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4456 /* Permit move/copy from personal rooms */
4457 if ((CC->room.QRflags & QR_MAILBOX)
4458 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4460 /* Permit only copy from public to personal room */
4462 && (!(CC->room.QRflags & QR_MAILBOX))
4463 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4465 /* Permit message removal from collaborative delete rooms */
4466 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4468 /* Users allowed to post into the target room may move into it too. */
4469 if ((CC->room.QRflags & QR_MAILBOX) &&
4470 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4472 /* User must have access to target room */
4473 if (!(ra & UA_KNOWN)) permit = 0;
4476 cprintf("%d Higher access required.\n",
4477 ERROR + HIGHER_ACCESS_REQUIRED);
4482 * Build our message set to be moved/copied
4484 msgs = malloc(num_msgs * sizeof(long));
4485 for (i=0; i<num_msgs; ++i) {
4486 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4487 msgs[i] = atol(msgtok);
4493 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4495 cprintf("%d Cannot store message(s) in %s: error %d\n",
4501 /* Now delete the message from the source room,
4502 * if this is a 'move' rather than a 'copy' operation.
4505 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4509 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4515 * GetMetaData() - Get the supplementary record for a message
4517 void GetMetaData(struct MetaData *smibuf, long msgnum)
4520 struct cdbdata *cdbsmi;
4523 memset(smibuf, 0, sizeof(struct MetaData));
4524 smibuf->meta_msgnum = msgnum;
4525 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4527 /* Use the negative of the message number for its supp record index */
4528 TheIndex = (0L - msgnum);
4530 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4531 if (cdbsmi == NULL) {
4532 return; /* record not found; go with defaults */
4534 memcpy(smibuf, cdbsmi->ptr,
4535 ((cdbsmi->len > sizeof(struct MetaData)) ?
4536 sizeof(struct MetaData) : cdbsmi->len));
4543 * PutMetaData() - (re)write supplementary record for a message
4545 void PutMetaData(struct MetaData *smibuf)
4549 /* Use the negative of the message number for the metadata db index */
4550 TheIndex = (0L - smibuf->meta_msgnum);
4552 cdb_store(CDB_MSGMAIN,
4553 &TheIndex, (int)sizeof(long),
4554 smibuf, (int)sizeof(struct MetaData));
4559 * AdjRefCount - submit an adjustment to the reference count for a message.
4560 * (These are just queued -- we actually process them later.)
4562 void AdjRefCount(long msgnum, int incr)
4564 struct arcq new_arcq;
4567 CtdlLogPrintf(CTDL_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4571 begin_critical_section(S_SUPPMSGMAIN);
4572 if (arcfp == NULL) {
4573 arcfp = fopen(file_arcq, "ab+");
4575 end_critical_section(S_SUPPMSGMAIN);
4577 /* msgnum < 0 means that we're trying to close the file */
4579 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4580 begin_critical_section(S_SUPPMSGMAIN);
4581 if (arcfp != NULL) {
4585 end_critical_section(S_SUPPMSGMAIN);
4590 * If we can't open the queue, perform the operation synchronously.
4592 if (arcfp == NULL) {
4593 TDAP_AdjRefCount(msgnum, incr);
4597 new_arcq.arcq_msgnum = msgnum;
4598 new_arcq.arcq_delta = incr;
4599 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4607 * TDAP_ProcessAdjRefCountQueue()
4609 * Process the queue of message count adjustments that was created by calls
4610 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4611 * for each one. This should be an "off hours" operation.
4613 int TDAP_ProcessAdjRefCountQueue(void)
4615 char file_arcq_temp[PATH_MAX];
4618 struct arcq arcq_rec;
4619 int num_records_processed = 0;
4621 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4623 begin_critical_section(S_SUPPMSGMAIN);
4624 if (arcfp != NULL) {
4629 r = link(file_arcq, file_arcq_temp);
4631 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4632 end_critical_section(S_SUPPMSGMAIN);
4633 return(num_records_processed);
4637 end_critical_section(S_SUPPMSGMAIN);
4639 fp = fopen(file_arcq_temp, "rb");
4641 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4642 return(num_records_processed);
4645 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4646 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4647 ++num_records_processed;
4651 r = unlink(file_arcq_temp);
4653 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4656 return(num_records_processed);
4662 * TDAP_AdjRefCount - adjust the reference count for a message.
4663 * This one does it "for real" because it's called by
4664 * the autopurger function that processes the queue
4665 * created by AdjRefCount(). If a message's reference
4666 * count becomes zero, we also delete the message from
4667 * disk and de-index it.
4669 void TDAP_AdjRefCount(long msgnum, int incr)
4672 struct MetaData smi;
4675 /* This is a *tight* critical section; please keep it that way, as
4676 * it may get called while nested in other critical sections.
4677 * Complicating this any further will surely cause deadlock!
4679 begin_critical_section(S_SUPPMSGMAIN);
4680 GetMetaData(&smi, msgnum);
4681 smi.meta_refcount += incr;
4683 end_critical_section(S_SUPPMSGMAIN);
4684 CtdlLogPrintf(CTDL_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
4685 msgnum, incr, smi.meta_refcount
4688 /* If the reference count is now zero, delete the message
4689 * (and its supplementary record as well).
4691 if (smi.meta_refcount == 0) {
4692 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4694 /* Call delete hooks with NULL room to show it has gone altogether */
4695 PerformDeleteHooks(NULL, msgnum);
4697 /* Remove from message base */
4699 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4700 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4702 /* Remove metadata record */
4703 delnum = (0L - msgnum);
4704 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4710 * Write a generic object to this room
4712 * Note: this could be much more efficient. Right now we use two temporary
4713 * files, and still pull the message into memory as with all others.
4715 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4716 char *content_type, /* MIME type of this object */
4717 char *raw_message, /* Data to be written */
4718 off_t raw_length, /* Size of raw_message */
4719 struct ctdluser *is_mailbox, /* Mailbox room? */
4720 int is_binary, /* Is encoding necessary? */
4721 int is_unique, /* Del others of this type? */
4722 unsigned int flags /* Internal save flags */
4726 struct ctdlroom qrbuf;
4727 char roomname[ROOMNAMELEN];
4728 struct CtdlMessage *msg;
4729 char *encoded_message = NULL;
4731 if (is_mailbox != NULL) {
4732 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4735 safestrncpy(roomname, req_room, sizeof(roomname));
4738 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4741 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4744 encoded_message = malloc((size_t)(raw_length + 4096));
4747 sprintf(encoded_message, "Content-type: %s\n", content_type);
4750 sprintf(&encoded_message[strlen(encoded_message)],
4751 "Content-transfer-encoding: base64\n\n"
4755 sprintf(&encoded_message[strlen(encoded_message)],
4756 "Content-transfer-encoding: 7bit\n\n"
4762 &encoded_message[strlen(encoded_message)],
4770 &encoded_message[strlen(encoded_message)],
4776 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4777 msg = malloc(sizeof(struct CtdlMessage));
4778 memset(msg, 0, sizeof(struct CtdlMessage));
4779 msg->cm_magic = CTDLMESSAGE_MAGIC;
4780 msg->cm_anon_type = MES_NORMAL;
4781 msg->cm_format_type = 4;
4782 msg->cm_fields['A'] = strdup(CC->user.fullname);
4783 msg->cm_fields['O'] = strdup(req_room);
4784 msg->cm_fields['N'] = strdup(config.c_nodename);
4785 msg->cm_fields['H'] = strdup(config.c_humannode);
4786 msg->cm_flags = flags;
4788 msg->cm_fields['M'] = encoded_message;
4790 /* Create the requested room if we have to. */
4791 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4792 CtdlCreateRoom(roomname,
4793 ( (is_mailbox != NULL) ? 5 : 3 ),
4794 "", 0, 1, 0, VIEW_BBS);
4796 /* If the caller specified this object as unique, delete all
4797 * other objects of this type that are currently in the room.
4800 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4801 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4804 /* Now write the data */
4805 CtdlSubmitMsg(msg, NULL, roomname, 0);
4806 CtdlFreeMessage(msg);
4814 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4815 config_msgnum = msgnum;
4819 char *CtdlGetSysConfig(char *sysconfname) {
4820 char hold_rm[ROOMNAMELEN];
4823 struct CtdlMessage *msg;
4826 strcpy(hold_rm, CC->room.QRname);
4827 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4828 CtdlGetRoom(&CC->room, hold_rm);
4833 /* We want the last (and probably only) config in this room */
4834 begin_critical_section(S_CONFIG);
4835 config_msgnum = (-1L);
4836 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4837 CtdlGetSysConfigBackend, NULL);
4838 msgnum = config_msgnum;
4839 end_critical_section(S_CONFIG);
4845 msg = CtdlFetchMessage(msgnum, 1);
4847 conf = strdup(msg->cm_fields['M']);
4848 CtdlFreeMessage(msg);
4855 CtdlGetRoom(&CC->room, hold_rm);
4857 if (conf != NULL) do {
4858 extract_token(buf, conf, 0, '\n', sizeof buf);
4859 strcpy(conf, &conf[strlen(buf)+1]);
4860 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4866 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4867 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4872 * Determine whether a given Internet address belongs to the current user
4874 int CtdlIsMe(char *addr, int addr_buf_len)
4876 struct recptypes *recp;
4879 recp = validate_recipients(addr, NULL, 0);
4880 if (recp == NULL) return(0);
4882 if (recp->num_local == 0) {
4883 free_recipients(recp);
4887 for (i=0; i<recp->num_local; ++i) {
4888 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4889 if (!strcasecmp(addr, CC->user.fullname)) {
4890 free_recipients(recp);
4895 free_recipients(recp);
4901 * Citadel protocol command to do the same
4903 void cmd_isme(char *argbuf) {
4906 if (CtdlAccessCheck(ac_logged_in)) return;
4907 extract_token(addr, argbuf, 0, '|', sizeof addr);
4909 if (CtdlIsMe(addr, sizeof addr)) {
4910 cprintf("%d %s\n", CIT_OK, addr);
4913 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4919 /*****************************************************************************/
4920 /* MODULE INITIALIZATION STUFF */
4921 /*****************************************************************************/
4923 CTDL_MODULE_INIT(msgbase)
4926 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4927 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4928 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4929 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4930 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4931 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4932 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4933 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4934 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4935 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4936 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4937 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4940 /* return our Subversion id for the Log */