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 StrBuf *FakeEncAuthor = NULL;
3495 msg = malloc(sizeof(struct CtdlMessage));
3496 memset(msg, 0, sizeof(struct CtdlMessage));
3497 msg->cm_magic = CTDLMESSAGE_MAGIC;
3498 msg->cm_anon_type = type;
3499 msg->cm_format_type = format_type;
3501 /* Don't confuse the poor folks if it's not routed mail. */
3502 strcpy(dest_node, "");
3504 if (recipient != NULL) striplt(recipient);
3505 if (recp_cc != NULL) striplt(recp_cc);
3507 /* Path or Return-Path */
3508 if (my_email == NULL) my_email = "";
3510 if (!IsEmptyStr(my_email)) {
3511 msg->cm_fields['P'] = strdup(my_email);
3514 snprintf(buf, sizeof buf, "%s", author->fullname);
3515 msg->cm_fields['P'] = strdup(buf);
3517 convert_spaces_to_underscores(msg->cm_fields['P']);
3519 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3520 msg->cm_fields['T'] = strdup(buf);
3522 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3523 FakeAuthor = NewStrBufPlain (fake_name, -1);
3526 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3528 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3529 msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3531 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3532 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3535 msg->cm_fields['O'] = strdup(CC->room.QRname);
3538 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3539 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3541 if ((recipient != NULL) && (recipient[0] != 0)) {
3542 msg->cm_fields['R'] = strdup(recipient);
3544 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3545 msg->cm_fields['Y'] = strdup(recp_cc);
3547 if (dest_node[0] != 0) {
3548 msg->cm_fields['D'] = strdup(dest_node);
3551 if (!IsEmptyStr(my_email)) {
3552 msg->cm_fields['F'] = strdup(my_email);
3554 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3555 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3558 if (subject != NULL) {
3561 length = strlen(subject);
3567 while ((subject[i] != '\0') &&
3568 (IsAscii = isascii(subject[i]) != 0 ))
3571 msg->cm_fields['U'] = strdup(subject);
3572 else /* ok, we've got utf8 in the string. */
3574 msg->cm_fields['U'] = rfc2047encode(subject, length);
3580 if (supplied_euid != NULL) {
3581 msg->cm_fields['E'] = strdup(supplied_euid);
3584 if (references != NULL) {
3585 if (!IsEmptyStr(references)) {
3586 msg->cm_fields['W'] = strdup(references);
3590 if (preformatted_text != NULL) {
3591 msg->cm_fields['M'] = preformatted_text;
3594 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3602 * Check to see whether we have permission to post a message in the current
3603 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3604 * returns 0 on success.
3606 int CtdlDoIHavePermissionToPostInThisRoom(
3609 const char* RemoteIdentifier,
3615 if (!(CC->logged_in) &&
3616 (PostPublic == POST_LOGGED_IN)) {
3617 snprintf(errmsgbuf, n, "Not logged in.");
3618 return (ERROR + NOT_LOGGED_IN);
3620 else if (PostPublic == CHECK_EXISTANCE) {
3621 return (0); // We're Evaling whether a recipient exists
3623 else if (!(CC->logged_in)) {
3625 if ((CC->room.QRflags & QR_READONLY)) {
3626 snprintf(errmsgbuf, n, "Not logged in.");
3627 return (ERROR + NOT_LOGGED_IN);
3629 if (CC->room.QRflags2 & QR2_MODERATED) {
3630 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3631 return (ERROR + NOT_LOGGED_IN);
3633 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3638 if (RemoteIdentifier == NULL)
3640 snprintf(errmsgbuf, n, "Need sender to permit access.");
3641 return (ERROR + USERNAME_REQUIRED);
3644 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3645 begin_critical_section(S_NETCONFIGS);
3646 if (!read_spoolcontrol_file(&sc, filename))
3648 end_critical_section(S_NETCONFIGS);
3649 snprintf(errmsgbuf, n,
3650 "This mailing list only accepts posts from subscribers.");
3651 return (ERROR + NO_SUCH_USER);
3653 end_critical_section(S_NETCONFIGS);
3654 found = is_recipient (sc, RemoteIdentifier);
3655 free_spoolcontrol_struct(&sc);
3660 snprintf(errmsgbuf, n,
3661 "This mailing list only accepts posts from subscribers.");
3662 return (ERROR + NO_SUCH_USER);
3669 if ((CC->user.axlevel < AxProbU)
3670 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3671 snprintf(errmsgbuf, n, "Need to be validated to enter "
3672 "(except in %s> to sysop)", MAILROOM);
3673 return (ERROR + HIGHER_ACCESS_REQUIRED);
3676 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3678 if ( (!(ra & UA_POSTALLOWED)) && (ra & UA_REPLYALLOWED) && (!is_reply) ) {
3680 * To be thorough, we ought to check to see if the message they are
3681 * replying to is actually a valid one in this room, but unless this
3682 * actually becomes a problem we'll go with high performance instead.
3684 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
3685 return (ERROR + HIGHER_ACCESS_REQUIRED);
3688 else if (!(ra & UA_POSTALLOWED)) {
3689 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3690 return (ERROR + HIGHER_ACCESS_REQUIRED);
3693 strcpy(errmsgbuf, "Ok");
3699 * Check to see if the specified user has Internet mail permission
3700 * (returns nonzero if permission is granted)
3702 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3704 /* Do not allow twits to send Internet mail */
3705 if (who->axlevel <= AxProbU) return(0);
3707 /* Globally enabled? */
3708 if (config.c_restrict == 0) return(1);
3710 /* User flagged ok? */
3711 if (who->flags & US_INTERNET) return(2);
3713 /* Aide level access? */
3714 if (who->axlevel >= AxAideU) return(3);
3716 /* No mail for you! */
3722 * Validate recipients, count delivery types and errors, and handle aliasing
3723 * FIXME check for dupes!!!!!
3725 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3726 * were specified, or the number of addresses found invalid.
3728 * Caller needs to free the result using free_recipients()
3730 struct recptypes *validate_recipients(const char *supplied_recipients,
3731 const char *RemoteIdentifier,
3733 struct recptypes *ret;
3734 char *recipients = NULL;
3735 char this_recp[256];
3736 char this_recp_cooked[256];
3742 struct ctdluser tempUS;
3743 struct ctdlroom tempQR;
3744 struct ctdlroom tempQR2;
3750 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3751 if (ret == NULL) return(NULL);
3753 /* Set all strings to null and numeric values to zero */
3754 memset(ret, 0, sizeof(struct recptypes));
3756 if (supplied_recipients == NULL) {
3757 recipients = strdup("");
3760 recipients = strdup(supplied_recipients);
3763 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3764 * actually need, but it's healthier for the heap than doing lots of tiny
3765 * realloc() calls instead.
3768 ret->errormsg = malloc(strlen(recipients) + 1024);
3769 ret->recp_local = malloc(strlen(recipients) + 1024);
3770 ret->recp_internet = malloc(strlen(recipients) + 1024);
3771 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3772 ret->recp_room = malloc(strlen(recipients) + 1024);
3773 ret->display_recp = malloc(strlen(recipients) + 1024);
3775 ret->errormsg[0] = 0;
3776 ret->recp_local[0] = 0;
3777 ret->recp_internet[0] = 0;
3778 ret->recp_ignet[0] = 0;
3779 ret->recp_room[0] = 0;
3780 ret->display_recp[0] = 0;
3782 ret->recptypes_magic = RECPTYPES_MAGIC;
3784 /* Change all valid separator characters to commas */
3785 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3786 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3787 recipients[i] = ',';
3791 /* Now start extracting recipients... */
3793 while (!IsEmptyStr(recipients)) {
3795 for (i=0; i<=strlen(recipients); ++i) {
3796 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3797 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3798 safestrncpy(this_recp, recipients, i+1);
3800 if (recipients[i] == ',') {
3801 strcpy(recipients, &recipients[i+1]);
3804 strcpy(recipients, "");
3811 if (IsEmptyStr(this_recp))
3813 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3815 mailtype = alias(this_recp);
3816 mailtype = alias(this_recp);
3817 mailtype = alias(this_recp);
3819 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3820 if (this_recp[j]=='_') {
3821 this_recp_cooked[j] = ' ';
3824 this_recp_cooked[j] = this_recp[j];
3827 this_recp_cooked[j] = '\0';
3832 if (!strcasecmp(this_recp, "sysop")) {
3834 strcpy(this_recp, config.c_aideroom);
3835 if (!IsEmptyStr(ret->recp_room)) {
3836 strcat(ret->recp_room, "|");
3838 strcat(ret->recp_room, this_recp);
3840 else if ( (!strncasecmp(this_recp, "room_", 5))
3841 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3843 /* Save room so we can restore it later */
3847 /* Check permissions to send mail to this room */
3848 err = CtdlDoIHavePermissionToPostInThisRoom(
3853 0 /* 0 = not a reply */
3862 if (!IsEmptyStr(ret->recp_room)) {
3863 strcat(ret->recp_room, "|");
3865 strcat(ret->recp_room, &this_recp_cooked[5]);
3868 /* Restore room in case something needs it */
3872 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3874 strcpy(this_recp, tempUS.fullname);
3875 if (!IsEmptyStr(ret->recp_local)) {
3876 strcat(ret->recp_local, "|");
3878 strcat(ret->recp_local, this_recp);
3880 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
3882 strcpy(this_recp, tempUS.fullname);
3883 if (!IsEmptyStr(ret->recp_local)) {
3884 strcat(ret->recp_local, "|");
3886 strcat(ret->recp_local, this_recp);
3894 /* Yes, you're reading this correctly: if the target
3895 * domain points back to the local system or an attached
3896 * Citadel directory, the address is invalid. That's
3897 * because if the address were valid, we would have
3898 * already translated it to a local address by now.
3900 if (IsDirectory(this_recp, 0)) {
3905 ++ret->num_internet;
3906 if (!IsEmptyStr(ret->recp_internet)) {
3907 strcat(ret->recp_internet, "|");
3909 strcat(ret->recp_internet, this_recp);
3914 if (!IsEmptyStr(ret->recp_ignet)) {
3915 strcat(ret->recp_ignet, "|");
3917 strcat(ret->recp_ignet, this_recp);
3925 if (IsEmptyStr(errmsg)) {
3926 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3929 snprintf(append, sizeof append, "%s", errmsg);
3931 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3932 if (!IsEmptyStr(ret->errormsg)) {
3933 strcat(ret->errormsg, "; ");
3935 strcat(ret->errormsg, append);
3939 if (IsEmptyStr(ret->display_recp)) {
3940 strcpy(append, this_recp);
3943 snprintf(append, sizeof append, ", %s", this_recp);
3945 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3946 strcat(ret->display_recp, append);
3951 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3952 ret->num_room + ret->num_error) == 0) {
3953 ret->num_error = (-1);
3954 strcpy(ret->errormsg, "No recipients specified.");
3957 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3958 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3959 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3960 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3961 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3962 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3970 * Destructor for struct recptypes
3972 void free_recipients(struct recptypes *valid) {
3974 if (valid == NULL) {
3978 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3979 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3983 if (valid->errormsg != NULL) free(valid->errormsg);
3984 if (valid->recp_local != NULL) free(valid->recp_local);
3985 if (valid->recp_internet != NULL) free(valid->recp_internet);
3986 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3987 if (valid->recp_room != NULL) free(valid->recp_room);
3988 if (valid->display_recp != NULL) free(valid->display_recp);
3989 if (valid->bounce_to != NULL) free(valid->bounce_to);
3990 if (valid->envelope_from != NULL) free(valid->envelope_from);
3997 * message entry - mode 0 (normal)
3999 void cmd_ent0(char *entargs)
4005 char supplied_euid[128];
4007 int format_type = 0;
4008 char newusername[256];
4009 char newuseremail[256];
4010 struct CtdlMessage *msg;
4014 struct recptypes *valid = NULL;
4015 struct recptypes *valid_to = NULL;
4016 struct recptypes *valid_cc = NULL;
4017 struct recptypes *valid_bcc = NULL;
4019 int subject_required = 0;
4024 int newuseremail_ok = 0;
4025 char references[SIZ];
4030 post = extract_int(entargs, 0);
4031 extract_token(recp, entargs, 1, '|', sizeof recp);
4032 anon_flag = extract_int(entargs, 2);
4033 format_type = extract_int(entargs, 3);
4034 extract_token(subject, entargs, 4, '|', sizeof subject);
4035 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4036 do_confirm = extract_int(entargs, 6);
4037 extract_token(cc, entargs, 7, '|', sizeof cc);
4038 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4039 switch(CC->room.QRdefaultview) {
4042 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4045 supplied_euid[0] = 0;
4048 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4049 extract_token(references, entargs, 11, '|', sizeof references);
4050 for (ptr=references; *ptr != 0; ++ptr) {
4051 if (*ptr == '!') *ptr = '|';
4054 /* first check to make sure the request is valid. */
4056 err = CtdlDoIHavePermissionToPostInThisRoom(
4061 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4065 cprintf("%d %s\n", err, errmsg);
4069 /* Check some other permission type things. */
4071 if (IsEmptyStr(newusername)) {
4072 strcpy(newusername, CC->user.fullname);
4074 if ( (CC->user.axlevel < AxAideU)
4075 && (strcasecmp(newusername, CC->user.fullname))
4076 && (strcasecmp(newusername, CC->cs_inet_fn))
4078 cprintf("%d You don't have permission to author messages as '%s'.\n",
4079 ERROR + HIGHER_ACCESS_REQUIRED,
4086 if (IsEmptyStr(newuseremail)) {
4087 newuseremail_ok = 1;
4090 if (!IsEmptyStr(newuseremail)) {
4091 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4092 newuseremail_ok = 1;
4094 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4095 j = num_tokens(CC->cs_inet_other_emails, '|');
4096 for (i=0; i<j; ++i) {
4097 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4098 if (!strcasecmp(newuseremail, buf)) {
4099 newuseremail_ok = 1;
4105 if (!newuseremail_ok) {
4106 cprintf("%d You don't have permission to author messages as '%s'.\n",
4107 ERROR + HIGHER_ACCESS_REQUIRED,
4113 CC->cs_flags |= CS_POSTING;
4115 /* In mailbox rooms we have to behave a little differently --
4116 * make sure the user has specified at least one recipient. Then
4117 * validate the recipient(s). We do this for the Mail> room, as
4118 * well as any room which has the "Mailbox" view set - unless it
4119 * is the DRAFTS room which does not require recipients
4122 if ( ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4123 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4124 ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4125 if (CC->user.axlevel < AxProbU) {
4126 strcpy(recp, "sysop");
4131 valid_to = validate_recipients(recp, NULL, 0);
4132 if (valid_to->num_error > 0) {
4133 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4134 free_recipients(valid_to);
4138 valid_cc = validate_recipients(cc, NULL, 0);
4139 if (valid_cc->num_error > 0) {
4140 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4141 free_recipients(valid_to);
4142 free_recipients(valid_cc);
4146 valid_bcc = validate_recipients(bcc, NULL, 0);
4147 if (valid_bcc->num_error > 0) {
4148 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4149 free_recipients(valid_to);
4150 free_recipients(valid_cc);
4151 free_recipients(valid_bcc);
4155 /* Recipient required, but none were specified */
4156 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4157 free_recipients(valid_to);
4158 free_recipients(valid_cc);
4159 free_recipients(valid_bcc);
4160 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4164 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4165 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4166 cprintf("%d You do not have permission "
4167 "to send Internet mail.\n",
4168 ERROR + HIGHER_ACCESS_REQUIRED);
4169 free_recipients(valid_to);
4170 free_recipients(valid_cc);
4171 free_recipients(valid_bcc);
4176 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)
4177 && (CC->user.axlevel < AxNetU) ) {
4178 cprintf("%d Higher access required for network mail.\n",
4179 ERROR + HIGHER_ACCESS_REQUIRED);
4180 free_recipients(valid_to);
4181 free_recipients(valid_cc);
4182 free_recipients(valid_bcc);
4186 if ((RESTRICT_INTERNET == 1)
4187 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4188 && ((CC->user.flags & US_INTERNET) == 0)
4189 && (!CC->internal_pgm)) {
4190 cprintf("%d You don't have access to Internet mail.\n",
4191 ERROR + HIGHER_ACCESS_REQUIRED);
4192 free_recipients(valid_to);
4193 free_recipients(valid_cc);
4194 free_recipients(valid_bcc);
4200 /* Is this a room which has anonymous-only or anonymous-option? */
4201 anonymous = MES_NORMAL;
4202 if (CC->room.QRflags & QR_ANONONLY) {
4203 anonymous = MES_ANONONLY;
4205 if (CC->room.QRflags & QR_ANONOPT) {
4206 if (anon_flag == 1) { /* only if the user requested it */
4207 anonymous = MES_ANONOPT;
4211 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4215 /* Recommend to the client that the use of a message subject is
4216 * strongly recommended in this room, if either the SUBJECTREQ flag
4217 * is set, or if there is one or more Internet email recipients.
4219 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4220 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4221 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4222 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4224 /* If we're only checking the validity of the request, return
4225 * success without creating the message.
4228 cprintf("%d %s|%d\n", CIT_OK,
4229 ((valid_to != NULL) ? valid_to->display_recp : ""),
4231 free_recipients(valid_to);
4232 free_recipients(valid_cc);
4233 free_recipients(valid_bcc);
4237 /* We don't need these anymore because we'll do it differently below */
4238 free_recipients(valid_to);
4239 free_recipients(valid_cc);
4240 free_recipients(valid_bcc);
4242 /* Read in the message from the client. */
4244 cprintf("%d send message\n", START_CHAT_MODE);
4246 cprintf("%d send message\n", SEND_LISTING);
4249 msg = CtdlMakeMessage(&CC->user, recp, cc,
4250 CC->room.QRname, anonymous, format_type,
4251 newusername, newuseremail, subject,
4252 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4255 /* Put together one big recipients struct containing to/cc/bcc all in
4256 * one. This is for the envelope.
4258 char *all_recps = malloc(SIZ * 3);
4259 strcpy(all_recps, recp);
4260 if (!IsEmptyStr(cc)) {
4261 if (!IsEmptyStr(all_recps)) {
4262 strcat(all_recps, ",");
4264 strcat(all_recps, cc);
4266 if (!IsEmptyStr(bcc)) {
4267 if (!IsEmptyStr(all_recps)) {
4268 strcat(all_recps, ",");
4270 strcat(all_recps, bcc);
4272 if (!IsEmptyStr(all_recps)) {
4273 valid = validate_recipients(all_recps, NULL, 0);
4281 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4284 cprintf("%ld\n", msgnum);
4286 cprintf("Message accepted.\n");
4289 cprintf("Internal error.\n");
4291 if (msg->cm_fields['E'] != NULL) {
4292 cprintf("%s\n", msg->cm_fields['E']);
4299 CtdlFreeMessage(msg);
4301 if (valid != NULL) {
4302 free_recipients(valid);
4310 * API function to delete messages which match a set of criteria
4311 * (returns the actual number of messages deleted)
4313 int CtdlDeleteMessages(char *room_name, /* which room */
4314 long *dmsgnums, /* array of msg numbers to be deleted */
4315 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4316 char *content_type /* or "" for any. regular expressions expected. */
4319 struct ctdlroom qrbuf;
4320 struct cdbdata *cdbfr;
4321 long *msglist = NULL;
4322 long *dellist = NULL;
4325 int num_deleted = 0;
4327 struct MetaData smi;
4330 int need_to_free_re = 0;
4332 if (content_type) if (!IsEmptyStr(content_type)) {
4333 regcomp(&re, content_type, 0);
4334 need_to_free_re = 1;
4336 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4337 room_name, num_dmsgnums, content_type);
4339 /* get room record, obtaining a lock... */
4340 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4341 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4343 if (need_to_free_re) regfree(&re);
4344 return (0); /* room not found */
4346 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4348 if (cdbfr != NULL) {
4349 dellist = malloc(cdbfr->len);
4350 msglist = (long *) cdbfr->ptr;
4351 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4352 num_msgs = cdbfr->len / sizeof(long);
4356 for (i = 0; i < num_msgs; ++i) {
4359 /* Set/clear a bit for each criterion */
4361 /* 0 messages in the list or a null list means that we are
4362 * interested in deleting any messages which meet the other criteria.
4364 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4365 delete_this |= 0x01;
4368 for (j=0; j<num_dmsgnums; ++j) {
4369 if (msglist[i] == dmsgnums[j]) {
4370 delete_this |= 0x01;
4375 if (IsEmptyStr(content_type)) {
4376 delete_this |= 0x02;
4378 GetMetaData(&smi, msglist[i]);
4379 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4380 delete_this |= 0x02;
4384 /* Delete message only if all bits are set */
4385 if (delete_this == 0x03) {
4386 dellist[num_deleted++] = msglist[i];
4391 num_msgs = sort_msglist(msglist, num_msgs);
4392 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4393 msglist, (int)(num_msgs * sizeof(long)));
4396 qrbuf.QRhighest = msglist[num_msgs - 1];
4398 qrbuf.QRhighest = 0;
4400 CtdlPutRoomLock(&qrbuf);
4402 /* Go through the messages we pulled out of the index, and decrement
4403 * their reference counts by 1. If this is the only room the message
4404 * was in, the reference count will reach zero and the message will
4405 * automatically be deleted from the database. We do this in a
4406 * separate pass because there might be plug-in hooks getting called,
4407 * and we don't want that happening during an S_ROOMS critical
4410 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4411 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4412 AdjRefCount(dellist[i], -1);
4415 /* Now free the memory we used, and go away. */
4416 if (msglist != NULL) free(msglist);
4417 if (dellist != NULL) free(dellist);
4418 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4419 if (need_to_free_re) regfree(&re);
4420 return (num_deleted);
4426 * Check whether the current user has permission to delete messages from
4427 * the current room (returns 1 for yes, 0 for no)
4429 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4431 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4432 if (ra & UA_DELETEALLOWED) return(1);
4440 * Delete message from current room
4442 void cmd_dele(char *args)
4451 extract_token(msgset, args, 0, '|', sizeof msgset);
4452 num_msgs = num_tokens(msgset, ',');
4454 cprintf("%d Nothing to do.\n", CIT_OK);
4458 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4459 cprintf("%d Higher access required.\n",
4460 ERROR + HIGHER_ACCESS_REQUIRED);
4465 * Build our message set to be moved/copied
4467 msgs = malloc(num_msgs * sizeof(long));
4468 for (i=0; i<num_msgs; ++i) {
4469 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4470 msgs[i] = atol(msgtok);
4473 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4477 cprintf("%d %d message%s deleted.\n", CIT_OK,
4478 num_deleted, ((num_deleted != 1) ? "s" : ""));
4480 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4488 * move or copy a message to another room
4490 void cmd_move(char *args)
4497 char targ[ROOMNAMELEN];
4498 struct ctdlroom qtemp;
4505 extract_token(msgset, args, 0, '|', sizeof msgset);
4506 num_msgs = num_tokens(msgset, ',');
4508 cprintf("%d Nothing to do.\n", CIT_OK);
4512 extract_token(targ, args, 1, '|', sizeof targ);
4513 convert_room_name_macros(targ, sizeof targ);
4514 targ[ROOMNAMELEN - 1] = 0;
4515 is_copy = extract_int(args, 2);
4517 if (CtdlGetRoom(&qtemp, targ) != 0) {
4518 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4522 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4523 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4527 CtdlGetUser(&CC->user, CC->curr_user);
4528 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4530 /* Check for permission to perform this operation.
4531 * Remember: "CC->room" is source, "qtemp" is target.
4535 /* Aides can move/copy */
4536 if (CC->user.axlevel >= AxAideU) permit = 1;
4538 /* Room aides can move/copy */
4539 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4541 /* Permit move/copy from personal rooms */
4542 if ((CC->room.QRflags & QR_MAILBOX)
4543 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4545 /* Permit only copy from public to personal room */
4547 && (!(CC->room.QRflags & QR_MAILBOX))
4548 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4550 /* Permit message removal from collaborative delete rooms */
4551 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4553 /* Users allowed to post into the target room may move into it too. */
4554 if ((CC->room.QRflags & QR_MAILBOX) &&
4555 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4557 /* User must have access to target room */
4558 if (!(ra & UA_KNOWN)) permit = 0;
4561 cprintf("%d Higher access required.\n",
4562 ERROR + HIGHER_ACCESS_REQUIRED);
4567 * Build our message set to be moved/copied
4569 msgs = malloc(num_msgs * sizeof(long));
4570 for (i=0; i<num_msgs; ++i) {
4571 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4572 msgs[i] = atol(msgtok);
4578 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4580 cprintf("%d Cannot store message(s) in %s: error %d\n",
4586 /* Now delete the message from the source room,
4587 * if this is a 'move' rather than a 'copy' operation.
4590 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4594 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4600 * GetMetaData() - Get the supplementary record for a message
4602 void GetMetaData(struct MetaData *smibuf, long msgnum)
4605 struct cdbdata *cdbsmi;
4608 memset(smibuf, 0, sizeof(struct MetaData));
4609 smibuf->meta_msgnum = msgnum;
4610 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4612 /* Use the negative of the message number for its supp record index */
4613 TheIndex = (0L - msgnum);
4615 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4616 if (cdbsmi == NULL) {
4617 return; /* record not found; go with defaults */
4619 memcpy(smibuf, cdbsmi->ptr,
4620 ((cdbsmi->len > sizeof(struct MetaData)) ?
4621 sizeof(struct MetaData) : cdbsmi->len));
4628 * PutMetaData() - (re)write supplementary record for a message
4630 void PutMetaData(struct MetaData *smibuf)
4634 /* Use the negative of the message number for the metadata db index */
4635 TheIndex = (0L - smibuf->meta_msgnum);
4637 cdb_store(CDB_MSGMAIN,
4638 &TheIndex, (int)sizeof(long),
4639 smibuf, (int)sizeof(struct MetaData));
4644 * AdjRefCount - submit an adjustment to the reference count for a message.
4645 * (These are just queued -- we actually process them later.)
4647 void AdjRefCount(long msgnum, int incr)
4649 struct arcq new_arcq;
4652 CtdlLogPrintf(CTDL_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4656 begin_critical_section(S_SUPPMSGMAIN);
4657 if (arcfp == NULL) {
4658 arcfp = fopen(file_arcq, "ab+");
4660 end_critical_section(S_SUPPMSGMAIN);
4662 /* msgnum < 0 means that we're trying to close the file */
4664 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4665 begin_critical_section(S_SUPPMSGMAIN);
4666 if (arcfp != NULL) {
4670 end_critical_section(S_SUPPMSGMAIN);
4675 * If we can't open the queue, perform the operation synchronously.
4677 if (arcfp == NULL) {
4678 TDAP_AdjRefCount(msgnum, incr);
4682 new_arcq.arcq_msgnum = msgnum;
4683 new_arcq.arcq_delta = incr;
4684 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4692 * TDAP_ProcessAdjRefCountQueue()
4694 * Process the queue of message count adjustments that was created by calls
4695 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4696 * for each one. This should be an "off hours" operation.
4698 int TDAP_ProcessAdjRefCountQueue(void)
4700 char file_arcq_temp[PATH_MAX];
4703 struct arcq arcq_rec;
4704 int num_records_processed = 0;
4706 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4708 begin_critical_section(S_SUPPMSGMAIN);
4709 if (arcfp != NULL) {
4714 r = link(file_arcq, file_arcq_temp);
4716 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4717 end_critical_section(S_SUPPMSGMAIN);
4718 return(num_records_processed);
4722 end_critical_section(S_SUPPMSGMAIN);
4724 fp = fopen(file_arcq_temp, "rb");
4726 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4727 return(num_records_processed);
4730 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4731 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4732 ++num_records_processed;
4736 r = unlink(file_arcq_temp);
4738 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4741 return(num_records_processed);
4747 * TDAP_AdjRefCount - adjust the reference count for a message.
4748 * This one does it "for real" because it's called by
4749 * the autopurger function that processes the queue
4750 * created by AdjRefCount(). If a message's reference
4751 * count becomes zero, we also delete the message from
4752 * disk and de-index it.
4754 void TDAP_AdjRefCount(long msgnum, int incr)
4757 struct MetaData smi;
4760 /* This is a *tight* critical section; please keep it that way, as
4761 * it may get called while nested in other critical sections.
4762 * Complicating this any further will surely cause deadlock!
4764 begin_critical_section(S_SUPPMSGMAIN);
4765 GetMetaData(&smi, msgnum);
4766 smi.meta_refcount += incr;
4768 end_critical_section(S_SUPPMSGMAIN);
4769 CtdlLogPrintf(CTDL_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
4770 msgnum, incr, smi.meta_refcount
4773 /* If the reference count is now zero, delete the message
4774 * (and its supplementary record as well).
4776 if (smi.meta_refcount == 0) {
4777 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4779 /* Call delete hooks with NULL room to show it has gone altogether */
4780 PerformDeleteHooks(NULL, msgnum);
4782 /* Remove from message base */
4784 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4785 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4787 /* Remove metadata record */
4788 delnum = (0L - msgnum);
4789 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4795 * Write a generic object to this room
4797 * Note: this could be much more efficient. Right now we use two temporary
4798 * files, and still pull the message into memory as with all others.
4800 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4801 char *content_type, /* MIME type of this object */
4802 char *raw_message, /* Data to be written */
4803 off_t raw_length, /* Size of raw_message */
4804 struct ctdluser *is_mailbox, /* Mailbox room? */
4805 int is_binary, /* Is encoding necessary? */
4806 int is_unique, /* Del others of this type? */
4807 unsigned int flags /* Internal save flags */
4811 struct ctdlroom qrbuf;
4812 char roomname[ROOMNAMELEN];
4813 struct CtdlMessage *msg;
4814 char *encoded_message = NULL;
4816 if (is_mailbox != NULL) {
4817 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4820 safestrncpy(roomname, req_room, sizeof(roomname));
4823 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4826 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4829 encoded_message = malloc((size_t)(raw_length + 4096));
4832 sprintf(encoded_message, "Content-type: %s\n", content_type);
4835 sprintf(&encoded_message[strlen(encoded_message)],
4836 "Content-transfer-encoding: base64\n\n"
4840 sprintf(&encoded_message[strlen(encoded_message)],
4841 "Content-transfer-encoding: 7bit\n\n"
4847 &encoded_message[strlen(encoded_message)],
4855 &encoded_message[strlen(encoded_message)],
4861 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4862 msg = malloc(sizeof(struct CtdlMessage));
4863 memset(msg, 0, sizeof(struct CtdlMessage));
4864 msg->cm_magic = CTDLMESSAGE_MAGIC;
4865 msg->cm_anon_type = MES_NORMAL;
4866 msg->cm_format_type = 4;
4867 msg->cm_fields['A'] = strdup(CC->user.fullname);
4868 msg->cm_fields['O'] = strdup(req_room);
4869 msg->cm_fields['N'] = strdup(config.c_nodename);
4870 msg->cm_fields['H'] = strdup(config.c_humannode);
4871 msg->cm_flags = flags;
4873 msg->cm_fields['M'] = encoded_message;
4875 /* Create the requested room if we have to. */
4876 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4877 CtdlCreateRoom(roomname,
4878 ( (is_mailbox != NULL) ? 5 : 3 ),
4879 "", 0, 1, 0, VIEW_BBS);
4881 /* If the caller specified this object as unique, delete all
4882 * other objects of this type that are currently in the room.
4885 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4886 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4889 /* Now write the data */
4890 CtdlSubmitMsg(msg, NULL, roomname, 0);
4891 CtdlFreeMessage(msg);
4899 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4900 config_msgnum = msgnum;
4904 char *CtdlGetSysConfig(char *sysconfname) {
4905 char hold_rm[ROOMNAMELEN];
4908 struct CtdlMessage *msg;
4911 strcpy(hold_rm, CC->room.QRname);
4912 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4913 CtdlGetRoom(&CC->room, hold_rm);
4918 /* We want the last (and probably only) config in this room */
4919 begin_critical_section(S_CONFIG);
4920 config_msgnum = (-1L);
4921 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4922 CtdlGetSysConfigBackend, NULL);
4923 msgnum = config_msgnum;
4924 end_critical_section(S_CONFIG);
4930 msg = CtdlFetchMessage(msgnum, 1);
4932 conf = strdup(msg->cm_fields['M']);
4933 CtdlFreeMessage(msg);
4940 CtdlGetRoom(&CC->room, hold_rm);
4942 if (conf != NULL) do {
4943 extract_token(buf, conf, 0, '\n', sizeof buf);
4944 strcpy(conf, &conf[strlen(buf)+1]);
4945 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4951 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4952 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4957 * Determine whether a given Internet address belongs to the current user
4959 int CtdlIsMe(char *addr, int addr_buf_len)
4961 struct recptypes *recp;
4964 recp = validate_recipients(addr, NULL, 0);
4965 if (recp == NULL) return(0);
4967 if (recp->num_local == 0) {
4968 free_recipients(recp);
4972 for (i=0; i<recp->num_local; ++i) {
4973 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4974 if (!strcasecmp(addr, CC->user.fullname)) {
4975 free_recipients(recp);
4980 free_recipients(recp);
4986 * Citadel protocol command to do the same
4988 void cmd_isme(char *argbuf) {
4991 if (CtdlAccessCheck(ac_logged_in)) return;
4992 extract_token(addr, argbuf, 0, '|', sizeof addr);
4994 if (CtdlIsMe(addr, sizeof addr)) {
4995 cprintf("%d %s\n", CIT_OK, addr);
4998 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5004 /*****************************************************************************/
5005 /* MODULE INITIALIZATION STUFF */
5006 /*****************************************************************************/
5008 CTDL_MODULE_INIT(msgbase)
5011 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5012 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5013 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5014 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5015 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5016 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5017 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5018 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5019 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5020 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5021 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5022 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5025 /* return our Subversion id for the Log */