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);
1100 #ifdef MESSAGE_IN_ROOM
1102 * Check if a message is in the current room.
1103 * This is used by CtdlFetchMessage to prevent random picking
1104 * of messages from users private rooms
1106 * The message list should probably be cached against the CC->room
1108 int CtdlMessageInRoom(long msgnum)
1111 struct cdbdata *cdbfr;
1113 /* Learn about the user and room in question */
1114 CtdlGetUser(&CC->user, CC->curr_user);
1115 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
1117 /* Load the message list */
1118 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1119 if (cdbfr != NULL) {
1120 long *msglist = NULL;
1125 msglist = (long *) cdbfr->ptr;
1126 num_msgs = cdbfr->len / sizeof(long);
1128 /* search for message msgnum */
1129 for (i=0; i<num_msgs; i++) {
1130 if (msglist[i] == msgnum) {
1145 * Load a message from disk into memory.
1146 * This is used by CtdlOutputMsg() and other fetch functions.
1148 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1149 * using the CtdlMessageFree() function.
1151 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1153 struct cdbdata *dmsgtext;
1154 struct CtdlMessage *ret = NULL;
1158 cit_uint8_t field_header;
1160 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1162 #ifdef MESSAGE_IN_ROOM
1163 if (!CtdlMessageInRoom(msgnum)) {
1164 CtdlLogPrintf(CTDL_DEBUG, "Message %ld not in current room\n", msgnum);
1169 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1170 if (dmsgtext == NULL) {
1173 mptr = dmsgtext->ptr;
1174 upper_bound = mptr + dmsgtext->len;
1176 /* Parse the three bytes that begin EVERY message on disk.
1177 * The first is always 0xFF, the on-disk magic number.
1178 * The second is the anonymous/public type byte.
1179 * The third is the format type byte (vari, fixed, or MIME).
1183 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1187 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1188 memset(ret, 0, sizeof(struct CtdlMessage));
1190 ret->cm_magic = CTDLMESSAGE_MAGIC;
1191 ret->cm_anon_type = *mptr++; /* Anon type byte */
1192 ret->cm_format_type = *mptr++; /* Format type byte */
1195 * The rest is zero or more arbitrary fields. Load them in.
1196 * We're done when we encounter either a zero-length field or
1197 * have just processed the 'M' (message text) field.
1200 if (mptr >= upper_bound) {
1203 field_header = *mptr++;
1204 ret->cm_fields[field_header] = strdup(mptr);
1206 while (*mptr++ != 0); /* advance to next field */
1208 } while ((mptr < upper_bound) && (field_header != 'M'));
1212 /* Always make sure there's something in the msg text field. If
1213 * it's NULL, the message text is most likely stored separately,
1214 * so go ahead and fetch that. Failing that, just set a dummy
1215 * body so other code doesn't barf.
1217 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1218 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1219 if (dmsgtext != NULL) {
1220 ret->cm_fields['M'] = dmsgtext->ptr;
1221 dmsgtext->ptr = NULL;
1225 if (ret->cm_fields['M'] == NULL) {
1226 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1229 /* Perform "before read" hooks (aborting if any return nonzero) */
1230 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1231 CtdlFreeMessage(ret);
1240 * Returns 1 if the supplied pointer points to a valid Citadel message.
1241 * If the pointer is NULL or the magic number check fails, returns 0.
1243 int is_valid_message(struct CtdlMessage *msg) {
1246 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1247 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1255 * 'Destructor' for struct CtdlMessage
1257 void CtdlFreeMessage(struct CtdlMessage *msg)
1261 if (is_valid_message(msg) == 0)
1263 if (msg != NULL) free (msg);
1267 for (i = 0; i < 256; ++i)
1268 if (msg->cm_fields[i] != NULL) {
1269 free(msg->cm_fields[i]);
1272 msg->cm_magic = 0; /* just in case */
1278 * Pre callback function for multipart/alternative
1280 * NOTE: this differs from the standard behavior for a reason. Normally when
1281 * displaying multipart/alternative you want to show the _last_ usable
1282 * format in the message. Here we show the _first_ one, because it's
1283 * usually text/plain. Since this set of functions is designed for text
1284 * output to non-MIME-aware clients, this is the desired behavior.
1287 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1288 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1289 char *cbid, void *cbuserdata)
1293 ma = (struct ma_info *)cbuserdata;
1294 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1295 if (!strcasecmp(cbtype, "multipart/alternative")) {
1299 if (!strcasecmp(cbtype, "message/rfc822")) {
1305 * Post callback function for multipart/alternative
1307 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1308 void *content, char *cbtype, char *cbcharset, size_t length,
1309 char *encoding, char *cbid, void *cbuserdata)
1313 ma = (struct ma_info *)cbuserdata;
1314 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1315 if (!strcasecmp(cbtype, "multipart/alternative")) {
1319 if (!strcasecmp(cbtype, "message/rfc822")) {
1325 * Inline callback function for mime parser that wants to display text
1327 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1328 void *content, char *cbtype, char *cbcharset, size_t length,
1329 char *encoding, char *cbid, void *cbuserdata)
1336 ma = (struct ma_info *)cbuserdata;
1338 CtdlLogPrintf(CTDL_DEBUG,
1339 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1340 partnum, filename, cbtype, (long)length);
1343 * If we're in the middle of a multipart/alternative scope and
1344 * we've already printed another section, skip this one.
1346 if ( (ma->is_ma) && (ma->did_print) ) {
1347 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1352 if ( (!strcasecmp(cbtype, "text/plain"))
1353 || (IsEmptyStr(cbtype)) ) {
1356 client_write(wptr, length);
1357 if (wptr[length-1] != '\n') {
1364 if (!strcasecmp(cbtype, "text/html")) {
1365 ptr = html_to_ascii(content, length, 80, 0);
1367 client_write(ptr, wlen);
1368 if (ptr[wlen-1] != '\n') {
1375 if (ma->use_fo_hooks) {
1376 if (PerformFixedOutputHooks(cbtype, content, length)) {
1377 /* above function returns nonzero if it handled the part */
1382 if (strncasecmp(cbtype, "multipart/", 10)) {
1383 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1384 partnum, filename, cbtype, (long)length);
1390 * The client is elegant and sophisticated and wants to be choosy about
1391 * MIME content types, so figure out which multipart/alternative part
1392 * we're going to send.
1394 * We use a system of weights. When we find a part that matches one of the
1395 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1396 * and then set ma->chosen_pref to that MIME type's position in our preference
1397 * list. If we then hit another match, we only replace the first match if
1398 * the preference value is lower.
1400 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1401 void *content, char *cbtype, char *cbcharset, size_t length,
1402 char *encoding, char *cbid, void *cbuserdata)
1408 ma = (struct ma_info *)cbuserdata;
1410 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1411 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1412 // I don't know if there are any side effects! Please TEST TEST TEST
1413 //if (ma->is_ma > 0) {
1415 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1416 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1417 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1418 if (i < ma->chosen_pref) {
1419 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1420 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1421 ma->chosen_pref = i;
1428 * Now that we've chosen our preferred part, output it.
1430 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1431 void *content, char *cbtype, char *cbcharset, size_t length,
1432 char *encoding, char *cbid, void *cbuserdata)
1436 int add_newline = 0;
1440 ma = (struct ma_info *)cbuserdata;
1442 /* This is not the MIME part you're looking for... */
1443 if (strcasecmp(partnum, ma->chosen_part)) return;
1445 /* If the content-type of this part is in our preferred formats
1446 * list, we can simply output it verbatim.
1448 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1449 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1450 if (!strcasecmp(buf, cbtype)) {
1451 /* Yeah! Go! W00t!! */
1453 text_content = (char *)content;
1454 if (text_content[length-1] != '\n') {
1457 cprintf("Content-type: %s", cbtype);
1458 if (!IsEmptyStr(cbcharset)) {
1459 cprintf("; charset=%s", cbcharset);
1461 cprintf("\nContent-length: %d\n",
1462 (int)(length + add_newline) );
1463 if (!IsEmptyStr(encoding)) {
1464 cprintf("Content-transfer-encoding: %s\n", encoding);
1467 cprintf("Content-transfer-encoding: 7bit\n");
1469 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1471 client_write(content, length);
1472 if (add_newline) cprintf("\n");
1477 /* No translations required or possible: output as text/plain */
1478 cprintf("Content-type: text/plain\n\n");
1479 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1480 length, encoding, cbid, cbuserdata);
1485 char desired_section[64];
1492 * Callback function for
1494 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1495 void *content, char *cbtype, char *cbcharset, size_t length,
1496 char *encoding, char *cbid, void *cbuserdata)
1498 struct encapmsg *encap;
1500 encap = (struct encapmsg *)cbuserdata;
1502 /* Only proceed if this is the desired section... */
1503 if (!strcasecmp(encap->desired_section, partnum)) {
1504 encap->msglen = length;
1505 encap->msg = malloc(length + 2);
1506 memcpy(encap->msg, content, length);
1513 * Determine whether the currently logged in session has permission to read
1514 * messages in the current room.
1516 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1517 if ( (!(CC->logged_in))
1518 && (!(CC->internal_pgm))
1519 && (!config.c_guest_logins)
1521 return(om_not_logged_in);
1528 * Get a message off disk. (returns om_* values found in msgbase.h)
1531 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1532 int mode, /* how would you like that message? */
1533 int headers_only, /* eschew the message body? */
1534 int do_proto, /* do Citadel protocol responses? */
1535 int crlf, /* Use CRLF newlines instead of LF? */
1536 char *section, /* NULL or a message/rfc822 section */
1537 int flags /* various flags; see msgbase.h */
1539 struct CtdlMessage *TheMessage = NULL;
1540 int retcode = om_no_such_msg;
1541 struct encapmsg encap;
1544 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1546 (section ? section : "<>")
1549 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1552 if (r == om_not_logged_in) {
1553 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1556 cprintf("%d An unknown error has occurred.\n", ERROR);
1562 #ifdef MESSAGE_IN_ROOM
1563 if (!CtdlMessageInRoom(msg_num)) {
1564 CtdlLogPrintf(CTDL_DEBUG, "Message %ld not in current room\n", msg_num);
1565 if (do_proto) cprintf("%d Can't locate msg %ld in room\n",
1566 ERROR + MESSAGE_NOT_FOUND, msg_num);
1567 return(om_no_such_msg);
1572 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1573 * request that we don't even bother loading the body into memory.
1575 if (headers_only == HEADERS_FAST) {
1576 TheMessage = CtdlFetchMessage(msg_num, 0);
1579 TheMessage = CtdlFetchMessage(msg_num, 1);
1582 if (TheMessage == NULL) {
1583 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1584 ERROR + MESSAGE_NOT_FOUND, msg_num);
1585 return(om_no_such_msg);
1588 /* Here is the weird form of this command, to process only an
1589 * encapsulated message/rfc822 section.
1591 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1592 memset(&encap, 0, sizeof encap);
1593 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1594 mime_parser(TheMessage->cm_fields['M'],
1596 *extract_encapsulated_message,
1597 NULL, NULL, (void *)&encap, 0
1599 CtdlFreeMessage(TheMessage);
1603 encap.msg[encap.msglen] = 0;
1604 TheMessage = convert_internet_message(encap.msg);
1605 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1607 /* Now we let it fall through to the bottom of this
1608 * function, because TheMessage now contains the
1609 * encapsulated message instead of the top-level
1610 * message. Isn't that neat?
1615 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1616 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1617 retcode = om_no_such_msg;
1622 /* Ok, output the message now */
1623 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1624 CtdlFreeMessage(TheMessage);
1630 char *qp_encode_email_addrs(char *source)
1632 char *user, *node, *name;
1633 const char headerStr[] = "=?UTF-8?Q?";
1637 int need_to_encode = 0;
1643 long nAddrPtrMax = 50;
1648 if (source == NULL) return source;
1649 if (IsEmptyStr(source)) return source;
1651 CtdlLogPrintf(CTDL_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1653 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1654 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1655 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1658 while (!IsEmptyStr (&source[i])) {
1659 if (nColons >= nAddrPtrMax){
1662 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1663 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1664 free (AddrPtr), AddrPtr = ptr;
1666 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1667 memset(&ptr[nAddrPtrMax], 0,
1668 sizeof (long) * nAddrPtrMax);
1670 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1671 free (AddrUtf8), AddrUtf8 = ptr;
1674 if (((unsigned char) source[i] < 32) ||
1675 ((unsigned char) source[i] > 126)) {
1677 AddrUtf8[nColons] = 1;
1679 if (source[i] == '"')
1680 InQuotes = !InQuotes;
1681 if (!InQuotes && source[i] == ',') {
1682 AddrPtr[nColons] = i;
1687 if (need_to_encode == 0) {
1694 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1695 Encoded = (char*) malloc (EncodedMaxLen);
1697 for (i = 0; i < nColons; i++)
1698 source[AddrPtr[i]++] = '\0';
1699 /* TODO: if libidn, this might get larger*/
1700 user = malloc(SourceLen + 1);
1701 node = malloc(SourceLen + 1);
1702 name = malloc(SourceLen + 1);
1706 for (i = 0; i < nColons && nPtr != NULL; i++) {
1707 nmax = EncodedMaxLen - (nPtr - Encoded);
1709 process_rfc822_addr(&source[AddrPtr[i]],
1713 /* TODO: libIDN here ! */
1714 if (IsEmptyStr(name)) {
1715 n = snprintf(nPtr, nmax,
1716 (i==0)?"%s@%s" : ",%s@%s",
1720 EncodedName = rfc2047encode(name, strlen(name));
1721 n = snprintf(nPtr, nmax,
1722 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1723 EncodedName, user, node);
1728 n = snprintf(nPtr, nmax,
1729 (i==0)?"%s" : ",%s",
1730 &source[AddrPtr[i]]);
1736 ptr = (char*) malloc(EncodedMaxLen * 2);
1737 memcpy(ptr, Encoded, EncodedMaxLen);
1738 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1739 free(Encoded), Encoded = ptr;
1741 i--; /* do it once more with properly lengthened buffer */
1744 for (i = 0; i < nColons; i++)
1745 source[--AddrPtr[i]] = ',';
1756 /* If the last item in a list of recipients was truncated to a partial address,
1757 * remove it completely in order to avoid choking libSieve
1759 void sanitize_truncated_recipient(char *str)
1762 if (num_tokens(str, ',') < 2) return;
1764 int len = strlen(str);
1765 if (len < 900) return;
1766 if (len > 998) str[998] = 0;
1768 char *cptr = strrchr(str, ',');
1771 char *lptr = strchr(cptr, '<');
1772 char *rptr = strchr(cptr, '>');
1774 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1780 void OutputCtdlMsgHeaders(
1781 struct CtdlMessage *TheMessage,
1782 int do_proto) /* do Citadel protocol responses? */
1788 char display_name[256];
1790 /* begin header processing loop for Citadel message format */
1791 safestrncpy(display_name, "<unknown>", sizeof display_name);
1792 if (TheMessage->cm_fields['A']) {
1793 strcpy(buf, TheMessage->cm_fields['A']);
1794 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1795 safestrncpy(display_name, "****", sizeof display_name);
1797 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1798 safestrncpy(display_name, "anonymous", sizeof display_name);
1801 safestrncpy(display_name, buf, sizeof display_name);
1803 if ((is_room_aide())
1804 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1805 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1806 size_t tmp = strlen(display_name);
1807 snprintf(&display_name[tmp],
1808 sizeof display_name - tmp,
1813 /* Don't show Internet address for users on the
1814 * local Citadel network.
1817 if (TheMessage->cm_fields['N'] != NULL)
1818 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1819 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1823 /* Now spew the header fields in the order we like them. */
1824 n = safestrncpy(allkeys, FORDER, sizeof allkeys);
1825 for (i=0; i<n; ++i) {
1826 k = (int) allkeys[i];
1828 if ( (TheMessage->cm_fields[k] != NULL)
1829 && (msgkeys[k] != NULL) ) {
1830 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1831 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1834 if (do_proto) cprintf("%s=%s\n",
1838 else if ((k == 'F') && (suppress_f)) {
1841 /* Masquerade display name if needed */
1843 if (do_proto) cprintf("%s=%s\n",
1845 TheMessage->cm_fields[k]
1854 void OutputRFC822MsgHeaders(
1855 struct CtdlMessage *TheMessage,
1856 int flags, /* should the bessage be exported clean */
1858 char *mid, long sizeof_mid,
1859 char *suser, long sizeof_suser,
1860 char *luser, long sizeof_luser,
1861 char *fuser, long sizeof_fuser,
1862 char *snode, long sizeof_snode)
1864 char datestamp[100];
1865 int subject_found = 0;
1871 for (i = 0; i < 256; ++i) {
1872 if (TheMessage->cm_fields[i]) {
1873 mptr = mpptr = TheMessage->cm_fields[i];
1876 safestrncpy(luser, mptr, sizeof_luser);
1877 safestrncpy(suser, mptr, sizeof_suser);
1879 else if (i == 'Y') {
1880 if ((flags & QP_EADDR) != 0) {
1881 mptr = qp_encode_email_addrs(mptr);
1883 sanitize_truncated_recipient(mptr);
1884 cprintf("CC: %s%s", mptr, nl);
1886 else if (i == 'P') {
1887 cprintf("Return-Path: %s%s", mptr, nl);
1889 else if (i == 'L') {
1890 cprintf("List-ID: %s%s", mptr, nl);
1892 else if (i == 'V') {
1893 if ((flags & QP_EADDR) != 0)
1894 mptr = qp_encode_email_addrs(mptr);
1895 cprintf("Envelope-To: %s%s", mptr, nl);
1897 else if (i == 'U') {
1898 cprintf("Subject: %s%s", mptr, nl);
1902 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1904 safestrncpy(fuser, mptr, sizeof_fuser);
1905 /* else if (i == 'O')
1906 cprintf("X-Citadel-Room: %s%s",
1909 safestrncpy(snode, mptr, sizeof_snode);
1912 if (haschar(mptr, '@') == 0)
1914 sanitize_truncated_recipient(mptr);
1915 cprintf("To: %s@%s", mptr, config.c_fqdn);
1920 if ((flags & QP_EADDR) != 0) {
1921 mptr = qp_encode_email_addrs(mptr);
1923 sanitize_truncated_recipient(mptr);
1924 cprintf("To: %s", mptr);
1928 else if (i == 'T') {
1929 datestring(datestamp, sizeof datestamp,
1930 atol(mptr), DATESTRING_RFC822);
1931 cprintf("Date: %s%s", datestamp, nl);
1933 else if (i == 'W') {
1934 cprintf("References: ");
1935 k = num_tokens(mptr, '|');
1936 for (j=0; j<k; ++j) {
1937 extract_token(buf, mptr, j, '|', sizeof buf);
1938 cprintf("<%s>", buf);
1947 else if (i == 'K') {
1948 cprintf("Reply-To: <%s>%s", mptr, nl);
1954 if (subject_found == 0) {
1955 cprintf("Subject: (no subject)%s", nl);
1960 void Dump_RFC822HeadersBody(
1961 struct CtdlMessage *TheMessage,
1962 int headers_only, /* eschew the message body? */
1963 int flags, /* should the bessage be exported clean? */
1967 cit_uint8_t prev_ch;
1969 const char *StartOfText = StrBufNOTNULL;
1972 int nllen = strlen(nl);
1975 mptr = TheMessage->cm_fields['M'];
1979 while (*mptr != '\0') {
1980 if (*mptr == '\r') {
1987 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1989 eoh = *(mptr+1) == '\n';
1993 StartOfText = strchr(StartOfText, '\n');
1994 StartOfText = strchr(StartOfText, '\n');
1997 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1998 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1999 ((headers_only != HEADERS_NONE) &&
2000 (headers_only != HEADERS_ONLY))
2002 if (*mptr == '\n') {
2003 memcpy(&outbuf[outlen], nl, nllen);
2005 outbuf[outlen] = '\0';
2008 outbuf[outlen++] = *mptr;
2012 if (flags & ESC_DOT)
2014 if ((prev_ch == '\n') &&
2016 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2018 outbuf[outlen++] = '.';
2023 if (outlen > 1000) {
2024 client_write(outbuf, outlen);
2029 client_write(outbuf, outlen);
2036 /* If the format type on disk is 1 (fixed-format), then we want
2037 * everything to be output completely literally ... regardless of
2038 * what message transfer format is in use.
2040 void DumpFormatFixed(
2041 struct CtdlMessage *TheMessage,
2042 int mode, /* how would you like that message? */
2049 int nllen = strlen (nl);
2052 mptr = TheMessage->cm_fields['M'];
2054 if (mode == MT_MIME) {
2055 cprintf("Content-type: text/plain\n\n");
2059 while (ch = *mptr++, ch > 0) {
2063 if ((buflen > 250) && (!xlline)){
2067 while ((buflen > 0) &&
2068 (!isspace(buf[buflen])))
2074 mptr -= tbuflen - buflen;
2079 /* if we reach the outer bounds of our buffer,
2080 abort without respect what whe purge. */
2083 (buflen > SIZ - nllen - 2)))
2087 memcpy (&buf[buflen], nl, nllen);
2091 client_write(buf, buflen);
2101 if (!IsEmptyStr(buf))
2102 cprintf("%s%s", buf, nl);
2106 * Get a message off disk. (returns om_* values found in msgbase.h)
2108 int CtdlOutputPreLoadedMsg(
2109 struct CtdlMessage *TheMessage,
2110 int mode, /* how would you like that message? */
2111 int headers_only, /* eschew the message body? */
2112 int do_proto, /* do Citadel protocol responses? */
2113 int crlf, /* Use CRLF newlines instead of LF? */
2114 int flags /* should the bessage be exported clean? */
2118 const char *nl; /* newline string */
2121 /* Buffers needed for RFC822 translation. These are all filled
2122 * using functions that are bounds-checked, and therefore we can
2123 * make them substantially smaller than SIZ.
2131 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2132 ((TheMessage == NULL) ? "NULL" : "not null"),
2133 mode, headers_only, do_proto, crlf);
2135 strcpy(mid, "unknown");
2136 nl = (crlf ? "\r\n" : "\n");
2138 if (!is_valid_message(TheMessage)) {
2139 CtdlLogPrintf(CTDL_ERR,
2140 "ERROR: invalid preloaded message for output\n");
2142 return(om_no_such_msg);
2145 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2146 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2148 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2149 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2152 /* Are we downloading a MIME component? */
2153 if (mode == MT_DOWNLOAD) {
2154 if (TheMessage->cm_format_type != FMT_RFC822) {
2156 cprintf("%d This is not a MIME message.\n",
2157 ERROR + ILLEGAL_VALUE);
2158 } else if (CC->download_fp != NULL) {
2159 if (do_proto) cprintf(
2160 "%d You already have a download open.\n",
2161 ERROR + RESOURCE_BUSY);
2163 /* Parse the message text component */
2164 mptr = TheMessage->cm_fields['M'];
2165 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2166 /* If there's no file open by this time, the requested
2167 * section wasn't found, so print an error
2169 if (CC->download_fp == NULL) {
2170 if (do_proto) cprintf(
2171 "%d Section %s not found.\n",
2172 ERROR + FILE_NOT_FOUND,
2173 CC->download_desired_section);
2176 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2179 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2180 * in a single server operation instead of opening a download file.
2182 if (mode == MT_SPEW_SECTION) {
2183 if (TheMessage->cm_format_type != FMT_RFC822) {
2185 cprintf("%d This is not a MIME message.\n",
2186 ERROR + ILLEGAL_VALUE);
2188 /* Parse the message text component */
2191 mptr = TheMessage->cm_fields['M'];
2192 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2193 /* If section wasn't found, print an error
2196 if (do_proto) cprintf(
2197 "%d Section %s not found.\n",
2198 ERROR + FILE_NOT_FOUND,
2199 CC->download_desired_section);
2202 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2205 /* now for the user-mode message reading loops */
2206 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2208 /* Does the caller want to skip the headers? */
2209 if (headers_only == HEADERS_NONE) goto START_TEXT;
2211 /* Tell the client which format type we're using. */
2212 if ( (mode == MT_CITADEL) && (do_proto) ) {
2213 cprintf("type=%d\n", TheMessage->cm_format_type);
2216 /* nhdr=yes means that we're only displaying headers, no body */
2217 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2218 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2221 cprintf("nhdr=yes\n");
2224 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2225 OutputCtdlMsgHeaders(TheMessage, do_proto);
2228 /* begin header processing loop for RFC822 transfer format */
2232 strcpy(snode, NODENAME);
2233 if (mode == MT_RFC822)
2234 OutputRFC822MsgHeaders(
2239 suser, sizeof(suser),
2240 luser, sizeof(luser),
2241 fuser, sizeof(fuser),
2242 snode, sizeof(snode)
2246 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2247 suser[i] = tolower(suser[i]);
2248 if (!isalnum(suser[i])) suser[i]='_';
2251 if (mode == MT_RFC822) {
2252 if (!strcasecmp(snode, NODENAME)) {
2253 safestrncpy(snode, FQDN, sizeof snode);
2256 /* Construct a fun message id */
2257 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2258 if (strchr(mid, '@')==NULL) {
2259 cprintf("@%s", snode);
2263 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2264 cprintf("From: \"----\" <x@x.org>%s", nl);
2266 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2267 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2269 else if (!IsEmptyStr(fuser)) {
2270 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2273 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2276 /* Blank line signifying RFC822 end-of-headers */
2277 if (TheMessage->cm_format_type != FMT_RFC822) {
2282 /* end header processing loop ... at this point, we're in the text */
2284 if (headers_only == HEADERS_FAST) goto DONE;
2286 /* Tell the client about the MIME parts in this message */
2287 if (TheMessage->cm_format_type == FMT_RFC822) {
2288 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2289 mptr = TheMessage->cm_fields['M'];
2290 memset(&ma, 0, sizeof(struct ma_info));
2291 mime_parser(mptr, NULL,
2292 (do_proto ? *list_this_part : NULL),
2293 (do_proto ? *list_this_pref : NULL),
2294 (do_proto ? *list_this_suff : NULL),
2297 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2298 Dump_RFC822HeadersBody(
2307 if (headers_only == HEADERS_ONLY) {
2311 /* signify start of msg text */
2312 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2313 if (do_proto) cprintf("text\n");
2316 if (TheMessage->cm_format_type == FMT_FIXED)
2319 mode, /* how would you like that message? */
2322 /* If the message on disk is format 0 (Citadel vari-format), we
2323 * output using the formatter at 80 columns. This is the final output
2324 * form if the transfer format is RFC822, but if the transfer format
2325 * is Citadel proprietary, it'll still work, because the indentation
2326 * for new paragraphs is correct and the client will reformat the
2327 * message to the reader's screen width.
2329 if (TheMessage->cm_format_type == FMT_CITADEL) {
2330 mptr = TheMessage->cm_fields['M'];
2332 if (mode == MT_MIME) {
2333 cprintf("Content-type: text/x-citadel-variformat\n\n");
2338 /* If the message on disk is format 4 (MIME), we've gotta hand it
2339 * off to the MIME parser. The client has already been told that
2340 * this message is format 1 (fixed format), so the callback function
2341 * we use will display those parts as-is.
2343 if (TheMessage->cm_format_type == FMT_RFC822) {
2344 memset(&ma, 0, sizeof(struct ma_info));
2346 if (mode == MT_MIME) {
2347 ma.use_fo_hooks = 0;
2348 strcpy(ma.chosen_part, "1");
2349 ma.chosen_pref = 9999;
2350 mime_parser(mptr, NULL,
2351 *choose_preferred, *fixed_output_pre,
2352 *fixed_output_post, (void *)&ma, 0);
2353 mime_parser(mptr, NULL,
2354 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2357 ma.use_fo_hooks = 1;
2358 mime_parser(mptr, NULL,
2359 *fixed_output, *fixed_output_pre,
2360 *fixed_output_post, (void *)&ma, 0);
2365 DONE: /* now we're done */
2366 if (do_proto) cprintf("000\n");
2372 * display a message (mode 0 - Citadel proprietary)
2374 void cmd_msg0(char *cmdbuf)
2377 int headers_only = HEADERS_ALL;
2379 msgid = extract_long(cmdbuf, 0);
2380 headers_only = extract_int(cmdbuf, 1);
2382 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2388 * display a message (mode 2 - RFC822)
2390 void cmd_msg2(char *cmdbuf)
2393 int headers_only = HEADERS_ALL;
2395 msgid = extract_long(cmdbuf, 0);
2396 headers_only = extract_int(cmdbuf, 1);
2398 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2404 * display a message (mode 3 - IGnet raw format - internal programs only)
2406 void cmd_msg3(char *cmdbuf)
2409 struct CtdlMessage *msg = NULL;
2412 if (CC->internal_pgm == 0) {
2413 cprintf("%d This command is for internal programs only.\n",
2414 ERROR + HIGHER_ACCESS_REQUIRED);
2418 msgnum = extract_long(cmdbuf, 0);
2419 msg = CtdlFetchMessage(msgnum, 1);
2421 cprintf("%d Message %ld not found.\n",
2422 ERROR + MESSAGE_NOT_FOUND, msgnum);
2426 serialize_message(&smr, msg);
2427 CtdlFreeMessage(msg);
2430 cprintf("%d Unable to serialize message\n",
2431 ERROR + INTERNAL_ERROR);
2435 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2436 client_write((char *)smr.ser, (int)smr.len);
2443 * Display a message using MIME content types
2445 void cmd_msg4(char *cmdbuf)
2450 msgid = extract_long(cmdbuf, 0);
2451 extract_token(section, cmdbuf, 1, '|', sizeof section);
2452 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2458 * Client tells us its preferred message format(s)
2460 void cmd_msgp(char *cmdbuf)
2462 if (!strcasecmp(cmdbuf, "dont_decode")) {
2463 CC->msg4_dont_decode = 1;
2464 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2467 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2468 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2474 * Open a component of a MIME message as a download file
2476 void cmd_opna(char *cmdbuf)
2479 char desired_section[128];
2481 msgid = extract_long(cmdbuf, 0);
2482 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2483 safestrncpy(CC->download_desired_section, desired_section,
2484 sizeof CC->download_desired_section);
2485 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2490 * Open a component of a MIME message and transmit it all at once
2492 void cmd_dlat(char *cmdbuf)
2495 char desired_section[128];
2497 msgid = extract_long(cmdbuf, 0);
2498 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2499 safestrncpy(CC->download_desired_section, desired_section,
2500 sizeof CC->download_desired_section);
2501 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2506 * Save one or more message pointers into a specified room
2507 * (Returns 0 for success, nonzero for failure)
2508 * roomname may be NULL to use the current room
2510 * Note that the 'supplied_msg' field may be set to NULL, in which case
2511 * the message will be fetched from disk, by number, if we need to perform
2512 * replication checks. This adds an additional database read, so if the
2513 * caller already has the message in memory then it should be supplied. (Obviously
2514 * this mode of operation only works if we're saving a single message.)
2516 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2517 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2520 char hold_rm[ROOMNAMELEN];
2521 struct cdbdata *cdbfr;
2524 long highest_msg = 0L;
2527 struct CtdlMessage *msg = NULL;
2529 long *msgs_to_be_merged = NULL;
2530 int num_msgs_to_be_merged = 0;
2532 CtdlLogPrintf(CTDL_DEBUG,
2533 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2534 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2537 strcpy(hold_rm, CC->room.QRname);
2540 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2541 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2542 if (num_newmsgs > 1) supplied_msg = NULL;
2544 /* Now the regular stuff */
2545 if (CtdlGetRoomLock(&CC->room,
2546 ((roomname != NULL) ? roomname : CC->room.QRname) )
2548 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2549 return(ERROR + ROOM_NOT_FOUND);
2553 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2554 num_msgs_to_be_merged = 0;
2557 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2558 if (cdbfr == NULL) {
2562 msglist = (long *) cdbfr->ptr;
2563 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2564 num_msgs = cdbfr->len / sizeof(long);
2569 /* Create a list of msgid's which were supplied by the caller, but do
2570 * not already exist in the target room. It is absolutely taboo to
2571 * have more than one reference to the same message in a room.
2573 for (i=0; i<num_newmsgs; ++i) {
2575 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2576 if (msglist[j] == newmsgidlist[i]) {
2581 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2585 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2588 * Now merge the new messages
2590 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2591 if (msglist == NULL) {
2592 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2594 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2595 num_msgs += num_msgs_to_be_merged;
2597 /* Sort the message list, so all the msgid's are in order */
2598 num_msgs = sort_msglist(msglist, num_msgs);
2600 /* Determine the highest message number */
2601 highest_msg = msglist[num_msgs - 1];
2603 /* Write it back to disk. */
2604 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2605 msglist, (int)(num_msgs * sizeof(long)));
2607 /* Free up the memory we used. */
2610 /* Update the highest-message pointer and unlock the room. */
2611 CC->room.QRhighest = highest_msg;
2612 CtdlPutRoomLock(&CC->room);
2614 /* Perform replication checks if necessary */
2615 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2616 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2618 for (i=0; i<num_msgs_to_be_merged; ++i) {
2619 msgid = msgs_to_be_merged[i];
2621 if (supplied_msg != NULL) {
2625 msg = CtdlFetchMessage(msgid, 0);
2629 ReplicationChecks(msg);
2631 /* If the message has an Exclusive ID, index that... */
2632 if (msg->cm_fields['E'] != NULL) {
2633 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2636 /* Free up the memory we may have allocated */
2637 if (msg != supplied_msg) {
2638 CtdlFreeMessage(msg);
2646 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2649 /* Submit this room for processing by hooks */
2650 PerformRoomHooks(&CC->room);
2652 /* Go back to the room we were in before we wandered here... */
2653 CtdlGetRoom(&CC->room, hold_rm);
2655 /* Bump the reference count for all messages which were merged */
2656 if (!suppress_refcount_adj) {
2657 for (i=0; i<num_msgs_to_be_merged; ++i) {
2658 AdjRefCount(msgs_to_be_merged[i], +1);
2662 /* Free up memory... */
2663 if (msgs_to_be_merged != NULL) {
2664 free(msgs_to_be_merged);
2667 /* Return success. */
2673 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2676 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2677 int do_repl_check, struct CtdlMessage *supplied_msg)
2679 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2686 * Message base operation to save a new message to the message store
2687 * (returns new message number)
2689 * This is the back end for CtdlSubmitMsg() and should not be directly
2690 * called by server-side modules.
2693 long send_message(struct CtdlMessage *msg) {
2701 /* Get a new message number */
2702 newmsgid = get_new_message_number();
2703 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2705 /* Generate an ID if we don't have one already */
2706 if (msg->cm_fields['I']==NULL) {
2707 msg->cm_fields['I'] = strdup(msgidbuf);
2710 /* If the message is big, set its body aside for storage elsewhere */
2711 if (msg->cm_fields['M'] != NULL) {
2712 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2714 holdM = msg->cm_fields['M'];
2715 msg->cm_fields['M'] = NULL;
2719 /* Serialize our data structure for storage in the database */
2720 serialize_message(&smr, msg);
2723 msg->cm_fields['M'] = holdM;
2727 cprintf("%d Unable to serialize message\n",
2728 ERROR + INTERNAL_ERROR);
2732 /* Write our little bundle of joy into the message base */
2733 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2734 smr.ser, smr.len) < 0) {
2735 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2739 cdb_store(CDB_BIGMSGS,
2749 /* Free the memory we used for the serialized message */
2752 /* Return the *local* message ID to the caller
2753 * (even if we're storing an incoming network message)
2761 * Serialize a struct CtdlMessage into the format used on disk and network.
2763 * This function loads up a "struct ser_ret" (defined in server.h) which
2764 * contains the length of the serialized message and a pointer to the
2765 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2767 void serialize_message(struct ser_ret *ret, /* return values */
2768 struct CtdlMessage *msg) /* unserialized msg */
2770 size_t wlen, fieldlen;
2772 static char *forder = FORDER;
2775 * Check for valid message format
2777 if (is_valid_message(msg) == 0) {
2778 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2785 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2786 ret->len = ret->len +
2787 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2789 ret->ser = malloc(ret->len);
2790 if (ret->ser == NULL) {
2791 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2792 (long)ret->len, strerror(errno));
2799 ret->ser[1] = msg->cm_anon_type;
2800 ret->ser[2] = msg->cm_format_type;
2803 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2804 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2805 ret->ser[wlen++] = (char)forder[i];
2806 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2807 wlen = wlen + fieldlen + 1;
2809 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2810 (long)ret->len, (long)wlen);
2817 * Serialize a struct CtdlMessage into the format used on disk and network.
2819 * This function loads up a "struct ser_ret" (defined in server.h) which
2820 * contains the length of the serialized message and a pointer to the
2821 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2823 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2824 long Siz) /* how many chars ? */
2828 static char *forder = FORDER;
2832 * Check for valid message format
2834 if (is_valid_message(msg) == 0) {
2835 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2839 buf = (char*) malloc (Siz + 1);
2843 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2844 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2845 msg->cm_fields[(int)forder[i]]);
2846 client_write (buf, strlen(buf));
2855 * Check to see if any messages already exist in the current room which
2856 * carry the same Exclusive ID as this one. If any are found, delete them.
2858 void ReplicationChecks(struct CtdlMessage *msg) {
2859 long old_msgnum = (-1L);
2861 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2863 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2866 /* No exclusive id? Don't do anything. */
2867 if (msg == NULL) return;
2868 if (msg->cm_fields['E'] == NULL) return;
2869 if (IsEmptyStr(msg->cm_fields['E'])) return;
2870 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2871 msg->cm_fields['E'], CC->room.QRname);*/
2873 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2874 if (old_msgnum > 0L) {
2875 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2876 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2883 * Save a message to disk and submit it into the delivery system.
2885 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2886 struct recptypes *recps, /* recipients (if mail) */
2887 char *force, /* force a particular room? */
2888 int flags /* should the message be exported clean? */
2890 char submit_filename[128];
2891 char generated_timestamp[32];
2892 char hold_rm[ROOMNAMELEN];
2893 char actual_rm[ROOMNAMELEN];
2894 char force_room[ROOMNAMELEN];
2895 char content_type[SIZ]; /* We have to learn this */
2896 char recipient[SIZ];
2898 const char *mptr = NULL;
2899 struct ctdluser userbuf;
2901 struct MetaData smi;
2902 FILE *network_fp = NULL;
2903 static int seqnum = 1;
2904 struct CtdlMessage *imsg = NULL;
2906 size_t instr_alloc = 0;
2908 char *hold_R, *hold_D;
2909 char *collected_addresses = NULL;
2910 struct addresses_to_be_filed *aptr = NULL;
2911 StrBuf *saved_rfc822_version = NULL;
2912 int qualified_for_journaling = 0;
2913 CitContext *CCC = MyContext();
2914 char bounce_to[1024] = "";
2918 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2919 if (is_valid_message(msg) == 0) return(-1); /* self check */
2921 /* If this message has no timestamp, we take the liberty of
2922 * giving it one, right now.
2924 if (msg->cm_fields['T'] == NULL) {
2925 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2926 msg->cm_fields['T'] = strdup(generated_timestamp);
2929 /* If this message has no path, we generate one.
2931 if (msg->cm_fields['P'] == NULL) {
2932 if (msg->cm_fields['A'] != NULL) {
2933 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2934 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2935 if (isspace(msg->cm_fields['P'][a])) {
2936 msg->cm_fields['P'][a] = ' ';
2941 msg->cm_fields['P'] = strdup("unknown");
2945 if (force == NULL) {
2946 strcpy(force_room, "");
2949 strcpy(force_room, force);
2952 /* Learn about what's inside, because it's what's inside that counts */
2953 if (msg->cm_fields['M'] == NULL) {
2954 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2958 switch (msg->cm_format_type) {
2960 strcpy(content_type, "text/x-citadel-variformat");
2963 strcpy(content_type, "text/plain");
2966 strcpy(content_type, "text/plain");
2967 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2970 safestrncpy(content_type, &mptr[13], sizeof content_type);
2971 striplt(content_type);
2972 aptr = content_type;
2973 while (!IsEmptyStr(aptr)) {
2985 /* Goto the correct room */
2986 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2987 strcpy(hold_rm, CCC->room.QRname);
2988 strcpy(actual_rm, CCC->room.QRname);
2989 if (recps != NULL) {
2990 strcpy(actual_rm, SENTITEMS);
2993 /* If the user is a twit, move to the twit room for posting */
2995 if (CCC->user.axlevel == AxProbU) {
2996 strcpy(hold_rm, actual_rm);
2997 strcpy(actual_rm, config.c_twitroom);
2998 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
3002 /* ...or if this message is destined for Aide> then go there. */
3003 if (!IsEmptyStr(force_room)) {
3004 strcpy(actual_rm, force_room);
3007 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
3008 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3009 /* CtdlGetRoom(&CCC->room, actual_rm); */
3010 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3014 * If this message has no O (room) field, generate one.
3016 if (msg->cm_fields['O'] == NULL) {
3017 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3020 /* Perform "before save" hooks (aborting if any return nonzero) */
3021 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
3022 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3025 * If this message has an Exclusive ID, and the room is replication
3026 * checking enabled, then do replication checks.
3028 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3029 ReplicationChecks(msg);
3032 /* Save it to disk */
3033 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
3034 newmsgid = send_message(msg);
3035 if (newmsgid <= 0L) return(-5);
3037 /* Write a supplemental message info record. This doesn't have to
3038 * be a critical section because nobody else knows about this message
3041 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
3042 memset(&smi, 0, sizeof(struct MetaData));
3043 smi.meta_msgnum = newmsgid;
3044 smi.meta_refcount = 0;
3045 safestrncpy(smi.meta_content_type, content_type,
3046 sizeof smi.meta_content_type);
3049 * Measure how big this message will be when rendered as RFC822.
3050 * We do this for two reasons:
3051 * 1. We need the RFC822 length for the new metadata record, so the
3052 * POP and IMAP services don't have to calculate message lengths
3053 * while the user is waiting (multiplied by potentially hundreds
3054 * or thousands of messages).
3055 * 2. If journaling is enabled, we will need an RFC822 version of the
3056 * message to attach to the journalized copy.
3058 if (CCC->redirect_buffer != NULL) {
3059 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3062 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3063 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3064 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3065 saved_rfc822_version = CCC->redirect_buffer;
3066 CCC->redirect_buffer = NULL;
3070 /* Now figure out where to store the pointers */
3071 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
3073 /* If this is being done by the networker delivering a private
3074 * message, we want to BYPASS saving the sender's copy (because there
3075 * is no local sender; it would otherwise go to the Trashcan).
3077 if ((!CCC->internal_pgm) || (recps == NULL)) {
3078 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3079 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
3080 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3084 /* For internet mail, drop a copy in the outbound queue room */
3085 if ((recps != NULL) && (recps->num_internet > 0)) {
3086 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3089 /* If other rooms are specified, drop them there too. */
3090 if ((recps != NULL) && (recps->num_room > 0))
3091 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3092 extract_token(recipient, recps->recp_room, i,
3093 '|', sizeof recipient);
3094 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
3095 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3098 /* Bump this user's messages posted counter. */
3099 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
3100 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3101 CCC->user.posted = CCC->user.posted + 1;
3102 CtdlPutUserLock(&CCC->user);
3104 /* Decide where bounces need to be delivered */
3105 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3106 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3108 else if (CCC->logged_in) {
3109 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3112 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3115 /* If this is private, local mail, make a copy in the
3116 * recipient's mailbox and bump the reference count.
3118 if ((recps != NULL) && (recps->num_local > 0))
3119 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3120 extract_token(recipient, recps->recp_local, i,
3121 '|', sizeof recipient);
3122 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
3124 if (CtdlGetUser(&userbuf, recipient) == 0) {
3125 // Add a flag so the Funambol module knows its mail
3126 msg->cm_fields['W'] = strdup(recipient);
3127 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3128 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3129 CtdlBumpNewMailCounter(userbuf.usernum);
3130 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3131 /* Generate a instruction message for the Funambol notification
3132 * server, in the same style as the SMTP queue
3135 instr = malloc(instr_alloc);
3136 snprintf(instr, instr_alloc,
3137 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3139 SPOOLMIME, newmsgid, (long)time(NULL),
3143 imsg = malloc(sizeof(struct CtdlMessage));
3144 memset(imsg, 0, sizeof(struct CtdlMessage));
3145 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3146 imsg->cm_anon_type = MES_NORMAL;
3147 imsg->cm_format_type = FMT_RFC822;
3148 imsg->cm_fields['A'] = strdup("Citadel");
3149 imsg->cm_fields['J'] = strdup("do not journal");
3150 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3151 imsg->cm_fields['W'] = strdup(recipient);
3152 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3153 CtdlFreeMessage(imsg);
3157 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
3158 CtdlSaveMsgPointerInRoom(config.c_aideroom,
3163 /* Perform "after save" hooks */
3164 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
3165 PerformMessageHooks(msg, EVT_AFTERSAVE);
3167 /* For IGnet mail, we have to save a new copy into the spooler for
3168 * each recipient, with the R and D fields set to the recipient and
3169 * destination-node. This has two ugly side effects: all other
3170 * recipients end up being unlisted in this recipient's copy of the
3171 * message, and it has to deliver multiple messages to the same
3172 * node. We'll revisit this again in a year or so when everyone has
3173 * a network spool receiver that can handle the new style messages.
3175 if ((recps != NULL) && (recps->num_ignet > 0))
3176 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3177 extract_token(recipient, recps->recp_ignet, i,
3178 '|', sizeof recipient);
3180 hold_R = msg->cm_fields['R'];
3181 hold_D = msg->cm_fields['D'];
3182 msg->cm_fields['R'] = malloc(SIZ);
3183 msg->cm_fields['D'] = malloc(128);
3184 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3185 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3187 serialize_message(&smr, msg);
3189 snprintf(submit_filename, sizeof submit_filename,
3190 "%s/netmail.%04lx.%04x.%04x",
3192 (long) getpid(), CCC->cs_pid, ++seqnum);
3193 network_fp = fopen(submit_filename, "wb+");
3194 if (network_fp != NULL) {
3195 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3201 free(msg->cm_fields['R']);
3202 free(msg->cm_fields['D']);
3203 msg->cm_fields['R'] = hold_R;
3204 msg->cm_fields['D'] = hold_D;
3207 /* Go back to the room we started from */
3208 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3209 if (strcasecmp(hold_rm, CCC->room.QRname))
3210 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3212 /* For internet mail, generate delivery instructions.
3213 * Yes, this is recursive. Deal with it. Infinite recursion does
3214 * not happen because the delivery instructions message does not
3215 * contain a recipient.
3217 if ((recps != NULL) && (recps->num_internet > 0)) {
3218 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3220 instr = malloc(instr_alloc);
3221 snprintf(instr, instr_alloc,
3222 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3224 SPOOLMIME, newmsgid, (long)time(NULL),
3228 if (recps->envelope_from != NULL) {
3229 tmp = strlen(instr);
3230 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3233 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3234 tmp = strlen(instr);
3235 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3236 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3237 instr_alloc = instr_alloc * 2;
3238 instr = realloc(instr, instr_alloc);
3240 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3243 imsg = malloc(sizeof(struct CtdlMessage));
3244 memset(imsg, 0, sizeof(struct CtdlMessage));
3245 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3246 imsg->cm_anon_type = MES_NORMAL;
3247 imsg->cm_format_type = FMT_RFC822;
3248 imsg->cm_fields['A'] = strdup("Citadel");
3249 imsg->cm_fields['J'] = strdup("do not journal");
3250 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3251 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3252 CtdlFreeMessage(imsg);
3256 * Any addresses to harvest for someone's address book?
3258 if ( (CCC->logged_in) && (recps != NULL) ) {
3259 collected_addresses = harvest_collected_addresses(msg);
3262 if (collected_addresses != NULL) {
3263 aptr = (struct addresses_to_be_filed *)
3264 malloc(sizeof(struct addresses_to_be_filed));
3265 CtdlMailboxName(actual_rm, sizeof actual_rm,
3266 &CCC->user, USERCONTACTSROOM);
3267 aptr->roomname = strdup(actual_rm);
3268 aptr->collected_addresses = collected_addresses;
3269 begin_critical_section(S_ATBF);
3272 end_critical_section(S_ATBF);
3276 * Determine whether this message qualifies for journaling.
3278 if (msg->cm_fields['J'] != NULL) {
3279 qualified_for_journaling = 0;
3282 if (recps == NULL) {
3283 qualified_for_journaling = config.c_journal_pubmsgs;
3285 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3286 qualified_for_journaling = config.c_journal_email;
3289 qualified_for_journaling = config.c_journal_pubmsgs;
3294 * Do we have to perform journaling? If so, hand off the saved
3295 * RFC822 version will be handed off to the journaler for background
3296 * submit. Otherwise, we have to free the memory ourselves.
3298 if (saved_rfc822_version != NULL) {
3299 if (qualified_for_journaling) {
3300 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3303 FreeStrBuf(&saved_rfc822_version);
3313 void aide_message (char *text, char *subject)
3315 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3320 * Convenience function for generating small administrative messages.
3322 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3323 int format_type, const char *subject)
3325 struct CtdlMessage *msg;
3326 struct recptypes *recp = NULL;
3328 msg = malloc(sizeof(struct CtdlMessage));
3329 memset(msg, 0, sizeof(struct CtdlMessage));
3330 msg->cm_magic = CTDLMESSAGE_MAGIC;
3331 msg->cm_anon_type = MES_NORMAL;
3332 msg->cm_format_type = format_type;
3335 msg->cm_fields['A'] = strdup(from);
3337 else if (fromaddr != NULL) {
3338 msg->cm_fields['A'] = strdup(fromaddr);
3339 if (strchr(msg->cm_fields['A'], '@')) {
3340 *strchr(msg->cm_fields['A'], '@') = 0;
3344 msg->cm_fields['A'] = strdup("Citadel");
3347 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3348 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3349 msg->cm_fields['N'] = strdup(NODENAME);
3351 msg->cm_fields['R'] = strdup(to);
3352 recp = validate_recipients(to, NULL, 0);
3354 if (subject != NULL) {
3355 msg->cm_fields['U'] = strdup(subject);
3357 msg->cm_fields['M'] = strdup(text);
3359 CtdlSubmitMsg(msg, recp, room, 0);
3360 CtdlFreeMessage(msg);
3361 if (recp != NULL) free_recipients(recp);
3367 * Back end function used by CtdlMakeMessage() and similar functions
3369 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3371 size_t maxlen, /* maximum message length */
3372 char *exist, /* if non-null, append to it;
3373 exist is ALWAYS freed */
3374 int crlf, /* CRLF newlines instead of LF */
3375 int *sock /* socket handle or 0 for this session's client socket */
3384 LineBuf = NewStrBufPlain(NULL, SIZ);
3385 if (exist == NULL) {
3386 Message = NewStrBufPlain(NULL, 4 * SIZ);
3389 Message = NewStrBufPlain(exist, -1);
3393 /* Do we need to change leading ".." to "." for SMTP escaping? */
3394 if ((tlen == 1) && (*terminator == '.')) {
3398 /* read in the lines of message text one by one */
3401 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3406 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3408 if ((StrLength(LineBuf) == tlen) &&
3409 (!strcmp(ChrPtr(LineBuf), terminator)))
3412 if ( (!flushing) && (!finished) ) {
3414 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3417 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3420 /* Unescape SMTP-style input of two dots at the beginning of the line */
3422 (StrLength(LineBuf) == 2) &&
3423 (!strcmp(ChrPtr(LineBuf), "..")))
3425 StrBufCutLeft(LineBuf, 1);
3428 StrBufAppendBuf(Message, LineBuf, 0);
3431 /* if we've hit the max msg length, flush the rest */
3432 if (StrLength(Message) >= maxlen) flushing = 1;
3434 } while (!finished);
3435 FreeStrBuf(&LineBuf);
3441 * Back end function used by CtdlMakeMessage() and similar functions
3443 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3445 size_t maxlen, /* maximum message length */
3446 char *exist, /* if non-null, append to it;
3447 exist is ALWAYS freed */
3448 int crlf, /* CRLF newlines instead of LF */
3449 int *sock /* socket handle or 0 for this session's client socket */
3454 Message = CtdlReadMessageBodyBuf(terminator,
3460 if (Message == NULL)
3463 return SmashStrBuf(&Message);
3468 * Build a binary message to be saved on disk.
3469 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3470 * will become part of the message. This means you are no longer
3471 * responsible for managing that memory -- it will be freed along with
3472 * the rest of the fields when CtdlFreeMessage() is called.)
3475 struct CtdlMessage *CtdlMakeMessage(
3476 struct ctdluser *author, /* author's user structure */
3477 char *recipient, /* NULL if it's not mail */
3478 char *recp_cc, /* NULL if it's not mail */
3479 char *room, /* room where it's going */
3480 int type, /* see MES_ types in header file */
3481 int format_type, /* variformat, plain text, MIME... */
3482 char *fake_name, /* who we're masquerading as */
3483 char *my_email, /* which of my email addresses to use (empty is ok) */
3484 char *subject, /* Subject (optional) */
3485 char *supplied_euid, /* ...or NULL if this is irrelevant */
3486 char *preformatted_text, /* ...or NULL to read text from client */
3487 char *references /* Thread references */
3489 char dest_node[256];
3491 struct CtdlMessage *msg;
3493 StrBuf *FakeEncAuthor = NULL;
3495 msg = malloc(sizeof(struct CtdlMessage));
3496 memset(msg, 0, sizeof(struct CtdlMessage));
3497 msg->cm_magic = CTDLMESSAGE_MAGIC;
3498 msg->cm_anon_type = type;
3499 msg->cm_format_type = format_type;
3501 /* Don't confuse the poor folks if it's not routed mail. */
3502 strcpy(dest_node, "");
3504 if (recipient != NULL) striplt(recipient);
3505 if (recp_cc != NULL) striplt(recp_cc);
3507 /* Path or Return-Path */
3508 if (my_email == NULL) my_email = "";
3510 if (!IsEmptyStr(my_email)) {
3511 msg->cm_fields['P'] = strdup(my_email);
3514 snprintf(buf, sizeof buf, "%s", author->fullname);
3515 msg->cm_fields['P'] = strdup(buf);
3517 convert_spaces_to_underscores(msg->cm_fields['P']);
3519 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3520 msg->cm_fields['T'] = strdup(buf);
3522 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3523 FakeAuthor = NewStrBufPlain (fake_name, -1);
3526 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3528 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3529 msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3530 FreeStrBuf(&FakeAuthor);
3532 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3533 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3536 msg->cm_fields['O'] = strdup(CC->room.QRname);
3539 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3540 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3542 if ((recipient != NULL) && (recipient[0] != 0)) {
3543 msg->cm_fields['R'] = strdup(recipient);
3545 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3546 msg->cm_fields['Y'] = strdup(recp_cc);
3548 if (dest_node[0] != 0) {
3549 msg->cm_fields['D'] = strdup(dest_node);
3552 if (!IsEmptyStr(my_email)) {
3553 msg->cm_fields['F'] = strdup(my_email);
3555 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3556 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3559 if (subject != NULL) {
3562 length = strlen(subject);
3568 while ((subject[i] != '\0') &&
3569 (IsAscii = isascii(subject[i]) != 0 ))
3572 msg->cm_fields['U'] = strdup(subject);
3573 else /* ok, we've got utf8 in the string. */
3575 msg->cm_fields['U'] = rfc2047encode(subject, length);
3581 if (supplied_euid != NULL) {
3582 msg->cm_fields['E'] = strdup(supplied_euid);
3585 if (references != NULL) {
3586 if (!IsEmptyStr(references)) {
3587 msg->cm_fields['W'] = strdup(references);
3591 if (preformatted_text != NULL) {
3592 msg->cm_fields['M'] = preformatted_text;
3595 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3603 * Check to see whether we have permission to post a message in the current
3604 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3605 * returns 0 on success.
3607 int CtdlDoIHavePermissionToPostInThisRoom(
3610 const char* RemoteIdentifier,
3616 if (!(CC->logged_in) &&
3617 (PostPublic == POST_LOGGED_IN)) {
3618 snprintf(errmsgbuf, n, "Not logged in.");
3619 return (ERROR + NOT_LOGGED_IN);
3621 else if (PostPublic == CHECK_EXISTANCE) {
3622 return (0); // We're Evaling whether a recipient exists
3624 else if (!(CC->logged_in)) {
3626 if ((CC->room.QRflags & QR_READONLY)) {
3627 snprintf(errmsgbuf, n, "Not logged in.");
3628 return (ERROR + NOT_LOGGED_IN);
3630 if (CC->room.QRflags2 & QR2_MODERATED) {
3631 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3632 return (ERROR + NOT_LOGGED_IN);
3634 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3639 if (RemoteIdentifier == NULL)
3641 snprintf(errmsgbuf, n, "Need sender to permit access.");
3642 return (ERROR + USERNAME_REQUIRED);
3645 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3646 begin_critical_section(S_NETCONFIGS);
3647 if (!read_spoolcontrol_file(&sc, filename))
3649 end_critical_section(S_NETCONFIGS);
3650 snprintf(errmsgbuf, n,
3651 "This mailing list only accepts posts from subscribers.");
3652 return (ERROR + NO_SUCH_USER);
3654 end_critical_section(S_NETCONFIGS);
3655 found = is_recipient (sc, RemoteIdentifier);
3656 free_spoolcontrol_struct(&sc);
3661 snprintf(errmsgbuf, n,
3662 "This mailing list only accepts posts from subscribers.");
3663 return (ERROR + NO_SUCH_USER);
3670 if ((CC->user.axlevel < AxProbU)
3671 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3672 snprintf(errmsgbuf, n, "Need to be validated to enter "
3673 "(except in %s> to sysop)", MAILROOM);
3674 return (ERROR + HIGHER_ACCESS_REQUIRED);
3677 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3679 if ( (!(ra & UA_POSTALLOWED)) && (ra & UA_REPLYALLOWED) && (!is_reply) ) {
3681 * To be thorough, we ought to check to see if the message they are
3682 * replying to is actually a valid one in this room, but unless this
3683 * actually becomes a problem we'll go with high performance instead.
3685 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
3686 return (ERROR + HIGHER_ACCESS_REQUIRED);
3689 else if (!(ra & UA_POSTALLOWED)) {
3690 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3691 return (ERROR + HIGHER_ACCESS_REQUIRED);
3694 strcpy(errmsgbuf, "Ok");
3700 * Check to see if the specified user has Internet mail permission
3701 * (returns nonzero if permission is granted)
3703 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3705 /* Do not allow twits to send Internet mail */
3706 if (who->axlevel <= AxProbU) return(0);
3708 /* Globally enabled? */
3709 if (config.c_restrict == 0) return(1);
3711 /* User flagged ok? */
3712 if (who->flags & US_INTERNET) return(2);
3714 /* Aide level access? */
3715 if (who->axlevel >= AxAideU) return(3);
3717 /* No mail for you! */
3723 * Validate recipients, count delivery types and errors, and handle aliasing
3724 * FIXME check for dupes!!!!!
3726 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3727 * were specified, or the number of addresses found invalid.
3729 * Caller needs to free the result using free_recipients()
3731 struct recptypes *validate_recipients(const char *supplied_recipients,
3732 const char *RemoteIdentifier,
3734 struct recptypes *ret;
3735 char *recipients = NULL;
3736 char this_recp[256];
3737 char this_recp_cooked[256];
3743 struct ctdluser tempUS;
3744 struct ctdlroom tempQR;
3745 struct ctdlroom tempQR2;
3751 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3752 if (ret == NULL) return(NULL);
3754 /* Set all strings to null and numeric values to zero */
3755 memset(ret, 0, sizeof(struct recptypes));
3757 if (supplied_recipients == NULL) {
3758 recipients = strdup("");
3761 recipients = strdup(supplied_recipients);
3764 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3765 * actually need, but it's healthier for the heap than doing lots of tiny
3766 * realloc() calls instead.
3769 ret->errormsg = malloc(strlen(recipients) + 1024);
3770 ret->recp_local = malloc(strlen(recipients) + 1024);
3771 ret->recp_internet = malloc(strlen(recipients) + 1024);
3772 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3773 ret->recp_room = malloc(strlen(recipients) + 1024);
3774 ret->display_recp = malloc(strlen(recipients) + 1024);
3776 ret->errormsg[0] = 0;
3777 ret->recp_local[0] = 0;
3778 ret->recp_internet[0] = 0;
3779 ret->recp_ignet[0] = 0;
3780 ret->recp_room[0] = 0;
3781 ret->display_recp[0] = 0;
3783 ret->recptypes_magic = RECPTYPES_MAGIC;
3785 /* Change all valid separator characters to commas */
3786 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3787 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3788 recipients[i] = ',';
3792 /* Now start extracting recipients... */
3794 while (!IsEmptyStr(recipients)) {
3796 for (i=0; i<=strlen(recipients); ++i) {
3797 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3798 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3799 safestrncpy(this_recp, recipients, i+1);
3801 if (recipients[i] == ',') {
3802 strcpy(recipients, &recipients[i+1]);
3805 strcpy(recipients, "");
3812 if (IsEmptyStr(this_recp))
3814 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3816 mailtype = alias(this_recp);
3817 mailtype = alias(this_recp);
3818 mailtype = alias(this_recp);
3820 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3821 if (this_recp[j]=='_') {
3822 this_recp_cooked[j] = ' ';
3825 this_recp_cooked[j] = this_recp[j];
3828 this_recp_cooked[j] = '\0';
3833 if (!strcasecmp(this_recp, "sysop")) {
3835 strcpy(this_recp, config.c_aideroom);
3836 if (!IsEmptyStr(ret->recp_room)) {
3837 strcat(ret->recp_room, "|");
3839 strcat(ret->recp_room, this_recp);
3841 else if ( (!strncasecmp(this_recp, "room_", 5))
3842 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3844 /* Save room so we can restore it later */
3848 /* Check permissions to send mail to this room */
3849 err = CtdlDoIHavePermissionToPostInThisRoom(
3854 0 /* 0 = not a reply */
3863 if (!IsEmptyStr(ret->recp_room)) {
3864 strcat(ret->recp_room, "|");
3866 strcat(ret->recp_room, &this_recp_cooked[5]);
3869 /* Restore room in case something needs it */
3873 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3875 strcpy(this_recp, tempUS.fullname);
3876 if (!IsEmptyStr(ret->recp_local)) {
3877 strcat(ret->recp_local, "|");
3879 strcat(ret->recp_local, this_recp);
3881 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
3883 strcpy(this_recp, tempUS.fullname);
3884 if (!IsEmptyStr(ret->recp_local)) {
3885 strcat(ret->recp_local, "|");
3887 strcat(ret->recp_local, this_recp);
3895 /* Yes, you're reading this correctly: if the target
3896 * domain points back to the local system or an attached
3897 * Citadel directory, the address is invalid. That's
3898 * because if the address were valid, we would have
3899 * already translated it to a local address by now.
3901 if (IsDirectory(this_recp, 0)) {
3906 ++ret->num_internet;
3907 if (!IsEmptyStr(ret->recp_internet)) {
3908 strcat(ret->recp_internet, "|");
3910 strcat(ret->recp_internet, this_recp);
3915 if (!IsEmptyStr(ret->recp_ignet)) {
3916 strcat(ret->recp_ignet, "|");
3918 strcat(ret->recp_ignet, this_recp);
3926 if (IsEmptyStr(errmsg)) {
3927 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3930 snprintf(append, sizeof append, "%s", errmsg);
3932 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3933 if (!IsEmptyStr(ret->errormsg)) {
3934 strcat(ret->errormsg, "; ");
3936 strcat(ret->errormsg, append);
3940 if (IsEmptyStr(ret->display_recp)) {
3941 strcpy(append, this_recp);
3944 snprintf(append, sizeof append, ", %s", this_recp);
3946 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3947 strcat(ret->display_recp, append);
3952 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3953 ret->num_room + ret->num_error) == 0) {
3954 ret->num_error = (-1);
3955 strcpy(ret->errormsg, "No recipients specified.");
3958 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3959 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3960 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3961 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3962 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3963 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3971 * Destructor for struct recptypes
3973 void free_recipients(struct recptypes *valid) {
3975 if (valid == NULL) {
3979 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3980 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3984 if (valid->errormsg != NULL) free(valid->errormsg);
3985 if (valid->recp_local != NULL) free(valid->recp_local);
3986 if (valid->recp_internet != NULL) free(valid->recp_internet);
3987 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3988 if (valid->recp_room != NULL) free(valid->recp_room);
3989 if (valid->display_recp != NULL) free(valid->display_recp);
3990 if (valid->bounce_to != NULL) free(valid->bounce_to);
3991 if (valid->envelope_from != NULL) free(valid->envelope_from);
3998 * message entry - mode 0 (normal)
4000 void cmd_ent0(char *entargs)
4006 char supplied_euid[128];
4008 int format_type = 0;
4009 char newusername[256];
4010 char newuseremail[256];
4011 struct CtdlMessage *msg;
4015 struct recptypes *valid = NULL;
4016 struct recptypes *valid_to = NULL;
4017 struct recptypes *valid_cc = NULL;
4018 struct recptypes *valid_bcc = NULL;
4020 int subject_required = 0;
4025 int newuseremail_ok = 0;
4026 char references[SIZ];
4031 post = extract_int(entargs, 0);
4032 extract_token(recp, entargs, 1, '|', sizeof recp);
4033 anon_flag = extract_int(entargs, 2);
4034 format_type = extract_int(entargs, 3);
4035 extract_token(subject, entargs, 4, '|', sizeof subject);
4036 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4037 do_confirm = extract_int(entargs, 6);
4038 extract_token(cc, entargs, 7, '|', sizeof cc);
4039 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4040 switch(CC->room.QRdefaultview) {
4043 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4046 supplied_euid[0] = 0;
4049 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4050 extract_token(references, entargs, 11, '|', sizeof references);
4051 for (ptr=references; *ptr != 0; ++ptr) {
4052 if (*ptr == '!') *ptr = '|';
4055 /* first check to make sure the request is valid. */
4057 err = CtdlDoIHavePermissionToPostInThisRoom(
4062 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4066 cprintf("%d %s\n", err, errmsg);
4070 /* Check some other permission type things. */
4072 if (IsEmptyStr(newusername)) {
4073 strcpy(newusername, CC->user.fullname);
4075 if ( (CC->user.axlevel < AxAideU)
4076 && (strcasecmp(newusername, CC->user.fullname))
4077 && (strcasecmp(newusername, CC->cs_inet_fn))
4079 cprintf("%d You don't have permission to author messages as '%s'.\n",
4080 ERROR + HIGHER_ACCESS_REQUIRED,
4087 if (IsEmptyStr(newuseremail)) {
4088 newuseremail_ok = 1;
4091 if (!IsEmptyStr(newuseremail)) {
4092 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4093 newuseremail_ok = 1;
4095 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4096 j = num_tokens(CC->cs_inet_other_emails, '|');
4097 for (i=0; i<j; ++i) {
4098 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4099 if (!strcasecmp(newuseremail, buf)) {
4100 newuseremail_ok = 1;
4106 if (!newuseremail_ok) {
4107 cprintf("%d You don't have permission to author messages as '%s'.\n",
4108 ERROR + HIGHER_ACCESS_REQUIRED,
4114 CC->cs_flags |= CS_POSTING;
4116 /* In mailbox rooms we have to behave a little differently --
4117 * make sure the user has specified at least one recipient. Then
4118 * validate the recipient(s). We do this for the Mail> room, as
4119 * well as any room which has the "Mailbox" view set - unless it
4120 * is the DRAFTS room which does not require recipients
4123 if ( ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4124 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4125 ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4126 if (CC->user.axlevel < AxProbU) {
4127 strcpy(recp, "sysop");
4132 valid_to = validate_recipients(recp, NULL, 0);
4133 if (valid_to->num_error > 0) {
4134 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4135 free_recipients(valid_to);
4139 valid_cc = validate_recipients(cc, NULL, 0);
4140 if (valid_cc->num_error > 0) {
4141 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4142 free_recipients(valid_to);
4143 free_recipients(valid_cc);
4147 valid_bcc = validate_recipients(bcc, NULL, 0);
4148 if (valid_bcc->num_error > 0) {
4149 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4150 free_recipients(valid_to);
4151 free_recipients(valid_cc);
4152 free_recipients(valid_bcc);
4156 /* Recipient required, but none were specified */
4157 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4158 free_recipients(valid_to);
4159 free_recipients(valid_cc);
4160 free_recipients(valid_bcc);
4161 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4165 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4166 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4167 cprintf("%d You do not have permission "
4168 "to send Internet mail.\n",
4169 ERROR + HIGHER_ACCESS_REQUIRED);
4170 free_recipients(valid_to);
4171 free_recipients(valid_cc);
4172 free_recipients(valid_bcc);
4177 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)
4178 && (CC->user.axlevel < AxNetU) ) {
4179 cprintf("%d Higher access required for network mail.\n",
4180 ERROR + HIGHER_ACCESS_REQUIRED);
4181 free_recipients(valid_to);
4182 free_recipients(valid_cc);
4183 free_recipients(valid_bcc);
4187 if ((RESTRICT_INTERNET == 1)
4188 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4189 && ((CC->user.flags & US_INTERNET) == 0)
4190 && (!CC->internal_pgm)) {
4191 cprintf("%d You don't have access to Internet mail.\n",
4192 ERROR + HIGHER_ACCESS_REQUIRED);
4193 free_recipients(valid_to);
4194 free_recipients(valid_cc);
4195 free_recipients(valid_bcc);
4201 /* Is this a room which has anonymous-only or anonymous-option? */
4202 anonymous = MES_NORMAL;
4203 if (CC->room.QRflags & QR_ANONONLY) {
4204 anonymous = MES_ANONONLY;
4206 if (CC->room.QRflags & QR_ANONOPT) {
4207 if (anon_flag == 1) { /* only if the user requested it */
4208 anonymous = MES_ANONOPT;
4212 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4216 /* Recommend to the client that the use of a message subject is
4217 * strongly recommended in this room, if either the SUBJECTREQ flag
4218 * is set, or if there is one or more Internet email recipients.
4220 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4221 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4222 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4223 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4225 /* If we're only checking the validity of the request, return
4226 * success without creating the message.
4229 cprintf("%d %s|%d\n", CIT_OK,
4230 ((valid_to != NULL) ? valid_to->display_recp : ""),
4232 free_recipients(valid_to);
4233 free_recipients(valid_cc);
4234 free_recipients(valid_bcc);
4238 /* We don't need these anymore because we'll do it differently below */
4239 free_recipients(valid_to);
4240 free_recipients(valid_cc);
4241 free_recipients(valid_bcc);
4243 /* Read in the message from the client. */
4245 cprintf("%d send message\n", START_CHAT_MODE);
4247 cprintf("%d send message\n", SEND_LISTING);
4250 msg = CtdlMakeMessage(&CC->user, recp, cc,
4251 CC->room.QRname, anonymous, format_type,
4252 newusername, newuseremail, subject,
4253 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4256 /* Put together one big recipients struct containing to/cc/bcc all in
4257 * one. This is for the envelope.
4259 char *all_recps = malloc(SIZ * 3);
4260 strcpy(all_recps, recp);
4261 if (!IsEmptyStr(cc)) {
4262 if (!IsEmptyStr(all_recps)) {
4263 strcat(all_recps, ",");
4265 strcat(all_recps, cc);
4267 if (!IsEmptyStr(bcc)) {
4268 if (!IsEmptyStr(all_recps)) {
4269 strcat(all_recps, ",");
4271 strcat(all_recps, bcc);
4273 if (!IsEmptyStr(all_recps)) {
4274 valid = validate_recipients(all_recps, NULL, 0);
4282 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4285 cprintf("%ld\n", msgnum);
4287 cprintf("Message accepted.\n");
4290 cprintf("Internal error.\n");
4292 if (msg->cm_fields['E'] != NULL) {
4293 cprintf("%s\n", msg->cm_fields['E']);
4300 CtdlFreeMessage(msg);
4302 if (valid != NULL) {
4303 free_recipients(valid);
4311 * API function to delete messages which match a set of criteria
4312 * (returns the actual number of messages deleted)
4314 int CtdlDeleteMessages(char *room_name, /* which room */
4315 long *dmsgnums, /* array of msg numbers to be deleted */
4316 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4317 char *content_type /* or "" for any. regular expressions expected. */
4320 struct ctdlroom qrbuf;
4321 struct cdbdata *cdbfr;
4322 long *msglist = NULL;
4323 long *dellist = NULL;
4326 int num_deleted = 0;
4328 struct MetaData smi;
4331 int need_to_free_re = 0;
4333 if (content_type) if (!IsEmptyStr(content_type)) {
4334 regcomp(&re, content_type, 0);
4335 need_to_free_re = 1;
4337 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4338 room_name, num_dmsgnums, content_type);
4340 /* get room record, obtaining a lock... */
4341 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4342 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4344 if (need_to_free_re) regfree(&re);
4345 return (0); /* room not found */
4347 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4349 if (cdbfr != NULL) {
4350 dellist = malloc(cdbfr->len);
4351 msglist = (long *) cdbfr->ptr;
4352 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4353 num_msgs = cdbfr->len / sizeof(long);
4357 for (i = 0; i < num_msgs; ++i) {
4360 /* Set/clear a bit for each criterion */
4362 /* 0 messages in the list or a null list means that we are
4363 * interested in deleting any messages which meet the other criteria.
4365 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4366 delete_this |= 0x01;
4369 for (j=0; j<num_dmsgnums; ++j) {
4370 if (msglist[i] == dmsgnums[j]) {
4371 delete_this |= 0x01;
4376 if (IsEmptyStr(content_type)) {
4377 delete_this |= 0x02;
4379 GetMetaData(&smi, msglist[i]);
4380 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4381 delete_this |= 0x02;
4385 /* Delete message only if all bits are set */
4386 if (delete_this == 0x03) {
4387 dellist[num_deleted++] = msglist[i];
4392 num_msgs = sort_msglist(msglist, num_msgs);
4393 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4394 msglist, (int)(num_msgs * sizeof(long)));
4397 qrbuf.QRhighest = msglist[num_msgs - 1];
4399 qrbuf.QRhighest = 0;
4401 CtdlPutRoomLock(&qrbuf);
4403 /* Go through the messages we pulled out of the index, and decrement
4404 * their reference counts by 1. If this is the only room the message
4405 * was in, the reference count will reach zero and the message will
4406 * automatically be deleted from the database. We do this in a
4407 * separate pass because there might be plug-in hooks getting called,
4408 * and we don't want that happening during an S_ROOMS critical
4411 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4412 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4413 AdjRefCount(dellist[i], -1);
4416 /* Now free the memory we used, and go away. */
4417 if (msglist != NULL) free(msglist);
4418 if (dellist != NULL) free(dellist);
4419 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4420 if (need_to_free_re) regfree(&re);
4421 return (num_deleted);
4427 * Check whether the current user has permission to delete messages from
4428 * the current room (returns 1 for yes, 0 for no)
4430 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4432 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4433 if (ra & UA_DELETEALLOWED) return(1);
4441 * Delete message from current room
4443 void cmd_dele(char *args)
4452 extract_token(msgset, args, 0, '|', sizeof msgset);
4453 num_msgs = num_tokens(msgset, ',');
4455 cprintf("%d Nothing to do.\n", CIT_OK);
4459 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4460 cprintf("%d Higher access required.\n",
4461 ERROR + HIGHER_ACCESS_REQUIRED);
4466 * Build our message set to be moved/copied
4468 msgs = malloc(num_msgs * sizeof(long));
4469 for (i=0; i<num_msgs; ++i) {
4470 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4471 msgs[i] = atol(msgtok);
4474 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4478 cprintf("%d %d message%s deleted.\n", CIT_OK,
4479 num_deleted, ((num_deleted != 1) ? "s" : ""));
4481 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4489 * move or copy a message to another room
4491 void cmd_move(char *args)
4498 char targ[ROOMNAMELEN];
4499 struct ctdlroom qtemp;
4506 extract_token(msgset, args, 0, '|', sizeof msgset);
4507 num_msgs = num_tokens(msgset, ',');
4509 cprintf("%d Nothing to do.\n", CIT_OK);
4513 extract_token(targ, args, 1, '|', sizeof targ);
4514 convert_room_name_macros(targ, sizeof targ);
4515 targ[ROOMNAMELEN - 1] = 0;
4516 is_copy = extract_int(args, 2);
4518 if (CtdlGetRoom(&qtemp, targ) != 0) {
4519 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4523 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4524 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4528 CtdlGetUser(&CC->user, CC->curr_user);
4529 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4531 /* Check for permission to perform this operation.
4532 * Remember: "CC->room" is source, "qtemp" is target.
4536 /* Aides can move/copy */
4537 if (CC->user.axlevel >= AxAideU) permit = 1;
4539 /* Room aides can move/copy */
4540 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4542 /* Permit move/copy from personal rooms */
4543 if ((CC->room.QRflags & QR_MAILBOX)
4544 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4546 /* Permit only copy from public to personal room */
4548 && (!(CC->room.QRflags & QR_MAILBOX))
4549 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4551 /* Permit message removal from collaborative delete rooms */
4552 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4554 /* Users allowed to post into the target room may move into it too. */
4555 if ((CC->room.QRflags & QR_MAILBOX) &&
4556 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4558 /* User must have access to target room */
4559 if (!(ra & UA_KNOWN)) permit = 0;
4562 cprintf("%d Higher access required.\n",
4563 ERROR + HIGHER_ACCESS_REQUIRED);
4568 * Build our message set to be moved/copied
4570 msgs = malloc(num_msgs * sizeof(long));
4571 for (i=0; i<num_msgs; ++i) {
4572 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4573 msgs[i] = atol(msgtok);
4579 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4581 cprintf("%d Cannot store message(s) in %s: error %d\n",
4587 /* Now delete the message from the source room,
4588 * if this is a 'move' rather than a 'copy' operation.
4591 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4595 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4601 * GetMetaData() - Get the supplementary record for a message
4603 void GetMetaData(struct MetaData *smibuf, long msgnum)
4606 struct cdbdata *cdbsmi;
4609 memset(smibuf, 0, sizeof(struct MetaData));
4610 smibuf->meta_msgnum = msgnum;
4611 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4613 /* Use the negative of the message number for its supp record index */
4614 TheIndex = (0L - msgnum);
4616 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4617 if (cdbsmi == NULL) {
4618 return; /* record not found; go with defaults */
4620 memcpy(smibuf, cdbsmi->ptr,
4621 ((cdbsmi->len > sizeof(struct MetaData)) ?
4622 sizeof(struct MetaData) : cdbsmi->len));
4629 * PutMetaData() - (re)write supplementary record for a message
4631 void PutMetaData(struct MetaData *smibuf)
4635 /* Use the negative of the message number for the metadata db index */
4636 TheIndex = (0L - smibuf->meta_msgnum);
4638 cdb_store(CDB_MSGMAIN,
4639 &TheIndex, (int)sizeof(long),
4640 smibuf, (int)sizeof(struct MetaData));
4645 * AdjRefCount - submit an adjustment to the reference count for a message.
4646 * (These are just queued -- we actually process them later.)
4648 void AdjRefCount(long msgnum, int incr)
4650 struct arcq new_arcq;
4653 CtdlLogPrintf(CTDL_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4657 begin_critical_section(S_SUPPMSGMAIN);
4658 if (arcfp == NULL) {
4659 arcfp = fopen(file_arcq, "ab+");
4661 end_critical_section(S_SUPPMSGMAIN);
4663 /* msgnum < 0 means that we're trying to close the file */
4665 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4666 begin_critical_section(S_SUPPMSGMAIN);
4667 if (arcfp != NULL) {
4671 end_critical_section(S_SUPPMSGMAIN);
4676 * If we can't open the queue, perform the operation synchronously.
4678 if (arcfp == NULL) {
4679 TDAP_AdjRefCount(msgnum, incr);
4683 new_arcq.arcq_msgnum = msgnum;
4684 new_arcq.arcq_delta = incr;
4685 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4693 * TDAP_ProcessAdjRefCountQueue()
4695 * Process the queue of message count adjustments that was created by calls
4696 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4697 * for each one. This should be an "off hours" operation.
4699 int TDAP_ProcessAdjRefCountQueue(void)
4701 char file_arcq_temp[PATH_MAX];
4704 struct arcq arcq_rec;
4705 int num_records_processed = 0;
4707 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4709 begin_critical_section(S_SUPPMSGMAIN);
4710 if (arcfp != NULL) {
4715 r = link(file_arcq, file_arcq_temp);
4717 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4718 end_critical_section(S_SUPPMSGMAIN);
4719 return(num_records_processed);
4723 end_critical_section(S_SUPPMSGMAIN);
4725 fp = fopen(file_arcq_temp, "rb");
4727 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4728 return(num_records_processed);
4731 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4732 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4733 ++num_records_processed;
4737 r = unlink(file_arcq_temp);
4739 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4742 return(num_records_processed);
4748 * TDAP_AdjRefCount - adjust the reference count for a message.
4749 * This one does it "for real" because it's called by
4750 * the autopurger function that processes the queue
4751 * created by AdjRefCount(). If a message's reference
4752 * count becomes zero, we also delete the message from
4753 * disk and de-index it.
4755 void TDAP_AdjRefCount(long msgnum, int incr)
4758 struct MetaData smi;
4761 /* This is a *tight* critical section; please keep it that way, as
4762 * it may get called while nested in other critical sections.
4763 * Complicating this any further will surely cause deadlock!
4765 begin_critical_section(S_SUPPMSGMAIN);
4766 GetMetaData(&smi, msgnum);
4767 smi.meta_refcount += incr;
4769 end_critical_section(S_SUPPMSGMAIN);
4770 CtdlLogPrintf(CTDL_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
4771 msgnum, incr, smi.meta_refcount
4774 /* If the reference count is now zero, delete the message
4775 * (and its supplementary record as well).
4777 if (smi.meta_refcount == 0) {
4778 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4780 /* Call delete hooks with NULL room to show it has gone altogether */
4781 PerformDeleteHooks(NULL, msgnum);
4783 /* Remove from message base */
4785 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4786 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4788 /* Remove metadata record */
4789 delnum = (0L - msgnum);
4790 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4796 * Write a generic object to this room
4798 * Note: this could be much more efficient. Right now we use two temporary
4799 * files, and still pull the message into memory as with all others.
4801 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4802 char *content_type, /* MIME type of this object */
4803 char *raw_message, /* Data to be written */
4804 off_t raw_length, /* Size of raw_message */
4805 struct ctdluser *is_mailbox, /* Mailbox room? */
4806 int is_binary, /* Is encoding necessary? */
4807 int is_unique, /* Del others of this type? */
4808 unsigned int flags /* Internal save flags */
4812 struct ctdlroom qrbuf;
4813 char roomname[ROOMNAMELEN];
4814 struct CtdlMessage *msg;
4815 char *encoded_message = NULL;
4817 if (is_mailbox != NULL) {
4818 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4821 safestrncpy(roomname, req_room, sizeof(roomname));
4824 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4827 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4830 encoded_message = malloc((size_t)(raw_length + 4096));
4833 sprintf(encoded_message, "Content-type: %s\n", content_type);
4836 sprintf(&encoded_message[strlen(encoded_message)],
4837 "Content-transfer-encoding: base64\n\n"
4841 sprintf(&encoded_message[strlen(encoded_message)],
4842 "Content-transfer-encoding: 7bit\n\n"
4848 &encoded_message[strlen(encoded_message)],
4856 &encoded_message[strlen(encoded_message)],
4862 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4863 msg = malloc(sizeof(struct CtdlMessage));
4864 memset(msg, 0, sizeof(struct CtdlMessage));
4865 msg->cm_magic = CTDLMESSAGE_MAGIC;
4866 msg->cm_anon_type = MES_NORMAL;
4867 msg->cm_format_type = 4;
4868 msg->cm_fields['A'] = strdup(CC->user.fullname);
4869 msg->cm_fields['O'] = strdup(req_room);
4870 msg->cm_fields['N'] = strdup(config.c_nodename);
4871 msg->cm_fields['H'] = strdup(config.c_humannode);
4872 msg->cm_flags = flags;
4874 msg->cm_fields['M'] = encoded_message;
4876 /* Create the requested room if we have to. */
4877 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4878 CtdlCreateRoom(roomname,
4879 ( (is_mailbox != NULL) ? 5 : 3 ),
4880 "", 0, 1, 0, VIEW_BBS);
4882 /* If the caller specified this object as unique, delete all
4883 * other objects of this type that are currently in the room.
4886 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4887 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4890 /* Now write the data */
4891 CtdlSubmitMsg(msg, NULL, roomname, 0);
4892 CtdlFreeMessage(msg);
4900 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4901 config_msgnum = msgnum;
4905 char *CtdlGetSysConfig(char *sysconfname) {
4906 char hold_rm[ROOMNAMELEN];
4909 struct CtdlMessage *msg;
4912 strcpy(hold_rm, CC->room.QRname);
4913 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4914 CtdlGetRoom(&CC->room, hold_rm);
4919 /* We want the last (and probably only) config in this room */
4920 begin_critical_section(S_CONFIG);
4921 config_msgnum = (-1L);
4922 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4923 CtdlGetSysConfigBackend, NULL);
4924 msgnum = config_msgnum;
4925 end_critical_section(S_CONFIG);
4931 msg = CtdlFetchMessage(msgnum, 1);
4933 conf = strdup(msg->cm_fields['M']);
4934 CtdlFreeMessage(msg);
4941 CtdlGetRoom(&CC->room, hold_rm);
4943 if (conf != NULL) do {
4944 extract_token(buf, conf, 0, '\n', sizeof buf);
4945 strcpy(conf, &conf[strlen(buf)+1]);
4946 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4952 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4953 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4958 * Determine whether a given Internet address belongs to the current user
4960 int CtdlIsMe(char *addr, int addr_buf_len)
4962 struct recptypes *recp;
4965 recp = validate_recipients(addr, NULL, 0);
4966 if (recp == NULL) return(0);
4968 if (recp->num_local == 0) {
4969 free_recipients(recp);
4973 for (i=0; i<recp->num_local; ++i) {
4974 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4975 if (!strcasecmp(addr, CC->user.fullname)) {
4976 free_recipients(recp);
4981 free_recipients(recp);
4987 * Citadel protocol command to do the same
4989 void cmd_isme(char *argbuf) {
4992 if (CtdlAccessCheck(ac_logged_in)) return;
4993 extract_token(addr, argbuf, 0, '|', sizeof addr);
4995 if (CtdlIsMe(addr, sizeof addr)) {
4996 cprintf("%d %s\n", CIT_OK, addr);
4999 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5005 /*****************************************************************************/
5006 /* MODULE INITIALIZATION STUFF */
5007 /*****************************************************************************/
5009 CTDL_MODULE_INIT(msgbase)
5012 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5013 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5014 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5015 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5016 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5017 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5018 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5019 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5020 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5021 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5022 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5023 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5026 /* return our Subversion id for the Log */