4 * Implements the message store.
6 * Copyright (c) 1987-2009 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_EQ) && (thismsg == ref))
763 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
765 CallBack(lastold, userdata);
769 if (CallBack) CallBack(thismsg, userdata);
773 cdb_free(cdbfr); /* Clean up */
774 if (need_to_free_re) regfree(&re);
775 return num_processed;
781 * cmd_msgs() - get list of message #'s in this room
782 * implements the MSGS server command using CtdlForEachMessage()
784 void cmd_msgs(char *cmdbuf)
793 int with_template = 0;
794 struct CtdlMessage *template = NULL;
795 char search_string[1024];
796 ForEachMsgCallback CallBack;
798 extract_token(which, cmdbuf, 0, '|', sizeof which);
799 cm_ref = extract_int(cmdbuf, 1);
800 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
801 with_template = extract_int(cmdbuf, 2);
802 switch (extract_int(cmdbuf, 3))
806 CallBack = simple_listing;
809 CallBack = headers_listing;
812 CallBack = headers_euid;
817 if (!strncasecmp(which, "OLD", 3))
819 else if (!strncasecmp(which, "NEW", 3))
821 else if (!strncasecmp(which, "FIRST", 5))
823 else if (!strncasecmp(which, "LAST", 4))
825 else if (!strncasecmp(which, "GT", 2))
827 else if (!strncasecmp(which, "SEARCH", 6))
832 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
833 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
837 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
838 cprintf("%d Full text index is not enabled on this server.\n",
839 ERROR + CMD_NOT_SUPPORTED);
845 cprintf("%d Send template then receive message list\n",
847 template = (struct CtdlMessage *)
848 malloc(sizeof(struct CtdlMessage));
849 memset(template, 0, sizeof(struct CtdlMessage));
850 template->cm_magic = CTDLMESSAGE_MAGIC;
851 template->cm_anon_type = MES_NORMAL;
853 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
854 extract_token(tfield, buf, 0, '|', sizeof tfield);
855 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
856 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
857 if (!strcasecmp(tfield, msgkeys[i])) {
858 template->cm_fields[i] =
866 cprintf("%d \n", LISTING_FOLLOWS);
869 CtdlForEachMessage(mode,
870 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
871 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
876 if (template != NULL) CtdlFreeMessage(template);
884 * help_subst() - support routine for help file viewer
886 void help_subst(char *strbuf, char *source, char *dest)
891 while (p = pattern2(strbuf, source), (p >= 0)) {
892 strcpy(workbuf, &strbuf[p + strlen(source)]);
893 strcpy(&strbuf[p], dest);
894 strcat(strbuf, workbuf);
899 void do_help_subst(char *buffer)
903 help_subst(buffer, "^nodename", config.c_nodename);
904 help_subst(buffer, "^humannode", config.c_humannode);
905 help_subst(buffer, "^fqdn", config.c_fqdn);
906 help_subst(buffer, "^username", CC->user.fullname);
907 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
908 help_subst(buffer, "^usernum", buf2);
909 help_subst(buffer, "^sysadm", config.c_sysadm);
910 help_subst(buffer, "^variantname", CITADEL);
911 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
912 help_subst(buffer, "^maxsessions", buf2);
913 help_subst(buffer, "^bbsdir", ctdl_message_dir);
919 * memfmout() - Citadel text formatter and paginator.
920 * Although the original purpose of this routine was to format
921 * text to the reader's screen width, all we're really using it
922 * for here is to format text out to 80 columns before sending it
923 * to the client. The client software may reformat it again.
926 char *mptr, /* where are we going to get our text from? */
927 char subst, /* nonzero if we should do substitutions */
928 char *nl) /* string to terminate lines with */
936 static int width = 80;
941 c = 1; /* c is the current pos */
945 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
947 buffer[strlen(buffer) + 1] = 0;
948 buffer[strlen(buffer)] = ch;
951 if (buffer[0] == '^')
952 do_help_subst(buffer);
954 buffer[strlen(buffer) + 1] = 0;
956 strcpy(buffer, &buffer[1]);
964 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
967 if (((old == 13) || (old == 10)) && (isspace(real))) {
972 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
973 cprintf("%s%s", nl, aaa);
982 if ((strlen(aaa) + c) > (width - 5)) {
991 if ((ch == 13) || (ch == 10)) {
992 cprintf("%s%s", aaa, nl);
999 cprintf("%s%s", aaa, nl);
1005 * Callback function for mime parser that simply lists the part
1007 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1008 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1009 char *cbid, void *cbuserdata)
1013 ma = (struct ma_info *)cbuserdata;
1014 if (ma->is_ma == 0) {
1015 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
1016 name, filename, partnum, disp, cbtype, (long)length, cbid);
1021 * Callback function for multipart prefix
1023 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1024 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1025 char *cbid, void *cbuserdata)
1029 ma = (struct ma_info *)cbuserdata;
1030 if (!strcasecmp(cbtype, "multipart/alternative")) {
1034 if (ma->is_ma == 0) {
1035 cprintf("pref=%s|%s\n", partnum, cbtype);
1040 * Callback function for multipart sufffix
1042 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1043 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1044 char *cbid, void *cbuserdata)
1048 ma = (struct ma_info *)cbuserdata;
1049 if (ma->is_ma == 0) {
1050 cprintf("suff=%s|%s\n", partnum, cbtype);
1052 if (!strcasecmp(cbtype, "multipart/alternative")) {
1059 * Callback function for mime parser that opens a section for downloading
1061 void mime_download(char *name, char *filename, char *partnum, char *disp,
1062 void *content, char *cbtype, char *cbcharset, size_t length,
1063 char *encoding, char *cbid, void *cbuserdata)
1067 /* Silently go away if there's already a download open. */
1068 if (CC->download_fp != NULL)
1072 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1073 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1075 CC->download_fp = tmpfile();
1076 if (CC->download_fp == NULL)
1079 rv = fwrite(content, length, 1, CC->download_fp);
1080 fflush(CC->download_fp);
1081 rewind(CC->download_fp);
1083 OpenCmdResult(filename, cbtype);
1090 * Callback function for mime parser that outputs a section all at once.
1091 * We can specify the desired section by part number *or* content-id.
1093 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1094 void *content, char *cbtype, char *cbcharset, size_t length,
1095 char *encoding, char *cbid, void *cbuserdata)
1097 int *found_it = (int *)cbuserdata;
1100 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1101 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1104 cprintf("%d %d|-1|%s|%s\n",
1110 client_write(content, length);
1117 * Load a message from disk into memory.
1118 * This is used by CtdlOutputMsg() and other fetch functions.
1120 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1121 * using the CtdlMessageFree() function.
1123 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1125 struct cdbdata *dmsgtext;
1126 struct CtdlMessage *ret = NULL;
1130 cit_uint8_t field_header;
1132 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1134 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1135 if (dmsgtext == NULL) {
1138 mptr = dmsgtext->ptr;
1139 upper_bound = mptr + dmsgtext->len;
1141 /* Parse the three bytes that begin EVERY message on disk.
1142 * The first is always 0xFF, the on-disk magic number.
1143 * The second is the anonymous/public type byte.
1144 * The third is the format type byte (vari, fixed, or MIME).
1148 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1152 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1153 memset(ret, 0, sizeof(struct CtdlMessage));
1155 ret->cm_magic = CTDLMESSAGE_MAGIC;
1156 ret->cm_anon_type = *mptr++; /* Anon type byte */
1157 ret->cm_format_type = *mptr++; /* Format type byte */
1160 * The rest is zero or more arbitrary fields. Load them in.
1161 * We're done when we encounter either a zero-length field or
1162 * have just processed the 'M' (message text) field.
1165 if (mptr >= upper_bound) {
1168 field_header = *mptr++;
1169 ret->cm_fields[field_header] = strdup(mptr);
1171 while (*mptr++ != 0); /* advance to next field */
1173 } while ((mptr < upper_bound) && (field_header != 'M'));
1177 /* Always make sure there's something in the msg text field. If
1178 * it's NULL, the message text is most likely stored separately,
1179 * so go ahead and fetch that. Failing that, just set a dummy
1180 * body so other code doesn't barf.
1182 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1183 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1184 if (dmsgtext != NULL) {
1185 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1189 if (ret->cm_fields['M'] == NULL) {
1190 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1193 /* Perform "before read" hooks (aborting if any return nonzero) */
1194 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1195 CtdlFreeMessage(ret);
1204 * Returns 1 if the supplied pointer points to a valid Citadel message.
1205 * If the pointer is NULL or the magic number check fails, returns 0.
1207 int is_valid_message(struct CtdlMessage *msg) {
1210 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1211 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1219 * 'Destructor' for struct CtdlMessage
1221 void CtdlFreeMessage(struct CtdlMessage *msg)
1225 if (is_valid_message(msg) == 0)
1227 if (msg != NULL) free (msg);
1231 for (i = 0; i < 256; ++i)
1232 if (msg->cm_fields[i] != NULL) {
1233 free(msg->cm_fields[i]);
1236 msg->cm_magic = 0; /* just in case */
1242 * Pre callback function for multipart/alternative
1244 * NOTE: this differs from the standard behavior for a reason. Normally when
1245 * displaying multipart/alternative you want to show the _last_ usable
1246 * format in the message. Here we show the _first_ one, because it's
1247 * usually text/plain. Since this set of functions is designed for text
1248 * output to non-MIME-aware clients, this is the desired behavior.
1251 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1252 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1253 char *cbid, void *cbuserdata)
1257 ma = (struct ma_info *)cbuserdata;
1258 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1259 if (!strcasecmp(cbtype, "multipart/alternative")) {
1263 if (!strcasecmp(cbtype, "message/rfc822")) {
1269 * Post callback function for multipart/alternative
1271 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1272 void *content, char *cbtype, char *cbcharset, size_t length,
1273 char *encoding, char *cbid, void *cbuserdata)
1277 ma = (struct ma_info *)cbuserdata;
1278 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1279 if (!strcasecmp(cbtype, "multipart/alternative")) {
1283 if (!strcasecmp(cbtype, "message/rfc822")) {
1289 * Inline callback function for mime parser that wants to display text
1291 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1292 void *content, char *cbtype, char *cbcharset, size_t length,
1293 char *encoding, char *cbid, void *cbuserdata)
1300 ma = (struct ma_info *)cbuserdata;
1302 CtdlLogPrintf(CTDL_DEBUG,
1303 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1304 partnum, filename, cbtype, (long)length);
1307 * If we're in the middle of a multipart/alternative scope and
1308 * we've already printed another section, skip this one.
1310 if ( (ma->is_ma) && (ma->did_print) ) {
1311 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1316 if ( (!strcasecmp(cbtype, "text/plain"))
1317 || (IsEmptyStr(cbtype)) ) {
1320 client_write(wptr, length);
1321 if (wptr[length-1] != '\n') {
1328 if (!strcasecmp(cbtype, "text/html")) {
1329 ptr = html_to_ascii(content, length, 80, 0);
1331 client_write(ptr, wlen);
1332 if (ptr[wlen-1] != '\n') {
1339 if (ma->use_fo_hooks) {
1340 if (PerformFixedOutputHooks(cbtype, content, length)) {
1341 /* above function returns nonzero if it handled the part */
1346 if (strncasecmp(cbtype, "multipart/", 10)) {
1347 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1348 partnum, filename, cbtype, (long)length);
1354 * The client is elegant and sophisticated and wants to be choosy about
1355 * MIME content types, so figure out which multipart/alternative part
1356 * we're going to send.
1358 * We use a system of weights. When we find a part that matches one of the
1359 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1360 * and then set ma->chosen_pref to that MIME type's position in our preference
1361 * list. If we then hit another match, we only replace the first match if
1362 * the preference value is lower.
1364 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1365 void *content, char *cbtype, char *cbcharset, size_t length,
1366 char *encoding, char *cbid, void *cbuserdata)
1372 ma = (struct ma_info *)cbuserdata;
1374 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1375 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1376 // I don't know if there are any side effects! Please TEST TEST TEST
1377 //if (ma->is_ma > 0) {
1379 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1380 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1381 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1382 if (i < ma->chosen_pref) {
1383 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1384 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1385 ma->chosen_pref = i;
1392 * Now that we've chosen our preferred part, output it.
1394 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1395 void *content, char *cbtype, char *cbcharset, size_t length,
1396 char *encoding, char *cbid, void *cbuserdata)
1400 int add_newline = 0;
1404 ma = (struct ma_info *)cbuserdata;
1406 /* This is not the MIME part you're looking for... */
1407 if (strcasecmp(partnum, ma->chosen_part)) return;
1409 /* If the content-type of this part is in our preferred formats
1410 * list, we can simply output it verbatim.
1412 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1413 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1414 if (!strcasecmp(buf, cbtype)) {
1415 /* Yeah! Go! W00t!! */
1417 text_content = (char *)content;
1418 if (text_content[length-1] != '\n') {
1421 cprintf("Content-type: %s", cbtype);
1422 if (!IsEmptyStr(cbcharset)) {
1423 cprintf("; charset=%s", cbcharset);
1425 cprintf("\nContent-length: %d\n",
1426 (int)(length + add_newline) );
1427 if (!IsEmptyStr(encoding)) {
1428 cprintf("Content-transfer-encoding: %s\n", encoding);
1431 cprintf("Content-transfer-encoding: 7bit\n");
1433 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1435 client_write(content, length);
1436 if (add_newline) cprintf("\n");
1441 /* No translations required or possible: output as text/plain */
1442 cprintf("Content-type: text/plain\n\n");
1443 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1444 length, encoding, cbid, cbuserdata);
1449 char desired_section[64];
1456 * Callback function for
1458 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1459 void *content, char *cbtype, char *cbcharset, size_t length,
1460 char *encoding, char *cbid, void *cbuserdata)
1462 struct encapmsg *encap;
1464 encap = (struct encapmsg *)cbuserdata;
1466 /* Only proceed if this is the desired section... */
1467 if (!strcasecmp(encap->desired_section, partnum)) {
1468 encap->msglen = length;
1469 encap->msg = malloc(length + 2);
1470 memcpy(encap->msg, content, length);
1481 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1482 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1483 return(om_not_logged_in);
1490 * Get a message off disk. (returns om_* values found in msgbase.h)
1493 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1494 int mode, /* how would you like that message? */
1495 int headers_only, /* eschew the message body? */
1496 int do_proto, /* do Citadel protocol responses? */
1497 int crlf, /* Use CRLF newlines instead of LF? */
1498 char *section, /* NULL or a message/rfc822 section */
1499 int flags /* various flags; see msgbase.h */
1501 struct CtdlMessage *TheMessage = NULL;
1502 int retcode = om_no_such_msg;
1503 struct encapmsg encap;
1506 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1508 (section ? section : "<>")
1511 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1514 if (r == om_not_logged_in) {
1515 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1518 cprintf("%d An unknown error has occurred.\n", ERROR);
1524 /* FIXME: check message id against msglist for this room */
1527 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1528 * request that we don't even bother loading the body into memory.
1530 if (headers_only == HEADERS_FAST) {
1531 TheMessage = CtdlFetchMessage(msg_num, 0);
1534 TheMessage = CtdlFetchMessage(msg_num, 1);
1537 if (TheMessage == NULL) {
1538 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1539 ERROR + MESSAGE_NOT_FOUND, msg_num);
1540 return(om_no_such_msg);
1543 /* Here is the weird form of this command, to process only an
1544 * encapsulated message/rfc822 section.
1546 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1547 memset(&encap, 0, sizeof encap);
1548 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1549 mime_parser(TheMessage->cm_fields['M'],
1551 *extract_encapsulated_message,
1552 NULL, NULL, (void *)&encap, 0
1554 CtdlFreeMessage(TheMessage);
1558 encap.msg[encap.msglen] = 0;
1559 TheMessage = convert_internet_message(encap.msg);
1560 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1562 /* Now we let it fall through to the bottom of this
1563 * function, because TheMessage now contains the
1564 * encapsulated message instead of the top-level
1565 * message. Isn't that neat?
1570 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1571 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1572 retcode = om_no_such_msg;
1577 /* Ok, output the message now */
1578 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1579 CtdlFreeMessage(TheMessage);
1585 char *qp_encode_email_addrs(char *source)
1587 char user[256], node[256], name[256];
1588 const char headerStr[] = "=?UTF-8?Q?";
1592 int need_to_encode = 0;
1598 long nAddrPtrMax = 50;
1603 if (source == NULL) return source;
1604 if (IsEmptyStr(source)) return source;
1606 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1607 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1608 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1611 while (!IsEmptyStr (&source[i])) {
1612 if (nColons >= nAddrPtrMax){
1615 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1616 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1617 free (AddrPtr), AddrPtr = ptr;
1619 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1620 memset(&ptr[nAddrPtrMax], 0,
1621 sizeof (long) * nAddrPtrMax);
1623 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1624 free (AddrUtf8), AddrUtf8 = ptr;
1627 if (((unsigned char) source[i] < 32) ||
1628 ((unsigned char) source[i] > 126)) {
1630 AddrUtf8[nColons] = 1;
1632 if (source[i] == '"')
1633 InQuotes = !InQuotes;
1634 if (!InQuotes && source[i] == ',') {
1635 AddrPtr[nColons] = i;
1640 if (need_to_encode == 0) {
1647 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1648 Encoded = (char*) malloc (EncodedMaxLen);
1650 for (i = 0; i < nColons; i++)
1651 source[AddrPtr[i]++] = '\0';
1655 for (i = 0; i < nColons && nPtr != NULL; i++) {
1656 nmax = EncodedMaxLen - (nPtr - Encoded);
1658 process_rfc822_addr(&source[AddrPtr[i]],
1662 /* TODO: libIDN here ! */
1663 if (IsEmptyStr(name)) {
1664 n = snprintf(nPtr, nmax,
1665 (i==0)?"%s@%s" : ",%s@%s",
1669 EncodedName = rfc2047encode(name, strlen(name));
1670 n = snprintf(nPtr, nmax,
1671 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1672 EncodedName, user, node);
1677 n = snprintf(nPtr, nmax,
1678 (i==0)?"%s" : ",%s",
1679 &source[AddrPtr[i]]);
1685 ptr = (char*) malloc(EncodedMaxLen * 2);
1686 memcpy(ptr, Encoded, EncodedMaxLen);
1687 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1688 free(Encoded), Encoded = ptr;
1690 i--; /* do it once more with properly lengthened buffer */
1693 for (i = 0; i < nColons; i++)
1694 source[--AddrPtr[i]] = ',';
1701 /* If the last item in a list of recipients was truncated to a partial address,
1702 * remove it completely in order to avoid choking libSieve
1704 void sanitize_truncated_recipient(char *str)
1707 if (num_tokens(str, ',') < 2) return;
1709 int len = strlen(str);
1710 if (len < 900) return;
1711 if (len > 998) str[998] = 0;
1713 char *cptr = strrchr(str, ',');
1716 char *lptr = strchr(cptr, '<');
1717 char *rptr = strchr(cptr, '>');
1719 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1727 * Get a message off disk. (returns om_* values found in msgbase.h)
1729 int CtdlOutputPreLoadedMsg(
1730 struct CtdlMessage *TheMessage,
1731 int mode, /* how would you like that message? */
1732 int headers_only, /* eschew the message body? */
1733 int do_proto, /* do Citadel protocol responses? */
1734 int crlf, /* Use CRLF newlines instead of LF? */
1735 int flags /* should the bessage be exported clean? */
1739 cit_uint8_t ch, prev_ch;
1741 char display_name[256];
1743 char *nl; /* newline string */
1745 int subject_found = 0;
1748 /* Buffers needed for RFC822 translation. These are all filled
1749 * using functions that are bounds-checked, and therefore we can
1750 * make them substantially smaller than SIZ.
1757 char datestamp[100];
1759 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1760 ((TheMessage == NULL) ? "NULL" : "not null"),
1761 mode, headers_only, do_proto, crlf);
1763 strcpy(mid, "unknown");
1764 nl = (crlf ? "\r\n" : "\n");
1766 if (!is_valid_message(TheMessage)) {
1767 CtdlLogPrintf(CTDL_ERR,
1768 "ERROR: invalid preloaded message for output\n");
1770 return(om_no_such_msg);
1773 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1774 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1776 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
1777 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
1780 /* Are we downloading a MIME component? */
1781 if (mode == MT_DOWNLOAD) {
1782 if (TheMessage->cm_format_type != FMT_RFC822) {
1784 cprintf("%d This is not a MIME message.\n",
1785 ERROR + ILLEGAL_VALUE);
1786 } else if (CC->download_fp != NULL) {
1787 if (do_proto) cprintf(
1788 "%d You already have a download open.\n",
1789 ERROR + RESOURCE_BUSY);
1791 /* Parse the message text component */
1792 mptr = TheMessage->cm_fields['M'];
1793 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1794 /* If there's no file open by this time, the requested
1795 * section wasn't found, so print an error
1797 if (CC->download_fp == NULL) {
1798 if (do_proto) cprintf(
1799 "%d Section %s not found.\n",
1800 ERROR + FILE_NOT_FOUND,
1801 CC->download_desired_section);
1804 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1807 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1808 * in a single server operation instead of opening a download file.
1810 if (mode == MT_SPEW_SECTION) {
1811 if (TheMessage->cm_format_type != FMT_RFC822) {
1813 cprintf("%d This is not a MIME message.\n",
1814 ERROR + ILLEGAL_VALUE);
1816 /* Parse the message text component */
1819 mptr = TheMessage->cm_fields['M'];
1820 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1821 /* If section wasn't found, print an error
1824 if (do_proto) cprintf(
1825 "%d Section %s not found.\n",
1826 ERROR + FILE_NOT_FOUND,
1827 CC->download_desired_section);
1830 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1833 /* now for the user-mode message reading loops */
1834 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1836 /* Does the caller want to skip the headers? */
1837 if (headers_only == HEADERS_NONE) goto START_TEXT;
1839 /* Tell the client which format type we're using. */
1840 if ( (mode == MT_CITADEL) && (do_proto) ) {
1841 cprintf("type=%d\n", TheMessage->cm_format_type);
1844 /* nhdr=yes means that we're only displaying headers, no body */
1845 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1846 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1849 cprintf("nhdr=yes\n");
1852 /* begin header processing loop for Citadel message format */
1854 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1856 safestrncpy(display_name, "<unknown>", sizeof display_name);
1857 if (TheMessage->cm_fields['A']) {
1858 strcpy(buf, TheMessage->cm_fields['A']);
1859 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1860 safestrncpy(display_name, "****", sizeof display_name);
1862 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1863 safestrncpy(display_name, "anonymous", sizeof display_name);
1866 safestrncpy(display_name, buf, sizeof display_name);
1868 if ((is_room_aide())
1869 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1870 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1871 size_t tmp = strlen(display_name);
1872 snprintf(&display_name[tmp],
1873 sizeof display_name - tmp,
1878 /* Don't show Internet address for users on the
1879 * local Citadel network.
1882 if (TheMessage->cm_fields['N'] != NULL)
1883 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1884 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1888 /* Now spew the header fields in the order we like them. */
1889 safestrncpy(allkeys, FORDER, sizeof allkeys);
1890 for (i=0; i<strlen(allkeys); ++i) {
1891 k = (int) allkeys[i];
1893 if ( (TheMessage->cm_fields[k] != NULL)
1894 && (msgkeys[k] != NULL) ) {
1895 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1896 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1899 if (do_proto) cprintf("%s=%s\n",
1903 else if ((k == 'F') && (suppress_f)) {
1906 /* Masquerade display name if needed */
1908 if (do_proto) cprintf("%s=%s\n",
1910 TheMessage->cm_fields[k]
1919 /* begin header processing loop for RFC822 transfer format */
1924 strcpy(snode, NODENAME);
1925 if (mode == MT_RFC822) {
1926 for (i = 0; i < 256; ++i) {
1927 if (TheMessage->cm_fields[i]) {
1928 mptr = mpptr = TheMessage->cm_fields[i];
1931 safestrncpy(luser, mptr, sizeof luser);
1932 safestrncpy(suser, mptr, sizeof suser);
1934 else if (i == 'Y') {
1935 if ((flags & QP_EADDR) != 0) {
1936 mptr = qp_encode_email_addrs(mptr);
1938 sanitize_truncated_recipient(mptr);
1939 cprintf("CC: %s%s", mptr, nl);
1941 else if (i == 'P') {
1942 cprintf("Return-Path: %s%s", mptr, nl);
1944 else if (i == 'L') {
1945 cprintf("List-ID: %s%s", mptr, nl);
1947 else if (i == 'V') {
1948 if ((flags & QP_EADDR) != 0)
1949 mptr = qp_encode_email_addrs(mptr);
1950 cprintf("Envelope-To: %s%s", mptr, nl);
1952 else if (i == 'U') {
1953 cprintf("Subject: %s%s", mptr, nl);
1957 safestrncpy(mid, mptr, sizeof mid);
1959 safestrncpy(fuser, mptr, sizeof fuser);
1960 /* else if (i == 'O')
1961 cprintf("X-Citadel-Room: %s%s",
1964 safestrncpy(snode, mptr, sizeof snode);
1967 if (haschar(mptr, '@') == 0)
1969 sanitize_truncated_recipient(mptr);
1970 cprintf("To: %s@%s", mptr, config.c_fqdn);
1975 if ((flags & QP_EADDR) != 0) {
1976 mptr = qp_encode_email_addrs(mptr);
1978 sanitize_truncated_recipient(mptr);
1979 cprintf("To: %s", mptr);
1983 else if (i == 'T') {
1984 datestring(datestamp, sizeof datestamp,
1985 atol(mptr), DATESTRING_RFC822);
1986 cprintf("Date: %s%s", datestamp, nl);
1988 else if (i == 'W') {
1989 cprintf("References: ");
1990 k = num_tokens(mptr, '|');
1991 for (j=0; j<k; ++j) {
1992 extract_token(buf, mptr, j, '|', sizeof buf);
1993 cprintf("<%s>", buf);
2006 if (subject_found == 0) {
2007 cprintf("Subject: (no subject)%s", nl);
2011 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2012 suser[i] = tolower(suser[i]);
2013 if (!isalnum(suser[i])) suser[i]='_';
2016 if (mode == MT_RFC822) {
2017 if (!strcasecmp(snode, NODENAME)) {
2018 safestrncpy(snode, FQDN, sizeof snode);
2021 /* Construct a fun message id */
2022 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2023 if (strchr(mid, '@')==NULL) {
2024 cprintf("@%s", snode);
2028 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2029 cprintf("From: \"----\" <x@x.org>%s", nl);
2031 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2032 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2034 else if (!IsEmptyStr(fuser)) {
2035 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2038 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2041 /* Blank line signifying RFC822 end-of-headers */
2042 if (TheMessage->cm_format_type != FMT_RFC822) {
2047 /* end header processing loop ... at this point, we're in the text */
2049 if (headers_only == HEADERS_FAST) goto DONE;
2050 mptr = TheMessage->cm_fields['M'];
2052 /* Tell the client about the MIME parts in this message */
2053 if (TheMessage->cm_format_type == FMT_RFC822) {
2054 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2055 memset(&ma, 0, sizeof(struct ma_info));
2056 mime_parser(mptr, NULL,
2057 (do_proto ? *list_this_part : NULL),
2058 (do_proto ? *list_this_pref : NULL),
2059 (do_proto ? *list_this_suff : NULL),
2062 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2063 char *start_of_text = NULL;
2064 start_of_text = strstr(mptr, "\n\r\n");
2065 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
2066 if (start_of_text == NULL) start_of_text = mptr;
2068 start_of_text = strstr(start_of_text, "\n");
2073 int nllen = strlen(nl);
2075 while (ch=*mptr, ch!=0) {
2081 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2082 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2083 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2086 sprintf(&outbuf[outlen], "%s", nl);
2090 outbuf[outlen++] = ch;
2094 if (flags & ESC_DOT)
2096 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2098 outbuf[outlen++] = '.';
2103 if (outlen > 1000) {
2104 client_write(outbuf, outlen);
2109 client_write(outbuf, outlen);
2117 if (headers_only == HEADERS_ONLY) {
2121 /* signify start of msg text */
2122 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2123 if (do_proto) cprintf("text\n");
2126 /* If the format type on disk is 1 (fixed-format), then we want
2127 * everything to be output completely literally ... regardless of
2128 * what message transfer format is in use.
2130 if (TheMessage->cm_format_type == FMT_FIXED) {
2132 if (mode == MT_MIME) {
2133 cprintf("Content-type: text/plain\n\n");
2137 while (ch = *mptr++, ch > 0) {
2140 if ((ch == 10) || (buflen > 250)) {
2142 cprintf("%s%s", buf, nl);
2151 if (!IsEmptyStr(buf))
2152 cprintf("%s%s", buf, nl);
2155 /* If the message on disk is format 0 (Citadel vari-format), we
2156 * output using the formatter at 80 columns. This is the final output
2157 * form if the transfer format is RFC822, but if the transfer format
2158 * is Citadel proprietary, it'll still work, because the indentation
2159 * for new paragraphs is correct and the client will reformat the
2160 * message to the reader's screen width.
2162 if (TheMessage->cm_format_type == FMT_CITADEL) {
2163 if (mode == MT_MIME) {
2164 cprintf("Content-type: text/x-citadel-variformat\n\n");
2166 memfmout(mptr, 0, nl);
2169 /* If the message on disk is format 4 (MIME), we've gotta hand it
2170 * off to the MIME parser. The client has already been told that
2171 * this message is format 1 (fixed format), so the callback function
2172 * we use will display those parts as-is.
2174 if (TheMessage->cm_format_type == FMT_RFC822) {
2175 memset(&ma, 0, sizeof(struct ma_info));
2177 if (mode == MT_MIME) {
2178 ma.use_fo_hooks = 0;
2179 strcpy(ma.chosen_part, "1");
2180 ma.chosen_pref = 9999;
2181 mime_parser(mptr, NULL,
2182 *choose_preferred, *fixed_output_pre,
2183 *fixed_output_post, (void *)&ma, 0);
2184 mime_parser(mptr, NULL,
2185 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2188 ma.use_fo_hooks = 1;
2189 mime_parser(mptr, NULL,
2190 *fixed_output, *fixed_output_pre,
2191 *fixed_output_post, (void *)&ma, 0);
2196 DONE: /* now we're done */
2197 if (do_proto) cprintf("000\n");
2204 * display a message (mode 0 - Citadel proprietary)
2206 void cmd_msg0(char *cmdbuf)
2209 int headers_only = HEADERS_ALL;
2211 msgid = extract_long(cmdbuf, 0);
2212 headers_only = extract_int(cmdbuf, 1);
2214 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2220 * display a message (mode 2 - RFC822)
2222 void cmd_msg2(char *cmdbuf)
2225 int headers_only = HEADERS_ALL;
2227 msgid = extract_long(cmdbuf, 0);
2228 headers_only = extract_int(cmdbuf, 1);
2230 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2236 * display a message (mode 3 - IGnet raw format - internal programs only)
2238 void cmd_msg3(char *cmdbuf)
2241 struct CtdlMessage *msg = NULL;
2244 if (CC->internal_pgm == 0) {
2245 cprintf("%d This command is for internal programs only.\n",
2246 ERROR + HIGHER_ACCESS_REQUIRED);
2250 msgnum = extract_long(cmdbuf, 0);
2251 msg = CtdlFetchMessage(msgnum, 1);
2253 cprintf("%d Message %ld not found.\n",
2254 ERROR + MESSAGE_NOT_FOUND, msgnum);
2258 serialize_message(&smr, msg);
2259 CtdlFreeMessage(msg);
2262 cprintf("%d Unable to serialize message\n",
2263 ERROR + INTERNAL_ERROR);
2267 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2268 client_write((char *)smr.ser, (int)smr.len);
2275 * Display a message using MIME content types
2277 void cmd_msg4(char *cmdbuf)
2282 msgid = extract_long(cmdbuf, 0);
2283 extract_token(section, cmdbuf, 1, '|', sizeof section);
2284 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2290 * Client tells us its preferred message format(s)
2292 void cmd_msgp(char *cmdbuf)
2294 if (!strcasecmp(cmdbuf, "dont_decode")) {
2295 CC->msg4_dont_decode = 1;
2296 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2299 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2300 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2306 * Open a component of a MIME message as a download file
2308 void cmd_opna(char *cmdbuf)
2311 char desired_section[128];
2313 msgid = extract_long(cmdbuf, 0);
2314 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2315 safestrncpy(CC->download_desired_section, desired_section,
2316 sizeof CC->download_desired_section);
2317 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2322 * Open a component of a MIME message and transmit it all at once
2324 void cmd_dlat(char *cmdbuf)
2327 char desired_section[128];
2329 msgid = extract_long(cmdbuf, 0);
2330 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2331 safestrncpy(CC->download_desired_section, desired_section,
2332 sizeof CC->download_desired_section);
2333 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2338 * Save one or more message pointers into a specified room
2339 * (Returns 0 for success, nonzero for failure)
2340 * roomname may be NULL to use the current room
2342 * Note that the 'supplied_msg' field may be set to NULL, in which case
2343 * the message will be fetched from disk, by number, if we need to perform
2344 * replication checks. This adds an additional database read, so if the
2345 * caller already has the message in memory then it should be supplied. (Obviously
2346 * this mode of operation only works if we're saving a single message.)
2348 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2349 int do_repl_check, struct CtdlMessage *supplied_msg)
2352 char hold_rm[ROOMNAMELEN];
2353 struct cdbdata *cdbfr;
2356 long highest_msg = 0L;
2359 struct CtdlMessage *msg = NULL;
2361 long *msgs_to_be_merged = NULL;
2362 int num_msgs_to_be_merged = 0;
2364 CtdlLogPrintf(CTDL_DEBUG,
2365 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2366 roomname, num_newmsgs, do_repl_check);
2368 strcpy(hold_rm, CC->room.QRname);
2371 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2372 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2373 if (num_newmsgs > 1) supplied_msg = NULL;
2375 /* Now the regular stuff */
2376 if (CtdlGetRoomLock(&CC->room,
2377 ((roomname != NULL) ? roomname : CC->room.QRname) )
2379 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2380 return(ERROR + ROOM_NOT_FOUND);
2384 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2385 num_msgs_to_be_merged = 0;
2388 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2389 if (cdbfr == NULL) {
2393 msglist = (long *) cdbfr->ptr;
2394 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2395 num_msgs = cdbfr->len / sizeof(long);
2400 /* Create a list of msgid's which were supplied by the caller, but do
2401 * not already exist in the target room. It is absolutely taboo to
2402 * have more than one reference to the same message in a room.
2404 for (i=0; i<num_newmsgs; ++i) {
2406 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2407 if (msglist[j] == newmsgidlist[i]) {
2412 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2416 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2419 * Now merge the new messages
2421 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2422 if (msglist == NULL) {
2423 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2425 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2426 num_msgs += num_msgs_to_be_merged;
2428 /* Sort the message list, so all the msgid's are in order */
2429 num_msgs = sort_msglist(msglist, num_msgs);
2431 /* Determine the highest message number */
2432 highest_msg = msglist[num_msgs - 1];
2434 /* Write it back to disk. */
2435 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2436 msglist, (int)(num_msgs * sizeof(long)));
2438 /* Free up the memory we used. */
2441 /* Update the highest-message pointer and unlock the room. */
2442 CC->room.QRhighest = highest_msg;
2443 CtdlPutRoomLock(&CC->room);
2445 /* Perform replication checks if necessary */
2446 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2447 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2449 for (i=0; i<num_msgs_to_be_merged; ++i) {
2450 msgid = msgs_to_be_merged[i];
2452 if (supplied_msg != NULL) {
2456 msg = CtdlFetchMessage(msgid, 0);
2460 ReplicationChecks(msg);
2462 /* If the message has an Exclusive ID, index that... */
2463 if (msg->cm_fields['E'] != NULL) {
2464 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2467 /* Free up the memory we may have allocated */
2468 if (msg != supplied_msg) {
2469 CtdlFreeMessage(msg);
2477 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2480 /* Submit this room for processing by hooks */
2481 PerformRoomHooks(&CC->room);
2483 /* Go back to the room we were in before we wandered here... */
2484 CtdlGetRoom(&CC->room, hold_rm);
2486 /* Bump the reference count for all messages which were merged */
2487 for (i=0; i<num_msgs_to_be_merged; ++i) {
2488 AdjRefCount(msgs_to_be_merged[i], +1);
2491 /* Free up memory... */
2492 if (msgs_to_be_merged != NULL) {
2493 free(msgs_to_be_merged);
2496 /* Return success. */
2502 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2505 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2506 int do_repl_check, struct CtdlMessage *supplied_msg)
2508 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2515 * Message base operation to save a new message to the message store
2516 * (returns new message number)
2518 * This is the back end for CtdlSubmitMsg() and should not be directly
2519 * called by server-side modules.
2522 long send_message(struct CtdlMessage *msg) {
2530 /* Get a new message number */
2531 newmsgid = get_new_message_number();
2532 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2534 /* Generate an ID if we don't have one already */
2535 if (msg->cm_fields['I']==NULL) {
2536 msg->cm_fields['I'] = strdup(msgidbuf);
2539 /* If the message is big, set its body aside for storage elsewhere */
2540 if (msg->cm_fields['M'] != NULL) {
2541 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2543 holdM = msg->cm_fields['M'];
2544 msg->cm_fields['M'] = NULL;
2548 /* Serialize our data structure for storage in the database */
2549 serialize_message(&smr, msg);
2552 msg->cm_fields['M'] = holdM;
2556 cprintf("%d Unable to serialize message\n",
2557 ERROR + INTERNAL_ERROR);
2561 /* Write our little bundle of joy into the message base */
2562 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2563 smr.ser, smr.len) < 0) {
2564 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2568 cdb_store(CDB_BIGMSGS,
2578 /* Free the memory we used for the serialized message */
2581 /* Return the *local* message ID to the caller
2582 * (even if we're storing an incoming network message)
2590 * Serialize a struct CtdlMessage into the format used on disk and network.
2592 * This function loads up a "struct ser_ret" (defined in server.h) which
2593 * contains the length of the serialized message and a pointer to the
2594 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2596 void serialize_message(struct ser_ret *ret, /* return values */
2597 struct CtdlMessage *msg) /* unserialized msg */
2599 size_t wlen, fieldlen;
2601 static char *forder = FORDER;
2604 * Check for valid message format
2606 if (is_valid_message(msg) == 0) {
2607 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2614 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2615 ret->len = ret->len +
2616 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2618 ret->ser = malloc(ret->len);
2619 if (ret->ser == NULL) {
2620 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2621 (long)ret->len, strerror(errno));
2628 ret->ser[1] = msg->cm_anon_type;
2629 ret->ser[2] = msg->cm_format_type;
2632 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2633 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2634 ret->ser[wlen++] = (char)forder[i];
2635 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2636 wlen = wlen + fieldlen + 1;
2638 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2639 (long)ret->len, (long)wlen);
2646 * Serialize a struct CtdlMessage into the format used on disk and network.
2648 * This function loads up a "struct ser_ret" (defined in server.h) which
2649 * contains the length of the serialized message and a pointer to the
2650 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2652 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2653 long Siz) /* how many chars ? */
2657 static char *forder = FORDER;
2661 * Check for valid message format
2663 if (is_valid_message(msg) == 0) {
2664 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2668 buf = (char*) malloc (Siz + 1);
2672 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2673 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2674 msg->cm_fields[(int)forder[i]]);
2675 client_write (buf, strlen(buf));
2684 * Check to see if any messages already exist in the current room which
2685 * carry the same Exclusive ID as this one. If any are found, delete them.
2687 void ReplicationChecks(struct CtdlMessage *msg) {
2688 long old_msgnum = (-1L);
2690 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2692 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2695 /* No exclusive id? Don't do anything. */
2696 if (msg == NULL) return;
2697 if (msg->cm_fields['E'] == NULL) return;
2698 if (IsEmptyStr(msg->cm_fields['E'])) return;
2699 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2700 msg->cm_fields['E'], CC->room.QRname);*/
2702 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2703 if (old_msgnum > 0L) {
2704 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2705 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2712 * Save a message to disk and submit it into the delivery system.
2714 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2715 struct recptypes *recps, /* recipients (if mail) */
2716 char *force, /* force a particular room? */
2717 int flags /* should the message be exported clean? */
2719 char submit_filename[128];
2720 char generated_timestamp[32];
2721 char hold_rm[ROOMNAMELEN];
2722 char actual_rm[ROOMNAMELEN];
2723 char force_room[ROOMNAMELEN];
2724 char content_type[SIZ]; /* We have to learn this */
2725 char recipient[SIZ];
2728 struct ctdluser userbuf;
2730 struct MetaData smi;
2731 FILE *network_fp = NULL;
2732 static int seqnum = 1;
2733 struct CtdlMessage *imsg = NULL;
2735 size_t instr_alloc = 0;
2737 char *hold_R, *hold_D;
2738 char *collected_addresses = NULL;
2739 struct addresses_to_be_filed *aptr = NULL;
2740 char *saved_rfc822_version = NULL;
2741 int qualified_for_journaling = 0;
2742 CitContext *CCC = CC; /* CachedCitContext - performance boost */
2743 char bounce_to[1024] = "";
2747 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2748 if (is_valid_message(msg) == 0) return(-1); /* self check */
2750 /* If this message has no timestamp, we take the liberty of
2751 * giving it one, right now.
2753 if (msg->cm_fields['T'] == NULL) {
2754 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2755 msg->cm_fields['T'] = strdup(generated_timestamp);
2758 /* If this message has no path, we generate one.
2760 if (msg->cm_fields['P'] == NULL) {
2761 if (msg->cm_fields['A'] != NULL) {
2762 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2763 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2764 if (isspace(msg->cm_fields['P'][a])) {
2765 msg->cm_fields['P'][a] = ' ';
2770 msg->cm_fields['P'] = strdup("unknown");
2774 if (force == NULL) {
2775 strcpy(force_room, "");
2778 strcpy(force_room, force);
2781 /* Learn about what's inside, because it's what's inside that counts */
2782 if (msg->cm_fields['M'] == NULL) {
2783 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2787 switch (msg->cm_format_type) {
2789 strcpy(content_type, "text/x-citadel-variformat");
2792 strcpy(content_type, "text/plain");
2795 strcpy(content_type, "text/plain");
2796 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2799 safestrncpy(content_type, &mptr[13], sizeof content_type);
2800 striplt(content_type);
2801 aptr = content_type;
2802 while (!IsEmptyStr(aptr)) {
2814 /* Goto the correct room */
2815 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2816 strcpy(hold_rm, CCC->room.QRname);
2817 strcpy(actual_rm, CCC->room.QRname);
2818 if (recps != NULL) {
2819 strcpy(actual_rm, SENTITEMS);
2822 /* If the user is a twit, move to the twit room for posting */
2824 if (CCC->user.axlevel == 2) {
2825 strcpy(hold_rm, actual_rm);
2826 strcpy(actual_rm, config.c_twitroom);
2827 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2831 /* ...or if this message is destined for Aide> then go there. */
2832 if (!IsEmptyStr(force_room)) {
2833 strcpy(actual_rm, force_room);
2836 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2837 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2838 /* CtdlGetRoom(&CCC->room, actual_rm); */
2839 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
2843 * If this message has no O (room) field, generate one.
2845 if (msg->cm_fields['O'] == NULL) {
2846 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2849 /* Perform "before save" hooks (aborting if any return nonzero) */
2850 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2851 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2854 * If this message has an Exclusive ID, and the room is replication
2855 * checking enabled, then do replication checks.
2857 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2858 ReplicationChecks(msg);
2861 /* Save it to disk */
2862 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2863 newmsgid = send_message(msg);
2864 if (newmsgid <= 0L) return(-5);
2866 /* Write a supplemental message info record. This doesn't have to
2867 * be a critical section because nobody else knows about this message
2870 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2871 memset(&smi, 0, sizeof(struct MetaData));
2872 smi.meta_msgnum = newmsgid;
2873 smi.meta_refcount = 0;
2874 safestrncpy(smi.meta_content_type, content_type,
2875 sizeof smi.meta_content_type);
2878 * Measure how big this message will be when rendered as RFC822.
2879 * We do this for two reasons:
2880 * 1. We need the RFC822 length for the new metadata record, so the
2881 * POP and IMAP services don't have to calculate message lengths
2882 * while the user is waiting (multiplied by potentially hundreds
2883 * or thousands of messages).
2884 * 2. If journaling is enabled, we will need an RFC822 version of the
2885 * message to attach to the journalized copy.
2887 if (CCC->redirect_buffer != NULL) {
2888 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2891 CCC->redirect_buffer = malloc(SIZ);
2892 CCC->redirect_len = 0;
2893 CCC->redirect_alloc = SIZ;
2894 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2895 smi.meta_rfc822_length = CCC->redirect_len;
2896 saved_rfc822_version = CCC->redirect_buffer;
2897 CCC->redirect_buffer = NULL;
2898 CCC->redirect_len = 0;
2899 CCC->redirect_alloc = 0;
2903 /* Now figure out where to store the pointers */
2904 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2906 /* If this is being done by the networker delivering a private
2907 * message, we want to BYPASS saving the sender's copy (because there
2908 * is no local sender; it would otherwise go to the Trashcan).
2910 if ((!CCC->internal_pgm) || (recps == NULL)) {
2911 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2912 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2913 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2917 /* For internet mail, drop a copy in the outbound queue room */
2918 if ((recps != NULL) && (recps->num_internet > 0)) {
2919 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2922 /* If other rooms are specified, drop them there too. */
2923 if ((recps != NULL) && (recps->num_room > 0))
2924 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2925 extract_token(recipient, recps->recp_room, i,
2926 '|', sizeof recipient);
2927 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2928 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2931 /* Bump this user's messages posted counter. */
2932 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2933 CtdlGetUserLock(&CCC->user, CCC->curr_user);
2934 CCC->user.posted = CCC->user.posted + 1;
2935 CtdlPutUserLock(&CCC->user);
2937 /* Decide where bounces need to be delivered */
2938 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2939 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2941 else if (CCC->logged_in) {
2942 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2945 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2948 /* If this is private, local mail, make a copy in the
2949 * recipient's mailbox and bump the reference count.
2951 if ((recps != NULL) && (recps->num_local > 0))
2952 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2953 extract_token(recipient, recps->recp_local, i,
2954 '|', sizeof recipient);
2955 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2957 if (CtdlGetUser(&userbuf, recipient) == 0) {
2958 // Add a flag so the Funambol module knows its mail
2959 msg->cm_fields['W'] = strdup(recipient);
2960 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2961 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2962 CtdlBumpNewMailCounter(userbuf.usernum);
2963 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2964 /* Generate a instruction message for the Funambol notification
2965 * server, in the same style as the SMTP queue
2968 instr = malloc(instr_alloc);
2969 snprintf(instr, instr_alloc,
2970 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2972 SPOOLMIME, newmsgid, (long)time(NULL),
2976 imsg = malloc(sizeof(struct CtdlMessage));
2977 memset(imsg, 0, sizeof(struct CtdlMessage));
2978 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2979 imsg->cm_anon_type = MES_NORMAL;
2980 imsg->cm_format_type = FMT_RFC822;
2981 imsg->cm_fields['A'] = strdup("Citadel");
2982 imsg->cm_fields['J'] = strdup("do not journal");
2983 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2984 imsg->cm_fields['W'] = strdup(recipient);
2985 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2986 CtdlFreeMessage(imsg);
2990 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2991 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2996 /* Perform "after save" hooks */
2997 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2998 PerformMessageHooks(msg, EVT_AFTERSAVE);
3000 /* For IGnet mail, we have to save a new copy into the spooler for
3001 * each recipient, with the R and D fields set to the recipient and
3002 * destination-node. This has two ugly side effects: all other
3003 * recipients end up being unlisted in this recipient's copy of the
3004 * message, and it has to deliver multiple messages to the same
3005 * node. We'll revisit this again in a year or so when everyone has
3006 * a network spool receiver that can handle the new style messages.
3008 if ((recps != NULL) && (recps->num_ignet > 0))
3009 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3010 extract_token(recipient, recps->recp_ignet, i,
3011 '|', sizeof recipient);
3013 hold_R = msg->cm_fields['R'];
3014 hold_D = msg->cm_fields['D'];
3015 msg->cm_fields['R'] = malloc(SIZ);
3016 msg->cm_fields['D'] = malloc(128);
3017 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3018 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3020 serialize_message(&smr, msg);
3022 snprintf(submit_filename, sizeof submit_filename,
3023 "%s/netmail.%04lx.%04x.%04x",
3025 (long) getpid(), CCC->cs_pid, ++seqnum);
3026 network_fp = fopen(submit_filename, "wb+");
3027 if (network_fp != NULL) {
3028 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3034 free(msg->cm_fields['R']);
3035 free(msg->cm_fields['D']);
3036 msg->cm_fields['R'] = hold_R;
3037 msg->cm_fields['D'] = hold_D;
3040 /* Go back to the room we started from */
3041 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3042 if (strcasecmp(hold_rm, CCC->room.QRname))
3043 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3045 /* For internet mail, generate delivery instructions.
3046 * Yes, this is recursive. Deal with it. Infinite recursion does
3047 * not happen because the delivery instructions message does not
3048 * contain a recipient.
3050 if ((recps != NULL) && (recps->num_internet > 0)) {
3051 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3053 instr = malloc(instr_alloc);
3054 snprintf(instr, instr_alloc,
3055 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3057 SPOOLMIME, newmsgid, (long)time(NULL),
3061 if (recps->envelope_from != NULL) {
3062 tmp = strlen(instr);
3063 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3066 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3067 tmp = strlen(instr);
3068 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3069 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3070 instr_alloc = instr_alloc * 2;
3071 instr = realloc(instr, instr_alloc);
3073 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3076 imsg = malloc(sizeof(struct CtdlMessage));
3077 memset(imsg, 0, sizeof(struct CtdlMessage));
3078 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3079 imsg->cm_anon_type = MES_NORMAL;
3080 imsg->cm_format_type = FMT_RFC822;
3081 imsg->cm_fields['A'] = strdup("Citadel");
3082 imsg->cm_fields['J'] = strdup("do not journal");
3083 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3084 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3085 CtdlFreeMessage(imsg);
3089 * Any addresses to harvest for someone's address book?
3091 if ( (CCC->logged_in) && (recps != NULL) ) {
3092 collected_addresses = harvest_collected_addresses(msg);
3095 if (collected_addresses != NULL) {
3096 aptr = (struct addresses_to_be_filed *)
3097 malloc(sizeof(struct addresses_to_be_filed));
3098 CtdlMailboxName(actual_rm, sizeof actual_rm,
3099 &CCC->user, USERCONTACTSROOM);
3100 aptr->roomname = strdup(actual_rm);
3101 aptr->collected_addresses = collected_addresses;
3102 begin_critical_section(S_ATBF);
3105 end_critical_section(S_ATBF);
3109 * Determine whether this message qualifies for journaling.
3111 if (msg->cm_fields['J'] != NULL) {
3112 qualified_for_journaling = 0;
3115 if (recps == NULL) {
3116 qualified_for_journaling = config.c_journal_pubmsgs;
3118 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3119 qualified_for_journaling = config.c_journal_email;
3122 qualified_for_journaling = config.c_journal_pubmsgs;
3127 * Do we have to perform journaling? If so, hand off the saved
3128 * RFC822 version will be handed off to the journaler for background
3129 * submit. Otherwise, we have to free the memory ourselves.
3131 if (saved_rfc822_version != NULL) {
3132 if (qualified_for_journaling) {
3133 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3136 free(saved_rfc822_version);
3146 void aide_message (char *text, char *subject)
3148 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3153 * Convenience function for generating small administrative messages.
3155 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3156 int format_type, const char *subject)
3158 struct CtdlMessage *msg;
3159 struct recptypes *recp = NULL;
3161 msg = malloc(sizeof(struct CtdlMessage));
3162 memset(msg, 0, sizeof(struct CtdlMessage));
3163 msg->cm_magic = CTDLMESSAGE_MAGIC;
3164 msg->cm_anon_type = MES_NORMAL;
3165 msg->cm_format_type = format_type;
3168 msg->cm_fields['A'] = strdup(from);
3170 else if (fromaddr != NULL) {
3171 msg->cm_fields['A'] = strdup(fromaddr);
3172 if (strchr(msg->cm_fields['A'], '@')) {
3173 *strchr(msg->cm_fields['A'], '@') = 0;
3177 msg->cm_fields['A'] = strdup("Citadel");
3180 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3181 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3182 msg->cm_fields['N'] = strdup(NODENAME);
3184 msg->cm_fields['R'] = strdup(to);
3185 recp = validate_recipients(to, NULL, 0);
3187 if (subject != NULL) {
3188 msg->cm_fields['U'] = strdup(subject);
3190 msg->cm_fields['M'] = strdup(text);
3192 CtdlSubmitMsg(msg, recp, room, 0);
3193 CtdlFreeMessage(msg);
3194 if (recp != NULL) free_recipients(recp);
3200 * Back end function used by CtdlMakeMessage() and similar functions
3202 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3203 size_t maxlen, /* maximum message length */
3204 char *exist, /* if non-null, append to it;
3205 exist is ALWAYS freed */
3206 int crlf, /* CRLF newlines instead of LF */
3207 int sock /* socket handle or 0 for this session's client socket */
3211 size_t message_len = 0;
3212 size_t buffer_len = 0;
3219 if (exist == NULL) {
3226 message_len = strlen(exist);
3227 buffer_len = message_len + 4096;
3228 m = realloc(exist, buffer_len);
3235 /* Do we need to change leading ".." to "." for SMTP escaping? */
3236 if (!strcmp(terminator, ".")) {
3240 /* flush the input if we have nowhere to store it */
3245 /* read in the lines of message text one by one */
3248 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3251 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3253 if (!strcmp(buf, terminator)) finished = 1;
3255 strcat(buf, "\r\n");
3261 /* Unescape SMTP-style input of two dots at the beginning of the line */
3263 if (!strncmp(buf, "..", 2)) {
3264 strcpy(buf, &buf[1]);
3268 if ( (!flushing) && (!finished) ) {
3269 /* Measure the line */
3270 linelen = strlen(buf);
3272 /* augment the buffer if we have to */
3273 if ((message_len + linelen) >= buffer_len) {
3274 ptr = realloc(m, (buffer_len * 2) );
3275 if (ptr == NULL) { /* flush if can't allocate */
3278 buffer_len = (buffer_len * 2);
3280 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3284 /* Add the new line to the buffer. NOTE: this loop must avoid
3285 * using functions like strcat() and strlen() because they
3286 * traverse the entire buffer upon every call, and doing that
3287 * for a multi-megabyte message slows it down beyond usability.
3289 strcpy(&m[message_len], buf);
3290 message_len += linelen;
3293 /* if we've hit the max msg length, flush the rest */
3294 if (message_len >= maxlen) flushing = 1;
3296 } while (!finished);
3304 * Build a binary message to be saved on disk.
3305 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3306 * will become part of the message. This means you are no longer
3307 * responsible for managing that memory -- it will be freed along with
3308 * the rest of the fields when CtdlFreeMessage() is called.)
3311 struct CtdlMessage *CtdlMakeMessage(
3312 struct ctdluser *author, /* author's user structure */
3313 char *recipient, /* NULL if it's not mail */
3314 char *recp_cc, /* NULL if it's not mail */
3315 char *room, /* room where it's going */
3316 int type, /* see MES_ types in header file */
3317 int format_type, /* variformat, plain text, MIME... */
3318 char *fake_name, /* who we're masquerading as */
3319 char *my_email, /* which of my email addresses to use (empty is ok) */
3320 char *subject, /* Subject (optional) */
3321 char *supplied_euid, /* ...or NULL if this is irrelevant */
3322 char *preformatted_text, /* ...or NULL to read text from client */
3323 char *references /* Thread references */
3325 char dest_node[256];
3327 struct CtdlMessage *msg;
3329 msg = malloc(sizeof(struct CtdlMessage));
3330 memset(msg, 0, sizeof(struct CtdlMessage));
3331 msg->cm_magic = CTDLMESSAGE_MAGIC;
3332 msg->cm_anon_type = type;
3333 msg->cm_format_type = format_type;
3335 /* Don't confuse the poor folks if it's not routed mail. */
3336 strcpy(dest_node, "");
3338 if (recipient != NULL) striplt(recipient);
3339 if (recp_cc != NULL) striplt(recp_cc);
3341 /* Path or Return-Path */
3342 if (my_email == NULL) my_email = "";
3344 if (!IsEmptyStr(my_email)) {
3345 msg->cm_fields['P'] = strdup(my_email);
3348 snprintf(buf, sizeof buf, "%s", author->fullname);
3349 msg->cm_fields['P'] = strdup(buf);
3351 convert_spaces_to_underscores(msg->cm_fields['P']);
3353 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3354 msg->cm_fields['T'] = strdup(buf);
3356 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3357 msg->cm_fields['A'] = strdup(fake_name);
3360 msg->cm_fields['A'] = strdup(author->fullname);
3363 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3364 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3367 msg->cm_fields['O'] = strdup(CC->room.QRname);
3370 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3371 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3373 if ((recipient != NULL) && (recipient[0] != 0)) {
3374 msg->cm_fields['R'] = strdup(recipient);
3376 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3377 msg->cm_fields['Y'] = strdup(recp_cc);
3379 if (dest_node[0] != 0) {
3380 msg->cm_fields['D'] = strdup(dest_node);
3383 if (!IsEmptyStr(my_email)) {
3384 msg->cm_fields['F'] = strdup(my_email);
3386 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3387 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3390 if (subject != NULL) {
3393 length = strlen(subject);
3399 while ((subject[i] != '\0') &&
3400 (IsAscii = isascii(subject[i]) != 0 ))
3403 msg->cm_fields['U'] = strdup(subject);
3404 else /* ok, we've got utf8 in the string. */
3406 msg->cm_fields['U'] = rfc2047encode(subject, length);
3412 if (supplied_euid != NULL) {
3413 msg->cm_fields['E'] = strdup(supplied_euid);
3416 if (references != NULL) {
3417 if (!IsEmptyStr(references)) {
3418 msg->cm_fields['W'] = strdup(references);
3422 if (preformatted_text != NULL) {
3423 msg->cm_fields['M'] = preformatted_text;
3426 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3434 * Check to see whether we have permission to post a message in the current
3435 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3436 * returns 0 on success.
3438 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3440 const char* RemoteIdentifier,
3444 if (!(CC->logged_in) &&
3445 (PostPublic == POST_LOGGED_IN)) {
3446 snprintf(errmsgbuf, n, "Not logged in.");
3447 return (ERROR + NOT_LOGGED_IN);
3449 else if (PostPublic == CHECK_EXISTANCE) {
3450 return (0); // We're Evaling whether a recipient exists
3452 else if (!(CC->logged_in)) {
3454 if ((CC->room.QRflags & QR_READONLY)) {
3455 snprintf(errmsgbuf, n, "Not logged in.");
3456 return (ERROR + NOT_LOGGED_IN);
3458 if (CC->room.QRflags2 & QR2_MODERATED) {
3459 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3460 return (ERROR + NOT_LOGGED_IN);
3462 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3467 if (RemoteIdentifier == NULL)
3469 snprintf(errmsgbuf, n, "Need sender to permit access.");
3470 return (ERROR + USERNAME_REQUIRED);
3473 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3474 begin_critical_section(S_NETCONFIGS);
3475 if (!read_spoolcontrol_file(&sc, filename))
3477 end_critical_section(S_NETCONFIGS);
3478 snprintf(errmsgbuf, n,
3479 "This mailing list only accepts posts from subscribers.");
3480 return (ERROR + NO_SUCH_USER);
3482 end_critical_section(S_NETCONFIGS);
3483 found = is_recipient (sc, RemoteIdentifier);
3484 free_spoolcontrol_struct(&sc);
3489 snprintf(errmsgbuf, n,
3490 "This mailing list only accepts posts from subscribers.");
3491 return (ERROR + NO_SUCH_USER);
3498 if ((CC->user.axlevel < 2)
3499 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3500 snprintf(errmsgbuf, n, "Need to be validated to enter "
3501 "(except in %s> to sysop)", MAILROOM);
3502 return (ERROR + HIGHER_ACCESS_REQUIRED);
3505 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3506 if (!(ra & UA_POSTALLOWED)) {
3507 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3508 return (ERROR + HIGHER_ACCESS_REQUIRED);
3511 strcpy(errmsgbuf, "Ok");
3517 * Check to see if the specified user has Internet mail permission
3518 * (returns nonzero if permission is granted)
3520 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3522 /* Do not allow twits to send Internet mail */
3523 if (who->axlevel <= 2) return(0);
3525 /* Globally enabled? */
3526 if (config.c_restrict == 0) return(1);
3528 /* User flagged ok? */
3529 if (who->flags & US_INTERNET) return(2);
3531 /* Aide level access? */
3532 if (who->axlevel >= 6) return(3);
3534 /* No mail for you! */
3540 * Validate recipients, count delivery types and errors, and handle aliasing
3541 * FIXME check for dupes!!!!!
3543 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3544 * were specified, or the number of addresses found invalid.
3546 * Caller needs to free the result using free_recipients()
3548 struct recptypes *validate_recipients(char *supplied_recipients,
3549 const char *RemoteIdentifier,
3551 struct recptypes *ret;
3552 char *recipients = NULL;
3553 char this_recp[256];
3554 char this_recp_cooked[256];
3560 struct ctdluser tempUS;
3561 struct ctdlroom tempQR;
3562 struct ctdlroom tempQR2;
3568 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3569 if (ret == NULL) return(NULL);
3571 /* Set all strings to null and numeric values to zero */
3572 memset(ret, 0, sizeof(struct recptypes));
3574 if (supplied_recipients == NULL) {
3575 recipients = strdup("");
3578 recipients = strdup(supplied_recipients);
3581 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3582 * actually need, but it's healthier for the heap than doing lots of tiny
3583 * realloc() calls instead.
3586 ret->errormsg = malloc(strlen(recipients) + 1024);
3587 ret->recp_local = malloc(strlen(recipients) + 1024);
3588 ret->recp_internet = malloc(strlen(recipients) + 1024);
3589 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3590 ret->recp_room = malloc(strlen(recipients) + 1024);
3591 ret->display_recp = malloc(strlen(recipients) + 1024);
3593 ret->errormsg[0] = 0;
3594 ret->recp_local[0] = 0;
3595 ret->recp_internet[0] = 0;
3596 ret->recp_ignet[0] = 0;
3597 ret->recp_room[0] = 0;
3598 ret->display_recp[0] = 0;
3600 ret->recptypes_magic = RECPTYPES_MAGIC;
3602 /* Change all valid separator characters to commas */
3603 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3604 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3605 recipients[i] = ',';
3609 /* Now start extracting recipients... */
3611 while (!IsEmptyStr(recipients)) {
3613 for (i=0; i<=strlen(recipients); ++i) {
3614 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3615 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3616 safestrncpy(this_recp, recipients, i+1);
3618 if (recipients[i] == ',') {
3619 strcpy(recipients, &recipients[i+1]);
3622 strcpy(recipients, "");
3629 if (IsEmptyStr(this_recp))
3631 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3633 mailtype = alias(this_recp);
3634 mailtype = alias(this_recp);
3635 mailtype = alias(this_recp);
3637 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3638 if (this_recp[j]=='_') {
3639 this_recp_cooked[j] = ' ';
3642 this_recp_cooked[j] = this_recp[j];
3645 this_recp_cooked[j] = '\0';
3650 if (!strcasecmp(this_recp, "sysop")) {
3652 strcpy(this_recp, config.c_aideroom);
3653 if (!IsEmptyStr(ret->recp_room)) {
3654 strcat(ret->recp_room, "|");
3656 strcat(ret->recp_room, this_recp);
3658 else if ( (!strncasecmp(this_recp, "room_", 5))
3659 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3661 /* Save room so we can restore it later */
3665 /* Check permissions to send mail to this room */
3666 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3678 if (!IsEmptyStr(ret->recp_room)) {
3679 strcat(ret->recp_room, "|");
3681 strcat(ret->recp_room, &this_recp_cooked[5]);
3684 /* Restore room in case something needs it */
3688 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3690 strcpy(this_recp, tempUS.fullname);
3691 if (!IsEmptyStr(ret->recp_local)) {
3692 strcat(ret->recp_local, "|");
3694 strcat(ret->recp_local, this_recp);
3696 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
3698 strcpy(this_recp, tempUS.fullname);
3699 if (!IsEmptyStr(ret->recp_local)) {
3700 strcat(ret->recp_local, "|");
3702 strcat(ret->recp_local, this_recp);
3710 /* Yes, you're reading this correctly: if the target
3711 * domain points back to the local system or an attached
3712 * Citadel directory, the address is invalid. That's
3713 * because if the address were valid, we would have
3714 * already translated it to a local address by now.
3716 if (IsDirectory(this_recp, 0)) {
3721 ++ret->num_internet;
3722 if (!IsEmptyStr(ret->recp_internet)) {
3723 strcat(ret->recp_internet, "|");
3725 strcat(ret->recp_internet, this_recp);
3730 if (!IsEmptyStr(ret->recp_ignet)) {
3731 strcat(ret->recp_ignet, "|");
3733 strcat(ret->recp_ignet, this_recp);
3741 if (IsEmptyStr(errmsg)) {
3742 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3745 snprintf(append, sizeof append, "%s", errmsg);
3747 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3748 if (!IsEmptyStr(ret->errormsg)) {
3749 strcat(ret->errormsg, "; ");
3751 strcat(ret->errormsg, append);
3755 if (IsEmptyStr(ret->display_recp)) {
3756 strcpy(append, this_recp);
3759 snprintf(append, sizeof append, ", %s", this_recp);
3761 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3762 strcat(ret->display_recp, append);
3767 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3768 ret->num_room + ret->num_error) == 0) {
3769 ret->num_error = (-1);
3770 strcpy(ret->errormsg, "No recipients specified.");
3773 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3774 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3775 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3776 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3777 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3778 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3786 * Destructor for struct recptypes
3788 void free_recipients(struct recptypes *valid) {
3790 if (valid == NULL) {
3794 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3795 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3799 if (valid->errormsg != NULL) free(valid->errormsg);
3800 if (valid->recp_local != NULL) free(valid->recp_local);
3801 if (valid->recp_internet != NULL) free(valid->recp_internet);
3802 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3803 if (valid->recp_room != NULL) free(valid->recp_room);
3804 if (valid->display_recp != NULL) free(valid->display_recp);
3805 if (valid->bounce_to != NULL) free(valid->bounce_to);
3806 if (valid->envelope_from != NULL) free(valid->envelope_from);
3813 * message entry - mode 0 (normal)
3815 void cmd_ent0(char *entargs)
3821 char supplied_euid[128];
3823 int format_type = 0;
3824 char newusername[256];
3825 char newuseremail[256];
3826 struct CtdlMessage *msg;
3830 struct recptypes *valid = NULL;
3831 struct recptypes *valid_to = NULL;
3832 struct recptypes *valid_cc = NULL;
3833 struct recptypes *valid_bcc = NULL;
3835 int subject_required = 0;
3840 int newuseremail_ok = 0;
3841 char references[SIZ];
3846 post = extract_int(entargs, 0);
3847 extract_token(recp, entargs, 1, '|', sizeof recp);
3848 anon_flag = extract_int(entargs, 2);
3849 format_type = extract_int(entargs, 3);
3850 extract_token(subject, entargs, 4, '|', sizeof subject);
3851 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3852 do_confirm = extract_int(entargs, 6);
3853 extract_token(cc, entargs, 7, '|', sizeof cc);
3854 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3855 switch(CC->room.QRdefaultview) {
3858 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3861 supplied_euid[0] = 0;
3864 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3865 extract_token(references, entargs, 11, '|', sizeof references);
3866 for (ptr=references; *ptr != 0; ++ptr) {
3867 if (*ptr == '!') *ptr = '|';
3870 /* first check to make sure the request is valid. */
3872 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3875 cprintf("%d %s\n", err, errmsg);
3879 /* Check some other permission type things. */
3881 if (IsEmptyStr(newusername)) {
3882 strcpy(newusername, CC->user.fullname);
3884 if ( (CC->user.axlevel < 6)
3885 && (strcasecmp(newusername, CC->user.fullname))
3886 && (strcasecmp(newusername, CC->cs_inet_fn))
3888 cprintf("%d You don't have permission to author messages as '%s'.\n",
3889 ERROR + HIGHER_ACCESS_REQUIRED,
3896 if (IsEmptyStr(newuseremail)) {
3897 newuseremail_ok = 1;
3900 if (!IsEmptyStr(newuseremail)) {
3901 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3902 newuseremail_ok = 1;
3904 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3905 j = num_tokens(CC->cs_inet_other_emails, '|');
3906 for (i=0; i<j; ++i) {
3907 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3908 if (!strcasecmp(newuseremail, buf)) {
3909 newuseremail_ok = 1;
3915 if (!newuseremail_ok) {
3916 cprintf("%d You don't have permission to author messages as '%s'.\n",
3917 ERROR + HIGHER_ACCESS_REQUIRED,
3923 CC->cs_flags |= CS_POSTING;
3925 /* In mailbox rooms we have to behave a little differently --
3926 * make sure the user has specified at least one recipient. Then
3927 * validate the recipient(s). We do this for the Mail> room, as
3928 * well as any room which has the "Mailbox" view set.
3931 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3932 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3934 if (CC->user.axlevel < 2) {
3935 strcpy(recp, "sysop");
3940 valid_to = validate_recipients(recp, NULL, 0);
3941 if (valid_to->num_error > 0) {
3942 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3943 free_recipients(valid_to);
3947 valid_cc = validate_recipients(cc, NULL, 0);
3948 if (valid_cc->num_error > 0) {
3949 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3950 free_recipients(valid_to);
3951 free_recipients(valid_cc);
3955 valid_bcc = validate_recipients(bcc, NULL, 0);
3956 if (valid_bcc->num_error > 0) {
3957 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3958 free_recipients(valid_to);
3959 free_recipients(valid_cc);
3960 free_recipients(valid_bcc);
3964 /* Recipient required, but none were specified */
3965 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3966 free_recipients(valid_to);
3967 free_recipients(valid_cc);
3968 free_recipients(valid_bcc);
3969 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3973 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3974 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3975 cprintf("%d You do not have permission "
3976 "to send Internet mail.\n",
3977 ERROR + HIGHER_ACCESS_REQUIRED);
3978 free_recipients(valid_to);
3979 free_recipients(valid_cc);
3980 free_recipients(valid_bcc);
3985 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)
3986 && (CC->user.axlevel < 4) ) {
3987 cprintf("%d Higher access required for network mail.\n",
3988 ERROR + HIGHER_ACCESS_REQUIRED);
3989 free_recipients(valid_to);
3990 free_recipients(valid_cc);
3991 free_recipients(valid_bcc);
3995 if ((RESTRICT_INTERNET == 1)
3996 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3997 && ((CC->user.flags & US_INTERNET) == 0)
3998 && (!CC->internal_pgm)) {
3999 cprintf("%d You don't have access to Internet mail.\n",
4000 ERROR + HIGHER_ACCESS_REQUIRED);
4001 free_recipients(valid_to);
4002 free_recipients(valid_cc);
4003 free_recipients(valid_bcc);
4009 /* Is this a room which has anonymous-only or anonymous-option? */
4010 anonymous = MES_NORMAL;
4011 if (CC->room.QRflags & QR_ANONONLY) {
4012 anonymous = MES_ANONONLY;
4014 if (CC->room.QRflags & QR_ANONOPT) {
4015 if (anon_flag == 1) { /* only if the user requested it */
4016 anonymous = MES_ANONOPT;
4020 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4024 /* Recommend to the client that the use of a message subject is
4025 * strongly recommended in this room, if either the SUBJECTREQ flag
4026 * is set, or if there is one or more Internet email recipients.
4028 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4029 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4030 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4031 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4033 /* If we're only checking the validity of the request, return
4034 * success without creating the message.
4037 cprintf("%d %s|%d\n", CIT_OK,
4038 ((valid_to != NULL) ? valid_to->display_recp : ""),
4040 free_recipients(valid_to);
4041 free_recipients(valid_cc);
4042 free_recipients(valid_bcc);
4046 /* We don't need these anymore because we'll do it differently below */
4047 free_recipients(valid_to);
4048 free_recipients(valid_cc);
4049 free_recipients(valid_bcc);
4051 /* Read in the message from the client. */
4053 cprintf("%d send message\n", START_CHAT_MODE);
4055 cprintf("%d send message\n", SEND_LISTING);
4058 msg = CtdlMakeMessage(&CC->user, recp, cc,
4059 CC->room.QRname, anonymous, format_type,
4060 newusername, newuseremail, subject,
4061 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4064 /* Put together one big recipients struct containing to/cc/bcc all in
4065 * one. This is for the envelope.
4067 char *all_recps = malloc(SIZ * 3);
4068 strcpy(all_recps, recp);
4069 if (!IsEmptyStr(cc)) {
4070 if (!IsEmptyStr(all_recps)) {
4071 strcat(all_recps, ",");
4073 strcat(all_recps, cc);
4075 if (!IsEmptyStr(bcc)) {
4076 if (!IsEmptyStr(all_recps)) {
4077 strcat(all_recps, ",");
4079 strcat(all_recps, bcc);
4081 if (!IsEmptyStr(all_recps)) {
4082 valid = validate_recipients(all_recps, NULL, 0);
4090 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4093 cprintf("%ld\n", msgnum);
4095 cprintf("Message accepted.\n");
4098 cprintf("Internal error.\n");
4100 if (msg->cm_fields['E'] != NULL) {
4101 cprintf("%s\n", msg->cm_fields['E']);
4108 CtdlFreeMessage(msg);
4110 if (valid != NULL) {
4111 free_recipients(valid);
4119 * API function to delete messages which match a set of criteria
4120 * (returns the actual number of messages deleted)
4122 int CtdlDeleteMessages(char *room_name, /* which room */
4123 long *dmsgnums, /* array of msg numbers to be deleted */
4124 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4125 char *content_type /* or "" for any. regular expressions expected. */
4128 struct ctdlroom qrbuf;
4129 struct cdbdata *cdbfr;
4130 long *msglist = NULL;
4131 long *dellist = NULL;
4134 int num_deleted = 0;
4136 struct MetaData smi;
4139 int need_to_free_re = 0;
4141 if (content_type) if (!IsEmptyStr(content_type)) {
4142 regcomp(&re, content_type, 0);
4143 need_to_free_re = 1;
4145 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4146 room_name, num_dmsgnums, content_type);
4148 /* get room record, obtaining a lock... */
4149 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4150 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4152 if (need_to_free_re) regfree(&re);
4153 return (0); /* room not found */
4155 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4157 if (cdbfr != NULL) {
4158 dellist = malloc(cdbfr->len);
4159 msglist = (long *) cdbfr->ptr;
4160 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4161 num_msgs = cdbfr->len / sizeof(long);
4165 for (i = 0; i < num_msgs; ++i) {
4168 /* Set/clear a bit for each criterion */
4170 /* 0 messages in the list or a null list means that we are
4171 * interested in deleting any messages which meet the other criteria.
4173 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4174 delete_this |= 0x01;
4177 for (j=0; j<num_dmsgnums; ++j) {
4178 if (msglist[i] == dmsgnums[j]) {
4179 delete_this |= 0x01;
4184 if (IsEmptyStr(content_type)) {
4185 delete_this |= 0x02;
4187 GetMetaData(&smi, msglist[i]);
4188 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4189 delete_this |= 0x02;
4193 /* Delete message only if all bits are set */
4194 if (delete_this == 0x03) {
4195 dellist[num_deleted++] = msglist[i];
4200 num_msgs = sort_msglist(msglist, num_msgs);
4201 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4202 msglist, (int)(num_msgs * sizeof(long)));
4204 qrbuf.QRhighest = msglist[num_msgs - 1];
4206 CtdlPutRoomLock(&qrbuf);
4208 /* Go through the messages we pulled out of the index, and decrement
4209 * their reference counts by 1. If this is the only room the message
4210 * was in, the reference count will reach zero and the message will
4211 * automatically be deleted from the database. We do this in a
4212 * separate pass because there might be plug-in hooks getting called,
4213 * and we don't want that happening during an S_ROOMS critical
4216 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4217 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4218 AdjRefCount(dellist[i], -1);
4221 /* Now free the memory we used, and go away. */
4222 if (msglist != NULL) free(msglist);
4223 if (dellist != NULL) free(dellist);
4224 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4225 if (need_to_free_re) regfree(&re);
4226 return (num_deleted);
4232 * Check whether the current user has permission to delete messages from
4233 * the current room (returns 1 for yes, 0 for no)
4235 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4237 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4238 if (ra & UA_DELETEALLOWED) return(1);
4246 * Delete message from current room
4248 void cmd_dele(char *args)
4257 extract_token(msgset, args, 0, '|', sizeof msgset);
4258 num_msgs = num_tokens(msgset, ',');
4260 cprintf("%d Nothing to do.\n", CIT_OK);
4264 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4265 cprintf("%d Higher access required.\n",
4266 ERROR + HIGHER_ACCESS_REQUIRED);
4271 * Build our message set to be moved/copied
4273 msgs = malloc(num_msgs * sizeof(long));
4274 for (i=0; i<num_msgs; ++i) {
4275 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4276 msgs[i] = atol(msgtok);
4279 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4283 cprintf("%d %d message%s deleted.\n", CIT_OK,
4284 num_deleted, ((num_deleted != 1) ? "s" : ""));
4286 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4294 * move or copy a message to another room
4296 void cmd_move(char *args)
4303 char targ[ROOMNAMELEN];
4304 struct ctdlroom qtemp;
4311 extract_token(msgset, args, 0, '|', sizeof msgset);
4312 num_msgs = num_tokens(msgset, ',');
4314 cprintf("%d Nothing to do.\n", CIT_OK);
4318 extract_token(targ, args, 1, '|', sizeof targ);
4319 convert_room_name_macros(targ, sizeof targ);
4320 targ[ROOMNAMELEN - 1] = 0;
4321 is_copy = extract_int(args, 2);
4323 if (CtdlGetRoom(&qtemp, targ) != 0) {
4324 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4328 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4329 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4333 CtdlGetUser(&CC->user, CC->curr_user);
4334 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4336 /* Check for permission to perform this operation.
4337 * Remember: "CC->room" is source, "qtemp" is target.
4341 /* Aides can move/copy */
4342 if (CC->user.axlevel >= 6) permit = 1;
4344 /* Room aides can move/copy */
4345 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4347 /* Permit move/copy from personal rooms */
4348 if ((CC->room.QRflags & QR_MAILBOX)
4349 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4351 /* Permit only copy from public to personal room */
4353 && (!(CC->room.QRflags & QR_MAILBOX))
4354 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4356 /* Permit message removal from collaborative delete rooms */
4357 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4359 /* Users allowed to post into the target room may move into it too. */
4360 if ((CC->room.QRflags & QR_MAILBOX) &&
4361 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4363 /* User must have access to target room */
4364 if (!(ra & UA_KNOWN)) permit = 0;
4367 cprintf("%d Higher access required.\n",
4368 ERROR + HIGHER_ACCESS_REQUIRED);
4373 * Build our message set to be moved/copied
4375 msgs = malloc(num_msgs * sizeof(long));
4376 for (i=0; i<num_msgs; ++i) {
4377 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4378 msgs[i] = atol(msgtok);
4384 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4386 cprintf("%d Cannot store message(s) in %s: error %d\n",
4392 /* Now delete the message from the source room,
4393 * if this is a 'move' rather than a 'copy' operation.
4396 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4400 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4406 * GetMetaData() - Get the supplementary record for a message
4408 void GetMetaData(struct MetaData *smibuf, long msgnum)
4411 struct cdbdata *cdbsmi;
4414 memset(smibuf, 0, sizeof(struct MetaData));
4415 smibuf->meta_msgnum = msgnum;
4416 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4418 /* Use the negative of the message number for its supp record index */
4419 TheIndex = (0L - msgnum);
4421 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4422 if (cdbsmi == NULL) {
4423 return; /* record not found; go with defaults */
4425 memcpy(smibuf, cdbsmi->ptr,
4426 ((cdbsmi->len > sizeof(struct MetaData)) ?
4427 sizeof(struct MetaData) : cdbsmi->len));
4434 * PutMetaData() - (re)write supplementary record for a message
4436 void PutMetaData(struct MetaData *smibuf)
4440 /* Use the negative of the message number for the metadata db index */
4441 TheIndex = (0L - smibuf->meta_msgnum);
4443 cdb_store(CDB_MSGMAIN,
4444 &TheIndex, (int)sizeof(long),
4445 smibuf, (int)sizeof(struct MetaData));
4450 * AdjRefCount - submit an adjustment to the reference count for a message.
4451 * (These are just queued -- we actually process them later.)
4453 void AdjRefCount(long msgnum, int incr)
4455 struct arcq new_arcq;
4458 begin_critical_section(S_SUPPMSGMAIN);
4459 if (arcfp == NULL) {
4460 arcfp = fopen(file_arcq, "ab+");
4462 end_critical_section(S_SUPPMSGMAIN);
4464 /* msgnum < 0 means that we're trying to close the file */
4466 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4467 begin_critical_section(S_SUPPMSGMAIN);
4468 if (arcfp != NULL) {
4472 end_critical_section(S_SUPPMSGMAIN);
4477 * If we can't open the queue, perform the operation synchronously.
4479 if (arcfp == NULL) {
4480 TDAP_AdjRefCount(msgnum, incr);
4484 new_arcq.arcq_msgnum = msgnum;
4485 new_arcq.arcq_delta = incr;
4486 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4494 * TDAP_ProcessAdjRefCountQueue()
4496 * Process the queue of message count adjustments that was created by calls
4497 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4498 * for each one. This should be an "off hours" operation.
4500 int TDAP_ProcessAdjRefCountQueue(void)
4502 char file_arcq_temp[PATH_MAX];
4505 struct arcq arcq_rec;
4506 int num_records_processed = 0;
4508 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4510 begin_critical_section(S_SUPPMSGMAIN);
4511 if (arcfp != NULL) {
4516 r = link(file_arcq, file_arcq_temp);
4518 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4519 end_critical_section(S_SUPPMSGMAIN);
4520 return(num_records_processed);
4524 end_critical_section(S_SUPPMSGMAIN);
4526 fp = fopen(file_arcq_temp, "rb");
4528 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4529 return(num_records_processed);
4532 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4533 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4534 ++num_records_processed;
4538 r = unlink(file_arcq_temp);
4540 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4543 return(num_records_processed);
4549 * TDAP_AdjRefCount - adjust the reference count for a message.
4550 * This one does it "for real" because it's called by
4551 * the autopurger function that processes the queue
4552 * created by AdjRefCount(). If a message's reference
4553 * count becomes zero, we also delete the message from
4554 * disk and de-index it.
4556 void TDAP_AdjRefCount(long msgnum, int incr)
4559 struct MetaData smi;
4562 /* This is a *tight* critical section; please keep it that way, as
4563 * it may get called while nested in other critical sections.
4564 * Complicating this any further will surely cause deadlock!
4566 begin_critical_section(S_SUPPMSGMAIN);
4567 GetMetaData(&smi, msgnum);
4568 smi.meta_refcount += incr;
4570 end_critical_section(S_SUPPMSGMAIN);
4571 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4572 msgnum, incr, smi.meta_refcount);
4574 /* If the reference count is now zero, delete the message
4575 * (and its supplementary record as well).
4577 if (smi.meta_refcount == 0) {
4578 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4580 /* Call delete hooks with NULL room to show it has gone altogether */
4581 PerformDeleteHooks(NULL, msgnum);
4583 /* Remove from message base */
4585 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4586 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4588 /* Remove metadata record */
4589 delnum = (0L - msgnum);
4590 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4596 * Write a generic object to this room
4598 * Note: this could be much more efficient. Right now we use two temporary
4599 * files, and still pull the message into memory as with all others.
4601 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4602 char *content_type, /* MIME type of this object */
4603 char *raw_message, /* Data to be written */
4604 off_t raw_length, /* Size of raw_message */
4605 struct ctdluser *is_mailbox, /* Mailbox room? */
4606 int is_binary, /* Is encoding necessary? */
4607 int is_unique, /* Del others of this type? */
4608 unsigned int flags /* Internal save flags */
4612 struct ctdlroom qrbuf;
4613 char roomname[ROOMNAMELEN];
4614 struct CtdlMessage *msg;
4615 char *encoded_message = NULL;
4617 if (is_mailbox != NULL) {
4618 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4621 safestrncpy(roomname, req_room, sizeof(roomname));
4624 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4627 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4630 encoded_message = malloc((size_t)(raw_length + 4096));
4633 sprintf(encoded_message, "Content-type: %s\n", content_type);
4636 sprintf(&encoded_message[strlen(encoded_message)],
4637 "Content-transfer-encoding: base64\n\n"
4641 sprintf(&encoded_message[strlen(encoded_message)],
4642 "Content-transfer-encoding: 7bit\n\n"
4648 &encoded_message[strlen(encoded_message)],
4656 &encoded_message[strlen(encoded_message)],
4662 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4663 msg = malloc(sizeof(struct CtdlMessage));
4664 memset(msg, 0, sizeof(struct CtdlMessage));
4665 msg->cm_magic = CTDLMESSAGE_MAGIC;
4666 msg->cm_anon_type = MES_NORMAL;
4667 msg->cm_format_type = 4;
4668 msg->cm_fields['A'] = strdup(CC->user.fullname);
4669 msg->cm_fields['O'] = strdup(req_room);
4670 msg->cm_fields['N'] = strdup(config.c_nodename);
4671 msg->cm_fields['H'] = strdup(config.c_humannode);
4672 msg->cm_flags = flags;
4674 msg->cm_fields['M'] = encoded_message;
4676 /* Create the requested room if we have to. */
4677 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4678 CtdlCreateRoom(roomname,
4679 ( (is_mailbox != NULL) ? 5 : 3 ),
4680 "", 0, 1, 0, VIEW_BBS);
4682 /* If the caller specified this object as unique, delete all
4683 * other objects of this type that are currently in the room.
4686 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4687 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4690 /* Now write the data */
4691 CtdlSubmitMsg(msg, NULL, roomname, 0);
4692 CtdlFreeMessage(msg);
4700 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4701 config_msgnum = msgnum;
4705 char *CtdlGetSysConfig(char *sysconfname) {
4706 char hold_rm[ROOMNAMELEN];
4709 struct CtdlMessage *msg;
4712 strcpy(hold_rm, CC->room.QRname);
4713 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4714 CtdlGetRoom(&CC->room, hold_rm);
4719 /* We want the last (and probably only) config in this room */
4720 begin_critical_section(S_CONFIG);
4721 config_msgnum = (-1L);
4722 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4723 CtdlGetSysConfigBackend, NULL);
4724 msgnum = config_msgnum;
4725 end_critical_section(S_CONFIG);
4731 msg = CtdlFetchMessage(msgnum, 1);
4733 conf = strdup(msg->cm_fields['M']);
4734 CtdlFreeMessage(msg);
4741 CtdlGetRoom(&CC->room, hold_rm);
4743 if (conf != NULL) do {
4744 extract_token(buf, conf, 0, '\n', sizeof buf);
4745 strcpy(conf, &conf[strlen(buf)+1]);
4746 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4752 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4753 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4758 * Determine whether a given Internet address belongs to the current user
4760 int CtdlIsMe(char *addr, int addr_buf_len)
4762 struct recptypes *recp;
4765 recp = validate_recipients(addr, NULL, 0);
4766 if (recp == NULL) return(0);
4768 if (recp->num_local == 0) {
4769 free_recipients(recp);
4773 for (i=0; i<recp->num_local; ++i) {
4774 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4775 if (!strcasecmp(addr, CC->user.fullname)) {
4776 free_recipients(recp);
4781 free_recipients(recp);
4787 * Citadel protocol command to do the same
4789 void cmd_isme(char *argbuf) {
4792 if (CtdlAccessCheck(ac_logged_in)) return;
4793 extract_token(addr, argbuf, 0, '|', sizeof addr);
4795 if (CtdlIsMe(addr, sizeof addr)) {
4796 cprintf("%d %s\n", CIT_OK, addr);
4799 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4805 /*****************************************************************************/
4806 /* MODULE INITIALIZATION STUFF */
4807 /*****************************************************************************/
4809 CTDL_MODULE_INIT(msgbase)
4812 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4813 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4814 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4815 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4816 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4817 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4818 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4819 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4820 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4821 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4822 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4823 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4826 /* return our Subversion id for the Log */