4 * Implements the message store.
6 * Copyright (c) 1987-2010 by the citadel.org team
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #if TIME_WITH_SYS_TIME
30 # include <sys/time.h>
34 # include <sys/time.h>
47 #include <sys/types.h>
49 #include <libcitadel.h>
52 #include "serv_extensions.h"
56 #include "sysdep_decls.h"
57 #include "citserver.h"
64 #include "internet_addressing.h"
65 #include "euidindex.h"
66 #include "journaling.h"
67 #include "citadel_dirs.h"
68 #include "clientsocket.h"
69 #include "serv_network.h"
72 #include "ctdl_module.h"
75 struct addresses_to_be_filed *atbf = NULL;
77 /* This temp file holds the queue of operations for AdjRefCount() */
78 static FILE *arcfp = NULL;
81 * This really belongs in serv_network.c, but I don't know how to export
82 * symbols between modules.
84 struct FilterList *filterlist = NULL;
88 * These are the four-character field headers we use when outputting
89 * messages in Citadel format (as opposed to RFC822 format).
92 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
93 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
94 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
95 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
96 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
97 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
98 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
99 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
128 * This function is self explanatory.
129 * (What can I say, I'm in a weird mood today...)
131 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
135 for (i = 0; i < strlen(name); ++i) {
136 if (name[i] == '@') {
137 while (isspace(name[i - 1]) && i > 0) {
138 strcpy(&name[i - 1], &name[i]);
141 while (isspace(name[i + 1])) {
142 strcpy(&name[i + 1], &name[i + 2]);
150 * Aliasing for network mail.
151 * (Error messages have been commented out, because this is a server.)
153 int alias(char *name)
154 { /* process alias and routing info for mail */
157 char aaa[SIZ], bbb[SIZ];
158 char *ignetcfg = NULL;
159 char *ignetmap = NULL;
165 char original_name[256];
166 safestrncpy(original_name, name, sizeof original_name);
169 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
170 stripallbut(name, '<', '>');
172 fp = fopen(file_mail_aliases, "r");
174 fp = fopen("/dev/null", "r");
181 while (fgets(aaa, sizeof aaa, fp) != NULL) {
182 while (isspace(name[0]))
183 strcpy(name, &name[1]);
184 aaa[strlen(aaa) - 1] = 0;
186 for (a = 0; a < strlen(aaa); ++a) {
188 strcpy(bbb, &aaa[a + 1]);
192 if (!strcasecmp(name, aaa))
197 /* Hit the Global Address Book */
198 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
202 if (strcasecmp(original_name, name)) {
203 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
206 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
207 for (a=0; a<strlen(name); ++a) {
208 if (name[a] == '@') {
209 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
211 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
216 /* determine local or remote type, see citadel.h */
217 at = haschar(name, '@');
218 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
219 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
220 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
222 /* figure out the delivery mode */
223 extract_token(node, name, 1, '@', sizeof node);
225 /* If there are one or more dots in the nodename, we assume that it
226 * is an FQDN and will attempt SMTP delivery to the Internet.
228 if (haschar(node, '.') > 0) {
229 return(MES_INTERNET);
232 /* Otherwise we look in the IGnet maps for a valid Citadel node.
233 * Try directly-connected nodes first...
235 ignetcfg = CtdlGetSysConfig(IGNETCFG);
236 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
237 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
238 extract_token(testnode, buf, 0, '|', sizeof testnode);
239 if (!strcasecmp(node, testnode)) {
247 * Then try nodes that are two or more hops away.
249 ignetmap = CtdlGetSysConfig(IGNETMAP);
250 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
251 extract_token(buf, ignetmap, i, '\n', sizeof buf);
252 extract_token(testnode, buf, 0, '|', sizeof testnode);
253 if (!strcasecmp(node, testnode)) {
260 /* If we get to this point it's an invalid node name */
266 * Back end for the MSGS command: output message number only.
268 void simple_listing(long msgnum, void *userdata)
270 cprintf("%ld\n", msgnum);
276 * Back end for the MSGS command: output header summary.
278 void headers_listing(long msgnum, void *userdata)
280 struct CtdlMessage *msg;
282 msg = CtdlFetchMessage(msgnum, 0);
284 cprintf("%ld|0|||||\n", msgnum);
288 cprintf("%ld|%s|%s|%s|%s|%s|\n",
290 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
291 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
292 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
293 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
294 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
296 CtdlFreeMessage(msg);
300 * Back end for the MSGS command: output EUID header.
302 void headers_euid(long msgnum, void *userdata)
304 struct CtdlMessage *msg;
306 msg = CtdlFetchMessage(msgnum, 0);
308 cprintf("%ld||\n", msgnum);
312 cprintf("%ld|%s|\n", msgnum, (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""));
313 CtdlFreeMessage(msg);
320 /* Determine if a given message matches the fields in a message template.
321 * Return 0 for a successful match.
323 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
326 /* If there aren't any fields in the template, all messages will
329 if (template == NULL) return(0);
331 /* Null messages are bogus. */
332 if (msg == NULL) return(1);
334 for (i='A'; i<='Z'; ++i) {
335 if (template->cm_fields[i] != NULL) {
336 if (msg->cm_fields[i] == NULL) {
337 /* Considered equal if temmplate is empty string */
338 if (IsEmptyStr(template->cm_fields[i])) continue;
341 if (strcasecmp(msg->cm_fields[i],
342 template->cm_fields[i])) return 1;
346 /* All compares succeeded: we have a match! */
353 * Retrieve the "seen" message list for the current room.
355 void CtdlGetSeen(char *buf, int which_set) {
358 /* Learn about the user and room in question */
359 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
361 if (which_set == ctdlsetseen_seen)
362 safestrncpy(buf, vbuf.v_seen, SIZ);
363 if (which_set == ctdlsetseen_answered)
364 safestrncpy(buf, vbuf.v_answered, SIZ);
370 * Manipulate the "seen msgs" string (or other message set strings)
372 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
373 int target_setting, int which_set,
374 struct ctdluser *which_user, struct ctdlroom *which_room) {
375 struct cdbdata *cdbfr;
389 char *is_set; /* actually an array of booleans */
391 /* Don't bother doing *anything* if we were passed a list of zero messages */
392 if (num_target_msgnums < 1) {
396 /* If no room was specified, we go with the current room. */
398 which_room = &CC->room;
401 /* If no user was specified, we go with the current user. */
403 which_user = &CC->user;
406 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
407 num_target_msgnums, target_msgnums[0],
408 (target_setting ? "SET" : "CLEAR"),
412 /* Learn about the user and room in question */
413 CtdlGetRelationship(&vbuf, which_user, which_room);
415 /* Load the message list */
416 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
418 msglist = (long *) cdbfr->ptr;
419 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
420 num_msgs = cdbfr->len / sizeof(long);
423 return; /* No messages at all? No further action. */
426 is_set = malloc(num_msgs * sizeof(char));
427 memset(is_set, 0, (num_msgs * sizeof(char)) );
429 /* Decide which message set we're manipulating */
431 case ctdlsetseen_seen:
432 vset = NewStrBufPlain(vbuf.v_seen, -1);
434 case ctdlsetseen_answered:
435 vset = NewStrBufPlain(vbuf.v_answered, -1);
442 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
443 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
444 for (i=0; i<num_msgs; ++i) {
445 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
447 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
448 for (k=0; k<num_target_msgnums; ++k) {
449 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
453 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
455 /* Translate the existing sequence set into an array of booleans */
456 setstr = NewStrBuf();
460 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
462 StrBufExtract_token(lostr, setstr, 0, ':');
463 if (StrBufNum_tokens(setstr, ':') >= 2) {
464 StrBufExtract_token(histr, setstr, 1, ':');
468 StrBufAppendBuf(histr, lostr, 0);
471 if (!strcmp(ChrPtr(histr), "*")) {
478 for (i = 0; i < num_msgs; ++i) {
479 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
489 /* Now translate the array of booleans back into a sequence set */
495 for (i=0; i<num_msgs; ++i) {
499 for (k=0; k<num_target_msgnums; ++k) {
500 if (msglist[i] == target_msgnums[k]) {
501 is_seen = target_setting;
505 if ((was_seen == 0) && (is_seen == 1)) {
508 else if ((was_seen == 1) && (is_seen == 0)) {
511 if (StrLength(vset) > 0) {
512 StrBufAppendBufPlain(vset, HKEY(","), 0);
515 StrBufAppendPrintf(vset, "%ld", hi);
518 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
522 if ((is_seen) && (i == num_msgs - 1)) {
523 if (StrLength(vset) > 0) {
524 StrBufAppendBufPlain(vset, HKEY(","), 0);
526 if ((i==0) || (was_seen == 0)) {
527 StrBufAppendPrintf(vset, "%ld", msglist[i]);
530 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
538 * We will have to stuff this string back into a 4096 byte buffer, so if it's
539 * larger than that now, truncate it by removing tokens from the beginning.
540 * The limit of 100 iterations is there to prevent an infinite loop in case
541 * something unexpected happens.
543 int number_of_truncations = 0;
544 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
545 StrBufRemove_token(vset, 0, ',');
546 ++number_of_truncations;
550 * If we're truncating the sequence set of messages marked with the 'seen' flag,
551 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
552 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
554 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
556 first_tok = NewStrBuf();
557 StrBufExtract_token(first_tok, vset, 0, ',');
558 StrBufRemove_token(vset, 0, ',');
560 if (StrBufNum_tokens(first_tok, ':') > 1) {
561 StrBufRemove_token(first_tok, 0, ':');
565 new_set = NewStrBuf();
566 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
567 StrBufAppendBuf(new_set, first_tok, 0);
568 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
569 StrBufAppendBuf(new_set, vset, 0);
572 FreeStrBuf(&first_tok);
576 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
578 /* Decide which message set we're manipulating */
580 case ctdlsetseen_seen:
581 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
583 case ctdlsetseen_answered:
584 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
590 CtdlSetRelationship(&vbuf, which_user, which_room);
596 * API function to perform an operation for each qualifying message in the
597 * current room. (Returns the number of messages processed.)
599 int CtdlForEachMessage(int mode, long ref, char *search_string,
601 struct CtdlMessage *compare,
602 ForEachMsgCallback CallBack,
608 struct cdbdata *cdbfr;
609 long *msglist = NULL;
611 int num_processed = 0;
614 struct CtdlMessage *msg = NULL;
617 int printed_lastold = 0;
618 int num_search_msgs = 0;
619 long *search_msgs = NULL;
621 int need_to_free_re = 0;
624 if ((content_type) && (!IsEmptyStr(content_type))) {
625 regcomp(&re, content_type, 0);
629 /* Learn about the user and room in question */
630 CtdlGetUser(&CC->user, CC->curr_user);
631 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
633 /* Load the message list */
634 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
636 msglist = (long *) cdbfr->ptr;
637 num_msgs = cdbfr->len / sizeof(long);
639 if (need_to_free_re) regfree(&re);
640 return 0; /* No messages at all? No further action. */
645 * Now begin the traversal.
647 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
649 /* If the caller is looking for a specific MIME type, filter
650 * out all messages which are not of the type requested.
652 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
654 /* This call to GetMetaData() sits inside this loop
655 * so that we only do the extra database read per msg
656 * if we need to. Doing the extra read all the time
657 * really kills the server. If we ever need to use
658 * metadata for another search criterion, we need to
659 * move the read somewhere else -- but still be smart
660 * enough to only do the read if the caller has
661 * specified something that will need it.
663 GetMetaData(&smi, msglist[a]);
665 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
666 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
672 num_msgs = sort_msglist(msglist, num_msgs);
674 /* If a template was supplied, filter out the messages which
675 * don't match. (This could induce some delays!)
678 if (compare != NULL) {
679 for (a = 0; a < num_msgs; ++a) {
680 msg = CtdlFetchMessage(msglist[a], 1);
682 if (CtdlMsgCmp(msg, compare)) {
685 CtdlFreeMessage(msg);
691 /* If a search string was specified, get a message list from
692 * the full text index and remove messages which aren't on both
696 * Since the lists are sorted and strictly ascending, and the
697 * output list is guaranteed to be shorter than or equal to the
698 * input list, we overwrite the bottom of the input list. This
699 * eliminates the need to memmove big chunks of the list over and
702 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
704 /* Call search module via hook mechanism.
705 * NULL means use any search function available.
706 * otherwise replace with a char * to name of search routine
708 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
710 if (num_search_msgs > 0) {
714 orig_num_msgs = num_msgs;
716 for (i=0; i<orig_num_msgs; ++i) {
717 for (j=0; j<num_search_msgs; ++j) {
718 if (msglist[i] == search_msgs[j]) {
719 msglist[num_msgs++] = msglist[i];
725 num_msgs = 0; /* No messages qualify */
727 if (search_msgs != NULL) free(search_msgs);
729 /* Now that we've purged messages which don't contain the search
730 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
737 * Now iterate through the message list, according to the
738 * criteria supplied by the caller.
741 for (a = 0; a < num_msgs; ++a) {
742 thismsg = msglist[a];
743 if (mode == MSGS_ALL) {
747 is_seen = is_msg_in_sequence_set(
748 vbuf.v_seen, thismsg);
749 if (is_seen) lastold = thismsg;
755 || ((mode == MSGS_OLD) && (is_seen))
756 || ((mode == MSGS_NEW) && (!is_seen))
757 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
758 || ((mode == MSGS_FIRST) && (a < ref))
759 || ((mode == MSGS_GT) && (thismsg > ref))
760 || ((mode == MSGS_LT) && (thismsg < ref))
761 || ((mode == MSGS_EQ) && (thismsg == ref))
764 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
766 CallBack(lastold, userdata);
770 if (CallBack) CallBack(thismsg, userdata);
774 cdb_free(cdbfr); /* Clean up */
775 if (need_to_free_re) regfree(&re);
776 return num_processed;
782 * cmd_msgs() - get list of message #'s in this room
783 * implements the MSGS server command using CtdlForEachMessage()
785 void cmd_msgs(char *cmdbuf)
794 int with_template = 0;
795 struct CtdlMessage *template = NULL;
796 char search_string[1024];
797 ForEachMsgCallback CallBack;
799 extract_token(which, cmdbuf, 0, '|', sizeof which);
800 cm_ref = extract_int(cmdbuf, 1);
801 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
802 with_template = extract_int(cmdbuf, 2);
803 switch (extract_int(cmdbuf, 3))
807 CallBack = simple_listing;
810 CallBack = headers_listing;
813 CallBack = headers_euid;
818 if (!strncasecmp(which, "OLD", 3))
820 else if (!strncasecmp(which, "NEW", 3))
822 else if (!strncasecmp(which, "FIRST", 5))
824 else if (!strncasecmp(which, "LAST", 4))
826 else if (!strncasecmp(which, "GT", 2))
828 else if (!strncasecmp(which, "LT", 2))
830 else if (!strncasecmp(which, "SEARCH", 6))
835 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
836 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
840 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
841 cprintf("%d Full text index is not enabled on this server.\n",
842 ERROR + CMD_NOT_SUPPORTED);
848 cprintf("%d Send template then receive message list\n",
850 template = (struct CtdlMessage *)
851 malloc(sizeof(struct CtdlMessage));
852 memset(template, 0, sizeof(struct CtdlMessage));
853 template->cm_magic = CTDLMESSAGE_MAGIC;
854 template->cm_anon_type = MES_NORMAL;
856 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
857 extract_token(tfield, buf, 0, '|', sizeof tfield);
858 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
859 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
860 if (!strcasecmp(tfield, msgkeys[i])) {
861 template->cm_fields[i] =
869 cprintf("%d \n", LISTING_FOLLOWS);
872 CtdlForEachMessage(mode,
873 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
874 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
879 if (template != NULL) CtdlFreeMessage(template);
887 * help_subst() - support routine for help file viewer
889 void help_subst(char *strbuf, char *source, char *dest)
894 while (p = pattern2(strbuf, source), (p >= 0)) {
895 strcpy(workbuf, &strbuf[p + strlen(source)]);
896 strcpy(&strbuf[p], dest);
897 strcat(strbuf, workbuf);
902 void do_help_subst(char *buffer)
906 help_subst(buffer, "^nodename", config.c_nodename);
907 help_subst(buffer, "^humannode", config.c_humannode);
908 help_subst(buffer, "^fqdn", config.c_fqdn);
909 help_subst(buffer, "^username", CC->user.fullname);
910 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
911 help_subst(buffer, "^usernum", buf2);
912 help_subst(buffer, "^sysadm", config.c_sysadm);
913 help_subst(buffer, "^variantname", CITADEL);
914 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
915 help_subst(buffer, "^maxsessions", buf2);
916 help_subst(buffer, "^bbsdir", ctdl_message_dir);
922 * memfmout() - Citadel text formatter and paginator.
923 * Although the original purpose of this routine was to format
924 * text to the reader's screen width, all we're really using it
925 * for here is to format text out to 80 columns before sending it
926 * to the client. The client software may reformat it again.
929 char *mptr, /* where are we going to get our text from? */
930 const char *nl) /* string to terminate lines with */
938 static int width = 80;
943 c = 1; /* c is the current pos */
951 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
954 if (((old == 13) || (old == 10)) && (isspace(real))) {
959 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
960 cprintf("%s%s", nl, aaa);
969 if ((strlen(aaa) + c) > (width - 5)) {
978 if ((ch == 13) || (ch == 10)) {
979 cprintf("%s%s", aaa, nl);
986 cprintf("%s%s", aaa, nl);
992 * Callback function for mime parser that simply lists the part
994 void list_this_part(char *name, char *filename, char *partnum, char *disp,
995 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
996 char *cbid, void *cbuserdata)
1000 ma = (struct ma_info *)cbuserdata;
1001 if (ma->is_ma == 0) {
1002 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
1003 name, filename, partnum, disp, cbtype, (long)length, cbid);
1008 * Callback function for multipart prefix
1010 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1011 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1012 char *cbid, void *cbuserdata)
1016 ma = (struct ma_info *)cbuserdata;
1017 if (!strcasecmp(cbtype, "multipart/alternative")) {
1021 if (ma->is_ma == 0) {
1022 cprintf("pref=%s|%s\n", partnum, cbtype);
1027 * Callback function for multipart sufffix
1029 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1030 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1031 char *cbid, void *cbuserdata)
1035 ma = (struct ma_info *)cbuserdata;
1036 if (ma->is_ma == 0) {
1037 cprintf("suff=%s|%s\n", partnum, cbtype);
1039 if (!strcasecmp(cbtype, "multipart/alternative")) {
1046 * Callback function for mime parser that opens a section for downloading
1048 void mime_download(char *name, char *filename, char *partnum, char *disp,
1049 void *content, char *cbtype, char *cbcharset, size_t length,
1050 char *encoding, char *cbid, void *cbuserdata)
1054 /* Silently go away if there's already a download open. */
1055 if (CC->download_fp != NULL)
1059 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1060 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1062 CC->download_fp = tmpfile();
1063 if (CC->download_fp == NULL)
1066 rv = fwrite(content, length, 1, CC->download_fp);
1067 fflush(CC->download_fp);
1068 rewind(CC->download_fp);
1070 OpenCmdResult(filename, cbtype);
1077 * Callback function for mime parser that outputs a section all at once.
1078 * We can specify the desired section by part number *or* content-id.
1080 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1081 void *content, char *cbtype, char *cbcharset, size_t length,
1082 char *encoding, char *cbid, void *cbuserdata)
1084 int *found_it = (int *)cbuserdata;
1087 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1088 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1091 cprintf("%d %d|-1|%s|%s\n",
1097 client_write(content, length);
1104 * Load a message from disk into memory.
1105 * This is used by CtdlOutputMsg() and other fetch functions.
1107 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1108 * using the CtdlMessageFree() function.
1110 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1112 struct cdbdata *dmsgtext;
1113 struct CtdlMessage *ret = NULL;
1117 cit_uint8_t field_header;
1119 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1121 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1122 if (dmsgtext == NULL) {
1125 mptr = dmsgtext->ptr;
1126 upper_bound = mptr + dmsgtext->len;
1128 /* Parse the three bytes that begin EVERY message on disk.
1129 * The first is always 0xFF, the on-disk magic number.
1130 * The second is the anonymous/public type byte.
1131 * The third is the format type byte (vari, fixed, or MIME).
1135 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1139 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1140 memset(ret, 0, sizeof(struct CtdlMessage));
1142 ret->cm_magic = CTDLMESSAGE_MAGIC;
1143 ret->cm_anon_type = *mptr++; /* Anon type byte */
1144 ret->cm_format_type = *mptr++; /* Format type byte */
1147 * The rest is zero or more arbitrary fields. Load them in.
1148 * We're done when we encounter either a zero-length field or
1149 * have just processed the 'M' (message text) field.
1152 if (mptr >= upper_bound) {
1155 field_header = *mptr++;
1156 ret->cm_fields[field_header] = strdup(mptr);
1158 while (*mptr++ != 0); /* advance to next field */
1160 } while ((mptr < upper_bound) && (field_header != 'M'));
1164 /* Always make sure there's something in the msg text field. If
1165 * it's NULL, the message text is most likely stored separately,
1166 * so go ahead and fetch that. Failing that, just set a dummy
1167 * body so other code doesn't barf.
1169 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1170 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1171 if (dmsgtext != NULL) {
1172 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1176 if (ret->cm_fields['M'] == NULL) {
1177 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1180 /* Perform "before read" hooks (aborting if any return nonzero) */
1181 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1182 CtdlFreeMessage(ret);
1191 * Returns 1 if the supplied pointer points to a valid Citadel message.
1192 * If the pointer is NULL or the magic number check fails, returns 0.
1194 int is_valid_message(struct CtdlMessage *msg) {
1197 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1198 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1206 * 'Destructor' for struct CtdlMessage
1208 void CtdlFreeMessage(struct CtdlMessage *msg)
1212 if (is_valid_message(msg) == 0)
1214 if (msg != NULL) free (msg);
1218 for (i = 0; i < 256; ++i)
1219 if (msg->cm_fields[i] != NULL) {
1220 free(msg->cm_fields[i]);
1223 msg->cm_magic = 0; /* just in case */
1229 * Pre callback function for multipart/alternative
1231 * NOTE: this differs from the standard behavior for a reason. Normally when
1232 * displaying multipart/alternative you want to show the _last_ usable
1233 * format in the message. Here we show the _first_ one, because it's
1234 * usually text/plain. Since this set of functions is designed for text
1235 * output to non-MIME-aware clients, this is the desired behavior.
1238 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1239 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1240 char *cbid, void *cbuserdata)
1244 ma = (struct ma_info *)cbuserdata;
1245 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1246 if (!strcasecmp(cbtype, "multipart/alternative")) {
1250 if (!strcasecmp(cbtype, "message/rfc822")) {
1256 * Post callback function for multipart/alternative
1258 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1259 void *content, char *cbtype, char *cbcharset, size_t length,
1260 char *encoding, char *cbid, void *cbuserdata)
1264 ma = (struct ma_info *)cbuserdata;
1265 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1266 if (!strcasecmp(cbtype, "multipart/alternative")) {
1270 if (!strcasecmp(cbtype, "message/rfc822")) {
1276 * Inline callback function for mime parser that wants to display text
1278 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1279 void *content, char *cbtype, char *cbcharset, size_t length,
1280 char *encoding, char *cbid, void *cbuserdata)
1287 ma = (struct ma_info *)cbuserdata;
1289 CtdlLogPrintf(CTDL_DEBUG,
1290 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1291 partnum, filename, cbtype, (long)length);
1294 * If we're in the middle of a multipart/alternative scope and
1295 * we've already printed another section, skip this one.
1297 if ( (ma->is_ma) && (ma->did_print) ) {
1298 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1303 if ( (!strcasecmp(cbtype, "text/plain"))
1304 || (IsEmptyStr(cbtype)) ) {
1307 client_write(wptr, length);
1308 if (wptr[length-1] != '\n') {
1315 if (!strcasecmp(cbtype, "text/html")) {
1316 ptr = html_to_ascii(content, length, 80, 0);
1318 client_write(ptr, wlen);
1319 if (ptr[wlen-1] != '\n') {
1326 if (ma->use_fo_hooks) {
1327 if (PerformFixedOutputHooks(cbtype, content, length)) {
1328 /* above function returns nonzero if it handled the part */
1333 if (strncasecmp(cbtype, "multipart/", 10)) {
1334 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1335 partnum, filename, cbtype, (long)length);
1341 * The client is elegant and sophisticated and wants to be choosy about
1342 * MIME content types, so figure out which multipart/alternative part
1343 * we're going to send.
1345 * We use a system of weights. When we find a part that matches one of the
1346 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1347 * and then set ma->chosen_pref to that MIME type's position in our preference
1348 * list. If we then hit another match, we only replace the first match if
1349 * the preference value is lower.
1351 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1352 void *content, char *cbtype, char *cbcharset, size_t length,
1353 char *encoding, char *cbid, void *cbuserdata)
1359 ma = (struct ma_info *)cbuserdata;
1361 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1362 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1363 // I don't know if there are any side effects! Please TEST TEST TEST
1364 //if (ma->is_ma > 0) {
1366 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1367 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1368 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1369 if (i < ma->chosen_pref) {
1370 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1371 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1372 ma->chosen_pref = i;
1379 * Now that we've chosen our preferred part, output it.
1381 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1382 void *content, char *cbtype, char *cbcharset, size_t length,
1383 char *encoding, char *cbid, void *cbuserdata)
1387 int add_newline = 0;
1391 ma = (struct ma_info *)cbuserdata;
1393 /* This is not the MIME part you're looking for... */
1394 if (strcasecmp(partnum, ma->chosen_part)) return;
1396 /* If the content-type of this part is in our preferred formats
1397 * list, we can simply output it verbatim.
1399 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1400 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1401 if (!strcasecmp(buf, cbtype)) {
1402 /* Yeah! Go! W00t!! */
1404 text_content = (char *)content;
1405 if (text_content[length-1] != '\n') {
1408 cprintf("Content-type: %s", cbtype);
1409 if (!IsEmptyStr(cbcharset)) {
1410 cprintf("; charset=%s", cbcharset);
1412 cprintf("\nContent-length: %d\n",
1413 (int)(length + add_newline) );
1414 if (!IsEmptyStr(encoding)) {
1415 cprintf("Content-transfer-encoding: %s\n", encoding);
1418 cprintf("Content-transfer-encoding: 7bit\n");
1420 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1422 client_write(content, length);
1423 if (add_newline) cprintf("\n");
1428 /* No translations required or possible: output as text/plain */
1429 cprintf("Content-type: text/plain\n\n");
1430 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1431 length, encoding, cbid, cbuserdata);
1436 char desired_section[64];
1443 * Callback function for
1445 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1446 void *content, char *cbtype, char *cbcharset, size_t length,
1447 char *encoding, char *cbid, void *cbuserdata)
1449 struct encapmsg *encap;
1451 encap = (struct encapmsg *)cbuserdata;
1453 /* Only proceed if this is the desired section... */
1454 if (!strcasecmp(encap->desired_section, partnum)) {
1455 encap->msglen = length;
1456 encap->msg = malloc(length + 2);
1457 memcpy(encap->msg, content, length);
1468 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1469 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1470 return(om_not_logged_in);
1477 * Get a message off disk. (returns om_* values found in msgbase.h)
1480 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1481 int mode, /* how would you like that message? */
1482 int headers_only, /* eschew the message body? */
1483 int do_proto, /* do Citadel protocol responses? */
1484 int crlf, /* Use CRLF newlines instead of LF? */
1485 char *section, /* NULL or a message/rfc822 section */
1486 int flags /* various flags; see msgbase.h */
1488 struct CtdlMessage *TheMessage = NULL;
1489 int retcode = om_no_such_msg;
1490 struct encapmsg encap;
1493 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1495 (section ? section : "<>")
1498 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1501 if (r == om_not_logged_in) {
1502 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1505 cprintf("%d An unknown error has occurred.\n", ERROR);
1511 /* FIXME: check message id against msglist for this room */
1514 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1515 * request that we don't even bother loading the body into memory.
1517 if (headers_only == HEADERS_FAST) {
1518 TheMessage = CtdlFetchMessage(msg_num, 0);
1521 TheMessage = CtdlFetchMessage(msg_num, 1);
1524 if (TheMessage == NULL) {
1525 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1526 ERROR + MESSAGE_NOT_FOUND, msg_num);
1527 return(om_no_such_msg);
1530 /* Here is the weird form of this command, to process only an
1531 * encapsulated message/rfc822 section.
1533 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1534 memset(&encap, 0, sizeof encap);
1535 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1536 mime_parser(TheMessage->cm_fields['M'],
1538 *extract_encapsulated_message,
1539 NULL, NULL, (void *)&encap, 0
1541 CtdlFreeMessage(TheMessage);
1545 encap.msg[encap.msglen] = 0;
1546 TheMessage = convert_internet_message(encap.msg);
1547 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1549 /* Now we let it fall through to the bottom of this
1550 * function, because TheMessage now contains the
1551 * encapsulated message instead of the top-level
1552 * message. Isn't that neat?
1557 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1558 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1559 retcode = om_no_such_msg;
1564 /* Ok, output the message now */
1565 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1566 CtdlFreeMessage(TheMessage);
1572 char *qp_encode_email_addrs(char *source)
1574 char user[256], node[256], name[256];
1575 const char headerStr[] = "=?UTF-8?Q?";
1579 int need_to_encode = 0;
1585 long nAddrPtrMax = 50;
1590 if (source == NULL) return source;
1591 if (IsEmptyStr(source)) return source;
1593 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1594 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1595 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1598 while (!IsEmptyStr (&source[i])) {
1599 if (nColons >= nAddrPtrMax){
1602 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1603 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1604 free (AddrPtr), AddrPtr = ptr;
1606 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1607 memset(&ptr[nAddrPtrMax], 0,
1608 sizeof (long) * nAddrPtrMax);
1610 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1611 free (AddrUtf8), AddrUtf8 = ptr;
1614 if (((unsigned char) source[i] < 32) ||
1615 ((unsigned char) source[i] > 126)) {
1617 AddrUtf8[nColons] = 1;
1619 if (source[i] == '"')
1620 InQuotes = !InQuotes;
1621 if (!InQuotes && source[i] == ',') {
1622 AddrPtr[nColons] = i;
1627 if (need_to_encode == 0) {
1634 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1635 Encoded = (char*) malloc (EncodedMaxLen);
1637 for (i = 0; i < nColons; i++)
1638 source[AddrPtr[i]++] = '\0';
1642 for (i = 0; i < nColons && nPtr != NULL; i++) {
1643 nmax = EncodedMaxLen - (nPtr - Encoded);
1645 process_rfc822_addr(&source[AddrPtr[i]],
1649 /* TODO: libIDN here ! */
1650 if (IsEmptyStr(name)) {
1651 n = snprintf(nPtr, nmax,
1652 (i==0)?"%s@%s" : ",%s@%s",
1656 EncodedName = rfc2047encode(name, strlen(name));
1657 n = snprintf(nPtr, nmax,
1658 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1659 EncodedName, user, node);
1664 n = snprintf(nPtr, nmax,
1665 (i==0)?"%s" : ",%s",
1666 &source[AddrPtr[i]]);
1672 ptr = (char*) malloc(EncodedMaxLen * 2);
1673 memcpy(ptr, Encoded, EncodedMaxLen);
1674 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1675 free(Encoded), Encoded = ptr;
1677 i--; /* do it once more with properly lengthened buffer */
1680 for (i = 0; i < nColons; i++)
1681 source[--AddrPtr[i]] = ',';
1688 /* If the last item in a list of recipients was truncated to a partial address,
1689 * remove it completely in order to avoid choking libSieve
1691 void sanitize_truncated_recipient(char *str)
1694 if (num_tokens(str, ',') < 2) return;
1696 int len = strlen(str);
1697 if (len < 900) return;
1698 if (len > 998) str[998] = 0;
1700 char *cptr = strrchr(str, ',');
1703 char *lptr = strchr(cptr, '<');
1704 char *rptr = strchr(cptr, '>');
1706 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1714 * Get a message off disk. (returns om_* values found in msgbase.h)
1716 int CtdlOutputPreLoadedMsg(
1717 struct CtdlMessage *TheMessage,
1718 int mode, /* how would you like that message? */
1719 int headers_only, /* eschew the message body? */
1720 int do_proto, /* do Citadel protocol responses? */
1721 int crlf, /* Use CRLF newlines instead of LF? */
1722 int flags /* should the bessage be exported clean? */
1726 cit_uint8_t ch, prev_ch;
1728 char display_name[256];
1730 const char *nl; /* newline string */
1732 int subject_found = 0;
1735 /* Buffers needed for RFC822 translation. These are all filled
1736 * using functions that are bounds-checked, and therefore we can
1737 * make them substantially smaller than SIZ.
1744 char datestamp[100];
1746 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1747 ((TheMessage == NULL) ? "NULL" : "not null"),
1748 mode, headers_only, do_proto, crlf);
1750 strcpy(mid, "unknown");
1751 nl = (crlf ? "\r\n" : "\n");
1753 if (!is_valid_message(TheMessage)) {
1754 CtdlLogPrintf(CTDL_ERR,
1755 "ERROR: invalid preloaded message for output\n");
1757 return(om_no_such_msg);
1760 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1761 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1763 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
1764 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
1767 /* Are we downloading a MIME component? */
1768 if (mode == MT_DOWNLOAD) {
1769 if (TheMessage->cm_format_type != FMT_RFC822) {
1771 cprintf("%d This is not a MIME message.\n",
1772 ERROR + ILLEGAL_VALUE);
1773 } else if (CC->download_fp != NULL) {
1774 if (do_proto) cprintf(
1775 "%d You already have a download open.\n",
1776 ERROR + RESOURCE_BUSY);
1778 /* Parse the message text component */
1779 mptr = TheMessage->cm_fields['M'];
1780 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1781 /* If there's no file open by this time, the requested
1782 * section wasn't found, so print an error
1784 if (CC->download_fp == NULL) {
1785 if (do_proto) cprintf(
1786 "%d Section %s not found.\n",
1787 ERROR + FILE_NOT_FOUND,
1788 CC->download_desired_section);
1791 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1794 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1795 * in a single server operation instead of opening a download file.
1797 if (mode == MT_SPEW_SECTION) {
1798 if (TheMessage->cm_format_type != FMT_RFC822) {
1800 cprintf("%d This is not a MIME message.\n",
1801 ERROR + ILLEGAL_VALUE);
1803 /* Parse the message text component */
1806 mptr = TheMessage->cm_fields['M'];
1807 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1808 /* If section wasn't found, print an error
1811 if (do_proto) cprintf(
1812 "%d Section %s not found.\n",
1813 ERROR + FILE_NOT_FOUND,
1814 CC->download_desired_section);
1817 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1820 /* now for the user-mode message reading loops */
1821 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1823 /* Does the caller want to skip the headers? */
1824 if (headers_only == HEADERS_NONE) goto START_TEXT;
1826 /* Tell the client which format type we're using. */
1827 if ( (mode == MT_CITADEL) && (do_proto) ) {
1828 cprintf("type=%d\n", TheMessage->cm_format_type);
1831 /* nhdr=yes means that we're only displaying headers, no body */
1832 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1833 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1836 cprintf("nhdr=yes\n");
1839 /* begin header processing loop for Citadel message format */
1841 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1843 safestrncpy(display_name, "<unknown>", sizeof display_name);
1844 if (TheMessage->cm_fields['A']) {
1845 strcpy(buf, TheMessage->cm_fields['A']);
1846 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1847 safestrncpy(display_name, "****", sizeof display_name);
1849 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1850 safestrncpy(display_name, "anonymous", sizeof display_name);
1853 safestrncpy(display_name, buf, sizeof display_name);
1855 if ((is_room_aide())
1856 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1857 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1858 size_t tmp = strlen(display_name);
1859 snprintf(&display_name[tmp],
1860 sizeof display_name - tmp,
1865 /* Don't show Internet address for users on the
1866 * local Citadel network.
1869 if (TheMessage->cm_fields['N'] != NULL)
1870 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1871 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1875 /* Now spew the header fields in the order we like them. */
1876 safestrncpy(allkeys, FORDER, sizeof allkeys);
1877 for (i=0; i<strlen(allkeys); ++i) {
1878 k = (int) allkeys[i];
1880 if ( (TheMessage->cm_fields[k] != NULL)
1881 && (msgkeys[k] != NULL) ) {
1882 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1883 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1886 if (do_proto) cprintf("%s=%s\n",
1890 else if ((k == 'F') && (suppress_f)) {
1893 /* Masquerade display name if needed */
1895 if (do_proto) cprintf("%s=%s\n",
1897 TheMessage->cm_fields[k]
1906 /* begin header processing loop for RFC822 transfer format */
1911 strcpy(snode, NODENAME);
1912 if (mode == MT_RFC822) {
1913 for (i = 0; i < 256; ++i) {
1914 if (TheMessage->cm_fields[i]) {
1915 mptr = mpptr = TheMessage->cm_fields[i];
1918 safestrncpy(luser, mptr, sizeof luser);
1919 safestrncpy(suser, mptr, sizeof suser);
1921 else if (i == 'Y') {
1922 if ((flags & QP_EADDR) != 0) {
1923 mptr = qp_encode_email_addrs(mptr);
1925 sanitize_truncated_recipient(mptr);
1926 cprintf("CC: %s%s", mptr, nl);
1928 else if (i == 'P') {
1929 cprintf("Return-Path: %s%s", mptr, nl);
1931 else if (i == 'L') {
1932 cprintf("List-ID: %s%s", mptr, nl);
1934 else if (i == 'V') {
1935 if ((flags & QP_EADDR) != 0)
1936 mptr = qp_encode_email_addrs(mptr);
1937 cprintf("Envelope-To: %s%s", mptr, nl);
1939 else if (i == 'U') {
1940 cprintf("Subject: %s%s", mptr, nl);
1944 safestrncpy(mid, mptr, sizeof mid);
1946 safestrncpy(fuser, mptr, sizeof fuser);
1947 /* else if (i == 'O')
1948 cprintf("X-Citadel-Room: %s%s",
1951 safestrncpy(snode, mptr, sizeof snode);
1954 if (haschar(mptr, '@') == 0)
1956 sanitize_truncated_recipient(mptr);
1957 cprintf("To: %s@%s", mptr, config.c_fqdn);
1962 if ((flags & QP_EADDR) != 0) {
1963 mptr = qp_encode_email_addrs(mptr);
1965 sanitize_truncated_recipient(mptr);
1966 cprintf("To: %s", mptr);
1970 else if (i == 'T') {
1971 datestring(datestamp, sizeof datestamp,
1972 atol(mptr), DATESTRING_RFC822);
1973 cprintf("Date: %s%s", datestamp, nl);
1975 else if (i == 'W') {
1976 cprintf("References: ");
1977 k = num_tokens(mptr, '|');
1978 for (j=0; j<k; ++j) {
1979 extract_token(buf, mptr, j, '|', sizeof buf);
1980 cprintf("<%s>", buf);
1993 if (subject_found == 0) {
1994 cprintf("Subject: (no subject)%s", nl);
1998 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1999 suser[i] = tolower(suser[i]);
2000 if (!isalnum(suser[i])) suser[i]='_';
2003 if (mode == MT_RFC822) {
2004 if (!strcasecmp(snode, NODENAME)) {
2005 safestrncpy(snode, FQDN, sizeof snode);
2008 /* Construct a fun message id */
2009 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2010 if (strchr(mid, '@')==NULL) {
2011 cprintf("@%s", snode);
2015 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2016 cprintf("From: \"----\" <x@x.org>%s", nl);
2018 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2019 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2021 else if (!IsEmptyStr(fuser)) {
2022 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2025 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2028 /* Blank line signifying RFC822 end-of-headers */
2029 if (TheMessage->cm_format_type != FMT_RFC822) {
2034 /* end header processing loop ... at this point, we're in the text */
2036 if (headers_only == HEADERS_FAST) goto DONE;
2037 mptr = TheMessage->cm_fields['M'];
2039 /* Tell the client about the MIME parts in this message */
2040 if (TheMessage->cm_format_type == FMT_RFC822) {
2041 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2042 memset(&ma, 0, sizeof(struct ma_info));
2043 mime_parser(mptr, NULL,
2044 (do_proto ? *list_this_part : NULL),
2045 (do_proto ? *list_this_pref : NULL),
2046 (do_proto ? *list_this_suff : NULL),
2049 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2050 char *start_of_text = NULL;
2051 start_of_text = strstr(mptr, "\n\r\n");
2052 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
2053 if (start_of_text == NULL) start_of_text = mptr;
2055 start_of_text = strstr(start_of_text, "\n");
2060 int nllen = strlen(nl);
2062 while (ch=*mptr, ch!=0) {
2068 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2069 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2070 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2073 sprintf(&outbuf[outlen], "%s", nl);
2077 outbuf[outlen++] = ch;
2081 if (flags & ESC_DOT)
2083 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2085 outbuf[outlen++] = '.';
2090 if (outlen > 1000) {
2091 client_write(outbuf, outlen);
2096 client_write(outbuf, outlen);
2104 if (headers_only == HEADERS_ONLY) {
2108 /* signify start of msg text */
2109 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2110 if (do_proto) cprintf("text\n");
2113 /* If the format type on disk is 1 (fixed-format), then we want
2114 * everything to be output completely literally ... regardless of
2115 * what message transfer format is in use.
2117 if (TheMessage->cm_format_type == FMT_FIXED) {
2119 if (mode == MT_MIME) {
2120 cprintf("Content-type: text/plain\n\n");
2124 while (ch = *mptr++, ch > 0) {
2127 if ((ch == 10) || (buflen > 250)) {
2129 cprintf("%s%s", buf, nl);
2138 if (!IsEmptyStr(buf))
2139 cprintf("%s%s", buf, nl);
2142 /* If the message on disk is format 0 (Citadel vari-format), we
2143 * output using the formatter at 80 columns. This is the final output
2144 * form if the transfer format is RFC822, but if the transfer format
2145 * is Citadel proprietary, it'll still work, because the indentation
2146 * for new paragraphs is correct and the client will reformat the
2147 * message to the reader's screen width.
2149 if (TheMessage->cm_format_type == FMT_CITADEL) {
2150 if (mode == MT_MIME) {
2151 cprintf("Content-type: text/x-citadel-variformat\n\n");
2156 /* If the message on disk is format 4 (MIME), we've gotta hand it
2157 * off to the MIME parser. The client has already been told that
2158 * this message is format 1 (fixed format), so the callback function
2159 * we use will display those parts as-is.
2161 if (TheMessage->cm_format_type == FMT_RFC822) {
2162 memset(&ma, 0, sizeof(struct ma_info));
2164 if (mode == MT_MIME) {
2165 ma.use_fo_hooks = 0;
2166 strcpy(ma.chosen_part, "1");
2167 ma.chosen_pref = 9999;
2168 mime_parser(mptr, NULL,
2169 *choose_preferred, *fixed_output_pre,
2170 *fixed_output_post, (void *)&ma, 0);
2171 mime_parser(mptr, NULL,
2172 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2175 ma.use_fo_hooks = 1;
2176 mime_parser(mptr, NULL,
2177 *fixed_output, *fixed_output_pre,
2178 *fixed_output_post, (void *)&ma, 0);
2183 DONE: /* now we're done */
2184 if (do_proto) cprintf("000\n");
2191 * display a message (mode 0 - Citadel proprietary)
2193 void cmd_msg0(char *cmdbuf)
2196 int headers_only = HEADERS_ALL;
2198 msgid = extract_long(cmdbuf, 0);
2199 headers_only = extract_int(cmdbuf, 1);
2201 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2207 * display a message (mode 2 - RFC822)
2209 void cmd_msg2(char *cmdbuf)
2212 int headers_only = HEADERS_ALL;
2214 msgid = extract_long(cmdbuf, 0);
2215 headers_only = extract_int(cmdbuf, 1);
2217 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2223 * display a message (mode 3 - IGnet raw format - internal programs only)
2225 void cmd_msg3(char *cmdbuf)
2228 struct CtdlMessage *msg = NULL;
2231 if (CC->internal_pgm == 0) {
2232 cprintf("%d This command is for internal programs only.\n",
2233 ERROR + HIGHER_ACCESS_REQUIRED);
2237 msgnum = extract_long(cmdbuf, 0);
2238 msg = CtdlFetchMessage(msgnum, 1);
2240 cprintf("%d Message %ld not found.\n",
2241 ERROR + MESSAGE_NOT_FOUND, msgnum);
2245 serialize_message(&smr, msg);
2246 CtdlFreeMessage(msg);
2249 cprintf("%d Unable to serialize message\n",
2250 ERROR + INTERNAL_ERROR);
2254 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2255 client_write((char *)smr.ser, (int)smr.len);
2262 * Display a message using MIME content types
2264 void cmd_msg4(char *cmdbuf)
2269 msgid = extract_long(cmdbuf, 0);
2270 extract_token(section, cmdbuf, 1, '|', sizeof section);
2271 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2277 * Client tells us its preferred message format(s)
2279 void cmd_msgp(char *cmdbuf)
2281 if (!strcasecmp(cmdbuf, "dont_decode")) {
2282 CC->msg4_dont_decode = 1;
2283 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2286 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2287 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2293 * Open a component of a MIME message as a download file
2295 void cmd_opna(char *cmdbuf)
2298 char desired_section[128];
2300 msgid = extract_long(cmdbuf, 0);
2301 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2302 safestrncpy(CC->download_desired_section, desired_section,
2303 sizeof CC->download_desired_section);
2304 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2309 * Open a component of a MIME message and transmit it all at once
2311 void cmd_dlat(char *cmdbuf)
2314 char desired_section[128];
2316 msgid = extract_long(cmdbuf, 0);
2317 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2318 safestrncpy(CC->download_desired_section, desired_section,
2319 sizeof CC->download_desired_section);
2320 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2325 * Save one or more message pointers into a specified room
2326 * (Returns 0 for success, nonzero for failure)
2327 * roomname may be NULL to use the current room
2329 * Note that the 'supplied_msg' field may be set to NULL, in which case
2330 * the message will be fetched from disk, by number, if we need to perform
2331 * replication checks. This adds an additional database read, so if the
2332 * caller already has the message in memory then it should be supplied. (Obviously
2333 * this mode of operation only works if we're saving a single message.)
2335 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2336 int do_repl_check, struct CtdlMessage *supplied_msg)
2339 char hold_rm[ROOMNAMELEN];
2340 struct cdbdata *cdbfr;
2343 long highest_msg = 0L;
2346 struct CtdlMessage *msg = NULL;
2348 long *msgs_to_be_merged = NULL;
2349 int num_msgs_to_be_merged = 0;
2351 CtdlLogPrintf(CTDL_DEBUG,
2352 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2353 roomname, num_newmsgs, do_repl_check);
2355 strcpy(hold_rm, CC->room.QRname);
2358 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2359 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2360 if (num_newmsgs > 1) supplied_msg = NULL;
2362 /* Now the regular stuff */
2363 if (CtdlGetRoomLock(&CC->room,
2364 ((roomname != NULL) ? roomname : CC->room.QRname) )
2366 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2367 return(ERROR + ROOM_NOT_FOUND);
2371 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2372 num_msgs_to_be_merged = 0;
2375 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2376 if (cdbfr == NULL) {
2380 msglist = (long *) cdbfr->ptr;
2381 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2382 num_msgs = cdbfr->len / sizeof(long);
2387 /* Create a list of msgid's which were supplied by the caller, but do
2388 * not already exist in the target room. It is absolutely taboo to
2389 * have more than one reference to the same message in a room.
2391 for (i=0; i<num_newmsgs; ++i) {
2393 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2394 if (msglist[j] == newmsgidlist[i]) {
2399 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2403 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2406 * Now merge the new messages
2408 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2409 if (msglist == NULL) {
2410 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2412 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2413 num_msgs += num_msgs_to_be_merged;
2415 /* Sort the message list, so all the msgid's are in order */
2416 num_msgs = sort_msglist(msglist, num_msgs);
2418 /* Determine the highest message number */
2419 highest_msg = msglist[num_msgs - 1];
2421 /* Write it back to disk. */
2422 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2423 msglist, (int)(num_msgs * sizeof(long)));
2425 /* Free up the memory we used. */
2428 /* Update the highest-message pointer and unlock the room. */
2429 CC->room.QRhighest = highest_msg;
2430 CtdlPutRoomLock(&CC->room);
2432 /* Perform replication checks if necessary */
2433 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2434 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2436 for (i=0; i<num_msgs_to_be_merged; ++i) {
2437 msgid = msgs_to_be_merged[i];
2439 if (supplied_msg != NULL) {
2443 msg = CtdlFetchMessage(msgid, 0);
2447 ReplicationChecks(msg);
2449 /* If the message has an Exclusive ID, index that... */
2450 if (msg->cm_fields['E'] != NULL) {
2451 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2454 /* Free up the memory we may have allocated */
2455 if (msg != supplied_msg) {
2456 CtdlFreeMessage(msg);
2464 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2467 /* Submit this room for processing by hooks */
2468 PerformRoomHooks(&CC->room);
2470 /* Go back to the room we were in before we wandered here... */
2471 CtdlGetRoom(&CC->room, hold_rm);
2473 /* Bump the reference count for all messages which were merged */
2474 for (i=0; i<num_msgs_to_be_merged; ++i) {
2475 AdjRefCount(msgs_to_be_merged[i], +1);
2478 /* Free up memory... */
2479 if (msgs_to_be_merged != NULL) {
2480 free(msgs_to_be_merged);
2483 /* Return success. */
2489 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2492 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2493 int do_repl_check, struct CtdlMessage *supplied_msg)
2495 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2502 * Message base operation to save a new message to the message store
2503 * (returns new message number)
2505 * This is the back end for CtdlSubmitMsg() and should not be directly
2506 * called by server-side modules.
2509 long send_message(struct CtdlMessage *msg) {
2517 /* Get a new message number */
2518 newmsgid = get_new_message_number();
2519 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2521 /* Generate an ID if we don't have one already */
2522 if (msg->cm_fields['I']==NULL) {
2523 msg->cm_fields['I'] = strdup(msgidbuf);
2526 /* If the message is big, set its body aside for storage elsewhere */
2527 if (msg->cm_fields['M'] != NULL) {
2528 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2530 holdM = msg->cm_fields['M'];
2531 msg->cm_fields['M'] = NULL;
2535 /* Serialize our data structure for storage in the database */
2536 serialize_message(&smr, msg);
2539 msg->cm_fields['M'] = holdM;
2543 cprintf("%d Unable to serialize message\n",
2544 ERROR + INTERNAL_ERROR);
2548 /* Write our little bundle of joy into the message base */
2549 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2550 smr.ser, smr.len) < 0) {
2551 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2555 cdb_store(CDB_BIGMSGS,
2565 /* Free the memory we used for the serialized message */
2568 /* Return the *local* message ID to the caller
2569 * (even if we're storing an incoming network message)
2577 * Serialize a struct CtdlMessage into the format used on disk and network.
2579 * This function loads up a "struct ser_ret" (defined in server.h) which
2580 * contains the length of the serialized message and a pointer to the
2581 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2583 void serialize_message(struct ser_ret *ret, /* return values */
2584 struct CtdlMessage *msg) /* unserialized msg */
2586 size_t wlen, fieldlen;
2588 static char *forder = FORDER;
2591 * Check for valid message format
2593 if (is_valid_message(msg) == 0) {
2594 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2601 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2602 ret->len = ret->len +
2603 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2605 ret->ser = malloc(ret->len);
2606 if (ret->ser == NULL) {
2607 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2608 (long)ret->len, strerror(errno));
2615 ret->ser[1] = msg->cm_anon_type;
2616 ret->ser[2] = msg->cm_format_type;
2619 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2620 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2621 ret->ser[wlen++] = (char)forder[i];
2622 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2623 wlen = wlen + fieldlen + 1;
2625 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2626 (long)ret->len, (long)wlen);
2633 * Serialize a struct CtdlMessage into the format used on disk and network.
2635 * This function loads up a "struct ser_ret" (defined in server.h) which
2636 * contains the length of the serialized message and a pointer to the
2637 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2639 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2640 long Siz) /* how many chars ? */
2644 static char *forder = FORDER;
2648 * Check for valid message format
2650 if (is_valid_message(msg) == 0) {
2651 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2655 buf = (char*) malloc (Siz + 1);
2659 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2660 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2661 msg->cm_fields[(int)forder[i]]);
2662 client_write (buf, strlen(buf));
2671 * Check to see if any messages already exist in the current room which
2672 * carry the same Exclusive ID as this one. If any are found, delete them.
2674 void ReplicationChecks(struct CtdlMessage *msg) {
2675 long old_msgnum = (-1L);
2677 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2679 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2682 /* No exclusive id? Don't do anything. */
2683 if (msg == NULL) return;
2684 if (msg->cm_fields['E'] == NULL) return;
2685 if (IsEmptyStr(msg->cm_fields['E'])) return;
2686 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2687 msg->cm_fields['E'], CC->room.QRname);*/
2689 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2690 if (old_msgnum > 0L) {
2691 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2692 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2699 * Save a message to disk and submit it into the delivery system.
2701 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2702 struct recptypes *recps, /* recipients (if mail) */
2703 char *force, /* force a particular room? */
2704 int flags /* should the message be exported clean? */
2706 char submit_filename[128];
2707 char generated_timestamp[32];
2708 char hold_rm[ROOMNAMELEN];
2709 char actual_rm[ROOMNAMELEN];
2710 char force_room[ROOMNAMELEN];
2711 char content_type[SIZ]; /* We have to learn this */
2712 char recipient[SIZ];
2715 struct ctdluser userbuf;
2717 struct MetaData smi;
2718 FILE *network_fp = NULL;
2719 static int seqnum = 1;
2720 struct CtdlMessage *imsg = NULL;
2722 size_t instr_alloc = 0;
2724 char *hold_R, *hold_D;
2725 char *collected_addresses = NULL;
2726 struct addresses_to_be_filed *aptr = NULL;
2727 char *saved_rfc822_version = NULL;
2728 int qualified_for_journaling = 0;
2729 CitContext *CCC = CC; /* CachedCitContext - performance boost */
2730 char bounce_to[1024] = "";
2734 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2735 if (is_valid_message(msg) == 0) return(-1); /* self check */
2737 /* If this message has no timestamp, we take the liberty of
2738 * giving it one, right now.
2740 if (msg->cm_fields['T'] == NULL) {
2741 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2742 msg->cm_fields['T'] = strdup(generated_timestamp);
2745 /* If this message has no path, we generate one.
2747 if (msg->cm_fields['P'] == NULL) {
2748 if (msg->cm_fields['A'] != NULL) {
2749 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2750 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2751 if (isspace(msg->cm_fields['P'][a])) {
2752 msg->cm_fields['P'][a] = ' ';
2757 msg->cm_fields['P'] = strdup("unknown");
2761 if (force == NULL) {
2762 strcpy(force_room, "");
2765 strcpy(force_room, force);
2768 /* Learn about what's inside, because it's what's inside that counts */
2769 if (msg->cm_fields['M'] == NULL) {
2770 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2774 switch (msg->cm_format_type) {
2776 strcpy(content_type, "text/x-citadel-variformat");
2779 strcpy(content_type, "text/plain");
2782 strcpy(content_type, "text/plain");
2783 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2786 safestrncpy(content_type, &mptr[13], sizeof content_type);
2787 striplt(content_type);
2788 aptr = content_type;
2789 while (!IsEmptyStr(aptr)) {
2801 /* Goto the correct room */
2802 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2803 strcpy(hold_rm, CCC->room.QRname);
2804 strcpy(actual_rm, CCC->room.QRname);
2805 if (recps != NULL) {
2806 strcpy(actual_rm, SENTITEMS);
2809 /* If the user is a twit, move to the twit room for posting */
2811 if (CCC->user.axlevel == 2) {
2812 strcpy(hold_rm, actual_rm);
2813 strcpy(actual_rm, config.c_twitroom);
2814 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2818 /* ...or if this message is destined for Aide> then go there. */
2819 if (!IsEmptyStr(force_room)) {
2820 strcpy(actual_rm, force_room);
2823 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2824 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2825 /* CtdlGetRoom(&CCC->room, actual_rm); */
2826 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
2830 * If this message has no O (room) field, generate one.
2832 if (msg->cm_fields['O'] == NULL) {
2833 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2836 /* Perform "before save" hooks (aborting if any return nonzero) */
2837 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2838 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2841 * If this message has an Exclusive ID, and the room is replication
2842 * checking enabled, then do replication checks.
2844 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2845 ReplicationChecks(msg);
2848 /* Save it to disk */
2849 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2850 newmsgid = send_message(msg);
2851 if (newmsgid <= 0L) return(-5);
2853 /* Write a supplemental message info record. This doesn't have to
2854 * be a critical section because nobody else knows about this message
2857 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2858 memset(&smi, 0, sizeof(struct MetaData));
2859 smi.meta_msgnum = newmsgid;
2860 smi.meta_refcount = 0;
2861 safestrncpy(smi.meta_content_type, content_type,
2862 sizeof smi.meta_content_type);
2865 * Measure how big this message will be when rendered as RFC822.
2866 * We do this for two reasons:
2867 * 1. We need the RFC822 length for the new metadata record, so the
2868 * POP and IMAP services don't have to calculate message lengths
2869 * while the user is waiting (multiplied by potentially hundreds
2870 * or thousands of messages).
2871 * 2. If journaling is enabled, we will need an RFC822 version of the
2872 * message to attach to the journalized copy.
2874 if (CCC->redirect_buffer != NULL) {
2875 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2878 CCC->redirect_buffer = malloc(SIZ);
2879 CCC->redirect_len = 0;
2880 CCC->redirect_alloc = SIZ;
2881 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2882 smi.meta_rfc822_length = CCC->redirect_len;
2883 saved_rfc822_version = CCC->redirect_buffer;
2884 CCC->redirect_buffer = NULL;
2885 CCC->redirect_len = 0;
2886 CCC->redirect_alloc = 0;
2890 /* Now figure out where to store the pointers */
2891 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2893 /* If this is being done by the networker delivering a private
2894 * message, we want to BYPASS saving the sender's copy (because there
2895 * is no local sender; it would otherwise go to the Trashcan).
2897 if ((!CCC->internal_pgm) || (recps == NULL)) {
2898 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2899 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2900 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2904 /* For internet mail, drop a copy in the outbound queue room */
2905 if ((recps != NULL) && (recps->num_internet > 0)) {
2906 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2909 /* If other rooms are specified, drop them there too. */
2910 if ((recps != NULL) && (recps->num_room > 0))
2911 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2912 extract_token(recipient, recps->recp_room, i,
2913 '|', sizeof recipient);
2914 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2915 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2918 /* Bump this user's messages posted counter. */
2919 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2920 CtdlGetUserLock(&CCC->user, CCC->curr_user);
2921 CCC->user.posted = CCC->user.posted + 1;
2922 CtdlPutUserLock(&CCC->user);
2924 /* Decide where bounces need to be delivered */
2925 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2926 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2928 else if (CCC->logged_in) {
2929 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2932 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2935 /* If this is private, local mail, make a copy in the
2936 * recipient's mailbox and bump the reference count.
2938 if ((recps != NULL) && (recps->num_local > 0))
2939 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2940 extract_token(recipient, recps->recp_local, i,
2941 '|', sizeof recipient);
2942 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2944 if (CtdlGetUser(&userbuf, recipient) == 0) {
2945 // Add a flag so the Funambol module knows its mail
2946 msg->cm_fields['W'] = strdup(recipient);
2947 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2948 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2949 CtdlBumpNewMailCounter(userbuf.usernum);
2950 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2951 /* Generate a instruction message for the Funambol notification
2952 * server, in the same style as the SMTP queue
2955 instr = malloc(instr_alloc);
2956 snprintf(instr, instr_alloc,
2957 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2959 SPOOLMIME, newmsgid, (long)time(NULL),
2963 imsg = malloc(sizeof(struct CtdlMessage));
2964 memset(imsg, 0, sizeof(struct CtdlMessage));
2965 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2966 imsg->cm_anon_type = MES_NORMAL;
2967 imsg->cm_format_type = FMT_RFC822;
2968 imsg->cm_fields['A'] = strdup("Citadel");
2969 imsg->cm_fields['J'] = strdup("do not journal");
2970 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2971 imsg->cm_fields['W'] = strdup(recipient);
2972 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2973 CtdlFreeMessage(imsg);
2977 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2978 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2983 /* Perform "after save" hooks */
2984 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2985 PerformMessageHooks(msg, EVT_AFTERSAVE);
2987 /* For IGnet mail, we have to save a new copy into the spooler for
2988 * each recipient, with the R and D fields set to the recipient and
2989 * destination-node. This has two ugly side effects: all other
2990 * recipients end up being unlisted in this recipient's copy of the
2991 * message, and it has to deliver multiple messages to the same
2992 * node. We'll revisit this again in a year or so when everyone has
2993 * a network spool receiver that can handle the new style messages.
2995 if ((recps != NULL) && (recps->num_ignet > 0))
2996 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2997 extract_token(recipient, recps->recp_ignet, i,
2998 '|', sizeof recipient);
3000 hold_R = msg->cm_fields['R'];
3001 hold_D = msg->cm_fields['D'];
3002 msg->cm_fields['R'] = malloc(SIZ);
3003 msg->cm_fields['D'] = malloc(128);
3004 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3005 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3007 serialize_message(&smr, msg);
3009 snprintf(submit_filename, sizeof submit_filename,
3010 "%s/netmail.%04lx.%04x.%04x",
3012 (long) getpid(), CCC->cs_pid, ++seqnum);
3013 network_fp = fopen(submit_filename, "wb+");
3014 if (network_fp != NULL) {
3015 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3021 free(msg->cm_fields['R']);
3022 free(msg->cm_fields['D']);
3023 msg->cm_fields['R'] = hold_R;
3024 msg->cm_fields['D'] = hold_D;
3027 /* Go back to the room we started from */
3028 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3029 if (strcasecmp(hold_rm, CCC->room.QRname))
3030 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3032 /* For internet mail, generate delivery instructions.
3033 * Yes, this is recursive. Deal with it. Infinite recursion does
3034 * not happen because the delivery instructions message does not
3035 * contain a recipient.
3037 if ((recps != NULL) && (recps->num_internet > 0)) {
3038 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3040 instr = malloc(instr_alloc);
3041 snprintf(instr, instr_alloc,
3042 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3044 SPOOLMIME, newmsgid, (long)time(NULL),
3048 if (recps->envelope_from != NULL) {
3049 tmp = strlen(instr);
3050 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3053 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3054 tmp = strlen(instr);
3055 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3056 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3057 instr_alloc = instr_alloc * 2;
3058 instr = realloc(instr, instr_alloc);
3060 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3063 imsg = malloc(sizeof(struct CtdlMessage));
3064 memset(imsg, 0, sizeof(struct CtdlMessage));
3065 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3066 imsg->cm_anon_type = MES_NORMAL;
3067 imsg->cm_format_type = FMT_RFC822;
3068 imsg->cm_fields['A'] = strdup("Citadel");
3069 imsg->cm_fields['J'] = strdup("do not journal");
3070 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3071 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3072 CtdlFreeMessage(imsg);
3076 * Any addresses to harvest for someone's address book?
3078 if ( (CCC->logged_in) && (recps != NULL) ) {
3079 collected_addresses = harvest_collected_addresses(msg);
3082 if (collected_addresses != NULL) {
3083 aptr = (struct addresses_to_be_filed *)
3084 malloc(sizeof(struct addresses_to_be_filed));
3085 CtdlMailboxName(actual_rm, sizeof actual_rm,
3086 &CCC->user, USERCONTACTSROOM);
3087 aptr->roomname = strdup(actual_rm);
3088 aptr->collected_addresses = collected_addresses;
3089 begin_critical_section(S_ATBF);
3092 end_critical_section(S_ATBF);
3096 * Determine whether this message qualifies for journaling.
3098 if (msg->cm_fields['J'] != NULL) {
3099 qualified_for_journaling = 0;
3102 if (recps == NULL) {
3103 qualified_for_journaling = config.c_journal_pubmsgs;
3105 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3106 qualified_for_journaling = config.c_journal_email;
3109 qualified_for_journaling = config.c_journal_pubmsgs;
3114 * Do we have to perform journaling? If so, hand off the saved
3115 * RFC822 version will be handed off to the journaler for background
3116 * submit. Otherwise, we have to free the memory ourselves.
3118 if (saved_rfc822_version != NULL) {
3119 if (qualified_for_journaling) {
3120 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3123 free(saved_rfc822_version);
3133 void aide_message (char *text, char *subject)
3135 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3140 * Convenience function for generating small administrative messages.
3142 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3143 int format_type, const char *subject)
3145 struct CtdlMessage *msg;
3146 struct recptypes *recp = NULL;
3148 msg = malloc(sizeof(struct CtdlMessage));
3149 memset(msg, 0, sizeof(struct CtdlMessage));
3150 msg->cm_magic = CTDLMESSAGE_MAGIC;
3151 msg->cm_anon_type = MES_NORMAL;
3152 msg->cm_format_type = format_type;
3155 msg->cm_fields['A'] = strdup(from);
3157 else if (fromaddr != NULL) {
3158 msg->cm_fields['A'] = strdup(fromaddr);
3159 if (strchr(msg->cm_fields['A'], '@')) {
3160 *strchr(msg->cm_fields['A'], '@') = 0;
3164 msg->cm_fields['A'] = strdup("Citadel");
3167 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3168 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3169 msg->cm_fields['N'] = strdup(NODENAME);
3171 msg->cm_fields['R'] = strdup(to);
3172 recp = validate_recipients(to, NULL, 0);
3174 if (subject != NULL) {
3175 msg->cm_fields['U'] = strdup(subject);
3177 msg->cm_fields['M'] = strdup(text);
3179 CtdlSubmitMsg(msg, recp, room, 0);
3180 CtdlFreeMessage(msg);
3181 if (recp != NULL) free_recipients(recp);
3187 * Back end function used by CtdlMakeMessage() and similar functions
3189 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3190 size_t maxlen, /* maximum message length */
3191 char *exist, /* if non-null, append to it;
3192 exist is ALWAYS freed */
3193 int crlf, /* CRLF newlines instead of LF */
3194 int sock /* socket handle or 0 for this session's client socket */
3198 size_t message_len = 0;
3199 size_t buffer_len = 0;
3206 if (exist == NULL) {
3213 message_len = strlen(exist);
3214 buffer_len = message_len + 4096;
3215 m = realloc(exist, buffer_len);
3222 /* Do we need to change leading ".." to "." for SMTP escaping? */
3223 if (!strcmp(terminator, ".")) {
3227 /* flush the input if we have nowhere to store it */
3232 /* read in the lines of message text one by one */
3235 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3238 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3240 if (!strcmp(buf, terminator)) finished = 1;
3242 strcat(buf, "\r\n");
3248 /* Unescape SMTP-style input of two dots at the beginning of the line */
3250 if (!strncmp(buf, "..", 2)) {
3251 strcpy(buf, &buf[1]);
3255 if ( (!flushing) && (!finished) ) {
3256 /* Measure the line */
3257 linelen = strlen(buf);
3259 /* augment the buffer if we have to */
3260 if ((message_len + linelen) >= buffer_len) {
3261 ptr = realloc(m, (buffer_len * 2) );
3262 if (ptr == NULL) { /* flush if can't allocate */
3265 buffer_len = (buffer_len * 2);
3267 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3271 /* Add the new line to the buffer. NOTE: this loop must avoid
3272 * using functions like strcat() and strlen() because they
3273 * traverse the entire buffer upon every call, and doing that
3274 * for a multi-megabyte message slows it down beyond usability.
3276 strcpy(&m[message_len], buf);
3277 message_len += linelen;
3280 /* if we've hit the max msg length, flush the rest */
3281 if (message_len >= maxlen) flushing = 1;
3283 } while (!finished);
3291 * Build a binary message to be saved on disk.
3292 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3293 * will become part of the message. This means you are no longer
3294 * responsible for managing that memory -- it will be freed along with
3295 * the rest of the fields when CtdlFreeMessage() is called.)
3298 struct CtdlMessage *CtdlMakeMessage(
3299 struct ctdluser *author, /* author's user structure */
3300 char *recipient, /* NULL if it's not mail */
3301 char *recp_cc, /* NULL if it's not mail */
3302 char *room, /* room where it's going */
3303 int type, /* see MES_ types in header file */
3304 int format_type, /* variformat, plain text, MIME... */
3305 char *fake_name, /* who we're masquerading as */
3306 char *my_email, /* which of my email addresses to use (empty is ok) */
3307 char *subject, /* Subject (optional) */
3308 char *supplied_euid, /* ...or NULL if this is irrelevant */
3309 char *preformatted_text, /* ...or NULL to read text from client */
3310 char *references /* Thread references */
3312 char dest_node[256];
3314 struct CtdlMessage *msg;
3316 msg = malloc(sizeof(struct CtdlMessage));
3317 memset(msg, 0, sizeof(struct CtdlMessage));
3318 msg->cm_magic = CTDLMESSAGE_MAGIC;
3319 msg->cm_anon_type = type;
3320 msg->cm_format_type = format_type;
3322 /* Don't confuse the poor folks if it's not routed mail. */
3323 strcpy(dest_node, "");
3325 if (recipient != NULL) striplt(recipient);
3326 if (recp_cc != NULL) striplt(recp_cc);
3328 /* Path or Return-Path */
3329 if (my_email == NULL) my_email = "";
3331 if (!IsEmptyStr(my_email)) {
3332 msg->cm_fields['P'] = strdup(my_email);
3335 snprintf(buf, sizeof buf, "%s", author->fullname);
3336 msg->cm_fields['P'] = strdup(buf);
3338 convert_spaces_to_underscores(msg->cm_fields['P']);
3340 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3341 msg->cm_fields['T'] = strdup(buf);
3343 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3344 msg->cm_fields['A'] = strdup(fake_name);
3347 msg->cm_fields['A'] = strdup(author->fullname);
3350 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3351 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3354 msg->cm_fields['O'] = strdup(CC->room.QRname);
3357 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3358 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3360 if ((recipient != NULL) && (recipient[0] != 0)) {
3361 msg->cm_fields['R'] = strdup(recipient);
3363 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3364 msg->cm_fields['Y'] = strdup(recp_cc);
3366 if (dest_node[0] != 0) {
3367 msg->cm_fields['D'] = strdup(dest_node);
3370 if (!IsEmptyStr(my_email)) {
3371 msg->cm_fields['F'] = strdup(my_email);
3373 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3374 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3377 if (subject != NULL) {
3380 length = strlen(subject);
3386 while ((subject[i] != '\0') &&
3387 (IsAscii = isascii(subject[i]) != 0 ))
3390 msg->cm_fields['U'] = strdup(subject);
3391 else /* ok, we've got utf8 in the string. */
3393 msg->cm_fields['U'] = rfc2047encode(subject, length);
3399 if (supplied_euid != NULL) {
3400 msg->cm_fields['E'] = strdup(supplied_euid);
3403 if (references != NULL) {
3404 if (!IsEmptyStr(references)) {
3405 msg->cm_fields['W'] = strdup(references);
3409 if (preformatted_text != NULL) {
3410 msg->cm_fields['M'] = preformatted_text;
3413 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3421 * Check to see whether we have permission to post a message in the current
3422 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3423 * returns 0 on success.
3425 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3427 const char* RemoteIdentifier,
3431 if (!(CC->logged_in) &&
3432 (PostPublic == POST_LOGGED_IN)) {
3433 snprintf(errmsgbuf, n, "Not logged in.");
3434 return (ERROR + NOT_LOGGED_IN);
3436 else if (PostPublic == CHECK_EXISTANCE) {
3437 return (0); // We're Evaling whether a recipient exists
3439 else if (!(CC->logged_in)) {
3441 if ((CC->room.QRflags & QR_READONLY)) {
3442 snprintf(errmsgbuf, n, "Not logged in.");
3443 return (ERROR + NOT_LOGGED_IN);
3445 if (CC->room.QRflags2 & QR2_MODERATED) {
3446 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3447 return (ERROR + NOT_LOGGED_IN);
3449 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3454 if (RemoteIdentifier == NULL)
3456 snprintf(errmsgbuf, n, "Need sender to permit access.");
3457 return (ERROR + USERNAME_REQUIRED);
3460 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3461 begin_critical_section(S_NETCONFIGS);
3462 if (!read_spoolcontrol_file(&sc, filename))
3464 end_critical_section(S_NETCONFIGS);
3465 snprintf(errmsgbuf, n,
3466 "This mailing list only accepts posts from subscribers.");
3467 return (ERROR + NO_SUCH_USER);
3469 end_critical_section(S_NETCONFIGS);
3470 found = is_recipient (sc, RemoteIdentifier);
3471 free_spoolcontrol_struct(&sc);
3476 snprintf(errmsgbuf, n,
3477 "This mailing list only accepts posts from subscribers.");
3478 return (ERROR + NO_SUCH_USER);
3485 if ((CC->user.axlevel < 2)
3486 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3487 snprintf(errmsgbuf, n, "Need to be validated to enter "
3488 "(except in %s> to sysop)", MAILROOM);
3489 return (ERROR + HIGHER_ACCESS_REQUIRED);
3492 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3493 if (!(ra & UA_POSTALLOWED)) {
3494 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3495 return (ERROR + HIGHER_ACCESS_REQUIRED);
3498 strcpy(errmsgbuf, "Ok");
3504 * Check to see if the specified user has Internet mail permission
3505 * (returns nonzero if permission is granted)
3507 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3509 /* Do not allow twits to send Internet mail */
3510 if (who->axlevel <= 2) return(0);
3512 /* Globally enabled? */
3513 if (config.c_restrict == 0) return(1);
3515 /* User flagged ok? */
3516 if (who->flags & US_INTERNET) return(2);
3518 /* Aide level access? */
3519 if (who->axlevel >= 6) return(3);
3521 /* No mail for you! */
3527 * Validate recipients, count delivery types and errors, and handle aliasing
3528 * FIXME check for dupes!!!!!
3530 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3531 * were specified, or the number of addresses found invalid.
3533 * Caller needs to free the result using free_recipients()
3535 struct recptypes *validate_recipients(char *supplied_recipients,
3536 const char *RemoteIdentifier,
3538 struct recptypes *ret;
3539 char *recipients = NULL;
3540 char this_recp[256];
3541 char this_recp_cooked[256];
3547 struct ctdluser tempUS;
3548 struct ctdlroom tempQR;
3549 struct ctdlroom tempQR2;
3555 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3556 if (ret == NULL) return(NULL);
3558 /* Set all strings to null and numeric values to zero */
3559 memset(ret, 0, sizeof(struct recptypes));
3561 if (supplied_recipients == NULL) {
3562 recipients = strdup("");
3565 recipients = strdup(supplied_recipients);
3568 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3569 * actually need, but it's healthier for the heap than doing lots of tiny
3570 * realloc() calls instead.
3573 ret->errormsg = malloc(strlen(recipients) + 1024);
3574 ret->recp_local = malloc(strlen(recipients) + 1024);
3575 ret->recp_internet = malloc(strlen(recipients) + 1024);
3576 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3577 ret->recp_room = malloc(strlen(recipients) + 1024);
3578 ret->display_recp = malloc(strlen(recipients) + 1024);
3580 ret->errormsg[0] = 0;
3581 ret->recp_local[0] = 0;
3582 ret->recp_internet[0] = 0;
3583 ret->recp_ignet[0] = 0;
3584 ret->recp_room[0] = 0;
3585 ret->display_recp[0] = 0;
3587 ret->recptypes_magic = RECPTYPES_MAGIC;
3589 /* Change all valid separator characters to commas */
3590 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3591 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3592 recipients[i] = ',';
3596 /* Now start extracting recipients... */
3598 while (!IsEmptyStr(recipients)) {
3600 for (i=0; i<=strlen(recipients); ++i) {
3601 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3602 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3603 safestrncpy(this_recp, recipients, i+1);
3605 if (recipients[i] == ',') {
3606 strcpy(recipients, &recipients[i+1]);
3609 strcpy(recipients, "");
3616 if (IsEmptyStr(this_recp))
3618 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3620 mailtype = alias(this_recp);
3621 mailtype = alias(this_recp);
3622 mailtype = alias(this_recp);
3624 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3625 if (this_recp[j]=='_') {
3626 this_recp_cooked[j] = ' ';
3629 this_recp_cooked[j] = this_recp[j];
3632 this_recp_cooked[j] = '\0';
3637 if (!strcasecmp(this_recp, "sysop")) {
3639 strcpy(this_recp, config.c_aideroom);
3640 if (!IsEmptyStr(ret->recp_room)) {
3641 strcat(ret->recp_room, "|");
3643 strcat(ret->recp_room, this_recp);
3645 else if ( (!strncasecmp(this_recp, "room_", 5))
3646 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3648 /* Save room so we can restore it later */
3652 /* Check permissions to send mail to this room */
3653 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3665 if (!IsEmptyStr(ret->recp_room)) {
3666 strcat(ret->recp_room, "|");
3668 strcat(ret->recp_room, &this_recp_cooked[5]);
3671 /* Restore room in case something needs it */
3675 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3677 strcpy(this_recp, tempUS.fullname);
3678 if (!IsEmptyStr(ret->recp_local)) {
3679 strcat(ret->recp_local, "|");
3681 strcat(ret->recp_local, this_recp);
3683 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
3685 strcpy(this_recp, tempUS.fullname);
3686 if (!IsEmptyStr(ret->recp_local)) {
3687 strcat(ret->recp_local, "|");
3689 strcat(ret->recp_local, this_recp);
3697 /* Yes, you're reading this correctly: if the target
3698 * domain points back to the local system or an attached
3699 * Citadel directory, the address is invalid. That's
3700 * because if the address were valid, we would have
3701 * already translated it to a local address by now.
3703 if (IsDirectory(this_recp, 0)) {
3708 ++ret->num_internet;
3709 if (!IsEmptyStr(ret->recp_internet)) {
3710 strcat(ret->recp_internet, "|");
3712 strcat(ret->recp_internet, this_recp);
3717 if (!IsEmptyStr(ret->recp_ignet)) {
3718 strcat(ret->recp_ignet, "|");
3720 strcat(ret->recp_ignet, this_recp);
3728 if (IsEmptyStr(errmsg)) {
3729 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3732 snprintf(append, sizeof append, "%s", errmsg);
3734 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3735 if (!IsEmptyStr(ret->errormsg)) {
3736 strcat(ret->errormsg, "; ");
3738 strcat(ret->errormsg, append);
3742 if (IsEmptyStr(ret->display_recp)) {
3743 strcpy(append, this_recp);
3746 snprintf(append, sizeof append, ", %s", this_recp);
3748 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3749 strcat(ret->display_recp, append);
3754 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3755 ret->num_room + ret->num_error) == 0) {
3756 ret->num_error = (-1);
3757 strcpy(ret->errormsg, "No recipients specified.");
3760 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3761 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3762 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3763 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3764 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3765 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3773 * Destructor for struct recptypes
3775 void free_recipients(struct recptypes *valid) {
3777 if (valid == NULL) {
3781 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3782 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3786 if (valid->errormsg != NULL) free(valid->errormsg);
3787 if (valid->recp_local != NULL) free(valid->recp_local);
3788 if (valid->recp_internet != NULL) free(valid->recp_internet);
3789 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3790 if (valid->recp_room != NULL) free(valid->recp_room);
3791 if (valid->display_recp != NULL) free(valid->display_recp);
3792 if (valid->bounce_to != NULL) free(valid->bounce_to);
3793 if (valid->envelope_from != NULL) free(valid->envelope_from);
3800 * message entry - mode 0 (normal)
3802 void cmd_ent0(char *entargs)
3808 char supplied_euid[128];
3810 int format_type = 0;
3811 char newusername[256];
3812 char newuseremail[256];
3813 struct CtdlMessage *msg;
3817 struct recptypes *valid = NULL;
3818 struct recptypes *valid_to = NULL;
3819 struct recptypes *valid_cc = NULL;
3820 struct recptypes *valid_bcc = NULL;
3822 int subject_required = 0;
3827 int newuseremail_ok = 0;
3828 char references[SIZ];
3833 post = extract_int(entargs, 0);
3834 extract_token(recp, entargs, 1, '|', sizeof recp);
3835 anon_flag = extract_int(entargs, 2);
3836 format_type = extract_int(entargs, 3);
3837 extract_token(subject, entargs, 4, '|', sizeof subject);
3838 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3839 do_confirm = extract_int(entargs, 6);
3840 extract_token(cc, entargs, 7, '|', sizeof cc);
3841 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3842 switch(CC->room.QRdefaultview) {
3845 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3848 supplied_euid[0] = 0;
3851 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3852 extract_token(references, entargs, 11, '|', sizeof references);
3853 for (ptr=references; *ptr != 0; ++ptr) {
3854 if (*ptr == '!') *ptr = '|';
3857 /* first check to make sure the request is valid. */
3859 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3862 cprintf("%d %s\n", err, errmsg);
3866 /* Check some other permission type things. */
3868 if (IsEmptyStr(newusername)) {
3869 strcpy(newusername, CC->user.fullname);
3871 if ( (CC->user.axlevel < 6)
3872 && (strcasecmp(newusername, CC->user.fullname))
3873 && (strcasecmp(newusername, CC->cs_inet_fn))
3875 cprintf("%d You don't have permission to author messages as '%s'.\n",
3876 ERROR + HIGHER_ACCESS_REQUIRED,
3883 if (IsEmptyStr(newuseremail)) {
3884 newuseremail_ok = 1;
3887 if (!IsEmptyStr(newuseremail)) {
3888 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3889 newuseremail_ok = 1;
3891 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3892 j = num_tokens(CC->cs_inet_other_emails, '|');
3893 for (i=0; i<j; ++i) {
3894 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3895 if (!strcasecmp(newuseremail, buf)) {
3896 newuseremail_ok = 1;
3902 if (!newuseremail_ok) {
3903 cprintf("%d You don't have permission to author messages as '%s'.\n",
3904 ERROR + HIGHER_ACCESS_REQUIRED,
3910 CC->cs_flags |= CS_POSTING;
3912 /* In mailbox rooms we have to behave a little differently --
3913 * make sure the user has specified at least one recipient. Then
3914 * validate the recipient(s). We do this for the Mail> room, as
3915 * well as any room which has the "Mailbox" view set.
3918 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3919 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3921 if (CC->user.axlevel < 2) {
3922 strcpy(recp, "sysop");
3927 valid_to = validate_recipients(recp, NULL, 0);
3928 if (valid_to->num_error > 0) {
3929 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3930 free_recipients(valid_to);
3934 valid_cc = validate_recipients(cc, NULL, 0);
3935 if (valid_cc->num_error > 0) {
3936 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3937 free_recipients(valid_to);
3938 free_recipients(valid_cc);
3942 valid_bcc = validate_recipients(bcc, NULL, 0);
3943 if (valid_bcc->num_error > 0) {
3944 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3945 free_recipients(valid_to);
3946 free_recipients(valid_cc);
3947 free_recipients(valid_bcc);
3951 /* Recipient required, but none were specified */
3952 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3953 free_recipients(valid_to);
3954 free_recipients(valid_cc);
3955 free_recipients(valid_bcc);
3956 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3960 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3961 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3962 cprintf("%d You do not have permission "
3963 "to send Internet mail.\n",
3964 ERROR + HIGHER_ACCESS_REQUIRED);
3965 free_recipients(valid_to);
3966 free_recipients(valid_cc);
3967 free_recipients(valid_bcc);
3972 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)
3973 && (CC->user.axlevel < 4) ) {
3974 cprintf("%d Higher access required for network mail.\n",
3975 ERROR + HIGHER_ACCESS_REQUIRED);
3976 free_recipients(valid_to);
3977 free_recipients(valid_cc);
3978 free_recipients(valid_bcc);
3982 if ((RESTRICT_INTERNET == 1)
3983 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3984 && ((CC->user.flags & US_INTERNET) == 0)
3985 && (!CC->internal_pgm)) {
3986 cprintf("%d You don't have access to Internet mail.\n",
3987 ERROR + HIGHER_ACCESS_REQUIRED);
3988 free_recipients(valid_to);
3989 free_recipients(valid_cc);
3990 free_recipients(valid_bcc);
3996 /* Is this a room which has anonymous-only or anonymous-option? */
3997 anonymous = MES_NORMAL;
3998 if (CC->room.QRflags & QR_ANONONLY) {
3999 anonymous = MES_ANONONLY;
4001 if (CC->room.QRflags & QR_ANONOPT) {
4002 if (anon_flag == 1) { /* only if the user requested it */
4003 anonymous = MES_ANONOPT;
4007 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4011 /* Recommend to the client that the use of a message subject is
4012 * strongly recommended in this room, if either the SUBJECTREQ flag
4013 * is set, or if there is one or more Internet email recipients.
4015 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4016 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4017 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4018 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4020 /* If we're only checking the validity of the request, return
4021 * success without creating the message.
4024 cprintf("%d %s|%d\n", CIT_OK,
4025 ((valid_to != NULL) ? valid_to->display_recp : ""),
4027 free_recipients(valid_to);
4028 free_recipients(valid_cc);
4029 free_recipients(valid_bcc);
4033 /* We don't need these anymore because we'll do it differently below */
4034 free_recipients(valid_to);
4035 free_recipients(valid_cc);
4036 free_recipients(valid_bcc);
4038 /* Read in the message from the client. */
4040 cprintf("%d send message\n", START_CHAT_MODE);
4042 cprintf("%d send message\n", SEND_LISTING);
4045 msg = CtdlMakeMessage(&CC->user, recp, cc,
4046 CC->room.QRname, anonymous, format_type,
4047 newusername, newuseremail, subject,
4048 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4051 /* Put together one big recipients struct containing to/cc/bcc all in
4052 * one. This is for the envelope.
4054 char *all_recps = malloc(SIZ * 3);
4055 strcpy(all_recps, recp);
4056 if (!IsEmptyStr(cc)) {
4057 if (!IsEmptyStr(all_recps)) {
4058 strcat(all_recps, ",");
4060 strcat(all_recps, cc);
4062 if (!IsEmptyStr(bcc)) {
4063 if (!IsEmptyStr(all_recps)) {
4064 strcat(all_recps, ",");
4066 strcat(all_recps, bcc);
4068 if (!IsEmptyStr(all_recps)) {
4069 valid = validate_recipients(all_recps, NULL, 0);
4077 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4080 cprintf("%ld\n", msgnum);
4082 cprintf("Message accepted.\n");
4085 cprintf("Internal error.\n");
4087 if (msg->cm_fields['E'] != NULL) {
4088 cprintf("%s\n", msg->cm_fields['E']);
4095 CtdlFreeMessage(msg);
4097 if (valid != NULL) {
4098 free_recipients(valid);
4106 * API function to delete messages which match a set of criteria
4107 * (returns the actual number of messages deleted)
4109 int CtdlDeleteMessages(char *room_name, /* which room */
4110 long *dmsgnums, /* array of msg numbers to be deleted */
4111 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4112 char *content_type /* or "" for any. regular expressions expected. */
4115 struct ctdlroom qrbuf;
4116 struct cdbdata *cdbfr;
4117 long *msglist = NULL;
4118 long *dellist = NULL;
4121 int num_deleted = 0;
4123 struct MetaData smi;
4126 int need_to_free_re = 0;
4128 if (content_type) if (!IsEmptyStr(content_type)) {
4129 regcomp(&re, content_type, 0);
4130 need_to_free_re = 1;
4132 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4133 room_name, num_dmsgnums, content_type);
4135 /* get room record, obtaining a lock... */
4136 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4137 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4139 if (need_to_free_re) regfree(&re);
4140 return (0); /* room not found */
4142 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4144 if (cdbfr != NULL) {
4145 dellist = malloc(cdbfr->len);
4146 msglist = (long *) cdbfr->ptr;
4147 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4148 num_msgs = cdbfr->len / sizeof(long);
4152 for (i = 0; i < num_msgs; ++i) {
4155 /* Set/clear a bit for each criterion */
4157 /* 0 messages in the list or a null list means that we are
4158 * interested in deleting any messages which meet the other criteria.
4160 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4161 delete_this |= 0x01;
4164 for (j=0; j<num_dmsgnums; ++j) {
4165 if (msglist[i] == dmsgnums[j]) {
4166 delete_this |= 0x01;
4171 if (IsEmptyStr(content_type)) {
4172 delete_this |= 0x02;
4174 GetMetaData(&smi, msglist[i]);
4175 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4176 delete_this |= 0x02;
4180 /* Delete message only if all bits are set */
4181 if (delete_this == 0x03) {
4182 dellist[num_deleted++] = msglist[i];
4187 num_msgs = sort_msglist(msglist, num_msgs);
4188 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4189 msglist, (int)(num_msgs * sizeof(long)));
4191 qrbuf.QRhighest = msglist[num_msgs - 1];
4193 CtdlPutRoomLock(&qrbuf);
4195 /* Go through the messages we pulled out of the index, and decrement
4196 * their reference counts by 1. If this is the only room the message
4197 * was in, the reference count will reach zero and the message will
4198 * automatically be deleted from the database. We do this in a
4199 * separate pass because there might be plug-in hooks getting called,
4200 * and we don't want that happening during an S_ROOMS critical
4203 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4204 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4205 AdjRefCount(dellist[i], -1);
4208 /* Now free the memory we used, and go away. */
4209 if (msglist != NULL) free(msglist);
4210 if (dellist != NULL) free(dellist);
4211 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4212 if (need_to_free_re) regfree(&re);
4213 return (num_deleted);
4219 * Check whether the current user has permission to delete messages from
4220 * the current room (returns 1 for yes, 0 for no)
4222 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4224 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4225 if (ra & UA_DELETEALLOWED) return(1);
4233 * Delete message from current room
4235 void cmd_dele(char *args)
4244 extract_token(msgset, args, 0, '|', sizeof msgset);
4245 num_msgs = num_tokens(msgset, ',');
4247 cprintf("%d Nothing to do.\n", CIT_OK);
4251 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4252 cprintf("%d Higher access required.\n",
4253 ERROR + HIGHER_ACCESS_REQUIRED);
4258 * Build our message set to be moved/copied
4260 msgs = malloc(num_msgs * sizeof(long));
4261 for (i=0; i<num_msgs; ++i) {
4262 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4263 msgs[i] = atol(msgtok);
4266 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4270 cprintf("%d %d message%s deleted.\n", CIT_OK,
4271 num_deleted, ((num_deleted != 1) ? "s" : ""));
4273 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4281 * move or copy a message to another room
4283 void cmd_move(char *args)
4290 char targ[ROOMNAMELEN];
4291 struct ctdlroom qtemp;
4298 extract_token(msgset, args, 0, '|', sizeof msgset);
4299 num_msgs = num_tokens(msgset, ',');
4301 cprintf("%d Nothing to do.\n", CIT_OK);
4305 extract_token(targ, args, 1, '|', sizeof targ);
4306 convert_room_name_macros(targ, sizeof targ);
4307 targ[ROOMNAMELEN - 1] = 0;
4308 is_copy = extract_int(args, 2);
4310 if (CtdlGetRoom(&qtemp, targ) != 0) {
4311 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4315 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4316 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4320 CtdlGetUser(&CC->user, CC->curr_user);
4321 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4323 /* Check for permission to perform this operation.
4324 * Remember: "CC->room" is source, "qtemp" is target.
4328 /* Aides can move/copy */
4329 if (CC->user.axlevel >= 6) permit = 1;
4331 /* Room aides can move/copy */
4332 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4334 /* Permit move/copy from personal rooms */
4335 if ((CC->room.QRflags & QR_MAILBOX)
4336 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4338 /* Permit only copy from public to personal room */
4340 && (!(CC->room.QRflags & QR_MAILBOX))
4341 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4343 /* Permit message removal from collaborative delete rooms */
4344 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4346 /* Users allowed to post into the target room may move into it too. */
4347 if ((CC->room.QRflags & QR_MAILBOX) &&
4348 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4350 /* User must have access to target room */
4351 if (!(ra & UA_KNOWN)) permit = 0;
4354 cprintf("%d Higher access required.\n",
4355 ERROR + HIGHER_ACCESS_REQUIRED);
4360 * Build our message set to be moved/copied
4362 msgs = malloc(num_msgs * sizeof(long));
4363 for (i=0; i<num_msgs; ++i) {
4364 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4365 msgs[i] = atol(msgtok);
4371 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4373 cprintf("%d Cannot store message(s) in %s: error %d\n",
4379 /* Now delete the message from the source room,
4380 * if this is a 'move' rather than a 'copy' operation.
4383 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4387 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4393 * GetMetaData() - Get the supplementary record for a message
4395 void GetMetaData(struct MetaData *smibuf, long msgnum)
4398 struct cdbdata *cdbsmi;
4401 memset(smibuf, 0, sizeof(struct MetaData));
4402 smibuf->meta_msgnum = msgnum;
4403 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4405 /* Use the negative of the message number for its supp record index */
4406 TheIndex = (0L - msgnum);
4408 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4409 if (cdbsmi == NULL) {
4410 return; /* record not found; go with defaults */
4412 memcpy(smibuf, cdbsmi->ptr,
4413 ((cdbsmi->len > sizeof(struct MetaData)) ?
4414 sizeof(struct MetaData) : cdbsmi->len));
4421 * PutMetaData() - (re)write supplementary record for a message
4423 void PutMetaData(struct MetaData *smibuf)
4427 /* Use the negative of the message number for the metadata db index */
4428 TheIndex = (0L - smibuf->meta_msgnum);
4430 cdb_store(CDB_MSGMAIN,
4431 &TheIndex, (int)sizeof(long),
4432 smibuf, (int)sizeof(struct MetaData));
4437 * AdjRefCount - submit an adjustment to the reference count for a message.
4438 * (These are just queued -- we actually process them later.)
4440 void AdjRefCount(long msgnum, int incr)
4442 struct arcq new_arcq;
4445 begin_critical_section(S_SUPPMSGMAIN);
4446 if (arcfp == NULL) {
4447 arcfp = fopen(file_arcq, "ab+");
4449 end_critical_section(S_SUPPMSGMAIN);
4451 /* msgnum < 0 means that we're trying to close the file */
4453 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4454 begin_critical_section(S_SUPPMSGMAIN);
4455 if (arcfp != NULL) {
4459 end_critical_section(S_SUPPMSGMAIN);
4464 * If we can't open the queue, perform the operation synchronously.
4466 if (arcfp == NULL) {
4467 TDAP_AdjRefCount(msgnum, incr);
4471 new_arcq.arcq_msgnum = msgnum;
4472 new_arcq.arcq_delta = incr;
4473 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4481 * TDAP_ProcessAdjRefCountQueue()
4483 * Process the queue of message count adjustments that was created by calls
4484 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4485 * for each one. This should be an "off hours" operation.
4487 int TDAP_ProcessAdjRefCountQueue(void)
4489 char file_arcq_temp[PATH_MAX];
4492 struct arcq arcq_rec;
4493 int num_records_processed = 0;
4495 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4497 begin_critical_section(S_SUPPMSGMAIN);
4498 if (arcfp != NULL) {
4503 r = link(file_arcq, file_arcq_temp);
4505 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4506 end_critical_section(S_SUPPMSGMAIN);
4507 return(num_records_processed);
4511 end_critical_section(S_SUPPMSGMAIN);
4513 fp = fopen(file_arcq_temp, "rb");
4515 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4516 return(num_records_processed);
4519 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4520 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4521 ++num_records_processed;
4525 r = unlink(file_arcq_temp);
4527 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4530 return(num_records_processed);
4536 * TDAP_AdjRefCount - adjust the reference count for a message.
4537 * This one does it "for real" because it's called by
4538 * the autopurger function that processes the queue
4539 * created by AdjRefCount(). If a message's reference
4540 * count becomes zero, we also delete the message from
4541 * disk and de-index it.
4543 void TDAP_AdjRefCount(long msgnum, int incr)
4546 struct MetaData smi;
4549 /* This is a *tight* critical section; please keep it that way, as
4550 * it may get called while nested in other critical sections.
4551 * Complicating this any further will surely cause deadlock!
4553 begin_critical_section(S_SUPPMSGMAIN);
4554 GetMetaData(&smi, msgnum);
4555 smi.meta_refcount += incr;
4557 end_critical_section(S_SUPPMSGMAIN);
4558 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4559 msgnum, incr, smi.meta_refcount);
4561 /* If the reference count is now zero, delete the message
4562 * (and its supplementary record as well).
4564 if (smi.meta_refcount == 0) {
4565 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4567 /* Call delete hooks with NULL room to show it has gone altogether */
4568 PerformDeleteHooks(NULL, msgnum);
4570 /* Remove from message base */
4572 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4573 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4575 /* Remove metadata record */
4576 delnum = (0L - msgnum);
4577 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4583 * Write a generic object to this room
4585 * Note: this could be much more efficient. Right now we use two temporary
4586 * files, and still pull the message into memory as with all others.
4588 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4589 char *content_type, /* MIME type of this object */
4590 char *raw_message, /* Data to be written */
4591 off_t raw_length, /* Size of raw_message */
4592 struct ctdluser *is_mailbox, /* Mailbox room? */
4593 int is_binary, /* Is encoding necessary? */
4594 int is_unique, /* Del others of this type? */
4595 unsigned int flags /* Internal save flags */
4599 struct ctdlroom qrbuf;
4600 char roomname[ROOMNAMELEN];
4601 struct CtdlMessage *msg;
4602 char *encoded_message = NULL;
4604 if (is_mailbox != NULL) {
4605 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4608 safestrncpy(roomname, req_room, sizeof(roomname));
4611 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4614 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4617 encoded_message = malloc((size_t)(raw_length + 4096));
4620 sprintf(encoded_message, "Content-type: %s\n", content_type);
4623 sprintf(&encoded_message[strlen(encoded_message)],
4624 "Content-transfer-encoding: base64\n\n"
4628 sprintf(&encoded_message[strlen(encoded_message)],
4629 "Content-transfer-encoding: 7bit\n\n"
4635 &encoded_message[strlen(encoded_message)],
4643 &encoded_message[strlen(encoded_message)],
4649 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4650 msg = malloc(sizeof(struct CtdlMessage));
4651 memset(msg, 0, sizeof(struct CtdlMessage));
4652 msg->cm_magic = CTDLMESSAGE_MAGIC;
4653 msg->cm_anon_type = MES_NORMAL;
4654 msg->cm_format_type = 4;
4655 msg->cm_fields['A'] = strdup(CC->user.fullname);
4656 msg->cm_fields['O'] = strdup(req_room);
4657 msg->cm_fields['N'] = strdup(config.c_nodename);
4658 msg->cm_fields['H'] = strdup(config.c_humannode);
4659 msg->cm_flags = flags;
4661 msg->cm_fields['M'] = encoded_message;
4663 /* Create the requested room if we have to. */
4664 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4665 CtdlCreateRoom(roomname,
4666 ( (is_mailbox != NULL) ? 5 : 3 ),
4667 "", 0, 1, 0, VIEW_BBS);
4669 /* If the caller specified this object as unique, delete all
4670 * other objects of this type that are currently in the room.
4673 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4674 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4677 /* Now write the data */
4678 CtdlSubmitMsg(msg, NULL, roomname, 0);
4679 CtdlFreeMessage(msg);
4687 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4688 config_msgnum = msgnum;
4692 char *CtdlGetSysConfig(char *sysconfname) {
4693 char hold_rm[ROOMNAMELEN];
4696 struct CtdlMessage *msg;
4699 strcpy(hold_rm, CC->room.QRname);
4700 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4701 CtdlGetRoom(&CC->room, hold_rm);
4706 /* We want the last (and probably only) config in this room */
4707 begin_critical_section(S_CONFIG);
4708 config_msgnum = (-1L);
4709 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4710 CtdlGetSysConfigBackend, NULL);
4711 msgnum = config_msgnum;
4712 end_critical_section(S_CONFIG);
4718 msg = CtdlFetchMessage(msgnum, 1);
4720 conf = strdup(msg->cm_fields['M']);
4721 CtdlFreeMessage(msg);
4728 CtdlGetRoom(&CC->room, hold_rm);
4730 if (conf != NULL) do {
4731 extract_token(buf, conf, 0, '\n', sizeof buf);
4732 strcpy(conf, &conf[strlen(buf)+1]);
4733 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4739 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4740 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4745 * Determine whether a given Internet address belongs to the current user
4747 int CtdlIsMe(char *addr, int addr_buf_len)
4749 struct recptypes *recp;
4752 recp = validate_recipients(addr, NULL, 0);
4753 if (recp == NULL) return(0);
4755 if (recp->num_local == 0) {
4756 free_recipients(recp);
4760 for (i=0; i<recp->num_local; ++i) {
4761 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4762 if (!strcasecmp(addr, CC->user.fullname)) {
4763 free_recipients(recp);
4768 free_recipients(recp);
4774 * Citadel protocol command to do the same
4776 void cmd_isme(char *argbuf) {
4779 if (CtdlAccessCheck(ac_logged_in)) return;
4780 extract_token(addr, argbuf, 0, '|', sizeof addr);
4782 if (CtdlIsMe(addr, sizeof addr)) {
4783 cprintf("%d %s\n", CIT_OK, addr);
4786 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4792 /*****************************************************************************/
4793 /* MODULE INITIALIZATION STUFF */
4794 /*****************************************************************************/
4796 CTDL_MODULE_INIT(msgbase)
4799 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4800 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4801 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4802 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4803 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4804 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4805 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4806 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4807 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4808 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4809 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4810 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4813 /* return our Subversion id for the Log */