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) < 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 msg = malloc(sizeof(struct CtdlMessage));
3494 memset(msg, 0, sizeof(struct CtdlMessage));
3495 msg->cm_magic = CTDLMESSAGE_MAGIC;
3496 msg->cm_anon_type = type;
3497 msg->cm_format_type = format_type;
3499 /* Don't confuse the poor folks if it's not routed mail. */
3500 strcpy(dest_node, "");
3502 if (recipient != NULL) striplt(recipient);
3503 if (recp_cc != NULL) striplt(recp_cc);
3505 /* Path or Return-Path */
3506 if (my_email == NULL) my_email = "";
3508 if (!IsEmptyStr(my_email)) {
3509 msg->cm_fields['P'] = strdup(my_email);
3512 snprintf(buf, sizeof buf, "%s", author->fullname);
3513 msg->cm_fields['P'] = strdup(buf);
3515 convert_spaces_to_underscores(msg->cm_fields['P']);
3517 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3518 msg->cm_fields['T'] = strdup(buf);
3520 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3521 msg->cm_fields['A'] = strdup(fake_name);
3524 msg->cm_fields['A'] = strdup(author->fullname);
3527 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3528 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3531 msg->cm_fields['O'] = strdup(CC->room.QRname);
3534 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3535 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3537 if ((recipient != NULL) && (recipient[0] != 0)) {
3538 msg->cm_fields['R'] = strdup(recipient);
3540 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3541 msg->cm_fields['Y'] = strdup(recp_cc);
3543 if (dest_node[0] != 0) {
3544 msg->cm_fields['D'] = strdup(dest_node);
3547 if (!IsEmptyStr(my_email)) {
3548 msg->cm_fields['F'] = strdup(my_email);
3550 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3551 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3554 if (subject != NULL) {
3557 length = strlen(subject);
3563 while ((subject[i] != '\0') &&
3564 (IsAscii = isascii(subject[i]) != 0 ))
3567 msg->cm_fields['U'] = strdup(subject);
3568 else /* ok, we've got utf8 in the string. */
3570 msg->cm_fields['U'] = rfc2047encode(subject, length);
3576 if (supplied_euid != NULL) {
3577 msg->cm_fields['E'] = strdup(supplied_euid);
3580 if (references != NULL) {
3581 if (!IsEmptyStr(references)) {
3582 msg->cm_fields['W'] = strdup(references);
3586 if (preformatted_text != NULL) {
3587 msg->cm_fields['M'] = preformatted_text;
3590 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3598 * Check to see whether we have permission to post a message in the current
3599 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3600 * returns 0 on success.
3602 int CtdlDoIHavePermissionToPostInThisRoom(
3605 const char* RemoteIdentifier,
3611 if (!(CC->logged_in) &&
3612 (PostPublic == POST_LOGGED_IN)) {
3613 snprintf(errmsgbuf, n, "Not logged in.");
3614 return (ERROR + NOT_LOGGED_IN);
3616 else if (PostPublic == CHECK_EXISTANCE) {
3617 return (0); // We're Evaling whether a recipient exists
3619 else if (!(CC->logged_in)) {
3621 if ((CC->room.QRflags & QR_READONLY)) {
3622 snprintf(errmsgbuf, n, "Not logged in.");
3623 return (ERROR + NOT_LOGGED_IN);
3625 if (CC->room.QRflags2 & QR2_MODERATED) {
3626 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3627 return (ERROR + NOT_LOGGED_IN);
3629 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3634 if (RemoteIdentifier == NULL)
3636 snprintf(errmsgbuf, n, "Need sender to permit access.");
3637 return (ERROR + USERNAME_REQUIRED);
3640 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3641 begin_critical_section(S_NETCONFIGS);
3642 if (!read_spoolcontrol_file(&sc, filename))
3644 end_critical_section(S_NETCONFIGS);
3645 snprintf(errmsgbuf, n,
3646 "This mailing list only accepts posts from subscribers.");
3647 return (ERROR + NO_SUCH_USER);
3649 end_critical_section(S_NETCONFIGS);
3650 found = is_recipient (sc, RemoteIdentifier);
3651 free_spoolcontrol_struct(&sc);
3656 snprintf(errmsgbuf, n,
3657 "This mailing list only accepts posts from subscribers.");
3658 return (ERROR + NO_SUCH_USER);
3665 if ((CC->user.axlevel < AxProbU)
3666 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3667 snprintf(errmsgbuf, n, "Need to be validated to enter "
3668 "(except in %s> to sysop)", MAILROOM);
3669 return (ERROR + HIGHER_ACCESS_REQUIRED);
3672 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3674 if ( (!(ra & UA_POSTALLOWED)) && (ra & UA_REPLYALLOWED) && (!is_reply) ) {
3676 * To be thorough, we ought to check to see if the message they are
3677 * replying to is actually a valid one in this room, but unless this
3678 * actually becomes a problem we'll go with high performance instead.
3680 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
3681 return (ERROR + HIGHER_ACCESS_REQUIRED);
3684 else if (!(ra & UA_POSTALLOWED)) {
3685 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3686 return (ERROR + HIGHER_ACCESS_REQUIRED);
3689 strcpy(errmsgbuf, "Ok");
3695 * Check to see if the specified user has Internet mail permission
3696 * (returns nonzero if permission is granted)
3698 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3700 /* Do not allow twits to send Internet mail */
3701 if (who->axlevel <= AxProbU) return(0);
3703 /* Globally enabled? */
3704 if (config.c_restrict == 0) return(1);
3706 /* User flagged ok? */
3707 if (who->flags & US_INTERNET) return(2);
3709 /* Aide level access? */
3710 if (who->axlevel >= AxAideU) return(3);
3712 /* No mail for you! */
3718 * Validate recipients, count delivery types and errors, and handle aliasing
3719 * FIXME check for dupes!!!!!
3721 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3722 * were specified, or the number of addresses found invalid.
3724 * Caller needs to free the result using free_recipients()
3726 struct recptypes *validate_recipients(const char *supplied_recipients,
3727 const char *RemoteIdentifier,
3729 struct recptypes *ret;
3730 char *recipients = NULL;
3731 char this_recp[256];
3732 char this_recp_cooked[256];
3738 struct ctdluser tempUS;
3739 struct ctdlroom tempQR;
3740 struct ctdlroom tempQR2;
3746 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3747 if (ret == NULL) return(NULL);
3749 /* Set all strings to null and numeric values to zero */
3750 memset(ret, 0, sizeof(struct recptypes));
3752 if (supplied_recipients == NULL) {
3753 recipients = strdup("");
3756 recipients = strdup(supplied_recipients);
3759 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3760 * actually need, but it's healthier for the heap than doing lots of tiny
3761 * realloc() calls instead.
3764 ret->errormsg = malloc(strlen(recipients) + 1024);
3765 ret->recp_local = malloc(strlen(recipients) + 1024);
3766 ret->recp_internet = malloc(strlen(recipients) + 1024);
3767 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3768 ret->recp_room = malloc(strlen(recipients) + 1024);
3769 ret->display_recp = malloc(strlen(recipients) + 1024);
3771 ret->errormsg[0] = 0;
3772 ret->recp_local[0] = 0;
3773 ret->recp_internet[0] = 0;
3774 ret->recp_ignet[0] = 0;
3775 ret->recp_room[0] = 0;
3776 ret->display_recp[0] = 0;
3778 ret->recptypes_magic = RECPTYPES_MAGIC;
3780 /* Change all valid separator characters to commas */
3781 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3782 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3783 recipients[i] = ',';
3787 /* Now start extracting recipients... */
3789 while (!IsEmptyStr(recipients)) {
3791 for (i=0; i<=strlen(recipients); ++i) {
3792 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3793 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3794 safestrncpy(this_recp, recipients, i+1);
3796 if (recipients[i] == ',') {
3797 strcpy(recipients, &recipients[i+1]);
3800 strcpy(recipients, "");
3807 if (IsEmptyStr(this_recp))
3809 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3811 mailtype = alias(this_recp);
3812 mailtype = alias(this_recp);
3813 mailtype = alias(this_recp);
3815 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3816 if (this_recp[j]=='_') {
3817 this_recp_cooked[j] = ' ';
3820 this_recp_cooked[j] = this_recp[j];
3823 this_recp_cooked[j] = '\0';
3828 if (!strcasecmp(this_recp, "sysop")) {
3830 strcpy(this_recp, config.c_aideroom);
3831 if (!IsEmptyStr(ret->recp_room)) {
3832 strcat(ret->recp_room, "|");
3834 strcat(ret->recp_room, this_recp);
3836 else if ( (!strncasecmp(this_recp, "room_", 5))
3837 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3839 /* Save room so we can restore it later */
3843 /* Check permissions to send mail to this room */
3844 err = CtdlDoIHavePermissionToPostInThisRoom(
3849 0 /* 0 = not a reply */
3858 if (!IsEmptyStr(ret->recp_room)) {
3859 strcat(ret->recp_room, "|");
3861 strcat(ret->recp_room, &this_recp_cooked[5]);
3864 /* Restore room in case something needs it */
3868 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3870 strcpy(this_recp, tempUS.fullname);
3871 if (!IsEmptyStr(ret->recp_local)) {
3872 strcat(ret->recp_local, "|");
3874 strcat(ret->recp_local, this_recp);
3876 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
3878 strcpy(this_recp, tempUS.fullname);
3879 if (!IsEmptyStr(ret->recp_local)) {
3880 strcat(ret->recp_local, "|");
3882 strcat(ret->recp_local, this_recp);
3890 /* Yes, you're reading this correctly: if the target
3891 * domain points back to the local system or an attached
3892 * Citadel directory, the address is invalid. That's
3893 * because if the address were valid, we would have
3894 * already translated it to a local address by now.
3896 if (IsDirectory(this_recp, 0)) {
3901 ++ret->num_internet;
3902 if (!IsEmptyStr(ret->recp_internet)) {
3903 strcat(ret->recp_internet, "|");
3905 strcat(ret->recp_internet, this_recp);
3910 if (!IsEmptyStr(ret->recp_ignet)) {
3911 strcat(ret->recp_ignet, "|");
3913 strcat(ret->recp_ignet, this_recp);
3921 if (IsEmptyStr(errmsg)) {
3922 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3925 snprintf(append, sizeof append, "%s", errmsg);
3927 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3928 if (!IsEmptyStr(ret->errormsg)) {
3929 strcat(ret->errormsg, "; ");
3931 strcat(ret->errormsg, append);
3935 if (IsEmptyStr(ret->display_recp)) {
3936 strcpy(append, this_recp);
3939 snprintf(append, sizeof append, ", %s", this_recp);
3941 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3942 strcat(ret->display_recp, append);
3947 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3948 ret->num_room + ret->num_error) == 0) {
3949 ret->num_error = (-1);
3950 strcpy(ret->errormsg, "No recipients specified.");
3953 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3954 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3955 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3956 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3957 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3958 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3966 * Destructor for struct recptypes
3968 void free_recipients(struct recptypes *valid) {
3970 if (valid == NULL) {
3974 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3975 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3979 if (valid->errormsg != NULL) free(valid->errormsg);
3980 if (valid->recp_local != NULL) free(valid->recp_local);
3981 if (valid->recp_internet != NULL) free(valid->recp_internet);
3982 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3983 if (valid->recp_room != NULL) free(valid->recp_room);
3984 if (valid->display_recp != NULL) free(valid->display_recp);
3985 if (valid->bounce_to != NULL) free(valid->bounce_to);
3986 if (valid->envelope_from != NULL) free(valid->envelope_from);
3993 * message entry - mode 0 (normal)
3995 void cmd_ent0(char *entargs)
4001 char supplied_euid[128];
4003 int format_type = 0;
4004 char newusername[256];
4005 char newuseremail[256];
4006 struct CtdlMessage *msg;
4010 struct recptypes *valid = NULL;
4011 struct recptypes *valid_to = NULL;
4012 struct recptypes *valid_cc = NULL;
4013 struct recptypes *valid_bcc = NULL;
4015 int subject_required = 0;
4020 int newuseremail_ok = 0;
4021 char references[SIZ];
4026 post = extract_int(entargs, 0);
4027 extract_token(recp, entargs, 1, '|', sizeof recp);
4028 anon_flag = extract_int(entargs, 2);
4029 format_type = extract_int(entargs, 3);
4030 extract_token(subject, entargs, 4, '|', sizeof subject);
4031 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4032 do_confirm = extract_int(entargs, 6);
4033 extract_token(cc, entargs, 7, '|', sizeof cc);
4034 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4035 switch(CC->room.QRdefaultview) {
4038 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4041 supplied_euid[0] = 0;
4044 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4045 extract_token(references, entargs, 11, '|', sizeof references);
4046 for (ptr=references; *ptr != 0; ++ptr) {
4047 if (*ptr == '!') *ptr = '|';
4050 /* first check to make sure the request is valid. */
4052 err = CtdlDoIHavePermissionToPostInThisRoom(
4057 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4061 cprintf("%d %s\n", err, errmsg);
4065 /* Check some other permission type things. */
4067 if (IsEmptyStr(newusername)) {
4068 strcpy(newusername, CC->user.fullname);
4070 if ( (CC->user.axlevel < AxAideU)
4071 && (strcasecmp(newusername, CC->user.fullname))
4072 && (strcasecmp(newusername, CC->cs_inet_fn))
4074 cprintf("%d You don't have permission to author messages as '%s'.\n",
4075 ERROR + HIGHER_ACCESS_REQUIRED,
4082 if (IsEmptyStr(newuseremail)) {
4083 newuseremail_ok = 1;
4086 if (!IsEmptyStr(newuseremail)) {
4087 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4088 newuseremail_ok = 1;
4090 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4091 j = num_tokens(CC->cs_inet_other_emails, '|');
4092 for (i=0; i<j; ++i) {
4093 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4094 if (!strcasecmp(newuseremail, buf)) {
4095 newuseremail_ok = 1;
4101 if (!newuseremail_ok) {
4102 cprintf("%d You don't have permission to author messages as '%s'.\n",
4103 ERROR + HIGHER_ACCESS_REQUIRED,
4109 CC->cs_flags |= CS_POSTING;
4111 /* In mailbox rooms we have to behave a little differently --
4112 * make sure the user has specified at least one recipient. Then
4113 * validate the recipient(s). We do this for the Mail> room, as
4114 * well as any room which has the "Mailbox" view set - unless it
4115 * is the DRAFTS room which does not require recipients
4118 if ( ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4119 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4120 ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4121 if (CC->user.axlevel < AxProbU) {
4122 strcpy(recp, "sysop");
4127 valid_to = validate_recipients(recp, NULL, 0);
4128 if (valid_to->num_error > 0) {
4129 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4130 free_recipients(valid_to);
4134 valid_cc = validate_recipients(cc, NULL, 0);
4135 if (valid_cc->num_error > 0) {
4136 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4137 free_recipients(valid_to);
4138 free_recipients(valid_cc);
4142 valid_bcc = validate_recipients(bcc, NULL, 0);
4143 if (valid_bcc->num_error > 0) {
4144 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4145 free_recipients(valid_to);
4146 free_recipients(valid_cc);
4147 free_recipients(valid_bcc);
4151 /* Recipient required, but none were specified */
4152 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4153 free_recipients(valid_to);
4154 free_recipients(valid_cc);
4155 free_recipients(valid_bcc);
4156 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4160 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4161 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4162 cprintf("%d You do not have permission "
4163 "to send Internet mail.\n",
4164 ERROR + HIGHER_ACCESS_REQUIRED);
4165 free_recipients(valid_to);
4166 free_recipients(valid_cc);
4167 free_recipients(valid_bcc);
4172 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)
4173 && (CC->user.axlevel < AxNetU) ) {
4174 cprintf("%d Higher access required for network mail.\n",
4175 ERROR + HIGHER_ACCESS_REQUIRED);
4176 free_recipients(valid_to);
4177 free_recipients(valid_cc);
4178 free_recipients(valid_bcc);
4182 if ((RESTRICT_INTERNET == 1)
4183 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4184 && ((CC->user.flags & US_INTERNET) == 0)
4185 && (!CC->internal_pgm)) {
4186 cprintf("%d You don't have access to Internet mail.\n",
4187 ERROR + HIGHER_ACCESS_REQUIRED);
4188 free_recipients(valid_to);
4189 free_recipients(valid_cc);
4190 free_recipients(valid_bcc);
4196 /* Is this a room which has anonymous-only or anonymous-option? */
4197 anonymous = MES_NORMAL;
4198 if (CC->room.QRflags & QR_ANONONLY) {
4199 anonymous = MES_ANONONLY;
4201 if (CC->room.QRflags & QR_ANONOPT) {
4202 if (anon_flag == 1) { /* only if the user requested it */
4203 anonymous = MES_ANONOPT;
4207 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4211 /* Recommend to the client that the use of a message subject is
4212 * strongly recommended in this room, if either the SUBJECTREQ flag
4213 * is set, or if there is one or more Internet email recipients.
4215 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4216 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4217 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4218 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4220 /* If we're only checking the validity of the request, return
4221 * success without creating the message.
4224 cprintf("%d %s|%d\n", CIT_OK,
4225 ((valid_to != NULL) ? valid_to->display_recp : ""),
4227 free_recipients(valid_to);
4228 free_recipients(valid_cc);
4229 free_recipients(valid_bcc);
4233 /* We don't need these anymore because we'll do it differently below */
4234 free_recipients(valid_to);
4235 free_recipients(valid_cc);
4236 free_recipients(valid_bcc);
4238 /* Read in the message from the client. */
4240 cprintf("%d send message\n", START_CHAT_MODE);
4242 cprintf("%d send message\n", SEND_LISTING);
4245 msg = CtdlMakeMessage(&CC->user, recp, cc,
4246 CC->room.QRname, anonymous, format_type,
4247 newusername, newuseremail, subject,
4248 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4251 /* Put together one big recipients struct containing to/cc/bcc all in
4252 * one. This is for the envelope.
4254 char *all_recps = malloc(SIZ * 3);
4255 strcpy(all_recps, recp);
4256 if (!IsEmptyStr(cc)) {
4257 if (!IsEmptyStr(all_recps)) {
4258 strcat(all_recps, ",");
4260 strcat(all_recps, cc);
4262 if (!IsEmptyStr(bcc)) {
4263 if (!IsEmptyStr(all_recps)) {
4264 strcat(all_recps, ",");
4266 strcat(all_recps, bcc);
4268 if (!IsEmptyStr(all_recps)) {
4269 valid = validate_recipients(all_recps, NULL, 0);
4277 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4280 cprintf("%ld\n", msgnum);
4282 cprintf("Message accepted.\n");
4285 cprintf("Internal error.\n");
4287 if (msg->cm_fields['E'] != NULL) {
4288 cprintf("%s\n", msg->cm_fields['E']);
4295 CtdlFreeMessage(msg);
4297 if (valid != NULL) {
4298 free_recipients(valid);
4306 * API function to delete messages which match a set of criteria
4307 * (returns the actual number of messages deleted)
4309 int CtdlDeleteMessages(char *room_name, /* which room */
4310 long *dmsgnums, /* array of msg numbers to be deleted */
4311 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4312 char *content_type /* or "" for any. regular expressions expected. */
4315 struct ctdlroom qrbuf;
4316 struct cdbdata *cdbfr;
4317 long *msglist = NULL;
4318 long *dellist = NULL;
4321 int num_deleted = 0;
4323 struct MetaData smi;
4326 int need_to_free_re = 0;
4328 if (content_type) if (!IsEmptyStr(content_type)) {
4329 regcomp(&re, content_type, 0);
4330 need_to_free_re = 1;
4332 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4333 room_name, num_dmsgnums, content_type);
4335 /* get room record, obtaining a lock... */
4336 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4337 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4339 if (need_to_free_re) regfree(&re);
4340 return (0); /* room not found */
4342 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4344 if (cdbfr != NULL) {
4345 dellist = malloc(cdbfr->len);
4346 msglist = (long *) cdbfr->ptr;
4347 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4348 num_msgs = cdbfr->len / sizeof(long);
4352 for (i = 0; i < num_msgs; ++i) {
4355 /* Set/clear a bit for each criterion */
4357 /* 0 messages in the list or a null list means that we are
4358 * interested in deleting any messages which meet the other criteria.
4360 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4361 delete_this |= 0x01;
4364 for (j=0; j<num_dmsgnums; ++j) {
4365 if (msglist[i] == dmsgnums[j]) {
4366 delete_this |= 0x01;
4371 if (IsEmptyStr(content_type)) {
4372 delete_this |= 0x02;
4374 GetMetaData(&smi, msglist[i]);
4375 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4376 delete_this |= 0x02;
4380 /* Delete message only if all bits are set */
4381 if (delete_this == 0x03) {
4382 dellist[num_deleted++] = msglist[i];
4387 num_msgs = sort_msglist(msglist, num_msgs);
4388 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4389 msglist, (int)(num_msgs * sizeof(long)));
4392 qrbuf.QRhighest = msglist[num_msgs - 1];
4394 qrbuf.QRhighest = 0;
4396 CtdlPutRoomLock(&qrbuf);
4398 /* Go through the messages we pulled out of the index, and decrement
4399 * their reference counts by 1. If this is the only room the message
4400 * was in, the reference count will reach zero and the message will
4401 * automatically be deleted from the database. We do this in a
4402 * separate pass because there might be plug-in hooks getting called,
4403 * and we don't want that happening during an S_ROOMS critical
4406 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4407 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4408 AdjRefCount(dellist[i], -1);
4411 /* Now free the memory we used, and go away. */
4412 if (msglist != NULL) free(msglist);
4413 if (dellist != NULL) free(dellist);
4414 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4415 if (need_to_free_re) regfree(&re);
4416 return (num_deleted);
4422 * Check whether the current user has permission to delete messages from
4423 * the current room (returns 1 for yes, 0 for no)
4425 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4427 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4428 if (ra & UA_DELETEALLOWED) return(1);
4436 * Delete message from current room
4438 void cmd_dele(char *args)
4447 extract_token(msgset, args, 0, '|', sizeof msgset);
4448 num_msgs = num_tokens(msgset, ',');
4450 cprintf("%d Nothing to do.\n", CIT_OK);
4454 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4455 cprintf("%d Higher access required.\n",
4456 ERROR + HIGHER_ACCESS_REQUIRED);
4461 * Build our message set to be moved/copied
4463 msgs = malloc(num_msgs * sizeof(long));
4464 for (i=0; i<num_msgs; ++i) {
4465 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4466 msgs[i] = atol(msgtok);
4469 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4473 cprintf("%d %d message%s deleted.\n", CIT_OK,
4474 num_deleted, ((num_deleted != 1) ? "s" : ""));
4476 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4484 * move or copy a message to another room
4486 void cmd_move(char *args)
4493 char targ[ROOMNAMELEN];
4494 struct ctdlroom qtemp;
4501 extract_token(msgset, args, 0, '|', sizeof msgset);
4502 num_msgs = num_tokens(msgset, ',');
4504 cprintf("%d Nothing to do.\n", CIT_OK);
4508 extract_token(targ, args, 1, '|', sizeof targ);
4509 convert_room_name_macros(targ, sizeof targ);
4510 targ[ROOMNAMELEN - 1] = 0;
4511 is_copy = extract_int(args, 2);
4513 if (CtdlGetRoom(&qtemp, targ) != 0) {
4514 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4518 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4519 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4523 CtdlGetUser(&CC->user, CC->curr_user);
4524 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4526 /* Check for permission to perform this operation.
4527 * Remember: "CC->room" is source, "qtemp" is target.
4531 /* Aides can move/copy */
4532 if (CC->user.axlevel >= AxAideU) permit = 1;
4534 /* Room aides can move/copy */
4535 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4537 /* Permit move/copy from personal rooms */
4538 if ((CC->room.QRflags & QR_MAILBOX)
4539 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4541 /* Permit only copy from public to personal room */
4543 && (!(CC->room.QRflags & QR_MAILBOX))
4544 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4546 /* Permit message removal from collaborative delete rooms */
4547 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4549 /* Users allowed to post into the target room may move into it too. */
4550 if ((CC->room.QRflags & QR_MAILBOX) &&
4551 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4553 /* User must have access to target room */
4554 if (!(ra & UA_KNOWN)) permit = 0;
4557 cprintf("%d Higher access required.\n",
4558 ERROR + HIGHER_ACCESS_REQUIRED);
4563 * Build our message set to be moved/copied
4565 msgs = malloc(num_msgs * sizeof(long));
4566 for (i=0; i<num_msgs; ++i) {
4567 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4568 msgs[i] = atol(msgtok);
4574 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4576 cprintf("%d Cannot store message(s) in %s: error %d\n",
4582 /* Now delete the message from the source room,
4583 * if this is a 'move' rather than a 'copy' operation.
4586 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4590 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4596 * GetMetaData() - Get the supplementary record for a message
4598 void GetMetaData(struct MetaData *smibuf, long msgnum)
4601 struct cdbdata *cdbsmi;
4604 memset(smibuf, 0, sizeof(struct MetaData));
4605 smibuf->meta_msgnum = msgnum;
4606 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4608 /* Use the negative of the message number for its supp record index */
4609 TheIndex = (0L - msgnum);
4611 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4612 if (cdbsmi == NULL) {
4613 return; /* record not found; go with defaults */
4615 memcpy(smibuf, cdbsmi->ptr,
4616 ((cdbsmi->len > sizeof(struct MetaData)) ?
4617 sizeof(struct MetaData) : cdbsmi->len));
4624 * PutMetaData() - (re)write supplementary record for a message
4626 void PutMetaData(struct MetaData *smibuf)
4630 /* Use the negative of the message number for the metadata db index */
4631 TheIndex = (0L - smibuf->meta_msgnum);
4633 cdb_store(CDB_MSGMAIN,
4634 &TheIndex, (int)sizeof(long),
4635 smibuf, (int)sizeof(struct MetaData));
4640 * AdjRefCount - submit an adjustment to the reference count for a message.
4641 * (These are just queued -- we actually process them later.)
4643 void AdjRefCount(long msgnum, int incr)
4645 struct arcq new_arcq;
4648 CtdlLogPrintf(CTDL_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4652 begin_critical_section(S_SUPPMSGMAIN);
4653 if (arcfp == NULL) {
4654 arcfp = fopen(file_arcq, "ab+");
4656 end_critical_section(S_SUPPMSGMAIN);
4658 /* msgnum < 0 means that we're trying to close the file */
4660 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4661 begin_critical_section(S_SUPPMSGMAIN);
4662 if (arcfp != NULL) {
4666 end_critical_section(S_SUPPMSGMAIN);
4671 * If we can't open the queue, perform the operation synchronously.
4673 if (arcfp == NULL) {
4674 TDAP_AdjRefCount(msgnum, incr);
4678 new_arcq.arcq_msgnum = msgnum;
4679 new_arcq.arcq_delta = incr;
4680 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4688 * TDAP_ProcessAdjRefCountQueue()
4690 * Process the queue of message count adjustments that was created by calls
4691 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4692 * for each one. This should be an "off hours" operation.
4694 int TDAP_ProcessAdjRefCountQueue(void)
4696 char file_arcq_temp[PATH_MAX];
4699 struct arcq arcq_rec;
4700 int num_records_processed = 0;
4702 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4704 begin_critical_section(S_SUPPMSGMAIN);
4705 if (arcfp != NULL) {
4710 r = link(file_arcq, file_arcq_temp);
4712 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4713 end_critical_section(S_SUPPMSGMAIN);
4714 return(num_records_processed);
4718 end_critical_section(S_SUPPMSGMAIN);
4720 fp = fopen(file_arcq_temp, "rb");
4722 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4723 return(num_records_processed);
4726 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4727 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4728 ++num_records_processed;
4732 r = unlink(file_arcq_temp);
4734 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4737 return(num_records_processed);
4743 * TDAP_AdjRefCount - adjust the reference count for a message.
4744 * This one does it "for real" because it's called by
4745 * the autopurger function that processes the queue
4746 * created by AdjRefCount(). If a message's reference
4747 * count becomes zero, we also delete the message from
4748 * disk and de-index it.
4750 void TDAP_AdjRefCount(long msgnum, int incr)
4753 struct MetaData smi;
4756 /* This is a *tight* critical section; please keep it that way, as
4757 * it may get called while nested in other critical sections.
4758 * Complicating this any further will surely cause deadlock!
4760 begin_critical_section(S_SUPPMSGMAIN);
4761 GetMetaData(&smi, msgnum);
4762 smi.meta_refcount += incr;
4764 end_critical_section(S_SUPPMSGMAIN);
4765 CtdlLogPrintf(CTDL_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
4766 msgnum, incr, smi.meta_refcount
4769 /* If the reference count is now zero, delete the message
4770 * (and its supplementary record as well).
4772 if (smi.meta_refcount == 0) {
4773 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4775 /* Call delete hooks with NULL room to show it has gone altogether */
4776 PerformDeleteHooks(NULL, msgnum);
4778 /* Remove from message base */
4780 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4781 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4783 /* Remove metadata record */
4784 delnum = (0L - msgnum);
4785 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4791 * Write a generic object to this room
4793 * Note: this could be much more efficient. Right now we use two temporary
4794 * files, and still pull the message into memory as with all others.
4796 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4797 char *content_type, /* MIME type of this object */
4798 char *raw_message, /* Data to be written */
4799 off_t raw_length, /* Size of raw_message */
4800 struct ctdluser *is_mailbox, /* Mailbox room? */
4801 int is_binary, /* Is encoding necessary? */
4802 int is_unique, /* Del others of this type? */
4803 unsigned int flags /* Internal save flags */
4807 struct ctdlroom qrbuf;
4808 char roomname[ROOMNAMELEN];
4809 struct CtdlMessage *msg;
4810 char *encoded_message = NULL;
4812 if (is_mailbox != NULL) {
4813 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4816 safestrncpy(roomname, req_room, sizeof(roomname));
4819 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4822 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4825 encoded_message = malloc((size_t)(raw_length + 4096));
4828 sprintf(encoded_message, "Content-type: %s\n", content_type);
4831 sprintf(&encoded_message[strlen(encoded_message)],
4832 "Content-transfer-encoding: base64\n\n"
4836 sprintf(&encoded_message[strlen(encoded_message)],
4837 "Content-transfer-encoding: 7bit\n\n"
4843 &encoded_message[strlen(encoded_message)],
4851 &encoded_message[strlen(encoded_message)],
4857 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4858 msg = malloc(sizeof(struct CtdlMessage));
4859 memset(msg, 0, sizeof(struct CtdlMessage));
4860 msg->cm_magic = CTDLMESSAGE_MAGIC;
4861 msg->cm_anon_type = MES_NORMAL;
4862 msg->cm_format_type = 4;
4863 msg->cm_fields['A'] = strdup(CC->user.fullname);
4864 msg->cm_fields['O'] = strdup(req_room);
4865 msg->cm_fields['N'] = strdup(config.c_nodename);
4866 msg->cm_fields['H'] = strdup(config.c_humannode);
4867 msg->cm_flags = flags;
4869 msg->cm_fields['M'] = encoded_message;
4871 /* Create the requested room if we have to. */
4872 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4873 CtdlCreateRoom(roomname,
4874 ( (is_mailbox != NULL) ? 5 : 3 ),
4875 "", 0, 1, 0, VIEW_BBS);
4877 /* If the caller specified this object as unique, delete all
4878 * other objects of this type that are currently in the room.
4881 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4882 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4885 /* Now write the data */
4886 CtdlSubmitMsg(msg, NULL, roomname, 0);
4887 CtdlFreeMessage(msg);
4895 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4896 config_msgnum = msgnum;
4900 char *CtdlGetSysConfig(char *sysconfname) {
4901 char hold_rm[ROOMNAMELEN];
4904 struct CtdlMessage *msg;
4907 strcpy(hold_rm, CC->room.QRname);
4908 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4909 CtdlGetRoom(&CC->room, hold_rm);
4914 /* We want the last (and probably only) config in this room */
4915 begin_critical_section(S_CONFIG);
4916 config_msgnum = (-1L);
4917 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4918 CtdlGetSysConfigBackend, NULL);
4919 msgnum = config_msgnum;
4920 end_critical_section(S_CONFIG);
4926 msg = CtdlFetchMessage(msgnum, 1);
4928 conf = strdup(msg->cm_fields['M']);
4929 CtdlFreeMessage(msg);
4936 CtdlGetRoom(&CC->room, hold_rm);
4938 if (conf != NULL) do {
4939 extract_token(buf, conf, 0, '\n', sizeof buf);
4940 strcpy(conf, &conf[strlen(buf)+1]);
4941 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4947 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4948 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4953 * Determine whether a given Internet address belongs to the current user
4955 int CtdlIsMe(char *addr, int addr_buf_len)
4957 struct recptypes *recp;
4960 recp = validate_recipients(addr, NULL, 0);
4961 if (recp == NULL) return(0);
4963 if (recp->num_local == 0) {
4964 free_recipients(recp);
4968 for (i=0; i<recp->num_local; ++i) {
4969 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4970 if (!strcasecmp(addr, CC->user.fullname)) {
4971 free_recipients(recp);
4976 free_recipients(recp);
4982 * Citadel protocol command to do the same
4984 void cmd_isme(char *argbuf) {
4987 if (CtdlAccessCheck(ac_logged_in)) return;
4988 extract_token(addr, argbuf, 0, '|', sizeof addr);
4990 if (CtdlIsMe(addr, sizeof addr)) {
4991 cprintf("%d %s\n", CIT_OK, addr);
4994 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5000 /*****************************************************************************/
5001 /* MODULE INITIALIZATION STUFF */
5002 /*****************************************************************************/
5004 CTDL_MODULE_INIT(msgbase)
5007 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5008 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5009 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5010 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5011 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5012 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5013 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5014 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5015 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5016 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5017 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5018 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5021 /* return our Subversion id for the Log */