4 * Implements the message store.
6 * Copyright (c) 1987-2010 by the citadel.org team
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #if TIME_WITH_SYS_TIME
30 # include <sys/time.h>
34 # include <sys/time.h>
47 #include <sys/types.h>
49 #include <libcitadel.h>
52 #include "serv_extensions.h"
56 #include "sysdep_decls.h"
57 #include "citserver.h"
64 #include "internet_addressing.h"
65 #include "euidindex.h"
66 #include "journaling.h"
67 #include "citadel_dirs.h"
68 #include "clientsocket.h"
69 #include "serv_network.h"
72 #include "ctdl_module.h"
75 struct addresses_to_be_filed *atbf = NULL;
77 /* This temp file holds the queue of operations for AdjRefCount() */
78 static FILE *arcfp = NULL;
81 * This really belongs in serv_network.c, but I don't know how to export
82 * symbols between modules.
84 struct FilterList *filterlist = NULL;
88 * These are the four-character field headers we use when outputting
89 * messages in Citadel format (as opposed to RFC822 format).
92 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
93 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
94 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
95 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
96 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
97 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
98 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
99 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
128 * This function is self explanatory.
129 * (What can I say, I'm in a weird mood today...)
131 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
135 for (i = 0; i < strlen(name); ++i) {
136 if (name[i] == '@') {
137 while (isspace(name[i - 1]) && i > 0) {
138 strcpy(&name[i - 1], &name[i]);
141 while (isspace(name[i + 1])) {
142 strcpy(&name[i + 1], &name[i + 2]);
150 * Aliasing for network mail.
151 * (Error messages have been commented out, because this is a server.)
153 int alias(char *name)
154 { /* process alias and routing info for mail */
157 char aaa[SIZ], bbb[SIZ];
158 char *ignetcfg = NULL;
159 char *ignetmap = NULL;
165 char original_name[256];
166 safestrncpy(original_name, name, sizeof original_name);
169 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
170 stripallbut(name, '<', '>');
172 fp = fopen(file_mail_aliases, "r");
174 fp = fopen("/dev/null", "r");
181 while (fgets(aaa, sizeof aaa, fp) != NULL) {
182 while (isspace(name[0]))
183 strcpy(name, &name[1]);
184 aaa[strlen(aaa) - 1] = 0;
186 for (a = 0; a < strlen(aaa); ++a) {
188 strcpy(bbb, &aaa[a + 1]);
192 if (!strcasecmp(name, aaa))
197 /* Hit the Global Address Book */
198 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
202 if (strcasecmp(original_name, name)) {
203 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
206 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
207 for (a=0; a<strlen(name); ++a) {
208 if (name[a] == '@') {
209 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
211 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
216 /* determine local or remote type, see citadel.h */
217 at = haschar(name, '@');
218 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
219 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
220 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
222 /* figure out the delivery mode */
223 extract_token(node, name, 1, '@', sizeof node);
225 /* If there are one or more dots in the nodename, we assume that it
226 * is an FQDN and will attempt SMTP delivery to the Internet.
228 if (haschar(node, '.') > 0) {
229 return(MES_INTERNET);
232 /* Otherwise we look in the IGnet maps for a valid Citadel node.
233 * Try directly-connected nodes first...
235 ignetcfg = CtdlGetSysConfig(IGNETCFG);
236 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
237 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
238 extract_token(testnode, buf, 0, '|', sizeof testnode);
239 if (!strcasecmp(node, testnode)) {
247 * Then try nodes that are two or more hops away.
249 ignetmap = CtdlGetSysConfig(IGNETMAP);
250 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
251 extract_token(buf, ignetmap, i, '\n', sizeof buf);
252 extract_token(testnode, buf, 0, '|', sizeof testnode);
253 if (!strcasecmp(node, testnode)) {
260 /* If we get to this point it's an invalid node name */
266 * Back end for the MSGS command: output message number only.
268 void simple_listing(long msgnum, void *userdata)
270 cprintf("%ld\n", msgnum);
276 * Back end for the MSGS command: output header summary.
278 void headers_listing(long msgnum, void *userdata)
280 struct CtdlMessage *msg;
282 msg = CtdlFetchMessage(msgnum, 0);
284 cprintf("%ld|0|||||\n", msgnum);
288 cprintf("%ld|%s|%s|%s|%s|%s|\n",
290 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
291 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
292 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
293 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
294 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
296 CtdlFreeMessage(msg);
300 * Back end for the MSGS command: output EUID header.
302 void headers_euid(long msgnum, void *userdata)
304 struct CtdlMessage *msg;
306 msg = CtdlFetchMessage(msgnum, 0);
308 cprintf("%ld||\n", msgnum);
312 cprintf("%ld|%s|\n", msgnum, (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""));
313 CtdlFreeMessage(msg);
320 /* Determine if a given message matches the fields in a message template.
321 * Return 0 for a successful match.
323 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
326 /* If there aren't any fields in the template, all messages will
329 if (template == NULL) return(0);
331 /* Null messages are bogus. */
332 if (msg == NULL) return(1);
334 for (i='A'; i<='Z'; ++i) {
335 if (template->cm_fields[i] != NULL) {
336 if (msg->cm_fields[i] == NULL) {
337 /* Considered equal if temmplate is empty string */
338 if (IsEmptyStr(template->cm_fields[i])) continue;
341 if (strcasecmp(msg->cm_fields[i],
342 template->cm_fields[i])) return 1;
346 /* All compares succeeded: we have a match! */
353 * Retrieve the "seen" message list for the current room.
355 void CtdlGetSeen(char *buf, int which_set) {
358 /* Learn about the user and room in question */
359 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
361 if (which_set == ctdlsetseen_seen)
362 safestrncpy(buf, vbuf.v_seen, SIZ);
363 if (which_set == ctdlsetseen_answered)
364 safestrncpy(buf, vbuf.v_answered, SIZ);
370 * Manipulate the "seen msgs" string (or other message set strings)
372 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
373 int target_setting, int which_set,
374 struct ctdluser *which_user, struct ctdlroom *which_room) {
375 struct cdbdata *cdbfr;
389 char *is_set; /* actually an array of booleans */
391 /* Don't bother doing *anything* if we were passed a list of zero messages */
392 if (num_target_msgnums < 1) {
396 /* If no room was specified, we go with the current room. */
398 which_room = &CC->room;
401 /* If no user was specified, we go with the current user. */
403 which_user = &CC->user;
406 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
407 num_target_msgnums, target_msgnums[0],
408 (target_setting ? "SET" : "CLEAR"),
412 /* Learn about the user and room in question */
413 CtdlGetRelationship(&vbuf, which_user, which_room);
415 /* Load the message list */
416 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
418 msglist = (long *) cdbfr->ptr;
419 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
420 num_msgs = cdbfr->len / sizeof(long);
423 return; /* No messages at all? No further action. */
426 is_set = malloc(num_msgs * sizeof(char));
427 memset(is_set, 0, (num_msgs * sizeof(char)) );
429 /* Decide which message set we're manipulating */
431 case ctdlsetseen_seen:
432 vset = NewStrBufPlain(vbuf.v_seen, -1);
434 case ctdlsetseen_answered:
435 vset = NewStrBufPlain(vbuf.v_answered, -1);
442 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
443 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
444 for (i=0; i<num_msgs; ++i) {
445 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
447 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
448 for (k=0; k<num_target_msgnums; ++k) {
449 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
453 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
455 /* Translate the existing sequence set into an array of booleans */
456 setstr = NewStrBuf();
460 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
462 StrBufExtract_token(lostr, setstr, 0, ':');
463 if (StrBufNum_tokens(setstr, ':') >= 2) {
464 StrBufExtract_token(histr, setstr, 1, ':');
468 StrBufAppendBuf(histr, lostr, 0);
471 if (!strcmp(ChrPtr(histr), "*")) {
478 for (i = 0; i < num_msgs; ++i) {
479 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
489 /* Now translate the array of booleans back into a sequence set */
495 for (i=0; i<num_msgs; ++i) {
499 for (k=0; k<num_target_msgnums; ++k) {
500 if (msglist[i] == target_msgnums[k]) {
501 is_seen = target_setting;
505 if ((was_seen == 0) && (is_seen == 1)) {
508 else if ((was_seen == 1) && (is_seen == 0)) {
511 if (StrLength(vset) > 0) {
512 StrBufAppendBufPlain(vset, HKEY(","), 0);
515 StrBufAppendPrintf(vset, "%ld", hi);
518 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
522 if ((is_seen) && (i == num_msgs - 1)) {
523 if (StrLength(vset) > 0) {
524 StrBufAppendBufPlain(vset, HKEY(","), 0);
526 if ((i==0) || (was_seen == 0)) {
527 StrBufAppendPrintf(vset, "%ld", msglist[i]);
530 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
538 * We will have to stuff this string back into a 4096 byte buffer, so if it's
539 * larger than that now, truncate it by removing tokens from the beginning.
540 * The limit of 100 iterations is there to prevent an infinite loop in case
541 * something unexpected happens.
543 int number_of_truncations = 0;
544 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
545 StrBufRemove_token(vset, 0, ',');
546 ++number_of_truncations;
550 * If we're truncating the sequence set of messages marked with the 'seen' flag,
551 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
552 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
554 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
556 first_tok = NewStrBuf();
557 StrBufExtract_token(first_tok, vset, 0, ',');
558 StrBufRemove_token(vset, 0, ',');
560 if (StrBufNum_tokens(first_tok, ':') > 1) {
561 StrBufRemove_token(first_tok, 0, ':');
565 new_set = NewStrBuf();
566 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
567 StrBufAppendBuf(new_set, first_tok, 0);
568 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
569 StrBufAppendBuf(new_set, vset, 0);
572 FreeStrBuf(&first_tok);
576 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
578 /* Decide which message set we're manipulating */
580 case ctdlsetseen_seen:
581 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
583 case ctdlsetseen_answered:
584 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
590 CtdlSetRelationship(&vbuf, which_user, which_room);
596 * API function to perform an operation for each qualifying message in the
597 * current room. (Returns the number of messages processed.)
599 int CtdlForEachMessage(int mode, long ref, char *search_string,
601 struct CtdlMessage *compare,
602 ForEachMsgCallback CallBack,
608 struct cdbdata *cdbfr;
609 long *msglist = NULL;
611 int num_processed = 0;
614 struct CtdlMessage *msg = NULL;
617 int printed_lastold = 0;
618 int num_search_msgs = 0;
619 long *search_msgs = NULL;
621 int need_to_free_re = 0;
624 if ((content_type) && (!IsEmptyStr(content_type))) {
625 regcomp(&re, content_type, 0);
629 /* Learn about the user and room in question */
630 CtdlGetUser(&CC->user, CC->curr_user);
631 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
633 /* Load the message list */
634 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
636 msglist = (long *) cdbfr->ptr;
637 num_msgs = cdbfr->len / sizeof(long);
639 if (need_to_free_re) regfree(&re);
640 return 0; /* No messages at all? No further action. */
645 * Now begin the traversal.
647 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
649 /* If the caller is looking for a specific MIME type, filter
650 * out all messages which are not of the type requested.
652 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
654 /* This call to GetMetaData() sits inside this loop
655 * so that we only do the extra database read per msg
656 * if we need to. Doing the extra read all the time
657 * really kills the server. If we ever need to use
658 * metadata for another search criterion, we need to
659 * move the read somewhere else -- but still be smart
660 * enough to only do the read if the caller has
661 * specified something that will need it.
663 GetMetaData(&smi, msglist[a]);
665 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
666 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
672 num_msgs = sort_msglist(msglist, num_msgs);
674 /* If a template was supplied, filter out the messages which
675 * don't match. (This could induce some delays!)
678 if (compare != NULL) {
679 for (a = 0; a < num_msgs; ++a) {
680 msg = CtdlFetchMessage(msglist[a], 1);
682 if (CtdlMsgCmp(msg, compare)) {
685 CtdlFreeMessage(msg);
691 /* If a search string was specified, get a message list from
692 * the full text index and remove messages which aren't on both
696 * Since the lists are sorted and strictly ascending, and the
697 * output list is guaranteed to be shorter than or equal to the
698 * input list, we overwrite the bottom of the input list. This
699 * eliminates the need to memmove big chunks of the list over and
702 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
704 /* Call search module via hook mechanism.
705 * NULL means use any search function available.
706 * otherwise replace with a char * to name of search routine
708 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
710 if (num_search_msgs > 0) {
714 orig_num_msgs = num_msgs;
716 for (i=0; i<orig_num_msgs; ++i) {
717 for (j=0; j<num_search_msgs; ++j) {
718 if (msglist[i] == search_msgs[j]) {
719 msglist[num_msgs++] = msglist[i];
725 num_msgs = 0; /* No messages qualify */
727 if (search_msgs != NULL) free(search_msgs);
729 /* Now that we've purged messages which don't contain the search
730 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
737 * Now iterate through the message list, according to the
738 * criteria supplied by the caller.
741 for (a = 0; a < num_msgs; ++a) {
742 thismsg = msglist[a];
743 if (mode == MSGS_ALL) {
747 is_seen = is_msg_in_sequence_set(
748 vbuf.v_seen, thismsg);
749 if (is_seen) lastold = thismsg;
755 || ((mode == MSGS_OLD) && (is_seen))
756 || ((mode == MSGS_NEW) && (!is_seen))
757 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
758 || ((mode == MSGS_FIRST) && (a < ref))
759 || ((mode == MSGS_GT) && (thismsg > ref))
760 || ((mode == MSGS_LT) && (thismsg < ref))
761 || ((mode == MSGS_EQ) && (thismsg == ref))
764 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
766 CallBack(lastold, userdata);
770 if (CallBack) CallBack(thismsg, userdata);
774 cdb_free(cdbfr); /* Clean up */
775 if (need_to_free_re) regfree(&re);
776 return num_processed;
782 * cmd_msgs() - get list of message #'s in this room
783 * implements the MSGS server command using CtdlForEachMessage()
785 void cmd_msgs(char *cmdbuf)
794 int with_template = 0;
795 struct CtdlMessage *template = NULL;
796 char search_string[1024];
797 ForEachMsgCallback CallBack;
799 extract_token(which, cmdbuf, 0, '|', sizeof which);
800 cm_ref = extract_int(cmdbuf, 1);
801 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
802 with_template = extract_int(cmdbuf, 2);
803 switch (extract_int(cmdbuf, 3))
807 CallBack = simple_listing;
810 CallBack = headers_listing;
813 CallBack = headers_euid;
818 if (!strncasecmp(which, "OLD", 3))
820 else if (!strncasecmp(which, "NEW", 3))
822 else if (!strncasecmp(which, "FIRST", 5))
824 else if (!strncasecmp(which, "LAST", 4))
826 else if (!strncasecmp(which, "GT", 2))
828 else if (!strncasecmp(which, "LT", 2))
830 else if (!strncasecmp(which, "SEARCH", 6))
835 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
836 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
840 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
841 cprintf("%d Full text index is not enabled on this server.\n",
842 ERROR + CMD_NOT_SUPPORTED);
848 cprintf("%d Send template then receive message list\n",
850 template = (struct CtdlMessage *)
851 malloc(sizeof(struct CtdlMessage));
852 memset(template, 0, sizeof(struct CtdlMessage));
853 template->cm_magic = CTDLMESSAGE_MAGIC;
854 template->cm_anon_type = MES_NORMAL;
856 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
857 extract_token(tfield, buf, 0, '|', sizeof tfield);
858 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
859 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
860 if (!strcasecmp(tfield, msgkeys[i])) {
861 template->cm_fields[i] =
869 cprintf("%d \n", LISTING_FOLLOWS);
872 CtdlForEachMessage(mode,
873 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
874 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
879 if (template != NULL) CtdlFreeMessage(template);
887 * help_subst() - support routine for help file viewer
889 void help_subst(char *strbuf, char *source, char *dest)
894 while (p = pattern2(strbuf, source), (p >= 0)) {
895 strcpy(workbuf, &strbuf[p + strlen(source)]);
896 strcpy(&strbuf[p], dest);
897 strcat(strbuf, workbuf);
902 void do_help_subst(char *buffer)
906 help_subst(buffer, "^nodename", config.c_nodename);
907 help_subst(buffer, "^humannode", config.c_humannode);
908 help_subst(buffer, "^fqdn", config.c_fqdn);
909 help_subst(buffer, "^username", CC->user.fullname);
910 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
911 help_subst(buffer, "^usernum", buf2);
912 help_subst(buffer, "^sysadm", config.c_sysadm);
913 help_subst(buffer, "^variantname", CITADEL);
914 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
915 help_subst(buffer, "^maxsessions", buf2);
916 help_subst(buffer, "^bbsdir", ctdl_message_dir);
922 * memfmout() - Citadel text formatter and paginator.
923 * Although the original purpose of this routine was to format
924 * text to the reader's screen width, all we're really using it
925 * for here is to format text out to 80 columns before sending it
926 * to the client. The client software may reformat it again.
929 char *mptr, /* where are we going to get our text from? */
930 const char *nl /* string to terminate lines with */
940 while (ch=*(mptr++), ch > 0) {
943 client_write(outbuf, len);
945 client_write(nl, nllen);
948 else if (ch == '\r') {
949 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
951 else if (isspace(ch)) {
952 if (column > 72) { /* Beyond 72 columns, break on the next space */
953 client_write(outbuf, len);
955 client_write(nl, nllen);
966 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
967 client_write(outbuf, len);
969 client_write(nl, nllen);
975 client_write(outbuf, len);
977 client_write(nl, nllen);
985 * Callback function for mime parser that simply lists the part
987 void list_this_part(char *name, char *filename, char *partnum, char *disp,
988 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
989 char *cbid, void *cbuserdata)
993 ma = (struct ma_info *)cbuserdata;
994 if (ma->is_ma == 0) {
995 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1008 * Callback function for multipart prefix
1010 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1011 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1012 char *cbid, void *cbuserdata)
1016 ma = (struct ma_info *)cbuserdata;
1017 if (!strcasecmp(cbtype, "multipart/alternative")) {
1021 if (ma->is_ma == 0) {
1022 cprintf("pref=%s|%s\n", partnum, cbtype);
1027 * Callback function for multipart sufffix
1029 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1030 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1031 char *cbid, void *cbuserdata)
1035 ma = (struct ma_info *)cbuserdata;
1036 if (ma->is_ma == 0) {
1037 cprintf("suff=%s|%s\n", partnum, cbtype);
1039 if (!strcasecmp(cbtype, "multipart/alternative")) {
1046 * Callback function for mime parser that opens a section for downloading
1048 void mime_download(char *name, char *filename, char *partnum, char *disp,
1049 void *content, char *cbtype, char *cbcharset, size_t length,
1050 char *encoding, char *cbid, void *cbuserdata)
1054 /* Silently go away if there's already a download open. */
1055 if (CC->download_fp != NULL)
1059 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1060 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1062 CC->download_fp = tmpfile();
1063 if (CC->download_fp == NULL)
1066 rv = fwrite(content, length, 1, CC->download_fp);
1067 fflush(CC->download_fp);
1068 rewind(CC->download_fp);
1070 OpenCmdResult(filename, cbtype);
1077 * Callback function for mime parser that outputs a section all at once.
1078 * We can specify the desired section by part number *or* content-id.
1080 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1081 void *content, char *cbtype, char *cbcharset, size_t length,
1082 char *encoding, char *cbid, void *cbuserdata)
1084 int *found_it = (int *)cbuserdata;
1087 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1088 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1091 cprintf("%d %d|-1|%s|%s|%s\n",
1098 client_write(content, length);
1105 * Load a message from disk into memory.
1106 * This is used by CtdlOutputMsg() and other fetch functions.
1108 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1109 * using the CtdlMessageFree() function.
1111 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1113 struct cdbdata *dmsgtext;
1114 struct CtdlMessage *ret = NULL;
1118 cit_uint8_t field_header;
1120 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1122 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1123 if (dmsgtext == NULL) {
1126 mptr = dmsgtext->ptr;
1127 upper_bound = mptr + dmsgtext->len;
1129 /* Parse the three bytes that begin EVERY message on disk.
1130 * The first is always 0xFF, the on-disk magic number.
1131 * The second is the anonymous/public type byte.
1132 * The third is the format type byte (vari, fixed, or MIME).
1136 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1140 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1141 memset(ret, 0, sizeof(struct CtdlMessage));
1143 ret->cm_magic = CTDLMESSAGE_MAGIC;
1144 ret->cm_anon_type = *mptr++; /* Anon type byte */
1145 ret->cm_format_type = *mptr++; /* Format type byte */
1148 * The rest is zero or more arbitrary fields. Load them in.
1149 * We're done when we encounter either a zero-length field or
1150 * have just processed the 'M' (message text) field.
1153 if (mptr >= upper_bound) {
1156 field_header = *mptr++;
1157 ret->cm_fields[field_header] = strdup(mptr);
1159 while (*mptr++ != 0); /* advance to next field */
1161 } while ((mptr < upper_bound) && (field_header != 'M'));
1165 /* Always make sure there's something in the msg text field. If
1166 * it's NULL, the message text is most likely stored separately,
1167 * so go ahead and fetch that. Failing that, just set a dummy
1168 * body so other code doesn't barf.
1170 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1171 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1172 if (dmsgtext != NULL) {
1173 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1177 if (ret->cm_fields['M'] == NULL) {
1178 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1181 /* Perform "before read" hooks (aborting if any return nonzero) */
1182 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1183 CtdlFreeMessage(ret);
1192 * Returns 1 if the supplied pointer points to a valid Citadel message.
1193 * If the pointer is NULL or the magic number check fails, returns 0.
1195 int is_valid_message(struct CtdlMessage *msg) {
1198 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1199 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1207 * 'Destructor' for struct CtdlMessage
1209 void CtdlFreeMessage(struct CtdlMessage *msg)
1213 if (is_valid_message(msg) == 0)
1215 if (msg != NULL) free (msg);
1219 for (i = 0; i < 256; ++i)
1220 if (msg->cm_fields[i] != NULL) {
1221 free(msg->cm_fields[i]);
1224 msg->cm_magic = 0; /* just in case */
1230 * Pre callback function for multipart/alternative
1232 * NOTE: this differs from the standard behavior for a reason. Normally when
1233 * displaying multipart/alternative you want to show the _last_ usable
1234 * format in the message. Here we show the _first_ one, because it's
1235 * usually text/plain. Since this set of functions is designed for text
1236 * output to non-MIME-aware clients, this is the desired behavior.
1239 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1240 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1241 char *cbid, void *cbuserdata)
1245 ma = (struct ma_info *)cbuserdata;
1246 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1247 if (!strcasecmp(cbtype, "multipart/alternative")) {
1251 if (!strcasecmp(cbtype, "message/rfc822")) {
1257 * Post callback function for multipart/alternative
1259 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1260 void *content, char *cbtype, char *cbcharset, size_t length,
1261 char *encoding, char *cbid, void *cbuserdata)
1265 ma = (struct ma_info *)cbuserdata;
1266 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1267 if (!strcasecmp(cbtype, "multipart/alternative")) {
1271 if (!strcasecmp(cbtype, "message/rfc822")) {
1277 * Inline callback function for mime parser that wants to display text
1279 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1280 void *content, char *cbtype, char *cbcharset, size_t length,
1281 char *encoding, char *cbid, void *cbuserdata)
1288 ma = (struct ma_info *)cbuserdata;
1290 CtdlLogPrintf(CTDL_DEBUG,
1291 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1292 partnum, filename, cbtype, (long)length);
1295 * If we're in the middle of a multipart/alternative scope and
1296 * we've already printed another section, skip this one.
1298 if ( (ma->is_ma) && (ma->did_print) ) {
1299 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1304 if ( (!strcasecmp(cbtype, "text/plain"))
1305 || (IsEmptyStr(cbtype)) ) {
1308 client_write(wptr, length);
1309 if (wptr[length-1] != '\n') {
1316 if (!strcasecmp(cbtype, "text/html")) {
1317 ptr = html_to_ascii(content, length, 80, 0);
1319 client_write(ptr, wlen);
1320 if (ptr[wlen-1] != '\n') {
1327 if (ma->use_fo_hooks) {
1328 if (PerformFixedOutputHooks(cbtype, content, length)) {
1329 /* above function returns nonzero if it handled the part */
1334 if (strncasecmp(cbtype, "multipart/", 10)) {
1335 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1336 partnum, filename, cbtype, (long)length);
1342 * The client is elegant and sophisticated and wants to be choosy about
1343 * MIME content types, so figure out which multipart/alternative part
1344 * we're going to send.
1346 * We use a system of weights. When we find a part that matches one of the
1347 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1348 * and then set ma->chosen_pref to that MIME type's position in our preference
1349 * list. If we then hit another match, we only replace the first match if
1350 * the preference value is lower.
1352 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1353 void *content, char *cbtype, char *cbcharset, size_t length,
1354 char *encoding, char *cbid, void *cbuserdata)
1360 ma = (struct ma_info *)cbuserdata;
1362 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1363 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1364 // I don't know if there are any side effects! Please TEST TEST TEST
1365 //if (ma->is_ma > 0) {
1367 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1368 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1369 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1370 if (i < ma->chosen_pref) {
1371 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1372 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1373 ma->chosen_pref = i;
1380 * Now that we've chosen our preferred part, output it.
1382 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1383 void *content, char *cbtype, char *cbcharset, size_t length,
1384 char *encoding, char *cbid, void *cbuserdata)
1388 int add_newline = 0;
1392 ma = (struct ma_info *)cbuserdata;
1394 /* This is not the MIME part you're looking for... */
1395 if (strcasecmp(partnum, ma->chosen_part)) return;
1397 /* If the content-type of this part is in our preferred formats
1398 * list, we can simply output it verbatim.
1400 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1401 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1402 if (!strcasecmp(buf, cbtype)) {
1403 /* Yeah! Go! W00t!! */
1405 text_content = (char *)content;
1406 if (text_content[length-1] != '\n') {
1409 cprintf("Content-type: %s", cbtype);
1410 if (!IsEmptyStr(cbcharset)) {
1411 cprintf("; charset=%s", cbcharset);
1413 cprintf("\nContent-length: %d\n",
1414 (int)(length + add_newline) );
1415 if (!IsEmptyStr(encoding)) {
1416 cprintf("Content-transfer-encoding: %s\n", encoding);
1419 cprintf("Content-transfer-encoding: 7bit\n");
1421 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1423 client_write(content, length);
1424 if (add_newline) cprintf("\n");
1429 /* No translations required or possible: output as text/plain */
1430 cprintf("Content-type: text/plain\n\n");
1431 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1432 length, encoding, cbid, cbuserdata);
1437 char desired_section[64];
1444 * Callback function for
1446 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1447 void *content, char *cbtype, char *cbcharset, size_t length,
1448 char *encoding, char *cbid, void *cbuserdata)
1450 struct encapmsg *encap;
1452 encap = (struct encapmsg *)cbuserdata;
1454 /* Only proceed if this is the desired section... */
1455 if (!strcasecmp(encap->desired_section, partnum)) {
1456 encap->msglen = length;
1457 encap->msg = malloc(length + 2);
1458 memcpy(encap->msg, content, length);
1469 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1470 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1471 return(om_not_logged_in);
1478 * Get a message off disk. (returns om_* values found in msgbase.h)
1481 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1482 int mode, /* how would you like that message? */
1483 int headers_only, /* eschew the message body? */
1484 int do_proto, /* do Citadel protocol responses? */
1485 int crlf, /* Use CRLF newlines instead of LF? */
1486 char *section, /* NULL or a message/rfc822 section */
1487 int flags /* various flags; see msgbase.h */
1489 struct CtdlMessage *TheMessage = NULL;
1490 int retcode = om_no_such_msg;
1491 struct encapmsg encap;
1494 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1496 (section ? section : "<>")
1499 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1502 if (r == om_not_logged_in) {
1503 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1506 cprintf("%d An unknown error has occurred.\n", ERROR);
1512 /* FIXME: check message id against msglist for this room */
1515 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1516 * request that we don't even bother loading the body into memory.
1518 if (headers_only == HEADERS_FAST) {
1519 TheMessage = CtdlFetchMessage(msg_num, 0);
1522 TheMessage = CtdlFetchMessage(msg_num, 1);
1525 if (TheMessage == NULL) {
1526 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1527 ERROR + MESSAGE_NOT_FOUND, msg_num);
1528 return(om_no_such_msg);
1531 /* Here is the weird form of this command, to process only an
1532 * encapsulated message/rfc822 section.
1534 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1535 memset(&encap, 0, sizeof encap);
1536 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1537 mime_parser(TheMessage->cm_fields['M'],
1539 *extract_encapsulated_message,
1540 NULL, NULL, (void *)&encap, 0
1542 CtdlFreeMessage(TheMessage);
1546 encap.msg[encap.msglen] = 0;
1547 TheMessage = convert_internet_message(encap.msg);
1548 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1550 /* Now we let it fall through to the bottom of this
1551 * function, because TheMessage now contains the
1552 * encapsulated message instead of the top-level
1553 * message. Isn't that neat?
1558 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1559 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1560 retcode = om_no_such_msg;
1565 /* Ok, output the message now */
1566 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1567 CtdlFreeMessage(TheMessage);
1573 char *qp_encode_email_addrs(char *source)
1575 char user[256], node[256], name[256];
1576 const char headerStr[] = "=?UTF-8?Q?";
1580 int need_to_encode = 0;
1586 long nAddrPtrMax = 50;
1591 if (source == NULL) return source;
1592 if (IsEmptyStr(source)) return source;
1594 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1595 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1596 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1599 while (!IsEmptyStr (&source[i])) {
1600 if (nColons >= nAddrPtrMax){
1603 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1604 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1605 free (AddrPtr), AddrPtr = ptr;
1607 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1608 memset(&ptr[nAddrPtrMax], 0,
1609 sizeof (long) * nAddrPtrMax);
1611 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1612 free (AddrUtf8), AddrUtf8 = ptr;
1615 if (((unsigned char) source[i] < 32) ||
1616 ((unsigned char) source[i] > 126)) {
1618 AddrUtf8[nColons] = 1;
1620 if (source[i] == '"')
1621 InQuotes = !InQuotes;
1622 if (!InQuotes && source[i] == ',') {
1623 AddrPtr[nColons] = i;
1628 if (need_to_encode == 0) {
1635 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1636 Encoded = (char*) malloc (EncodedMaxLen);
1638 for (i = 0; i < nColons; i++)
1639 source[AddrPtr[i]++] = '\0';
1643 for (i = 0; i < nColons && nPtr != NULL; i++) {
1644 nmax = EncodedMaxLen - (nPtr - Encoded);
1646 process_rfc822_addr(&source[AddrPtr[i]],
1650 /* TODO: libIDN here ! */
1651 if (IsEmptyStr(name)) {
1652 n = snprintf(nPtr, nmax,
1653 (i==0)?"%s@%s" : ",%s@%s",
1657 EncodedName = rfc2047encode(name, strlen(name));
1658 n = snprintf(nPtr, nmax,
1659 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1660 EncodedName, user, node);
1665 n = snprintf(nPtr, nmax,
1666 (i==0)?"%s" : ",%s",
1667 &source[AddrPtr[i]]);
1673 ptr = (char*) malloc(EncodedMaxLen * 2);
1674 memcpy(ptr, Encoded, EncodedMaxLen);
1675 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1676 free(Encoded), Encoded = ptr;
1678 i--; /* do it once more with properly lengthened buffer */
1681 for (i = 0; i < nColons; i++)
1682 source[--AddrPtr[i]] = ',';
1689 /* If the last item in a list of recipients was truncated to a partial address,
1690 * remove it completely in order to avoid choking libSieve
1692 void sanitize_truncated_recipient(char *str)
1695 if (num_tokens(str, ',') < 2) return;
1697 int len = strlen(str);
1698 if (len < 900) return;
1699 if (len > 998) str[998] = 0;
1701 char *cptr = strrchr(str, ',');
1704 char *lptr = strchr(cptr, '<');
1705 char *rptr = strchr(cptr, '>');
1707 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1717 * Get a message off disk. (returns om_* values found in msgbase.h)
1719 int CtdlOutputPreLoadedMsg(
1720 struct CtdlMessage *TheMessage,
1721 int mode, /* how would you like that message? */
1722 int headers_only, /* eschew the message body? */
1723 int do_proto, /* do Citadel protocol responses? */
1724 int crlf, /* Use CRLF newlines instead of LF? */
1725 int flags /* should the bessage be exported clean? */
1729 cit_uint8_t ch, prev_ch;
1731 char display_name[256];
1733 const char *nl; /* newline string */
1735 int subject_found = 0;
1738 /* Buffers needed for RFC822 translation. These are all filled
1739 * using functions that are bounds-checked, and therefore we can
1740 * make them substantially smaller than SIZ.
1747 char datestamp[100];
1749 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1750 ((TheMessage == NULL) ? "NULL" : "not null"),
1751 mode, headers_only, do_proto, crlf);
1753 strcpy(mid, "unknown");
1754 nl = (crlf ? "\r\n" : "\n");
1756 if (!is_valid_message(TheMessage)) {
1757 CtdlLogPrintf(CTDL_ERR,
1758 "ERROR: invalid preloaded message for output\n");
1760 return(om_no_such_msg);
1763 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1764 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1766 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
1767 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
1770 /* Are we downloading a MIME component? */
1771 if (mode == MT_DOWNLOAD) {
1772 if (TheMessage->cm_format_type != FMT_RFC822) {
1774 cprintf("%d This is not a MIME message.\n",
1775 ERROR + ILLEGAL_VALUE);
1776 } else if (CC->download_fp != NULL) {
1777 if (do_proto) cprintf(
1778 "%d You already have a download open.\n",
1779 ERROR + RESOURCE_BUSY);
1781 /* Parse the message text component */
1782 mptr = TheMessage->cm_fields['M'];
1783 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1784 /* If there's no file open by this time, the requested
1785 * section wasn't found, so print an error
1787 if (CC->download_fp == NULL) {
1788 if (do_proto) cprintf(
1789 "%d Section %s not found.\n",
1790 ERROR + FILE_NOT_FOUND,
1791 CC->download_desired_section);
1794 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1797 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1798 * in a single server operation instead of opening a download file.
1800 if (mode == MT_SPEW_SECTION) {
1801 if (TheMessage->cm_format_type != FMT_RFC822) {
1803 cprintf("%d This is not a MIME message.\n",
1804 ERROR + ILLEGAL_VALUE);
1806 /* Parse the message text component */
1809 mptr = TheMessage->cm_fields['M'];
1810 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1811 /* If section wasn't found, print an error
1814 if (do_proto) cprintf(
1815 "%d Section %s not found.\n",
1816 ERROR + FILE_NOT_FOUND,
1817 CC->download_desired_section);
1820 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1823 /* now for the user-mode message reading loops */
1824 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1826 /* Does the caller want to skip the headers? */
1827 if (headers_only == HEADERS_NONE) goto START_TEXT;
1829 /* Tell the client which format type we're using. */
1830 if ( (mode == MT_CITADEL) && (do_proto) ) {
1831 cprintf("type=%d\n", TheMessage->cm_format_type);
1834 /* nhdr=yes means that we're only displaying headers, no body */
1835 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1836 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1839 cprintf("nhdr=yes\n");
1842 /* begin header processing loop for Citadel message format */
1844 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1846 safestrncpy(display_name, "<unknown>", sizeof display_name);
1847 if (TheMessage->cm_fields['A']) {
1848 strcpy(buf, TheMessage->cm_fields['A']);
1849 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1850 safestrncpy(display_name, "****", sizeof display_name);
1852 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1853 safestrncpy(display_name, "anonymous", sizeof display_name);
1856 safestrncpy(display_name, buf, sizeof display_name);
1858 if ((is_room_aide())
1859 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1860 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1861 size_t tmp = strlen(display_name);
1862 snprintf(&display_name[tmp],
1863 sizeof display_name - tmp,
1868 /* Don't show Internet address for users on the
1869 * local Citadel network.
1872 if (TheMessage->cm_fields['N'] != NULL)
1873 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1874 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1878 /* Now spew the header fields in the order we like them. */
1879 safestrncpy(allkeys, FORDER, sizeof allkeys);
1880 for (i=0; i<strlen(allkeys); ++i) {
1881 k = (int) allkeys[i];
1883 if ( (TheMessage->cm_fields[k] != NULL)
1884 && (msgkeys[k] != NULL) ) {
1885 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1886 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1889 if (do_proto) cprintf("%s=%s\n",
1893 else if ((k == 'F') && (suppress_f)) {
1896 /* Masquerade display name if needed */
1898 if (do_proto) cprintf("%s=%s\n",
1900 TheMessage->cm_fields[k]
1909 /* begin header processing loop for RFC822 transfer format */
1914 strcpy(snode, NODENAME);
1915 if (mode == MT_RFC822) {
1916 for (i = 0; i < 256; ++i) {
1917 if (TheMessage->cm_fields[i]) {
1918 mptr = mpptr = TheMessage->cm_fields[i];
1921 safestrncpy(luser, mptr, sizeof luser);
1922 safestrncpy(suser, mptr, sizeof suser);
1924 else if (i == 'Y') {
1925 if ((flags & QP_EADDR) != 0) {
1926 mptr = qp_encode_email_addrs(mptr);
1928 sanitize_truncated_recipient(mptr);
1929 cprintf("CC: %s%s", mptr, nl);
1931 else if (i == 'P') {
1932 cprintf("Return-Path: %s%s", mptr, nl);
1934 else if (i == 'L') {
1935 cprintf("List-ID: %s%s", mptr, nl);
1937 else if (i == 'V') {
1938 if ((flags & QP_EADDR) != 0)
1939 mptr = qp_encode_email_addrs(mptr);
1940 cprintf("Envelope-To: %s%s", mptr, nl);
1942 else if (i == 'U') {
1943 cprintf("Subject: %s%s", mptr, nl);
1947 safestrncpy(mid, mptr, sizeof mid);
1949 safestrncpy(fuser, mptr, sizeof fuser);
1950 /* else if (i == 'O')
1951 cprintf("X-Citadel-Room: %s%s",
1954 safestrncpy(snode, mptr, sizeof snode);
1957 if (haschar(mptr, '@') == 0)
1959 sanitize_truncated_recipient(mptr);
1960 cprintf("To: %s@%s", mptr, config.c_fqdn);
1965 if ((flags & QP_EADDR) != 0) {
1966 mptr = qp_encode_email_addrs(mptr);
1968 sanitize_truncated_recipient(mptr);
1969 cprintf("To: %s", mptr);
1973 else if (i == 'T') {
1974 datestring(datestamp, sizeof datestamp,
1975 atol(mptr), DATESTRING_RFC822);
1976 cprintf("Date: %s%s", datestamp, nl);
1978 else if (i == 'W') {
1979 cprintf("References: ");
1980 k = num_tokens(mptr, '|');
1981 for (j=0; j<k; ++j) {
1982 extract_token(buf, mptr, j, '|', sizeof buf);
1983 cprintf("<%s>", buf);
1996 if (subject_found == 0) {
1997 cprintf("Subject: (no subject)%s", nl);
2001 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2002 suser[i] = tolower(suser[i]);
2003 if (!isalnum(suser[i])) suser[i]='_';
2006 if (mode == MT_RFC822) {
2007 if (!strcasecmp(snode, NODENAME)) {
2008 safestrncpy(snode, FQDN, sizeof snode);
2011 /* Construct a fun message id */
2012 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2013 if (strchr(mid, '@')==NULL) {
2014 cprintf("@%s", snode);
2018 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2019 cprintf("From: \"----\" <x@x.org>%s", nl);
2021 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2022 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2024 else if (!IsEmptyStr(fuser)) {
2025 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2028 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2031 /* Blank line signifying RFC822 end-of-headers */
2032 if (TheMessage->cm_format_type != FMT_RFC822) {
2037 /* end header processing loop ... at this point, we're in the text */
2039 if (headers_only == HEADERS_FAST) goto DONE;
2040 mptr = TheMessage->cm_fields['M'];
2042 /* Tell the client about the MIME parts in this message */
2043 if (TheMessage->cm_format_type == FMT_RFC822) {
2044 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2045 memset(&ma, 0, sizeof(struct ma_info));
2046 mime_parser(mptr, NULL,
2047 (do_proto ? *list_this_part : NULL),
2048 (do_proto ? *list_this_pref : NULL),
2049 (do_proto ? *list_this_suff : NULL),
2052 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2054 const char *StartOfText = StrBufNOTNULL;
2058 int nllen = strlen(nl);
2060 while (*mptr != '\0') {
2061 if (*mptr == '\r') {
2068 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2070 eoh = *(mptr+1) == '\n';
2074 StartOfText = strchr(StartOfText, '\n');
2075 StartOfText = strchr(StartOfText, '\n');
2078 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2079 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2080 ((headers_only != HEADERS_NONE) &&
2081 (headers_only != HEADERS_ONLY))
2083 if (*mptr == '\n') {
2084 memcpy(&outbuf[outlen], nl, nllen);
2086 outbuf[outlen] = '\0';
2089 outbuf[outlen++] = *mptr;
2093 if (flags & ESC_DOT)
2095 if ((prev_ch == '\n') &&
2097 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2099 outbuf[outlen++] = '.';
2104 if (outlen > 1000) {
2105 client_write(outbuf, outlen);
2110 client_write(outbuf, outlen);
2118 if (headers_only == HEADERS_ONLY) {
2122 /* signify start of msg text */
2123 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2124 if (do_proto) cprintf("text\n");
2127 /* If the format type on disk is 1 (fixed-format), then we want
2128 * everything to be output completely literally ... regardless of
2129 * what message transfer format is in use.
2131 if (TheMessage->cm_format_type == FMT_FIXED) {
2134 int nllen = strlen (nl);
2135 if (mode == MT_MIME) {
2136 cprintf("Content-type: text/plain\n\n");
2140 while (ch = *mptr++, ch > 0) {
2144 if ((buflen > 250) && (!xlline)){
2148 while ((buflen > 0) &&
2149 (!isspace(buf[buflen])))
2155 mptr -= tbuflen - buflen;
2160 /* if we reach the outer bounds of our buffer,
2161 abort without respect what whe purge. */
2164 (buflen > SIZ - nllen - 2)))
2168 memcpy (&buf[buflen], nl, nllen);
2172 client_write(buf, buflen);
2182 if (!IsEmptyStr(buf))
2183 cprintf("%s%s", buf, nl);
2186 /* If the message on disk is format 0 (Citadel vari-format), we
2187 * output using the formatter at 80 columns. This is the final output
2188 * form if the transfer format is RFC822, but if the transfer format
2189 * is Citadel proprietary, it'll still work, because the indentation
2190 * for new paragraphs is correct and the client will reformat the
2191 * message to the reader's screen width.
2193 if (TheMessage->cm_format_type == FMT_CITADEL) {
2194 if (mode == MT_MIME) {
2195 cprintf("Content-type: text/x-citadel-variformat\n\n");
2200 /* If the message on disk is format 4 (MIME), we've gotta hand it
2201 * off to the MIME parser. The client has already been told that
2202 * this message is format 1 (fixed format), so the callback function
2203 * we use will display those parts as-is.
2205 if (TheMessage->cm_format_type == FMT_RFC822) {
2206 memset(&ma, 0, sizeof(struct ma_info));
2208 if (mode == MT_MIME) {
2209 ma.use_fo_hooks = 0;
2210 strcpy(ma.chosen_part, "1");
2211 ma.chosen_pref = 9999;
2212 mime_parser(mptr, NULL,
2213 *choose_preferred, *fixed_output_pre,
2214 *fixed_output_post, (void *)&ma, 0);
2215 mime_parser(mptr, NULL,
2216 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2219 ma.use_fo_hooks = 1;
2220 mime_parser(mptr, NULL,
2221 *fixed_output, *fixed_output_pre,
2222 *fixed_output_post, (void *)&ma, 0);
2227 DONE: /* now we're done */
2228 if (do_proto) cprintf("000\n");
2233 #else /* NEW_COPLM */
2236 * Get a message off disk. (returns om_* values found in msgbase.h)
2237 * THIS IS FROM r8422 AND WILL BE ELIMINATED ONCE THE NEW VERSION IS DEBUGGED
2239 int CtdlOutputPreLoadedMsg(
2240 struct CtdlMessage *TheMessage,
2241 int mode, /* how would you like that message? */
2242 int headers_only, /* eschew the message body? */
2243 int do_proto, /* do Citadel protocol responses? */
2244 int crlf, /* Use CRLF newlines instead of LF? */
2245 int flags /* should the bessage be exported clean? */
2249 cit_uint8_t ch, prev_ch;
2251 char display_name[256];
2253 const char *nl; /* newline string */
2255 int subject_found = 0;
2258 /* Buffers needed for RFC822 translation. These are all filled
2259 * using functions that are bounds-checked, and therefore we can
2260 * make them substantially smaller than SIZ.
2267 char datestamp[100];
2269 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2270 ((TheMessage == NULL) ? "NULL" : "not null"),
2271 mode, headers_only, do_proto, crlf);
2273 strcpy(mid, "unknown");
2274 nl = (crlf ? "\r\n" : "\n");
2276 if (!is_valid_message(TheMessage)) {
2277 CtdlLogPrintf(CTDL_ERR,
2278 "ERROR: invalid preloaded message for output\n");
2280 return(om_no_such_msg);
2283 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2284 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2286 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2287 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2290 /* Are we downloading a MIME component? */
2291 if (mode == MT_DOWNLOAD) {
2292 if (TheMessage->cm_format_type != FMT_RFC822) {
2294 cprintf("%d This is not a MIME message.\n",
2295 ERROR + ILLEGAL_VALUE);
2296 } else if (CC->download_fp != NULL) {
2297 if (do_proto) cprintf(
2298 "%d You already have a download open.\n",
2299 ERROR + RESOURCE_BUSY);
2301 /* Parse the message text component */
2302 mptr = TheMessage->cm_fields['M'];
2303 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2304 /* If there's no file open by this time, the requested
2305 * section wasn't found, so print an error
2307 if (CC->download_fp == NULL) {
2308 if (do_proto) cprintf(
2309 "%d Section %s not found.\n",
2310 ERROR + FILE_NOT_FOUND,
2311 CC->download_desired_section);
2314 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2317 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2318 * in a single server operation instead of opening a download file.
2320 if (mode == MT_SPEW_SECTION) {
2321 if (TheMessage->cm_format_type != FMT_RFC822) {
2323 cprintf("%d This is not a MIME message.\n",
2324 ERROR + ILLEGAL_VALUE);
2326 /* Parse the message text component */
2329 mptr = TheMessage->cm_fields['M'];
2330 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2331 /* If section wasn't found, print an error
2334 if (do_proto) cprintf(
2335 "%d Section %s not found.\n",
2336 ERROR + FILE_NOT_FOUND,
2337 CC->download_desired_section);
2340 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2343 /* now for the user-mode message reading loops */
2344 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2346 /* Does the caller want to skip the headers? */
2347 if (headers_only == HEADERS_NONE) goto START_TEXT;
2349 /* Tell the client which format type we're using. */
2350 if ( (mode == MT_CITADEL) && (do_proto) ) {
2351 cprintf("type=%d\n", TheMessage->cm_format_type);
2354 /* nhdr=yes means that we're only displaying headers, no body */
2355 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2356 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2359 cprintf("nhdr=yes\n");
2362 /* begin header processing loop for Citadel message format */
2364 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
2366 safestrncpy(display_name, "<unknown>", sizeof display_name);
2367 if (TheMessage->cm_fields['A']) {
2368 strcpy(buf, TheMessage->cm_fields['A']);
2369 if (TheMessage->cm_anon_type == MES_ANONONLY) {
2370 safestrncpy(display_name, "****", sizeof display_name);
2372 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
2373 safestrncpy(display_name, "anonymous", sizeof display_name);
2376 safestrncpy(display_name, buf, sizeof display_name);
2378 if ((is_room_aide())
2379 && ((TheMessage->cm_anon_type == MES_ANONONLY)
2380 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
2381 size_t tmp = strlen(display_name);
2382 snprintf(&display_name[tmp],
2383 sizeof display_name - tmp,
2388 /* Don't show Internet address for users on the
2389 * local Citadel network.
2392 if (TheMessage->cm_fields['N'] != NULL)
2393 if (!IsEmptyStr(TheMessage->cm_fields['N']))
2394 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
2398 /* Now spew the header fields in the order we like them. */
2399 safestrncpy(allkeys, FORDER, sizeof allkeys);
2400 for (i=0; i<strlen(allkeys); ++i) {
2401 k = (int) allkeys[i];
2403 if ( (TheMessage->cm_fields[k] != NULL)
2404 && (msgkeys[k] != NULL) ) {
2405 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
2406 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
2409 if (do_proto) cprintf("%s=%s\n",
2413 else if ((k == 'F') && (suppress_f)) {
2416 /* Masquerade display name if needed */
2418 if (do_proto) cprintf("%s=%s\n",
2420 TheMessage->cm_fields[k]
2429 /* begin header processing loop for RFC822 transfer format */
2434 strcpy(snode, NODENAME);
2435 if (mode == MT_RFC822) {
2436 for (i = 0; i < 256; ++i) {
2437 if (TheMessage->cm_fields[i]) {
2438 mptr = mpptr = TheMessage->cm_fields[i];
2441 safestrncpy(luser, mptr, sizeof luser);
2442 safestrncpy(suser, mptr, sizeof suser);
2444 else if (i == 'Y') {
2445 if ((flags & QP_EADDR) != 0) {
2446 mptr = qp_encode_email_addrs(mptr);
2448 sanitize_truncated_recipient(mptr);
2449 cprintf("CC: %s%s", mptr, nl);
2451 else if (i == 'P') {
2452 cprintf("Return-Path: %s%s", mptr, nl);
2454 else if (i == 'L') {
2455 cprintf("List-ID: %s%s", mptr, nl);
2457 else if (i == 'V') {
2458 if ((flags & QP_EADDR) != 0)
2459 mptr = qp_encode_email_addrs(mptr);
2460 cprintf("Envelope-To: %s%s", mptr, nl);
2462 else if (i == 'U') {
2463 cprintf("Subject: %s%s", mptr, nl);
2467 safestrncpy(mid, mptr, sizeof mid);
2469 safestrncpy(fuser, mptr, sizeof fuser);
2470 /* else if (i == 'O')
2471 cprintf("X-Citadel-Room: %s%s",
2474 safestrncpy(snode, mptr, sizeof snode);
2477 if (haschar(mptr, '@') == 0)
2479 sanitize_truncated_recipient(mptr);
2480 cprintf("To: %s@%s", mptr, config.c_fqdn);
2485 if ((flags & QP_EADDR) != 0) {
2486 mptr = qp_encode_email_addrs(mptr);
2488 sanitize_truncated_recipient(mptr);
2489 cprintf("To: %s", mptr);
2493 else if (i == 'T') {
2494 datestring(datestamp, sizeof datestamp,
2495 atol(mptr), DATESTRING_RFC822);
2496 cprintf("Date: %s%s", datestamp, nl);
2498 else if (i == 'W') {
2499 cprintf("References: ");
2500 k = num_tokens(mptr, '|');
2501 for (j=0; j<k; ++j) {
2502 extract_token(buf, mptr, j, '|', sizeof buf);
2503 cprintf("<%s>", buf);
2516 if (subject_found == 0) {
2517 cprintf("Subject: (no subject)%s", nl);
2521 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2522 suser[i] = tolower(suser[i]);
2523 if (!isalnum(suser[i])) suser[i]='_';
2526 if (mode == MT_RFC822) {
2527 if (!strcasecmp(snode, NODENAME)) {
2528 safestrncpy(snode, FQDN, sizeof snode);
2531 /* Construct a fun message id */
2532 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2533 if (strchr(mid, '@')==NULL) {
2534 cprintf("@%s", snode);
2538 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2539 cprintf("From: \"----\" <x@x.org>%s", nl);
2541 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2542 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2544 else if (!IsEmptyStr(fuser)) {
2545 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2548 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2551 /* Blank line signifying RFC822 end-of-headers */
2552 if (TheMessage->cm_format_type != FMT_RFC822) {
2557 /* end header processing loop ... at this point, we're in the text */
2559 if (headers_only == HEADERS_FAST) goto DONE;
2560 mptr = TheMessage->cm_fields['M'];
2562 /* Tell the client about the MIME parts in this message */
2563 if (TheMessage->cm_format_type == FMT_RFC822) {
2564 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2565 memset(&ma, 0, sizeof(struct ma_info));
2566 mime_parser(mptr, NULL,
2567 (do_proto ? *list_this_part : NULL),
2568 (do_proto ? *list_this_pref : NULL),
2569 (do_proto ? *list_this_suff : NULL),
2572 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2573 char *start_of_text = NULL;
2574 start_of_text = strstr(mptr, "\n\r\n");
2575 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
2576 if (start_of_text == NULL) start_of_text = mptr;
2578 start_of_text = strstr(start_of_text, "\n");
2583 int nllen = strlen(nl);
2585 while (ch=*mptr, ch!=0) {
2591 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2592 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2593 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2596 sprintf(&outbuf[outlen], "%s", nl);
2600 outbuf[outlen++] = ch;
2604 if (flags & ESC_DOT)
2606 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2608 outbuf[outlen++] = '.';
2613 if (outlen > 1000) {
2614 client_write(outbuf, outlen);
2619 client_write(outbuf, outlen);
2627 if (headers_only == HEADERS_ONLY) {
2631 /* signify start of msg text */
2632 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2633 if (do_proto) cprintf("text\n");
2636 /* If the format type on disk is 1 (fixed-format), then we want
2637 * everything to be output completely literally ... regardless of
2638 * what message transfer format is in use.
2640 if (TheMessage->cm_format_type == FMT_FIXED) {
2643 int nllen = strlen (nl);
2644 if (mode == MT_MIME) {
2645 cprintf("Content-type: text/plain\n\n");
2649 while (ch = *mptr++, ch > 0) {
2653 if ((buflen > 250) && (!xlline)){
2657 while ((buflen > 0) &&
2658 (!isspace(buf[buflen])))
2664 mptr -= tbuflen - buflen;
2669 /* if we reach the outer bounds of our buffer,
2670 abort without respect what whe purge. */
2673 (buflen > SIZ - nllen - 2)))
2677 memcpy (&buf[buflen], nl, nllen);
2681 client_write(buf, buflen);
2691 if (!IsEmptyStr(buf))
2692 cprintf("%s%s", buf, nl);
2695 /* If the message on disk is format 0 (Citadel vari-format), we
2696 * output using the formatter at 80 columns. This is the final output
2697 * form if the transfer format is RFC822, but if the transfer format
2698 * is Citadel proprietary, it'll still work, because the indentation
2699 * for new paragraphs is correct and the client will reformat the
2700 * message to the reader's screen width.
2702 if (TheMessage->cm_format_type == FMT_CITADEL) {
2703 if (mode == MT_MIME) {
2704 cprintf("Content-type: text/x-citadel-variformat\n\n");
2709 /* If the message on disk is format 4 (MIME), we've gotta hand it
2710 * off to the MIME parser. The client has already been told that
2711 * this message is format 1 (fixed format), so the callback function
2712 * we use will display those parts as-is.
2714 if (TheMessage->cm_format_type == FMT_RFC822) {
2715 memset(&ma, 0, sizeof(struct ma_info));
2717 if (mode == MT_MIME) {
2718 ma.use_fo_hooks = 0;
2719 strcpy(ma.chosen_part, "1");
2720 ma.chosen_pref = 9999;
2721 mime_parser(mptr, NULL,
2722 *choose_preferred, *fixed_output_pre,
2723 *fixed_output_post, (void *)&ma, 0);
2724 mime_parser(mptr, NULL,
2725 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2728 ma.use_fo_hooks = 1;
2729 mime_parser(mptr, NULL,
2730 *fixed_output, *fixed_output_pre,
2731 *fixed_output_post, (void *)&ma, 0);
2736 DONE: /* now we're done */
2737 if (do_proto) cprintf("000\n");
2741 #endif /* NEW_COPLM */
2745 * display a message (mode 0 - Citadel proprietary)
2747 void cmd_msg0(char *cmdbuf)
2750 int headers_only = HEADERS_ALL;
2752 msgid = extract_long(cmdbuf, 0);
2753 headers_only = extract_int(cmdbuf, 1);
2755 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2761 * display a message (mode 2 - RFC822)
2763 void cmd_msg2(char *cmdbuf)
2766 int headers_only = HEADERS_ALL;
2768 msgid = extract_long(cmdbuf, 0);
2769 headers_only = extract_int(cmdbuf, 1);
2771 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2777 * display a message (mode 3 - IGnet raw format - internal programs only)
2779 void cmd_msg3(char *cmdbuf)
2782 struct CtdlMessage *msg = NULL;
2785 if (CC->internal_pgm == 0) {
2786 cprintf("%d This command is for internal programs only.\n",
2787 ERROR + HIGHER_ACCESS_REQUIRED);
2791 msgnum = extract_long(cmdbuf, 0);
2792 msg = CtdlFetchMessage(msgnum, 1);
2794 cprintf("%d Message %ld not found.\n",
2795 ERROR + MESSAGE_NOT_FOUND, msgnum);
2799 serialize_message(&smr, msg);
2800 CtdlFreeMessage(msg);
2803 cprintf("%d Unable to serialize message\n",
2804 ERROR + INTERNAL_ERROR);
2808 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2809 client_write((char *)smr.ser, (int)smr.len);
2816 * Display a message using MIME content types
2818 void cmd_msg4(char *cmdbuf)
2823 msgid = extract_long(cmdbuf, 0);
2824 extract_token(section, cmdbuf, 1, '|', sizeof section);
2825 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2831 * Client tells us its preferred message format(s)
2833 void cmd_msgp(char *cmdbuf)
2835 if (!strcasecmp(cmdbuf, "dont_decode")) {
2836 CC->msg4_dont_decode = 1;
2837 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2840 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2841 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2847 * Open a component of a MIME message as a download file
2849 void cmd_opna(char *cmdbuf)
2852 char desired_section[128];
2854 msgid = extract_long(cmdbuf, 0);
2855 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2856 safestrncpy(CC->download_desired_section, desired_section,
2857 sizeof CC->download_desired_section);
2858 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2863 * Open a component of a MIME message and transmit it all at once
2865 void cmd_dlat(char *cmdbuf)
2868 char desired_section[128];
2870 msgid = extract_long(cmdbuf, 0);
2871 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2872 safestrncpy(CC->download_desired_section, desired_section,
2873 sizeof CC->download_desired_section);
2874 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2879 * Save one or more message pointers into a specified room
2880 * (Returns 0 for success, nonzero for failure)
2881 * roomname may be NULL to use the current room
2883 * Note that the 'supplied_msg' field may be set to NULL, in which case
2884 * the message will be fetched from disk, by number, if we need to perform
2885 * replication checks. This adds an additional database read, so if the
2886 * caller already has the message in memory then it should be supplied. (Obviously
2887 * this mode of operation only works if we're saving a single message.)
2889 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2890 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2893 char hold_rm[ROOMNAMELEN];
2894 struct cdbdata *cdbfr;
2897 long highest_msg = 0L;
2900 struct CtdlMessage *msg = NULL;
2902 long *msgs_to_be_merged = NULL;
2903 int num_msgs_to_be_merged = 0;
2905 CtdlLogPrintf(CTDL_DEBUG,
2906 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2907 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2910 strcpy(hold_rm, CC->room.QRname);
2913 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2914 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2915 if (num_newmsgs > 1) supplied_msg = NULL;
2917 /* Now the regular stuff */
2918 if (CtdlGetRoomLock(&CC->room,
2919 ((roomname != NULL) ? roomname : CC->room.QRname) )
2921 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2922 return(ERROR + ROOM_NOT_FOUND);
2926 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2927 num_msgs_to_be_merged = 0;
2930 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2931 if (cdbfr == NULL) {
2935 msglist = (long *) cdbfr->ptr;
2936 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2937 num_msgs = cdbfr->len / sizeof(long);
2942 /* Create a list of msgid's which were supplied by the caller, but do
2943 * not already exist in the target room. It is absolutely taboo to
2944 * have more than one reference to the same message in a room.
2946 for (i=0; i<num_newmsgs; ++i) {
2948 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2949 if (msglist[j] == newmsgidlist[i]) {
2954 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2958 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2961 * Now merge the new messages
2963 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2964 if (msglist == NULL) {
2965 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2967 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2968 num_msgs += num_msgs_to_be_merged;
2970 /* Sort the message list, so all the msgid's are in order */
2971 num_msgs = sort_msglist(msglist, num_msgs);
2973 /* Determine the highest message number */
2974 highest_msg = msglist[num_msgs - 1];
2976 /* Write it back to disk. */
2977 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2978 msglist, (int)(num_msgs * sizeof(long)));
2980 /* Free up the memory we used. */
2983 /* Update the highest-message pointer and unlock the room. */
2984 CC->room.QRhighest = highest_msg;
2985 CtdlPutRoomLock(&CC->room);
2987 /* Perform replication checks if necessary */
2988 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2989 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2991 for (i=0; i<num_msgs_to_be_merged; ++i) {
2992 msgid = msgs_to_be_merged[i];
2994 if (supplied_msg != NULL) {
2998 msg = CtdlFetchMessage(msgid, 0);
3002 ReplicationChecks(msg);
3004 /* If the message has an Exclusive ID, index that... */
3005 if (msg->cm_fields['E'] != NULL) {
3006 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
3009 /* Free up the memory we may have allocated */
3010 if (msg != supplied_msg) {
3011 CtdlFreeMessage(msg);
3019 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
3022 /* Submit this room for processing by hooks */
3023 PerformRoomHooks(&CC->room);
3025 /* Go back to the room we were in before we wandered here... */
3026 CtdlGetRoom(&CC->room, hold_rm);
3028 /* Bump the reference count for all messages which were merged */
3029 if (!suppress_refcount_adj) {
3030 for (i=0; i<num_msgs_to_be_merged; ++i) {
3031 AdjRefCount(msgs_to_be_merged[i], +1);
3035 /* Free up memory... */
3036 if (msgs_to_be_merged != NULL) {
3037 free(msgs_to_be_merged);
3040 /* Return success. */
3046 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
3049 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
3050 int do_repl_check, struct CtdlMessage *supplied_msg)
3052 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
3059 * Message base operation to save a new message to the message store
3060 * (returns new message number)
3062 * This is the back end for CtdlSubmitMsg() and should not be directly
3063 * called by server-side modules.
3066 long send_message(struct CtdlMessage *msg) {
3074 /* Get a new message number */
3075 newmsgid = get_new_message_number();
3076 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
3078 /* Generate an ID if we don't have one already */
3079 if (msg->cm_fields['I']==NULL) {
3080 msg->cm_fields['I'] = strdup(msgidbuf);
3083 /* If the message is big, set its body aside for storage elsewhere */
3084 if (msg->cm_fields['M'] != NULL) {
3085 if (strlen(msg->cm_fields['M']) > BIGMSG) {
3087 holdM = msg->cm_fields['M'];
3088 msg->cm_fields['M'] = NULL;
3092 /* Serialize our data structure for storage in the database */
3093 serialize_message(&smr, msg);
3096 msg->cm_fields['M'] = holdM;
3100 cprintf("%d Unable to serialize message\n",
3101 ERROR + INTERNAL_ERROR);
3105 /* Write our little bundle of joy into the message base */
3106 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
3107 smr.ser, smr.len) < 0) {
3108 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
3112 cdb_store(CDB_BIGMSGS,
3122 /* Free the memory we used for the serialized message */
3125 /* Return the *local* message ID to the caller
3126 * (even if we're storing an incoming network message)
3134 * Serialize a struct CtdlMessage into the format used on disk and network.
3136 * This function loads up a "struct ser_ret" (defined in server.h) which
3137 * contains the length of the serialized message and a pointer to the
3138 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
3140 void serialize_message(struct ser_ret *ret, /* return values */
3141 struct CtdlMessage *msg) /* unserialized msg */
3143 size_t wlen, fieldlen;
3145 static char *forder = FORDER;
3148 * Check for valid message format
3150 if (is_valid_message(msg) == 0) {
3151 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
3158 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
3159 ret->len = ret->len +
3160 strlen(msg->cm_fields[(int)forder[i]]) + 2;
3162 ret->ser = malloc(ret->len);
3163 if (ret->ser == NULL) {
3164 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
3165 (long)ret->len, strerror(errno));
3172 ret->ser[1] = msg->cm_anon_type;
3173 ret->ser[2] = msg->cm_format_type;
3176 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
3177 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
3178 ret->ser[wlen++] = (char)forder[i];
3179 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
3180 wlen = wlen + fieldlen + 1;
3182 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
3183 (long)ret->len, (long)wlen);
3190 * Serialize a struct CtdlMessage into the format used on disk and network.
3192 * This function loads up a "struct ser_ret" (defined in server.h) which
3193 * contains the length of the serialized message and a pointer to the
3194 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
3196 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
3197 long Siz) /* how many chars ? */
3201 static char *forder = FORDER;
3205 * Check for valid message format
3207 if (is_valid_message(msg) == 0) {
3208 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
3212 buf = (char*) malloc (Siz + 1);
3216 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
3217 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
3218 msg->cm_fields[(int)forder[i]]);
3219 client_write (buf, strlen(buf));
3228 * Check to see if any messages already exist in the current room which
3229 * carry the same Exclusive ID as this one. If any are found, delete them.
3231 void ReplicationChecks(struct CtdlMessage *msg) {
3232 long old_msgnum = (-1L);
3234 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
3236 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
3239 /* No exclusive id? Don't do anything. */
3240 if (msg == NULL) return;
3241 if (msg->cm_fields['E'] == NULL) return;
3242 if (IsEmptyStr(msg->cm_fields['E'])) return;
3243 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
3244 msg->cm_fields['E'], CC->room.QRname);*/
3246 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
3247 if (old_msgnum > 0L) {
3248 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
3249 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
3256 * Save a message to disk and submit it into the delivery system.
3258 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
3259 struct recptypes *recps, /* recipients (if mail) */
3260 char *force, /* force a particular room? */
3261 int flags /* should the message be exported clean? */
3263 char submit_filename[128];
3264 char generated_timestamp[32];
3265 char hold_rm[ROOMNAMELEN];
3266 char actual_rm[ROOMNAMELEN];
3267 char force_room[ROOMNAMELEN];
3268 char content_type[SIZ]; /* We have to learn this */
3269 char recipient[SIZ];
3271 const char *mptr = NULL;
3272 struct ctdluser userbuf;
3274 struct MetaData smi;
3275 FILE *network_fp = NULL;
3276 static int seqnum = 1;
3277 struct CtdlMessage *imsg = NULL;
3279 size_t instr_alloc = 0;
3281 char *hold_R, *hold_D;
3282 char *collected_addresses = NULL;
3283 struct addresses_to_be_filed *aptr = NULL;
3284 char *saved_rfc822_version = NULL;
3285 int qualified_for_journaling = 0;
3286 CitContext *CCC = CC; /* CachedCitContext - performance boost */
3287 char bounce_to[1024] = "";
3291 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
3292 if (is_valid_message(msg) == 0) return(-1); /* self check */
3294 /* If this message has no timestamp, we take the liberty of
3295 * giving it one, right now.
3297 if (msg->cm_fields['T'] == NULL) {
3298 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
3299 msg->cm_fields['T'] = strdup(generated_timestamp);
3302 /* If this message has no path, we generate one.
3304 if (msg->cm_fields['P'] == NULL) {
3305 if (msg->cm_fields['A'] != NULL) {
3306 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
3307 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
3308 if (isspace(msg->cm_fields['P'][a])) {
3309 msg->cm_fields['P'][a] = ' ';
3314 msg->cm_fields['P'] = strdup("unknown");
3318 if (force == NULL) {
3319 strcpy(force_room, "");
3322 strcpy(force_room, force);
3325 /* Learn about what's inside, because it's what's inside that counts */
3326 if (msg->cm_fields['M'] == NULL) {
3327 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
3331 switch (msg->cm_format_type) {
3333 strcpy(content_type, "text/x-citadel-variformat");
3336 strcpy(content_type, "text/plain");
3339 strcpy(content_type, "text/plain");
3340 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
3343 safestrncpy(content_type, &mptr[13], sizeof content_type);
3344 striplt(content_type);
3345 aptr = content_type;
3346 while (!IsEmptyStr(aptr)) {
3358 /* Goto the correct room */
3359 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
3360 strcpy(hold_rm, CCC->room.QRname);
3361 strcpy(actual_rm, CCC->room.QRname);
3362 if (recps != NULL) {
3363 strcpy(actual_rm, SENTITEMS);
3366 /* If the user is a twit, move to the twit room for posting */
3368 if (CCC->user.axlevel == AxProbU) {
3369 strcpy(hold_rm, actual_rm);
3370 strcpy(actual_rm, config.c_twitroom);
3371 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
3375 /* ...or if this message is destined for Aide> then go there. */
3376 if (!IsEmptyStr(force_room)) {
3377 strcpy(actual_rm, force_room);
3380 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
3381 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3382 /* CtdlGetRoom(&CCC->room, actual_rm); */
3383 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3387 * If this message has no O (room) field, generate one.
3389 if (msg->cm_fields['O'] == NULL) {
3390 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3393 /* Perform "before save" hooks (aborting if any return nonzero) */
3394 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
3395 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3398 * If this message has an Exclusive ID, and the room is replication
3399 * checking enabled, then do replication checks.
3401 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3402 ReplicationChecks(msg);
3405 /* Save it to disk */
3406 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
3407 newmsgid = send_message(msg);
3408 if (newmsgid <= 0L) return(-5);
3410 /* Write a supplemental message info record. This doesn't have to
3411 * be a critical section because nobody else knows about this message
3414 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
3415 memset(&smi, 0, sizeof(struct MetaData));
3416 smi.meta_msgnum = newmsgid;
3417 smi.meta_refcount = 0;
3418 safestrncpy(smi.meta_content_type, content_type,
3419 sizeof smi.meta_content_type);
3422 * Measure how big this message will be when rendered as RFC822.
3423 * We do this for two reasons:
3424 * 1. We need the RFC822 length for the new metadata record, so the
3425 * POP and IMAP services don't have to calculate message lengths
3426 * while the user is waiting (multiplied by potentially hundreds
3427 * or thousands of messages).
3428 * 2. If journaling is enabled, we will need an RFC822 version of the
3429 * message to attach to the journalized copy.
3431 if (CCC->redirect_buffer != NULL) {
3432 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3435 CCC->redirect_buffer = malloc(SIZ);
3436 CCC->redirect_len = 0;
3437 CCC->redirect_alloc = SIZ;
3438 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3439 smi.meta_rfc822_length = CCC->redirect_len;
3440 saved_rfc822_version = CCC->redirect_buffer;
3441 CCC->redirect_buffer = NULL;
3442 CCC->redirect_len = 0;
3443 CCC->redirect_alloc = 0;
3447 /* Now figure out where to store the pointers */
3448 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
3450 /* If this is being done by the networker delivering a private
3451 * message, we want to BYPASS saving the sender's copy (because there
3452 * is no local sender; it would otherwise go to the Trashcan).
3454 if ((!CCC->internal_pgm) || (recps == NULL)) {
3455 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3456 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
3457 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3461 /* For internet mail, drop a copy in the outbound queue room */
3462 if ((recps != NULL) && (recps->num_internet > 0)) {
3463 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3466 /* If other rooms are specified, drop them there too. */
3467 if ((recps != NULL) && (recps->num_room > 0))
3468 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3469 extract_token(recipient, recps->recp_room, i,
3470 '|', sizeof recipient);
3471 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
3472 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3475 /* Bump this user's messages posted counter. */
3476 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
3477 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3478 CCC->user.posted = CCC->user.posted + 1;
3479 CtdlPutUserLock(&CCC->user);
3481 /* Decide where bounces need to be delivered */
3482 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3483 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3485 else if (CCC->logged_in) {
3486 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3489 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3492 /* If this is private, local mail, make a copy in the
3493 * recipient's mailbox and bump the reference count.
3495 if ((recps != NULL) && (recps->num_local > 0))
3496 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3497 extract_token(recipient, recps->recp_local, i,
3498 '|', sizeof recipient);
3499 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
3501 if (CtdlGetUser(&userbuf, recipient) == 0) {
3502 // Add a flag so the Funambol module knows its mail
3503 msg->cm_fields['W'] = strdup(recipient);
3504 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3505 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3506 CtdlBumpNewMailCounter(userbuf.usernum);
3507 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3508 /* Generate a instruction message for the Funambol notification
3509 * server, in the same style as the SMTP queue
3512 instr = malloc(instr_alloc);
3513 snprintf(instr, instr_alloc,
3514 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3516 SPOOLMIME, newmsgid, (long)time(NULL),
3520 imsg = malloc(sizeof(struct CtdlMessage));
3521 memset(imsg, 0, sizeof(struct CtdlMessage));
3522 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3523 imsg->cm_anon_type = MES_NORMAL;
3524 imsg->cm_format_type = FMT_RFC822;
3525 imsg->cm_fields['A'] = strdup("Citadel");
3526 imsg->cm_fields['J'] = strdup("do not journal");
3527 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3528 imsg->cm_fields['W'] = strdup(recipient);
3529 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3530 CtdlFreeMessage(imsg);
3534 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
3535 CtdlSaveMsgPointerInRoom(config.c_aideroom,
3540 /* Perform "after save" hooks */
3541 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
3542 PerformMessageHooks(msg, EVT_AFTERSAVE);
3544 /* For IGnet mail, we have to save a new copy into the spooler for
3545 * each recipient, with the R and D fields set to the recipient and
3546 * destination-node. This has two ugly side effects: all other
3547 * recipients end up being unlisted in this recipient's copy of the
3548 * message, and it has to deliver multiple messages to the same
3549 * node. We'll revisit this again in a year or so when everyone has
3550 * a network spool receiver that can handle the new style messages.
3552 if ((recps != NULL) && (recps->num_ignet > 0))
3553 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3554 extract_token(recipient, recps->recp_ignet, i,
3555 '|', sizeof recipient);
3557 hold_R = msg->cm_fields['R'];
3558 hold_D = msg->cm_fields['D'];
3559 msg->cm_fields['R'] = malloc(SIZ);
3560 msg->cm_fields['D'] = malloc(128);
3561 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3562 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3564 serialize_message(&smr, msg);
3566 snprintf(submit_filename, sizeof submit_filename,
3567 "%s/netmail.%04lx.%04x.%04x",
3569 (long) getpid(), CCC->cs_pid, ++seqnum);
3570 network_fp = fopen(submit_filename, "wb+");
3571 if (network_fp != NULL) {
3572 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3578 free(msg->cm_fields['R']);
3579 free(msg->cm_fields['D']);
3580 msg->cm_fields['R'] = hold_R;
3581 msg->cm_fields['D'] = hold_D;
3584 /* Go back to the room we started from */
3585 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3586 if (strcasecmp(hold_rm, CCC->room.QRname))
3587 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3589 /* For internet mail, generate delivery instructions.
3590 * Yes, this is recursive. Deal with it. Infinite recursion does
3591 * not happen because the delivery instructions message does not
3592 * contain a recipient.
3594 if ((recps != NULL) && (recps->num_internet > 0)) {
3595 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3597 instr = malloc(instr_alloc);
3598 snprintf(instr, instr_alloc,
3599 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3601 SPOOLMIME, newmsgid, (long)time(NULL),
3605 if (recps->envelope_from != NULL) {
3606 tmp = strlen(instr);
3607 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3610 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3611 tmp = strlen(instr);
3612 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3613 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3614 instr_alloc = instr_alloc * 2;
3615 instr = realloc(instr, instr_alloc);
3617 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3620 imsg = malloc(sizeof(struct CtdlMessage));
3621 memset(imsg, 0, sizeof(struct CtdlMessage));
3622 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3623 imsg->cm_anon_type = MES_NORMAL;
3624 imsg->cm_format_type = FMT_RFC822;
3625 imsg->cm_fields['A'] = strdup("Citadel");
3626 imsg->cm_fields['J'] = strdup("do not journal");
3627 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3628 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3629 CtdlFreeMessage(imsg);
3633 * Any addresses to harvest for someone's address book?
3635 if ( (CCC->logged_in) && (recps != NULL) ) {
3636 collected_addresses = harvest_collected_addresses(msg);
3639 if (collected_addresses != NULL) {
3640 aptr = (struct addresses_to_be_filed *)
3641 malloc(sizeof(struct addresses_to_be_filed));
3642 CtdlMailboxName(actual_rm, sizeof actual_rm,
3643 &CCC->user, USERCONTACTSROOM);
3644 aptr->roomname = strdup(actual_rm);
3645 aptr->collected_addresses = collected_addresses;
3646 begin_critical_section(S_ATBF);
3649 end_critical_section(S_ATBF);
3653 * Determine whether this message qualifies for journaling.
3655 if (msg->cm_fields['J'] != NULL) {
3656 qualified_for_journaling = 0;
3659 if (recps == NULL) {
3660 qualified_for_journaling = config.c_journal_pubmsgs;
3662 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3663 qualified_for_journaling = config.c_journal_email;
3666 qualified_for_journaling = config.c_journal_pubmsgs;
3671 * Do we have to perform journaling? If so, hand off the saved
3672 * RFC822 version will be handed off to the journaler for background
3673 * submit. Otherwise, we have to free the memory ourselves.
3675 if (saved_rfc822_version != NULL) {
3676 if (qualified_for_journaling) {
3677 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3680 free(saved_rfc822_version);
3690 void aide_message (char *text, char *subject)
3692 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3697 * Convenience function for generating small administrative messages.
3699 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3700 int format_type, const char *subject)
3702 struct CtdlMessage *msg;
3703 struct recptypes *recp = NULL;
3705 msg = malloc(sizeof(struct CtdlMessage));
3706 memset(msg, 0, sizeof(struct CtdlMessage));
3707 msg->cm_magic = CTDLMESSAGE_MAGIC;
3708 msg->cm_anon_type = MES_NORMAL;
3709 msg->cm_format_type = format_type;
3712 msg->cm_fields['A'] = strdup(from);
3714 else if (fromaddr != NULL) {
3715 msg->cm_fields['A'] = strdup(fromaddr);
3716 if (strchr(msg->cm_fields['A'], '@')) {
3717 *strchr(msg->cm_fields['A'], '@') = 0;
3721 msg->cm_fields['A'] = strdup("Citadel");
3724 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3725 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3726 msg->cm_fields['N'] = strdup(NODENAME);
3728 msg->cm_fields['R'] = strdup(to);
3729 recp = validate_recipients(to, NULL, 0);
3731 if (subject != NULL) {
3732 msg->cm_fields['U'] = strdup(subject);
3734 msg->cm_fields['M'] = strdup(text);
3736 CtdlSubmitMsg(msg, recp, room, 0);
3737 CtdlFreeMessage(msg);
3738 if (recp != NULL) free_recipients(recp);
3744 * Back end function used by CtdlMakeMessage() and similar functions
3746 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3748 size_t maxlen, /* maximum message length */
3749 char *exist, /* if non-null, append to it;
3750 exist is ALWAYS freed */
3751 int crlf, /* CRLF newlines instead of LF */
3752 int *sock /* socket handle or 0 for this session's client socket */
3761 LineBuf = NewStrBufPlain(NULL, SIZ);
3762 if (exist == NULL) {
3763 Message = NewStrBufPlain(NULL, 4 * SIZ);
3766 Message = NewStrBufPlain(exist, -1);
3770 /* Do we need to change leading ".." to "." for SMTP escaping? */
3771 if ((tlen == 1) && (*terminator == '.')) {
3775 /* read in the lines of message text one by one */
3778 if ((CtdlSockGetLine(sock, LineBuf) < 0) ||
3783 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3785 if ((StrLength(LineBuf) == tlen) &&
3786 (!strcmp(ChrPtr(LineBuf), terminator)))
3789 if ( (!flushing) && (!finished) ) {
3791 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3794 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3797 /* Unescape SMTP-style input of two dots at the beginning of the line */
3799 (StrLength(LineBuf) == 2) &&
3800 (!strcmp(ChrPtr(LineBuf), "..")))
3802 StrBufCutLeft(LineBuf, 1);
3805 StrBufAppendBuf(Message, LineBuf, 0);
3808 /* if we've hit the max msg length, flush the rest */
3809 if (StrLength(Message) >= maxlen) flushing = 1;
3811 } while (!finished);
3812 FreeStrBuf(&LineBuf);
3818 * Back end function used by CtdlMakeMessage() and similar functions
3820 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3822 size_t maxlen, /* maximum message length */
3823 char *exist, /* if non-null, append to it;
3824 exist is ALWAYS freed */
3825 int crlf, /* CRLF newlines instead of LF */
3826 int *sock /* socket handle or 0 for this session's client socket */
3831 Message = CtdlReadMessageBodyBuf(terminator,
3837 if (Message == NULL)
3840 return SmashStrBuf(&Message);
3845 * Build a binary message to be saved on disk.
3846 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3847 * will become part of the message. This means you are no longer
3848 * responsible for managing that memory -- it will be freed along with
3849 * the rest of the fields when CtdlFreeMessage() is called.)
3852 struct CtdlMessage *CtdlMakeMessage(
3853 struct ctdluser *author, /* author's user structure */
3854 char *recipient, /* NULL if it's not mail */
3855 char *recp_cc, /* NULL if it's not mail */
3856 char *room, /* room where it's going */
3857 int type, /* see MES_ types in header file */
3858 int format_type, /* variformat, plain text, MIME... */
3859 char *fake_name, /* who we're masquerading as */
3860 char *my_email, /* which of my email addresses to use (empty is ok) */
3861 char *subject, /* Subject (optional) */
3862 char *supplied_euid, /* ...or NULL if this is irrelevant */
3863 char *preformatted_text, /* ...or NULL to read text from client */
3864 char *references /* Thread references */
3866 char dest_node[256];
3868 struct CtdlMessage *msg;
3870 msg = malloc(sizeof(struct CtdlMessage));
3871 memset(msg, 0, sizeof(struct CtdlMessage));
3872 msg->cm_magic = CTDLMESSAGE_MAGIC;
3873 msg->cm_anon_type = type;
3874 msg->cm_format_type = format_type;
3876 /* Don't confuse the poor folks if it's not routed mail. */
3877 strcpy(dest_node, "");
3879 if (recipient != NULL) striplt(recipient);
3880 if (recp_cc != NULL) striplt(recp_cc);
3882 /* Path or Return-Path */
3883 if (my_email == NULL) my_email = "";
3885 if (!IsEmptyStr(my_email)) {
3886 msg->cm_fields['P'] = strdup(my_email);
3889 snprintf(buf, sizeof buf, "%s", author->fullname);
3890 msg->cm_fields['P'] = strdup(buf);
3892 convert_spaces_to_underscores(msg->cm_fields['P']);
3894 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3895 msg->cm_fields['T'] = strdup(buf);
3897 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3898 msg->cm_fields['A'] = strdup(fake_name);
3901 msg->cm_fields['A'] = strdup(author->fullname);
3904 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3905 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3908 msg->cm_fields['O'] = strdup(CC->room.QRname);
3911 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3912 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3914 if ((recipient != NULL) && (recipient[0] != 0)) {
3915 msg->cm_fields['R'] = strdup(recipient);
3917 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3918 msg->cm_fields['Y'] = strdup(recp_cc);
3920 if (dest_node[0] != 0) {
3921 msg->cm_fields['D'] = strdup(dest_node);
3924 if (!IsEmptyStr(my_email)) {
3925 msg->cm_fields['F'] = strdup(my_email);
3927 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3928 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3931 if (subject != NULL) {
3934 length = strlen(subject);
3940 while ((subject[i] != '\0') &&
3941 (IsAscii = isascii(subject[i]) != 0 ))
3944 msg->cm_fields['U'] = strdup(subject);
3945 else /* ok, we've got utf8 in the string. */
3947 msg->cm_fields['U'] = rfc2047encode(subject, length);
3953 if (supplied_euid != NULL) {
3954 msg->cm_fields['E'] = strdup(supplied_euid);
3957 if (references != NULL) {
3958 if (!IsEmptyStr(references)) {
3959 msg->cm_fields['W'] = strdup(references);
3963 if (preformatted_text != NULL) {
3964 msg->cm_fields['M'] = preformatted_text;
3967 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3975 * Check to see whether we have permission to post a message in the current
3976 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3977 * returns 0 on success.
3979 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3981 const char* RemoteIdentifier,
3985 if (!(CC->logged_in) &&
3986 (PostPublic == POST_LOGGED_IN)) {
3987 snprintf(errmsgbuf, n, "Not logged in.");
3988 return (ERROR + NOT_LOGGED_IN);
3990 else if (PostPublic == CHECK_EXISTANCE) {
3991 return (0); // We're Evaling whether a recipient exists
3993 else if (!(CC->logged_in)) {
3995 if ((CC->room.QRflags & QR_READONLY)) {
3996 snprintf(errmsgbuf, n, "Not logged in.");
3997 return (ERROR + NOT_LOGGED_IN);
3999 if (CC->room.QRflags2 & QR2_MODERATED) {
4000 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
4001 return (ERROR + NOT_LOGGED_IN);
4003 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
4008 if (RemoteIdentifier == NULL)
4010 snprintf(errmsgbuf, n, "Need sender to permit access.");
4011 return (ERROR + USERNAME_REQUIRED);
4014 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
4015 begin_critical_section(S_NETCONFIGS);
4016 if (!read_spoolcontrol_file(&sc, filename))
4018 end_critical_section(S_NETCONFIGS);
4019 snprintf(errmsgbuf, n,
4020 "This mailing list only accepts posts from subscribers.");
4021 return (ERROR + NO_SUCH_USER);
4023 end_critical_section(S_NETCONFIGS);
4024 found = is_recipient (sc, RemoteIdentifier);
4025 free_spoolcontrol_struct(&sc);
4030 snprintf(errmsgbuf, n,
4031 "This mailing list only accepts posts from subscribers.");
4032 return (ERROR + NO_SUCH_USER);
4039 if ((CC->user.axlevel < AxProbU)
4040 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
4041 snprintf(errmsgbuf, n, "Need to be validated to enter "
4042 "(except in %s> to sysop)", MAILROOM);
4043 return (ERROR + HIGHER_ACCESS_REQUIRED);
4046 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4047 if (!(ra & UA_POSTALLOWED)) {
4048 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
4049 return (ERROR + HIGHER_ACCESS_REQUIRED);
4052 strcpy(errmsgbuf, "Ok");
4058 * Check to see if the specified user has Internet mail permission
4059 * (returns nonzero if permission is granted)
4061 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
4063 /* Do not allow twits to send Internet mail */
4064 if (who->axlevel <= AxProbU) return(0);
4066 /* Globally enabled? */
4067 if (config.c_restrict == 0) return(1);
4069 /* User flagged ok? */
4070 if (who->flags & US_INTERNET) return(2);
4072 /* Aide level access? */
4073 if (who->axlevel >= AxAideU) return(3);
4075 /* No mail for you! */
4081 * Validate recipients, count delivery types and errors, and handle aliasing
4082 * FIXME check for dupes!!!!!
4084 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
4085 * were specified, or the number of addresses found invalid.
4087 * Caller needs to free the result using free_recipients()
4089 struct recptypes *validate_recipients(const char *supplied_recipients,
4090 const char *RemoteIdentifier,
4092 struct recptypes *ret;
4093 char *recipients = NULL;
4094 char this_recp[256];
4095 char this_recp_cooked[256];
4101 struct ctdluser tempUS;
4102 struct ctdlroom tempQR;
4103 struct ctdlroom tempQR2;
4109 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4110 if (ret == NULL) return(NULL);
4112 /* Set all strings to null and numeric values to zero */
4113 memset(ret, 0, sizeof(struct recptypes));
4115 if (supplied_recipients == NULL) {
4116 recipients = strdup("");
4119 recipients = strdup(supplied_recipients);
4122 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4123 * actually need, but it's healthier for the heap than doing lots of tiny
4124 * realloc() calls instead.
4127 ret->errormsg = malloc(strlen(recipients) + 1024);
4128 ret->recp_local = malloc(strlen(recipients) + 1024);
4129 ret->recp_internet = malloc(strlen(recipients) + 1024);
4130 ret->recp_ignet = malloc(strlen(recipients) + 1024);
4131 ret->recp_room = malloc(strlen(recipients) + 1024);
4132 ret->display_recp = malloc(strlen(recipients) + 1024);
4134 ret->errormsg[0] = 0;
4135 ret->recp_local[0] = 0;
4136 ret->recp_internet[0] = 0;
4137 ret->recp_ignet[0] = 0;
4138 ret->recp_room[0] = 0;
4139 ret->display_recp[0] = 0;
4141 ret->recptypes_magic = RECPTYPES_MAGIC;
4143 /* Change all valid separator characters to commas */
4144 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4145 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4146 recipients[i] = ',';
4150 /* Now start extracting recipients... */
4152 while (!IsEmptyStr(recipients)) {
4154 for (i=0; i<=strlen(recipients); ++i) {
4155 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4156 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4157 safestrncpy(this_recp, recipients, i+1);
4159 if (recipients[i] == ',') {
4160 strcpy(recipients, &recipients[i+1]);
4163 strcpy(recipients, "");
4170 if (IsEmptyStr(this_recp))
4172 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4174 mailtype = alias(this_recp);
4175 mailtype = alias(this_recp);
4176 mailtype = alias(this_recp);
4178 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
4179 if (this_recp[j]=='_') {
4180 this_recp_cooked[j] = ' ';
4183 this_recp_cooked[j] = this_recp[j];
4186 this_recp_cooked[j] = '\0';
4191 if (!strcasecmp(this_recp, "sysop")) {
4193 strcpy(this_recp, config.c_aideroom);
4194 if (!IsEmptyStr(ret->recp_room)) {
4195 strcat(ret->recp_room, "|");
4197 strcat(ret->recp_room, this_recp);
4199 else if ( (!strncasecmp(this_recp, "room_", 5))
4200 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4202 /* Save room so we can restore it later */
4206 /* Check permissions to send mail to this room */
4207 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
4219 if (!IsEmptyStr(ret->recp_room)) {
4220 strcat(ret->recp_room, "|");
4222 strcat(ret->recp_room, &this_recp_cooked[5]);
4225 /* Restore room in case something needs it */
4229 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4231 strcpy(this_recp, tempUS.fullname);
4232 if (!IsEmptyStr(ret->recp_local)) {
4233 strcat(ret->recp_local, "|");
4235 strcat(ret->recp_local, this_recp);
4237 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4239 strcpy(this_recp, tempUS.fullname);
4240 if (!IsEmptyStr(ret->recp_local)) {
4241 strcat(ret->recp_local, "|");
4243 strcat(ret->recp_local, this_recp);
4251 /* Yes, you're reading this correctly: if the target
4252 * domain points back to the local system or an attached
4253 * Citadel directory, the address is invalid. That's
4254 * because if the address were valid, we would have
4255 * already translated it to a local address by now.
4257 if (IsDirectory(this_recp, 0)) {
4262 ++ret->num_internet;
4263 if (!IsEmptyStr(ret->recp_internet)) {
4264 strcat(ret->recp_internet, "|");
4266 strcat(ret->recp_internet, this_recp);
4271 if (!IsEmptyStr(ret->recp_ignet)) {
4272 strcat(ret->recp_ignet, "|");
4274 strcat(ret->recp_ignet, this_recp);
4282 if (IsEmptyStr(errmsg)) {
4283 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4286 snprintf(append, sizeof append, "%s", errmsg);
4288 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4289 if (!IsEmptyStr(ret->errormsg)) {
4290 strcat(ret->errormsg, "; ");
4292 strcat(ret->errormsg, append);
4296 if (IsEmptyStr(ret->display_recp)) {
4297 strcpy(append, this_recp);
4300 snprintf(append, sizeof append, ", %s", this_recp);
4302 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4303 strcat(ret->display_recp, append);
4308 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4309 ret->num_room + ret->num_error) == 0) {
4310 ret->num_error = (-1);
4311 strcpy(ret->errormsg, "No recipients specified.");
4314 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
4315 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4316 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4317 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4318 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4319 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4327 * Destructor for struct recptypes
4329 void free_recipients(struct recptypes *valid) {
4331 if (valid == NULL) {
4335 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4336 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4340 if (valid->errormsg != NULL) free(valid->errormsg);
4341 if (valid->recp_local != NULL) free(valid->recp_local);
4342 if (valid->recp_internet != NULL) free(valid->recp_internet);
4343 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4344 if (valid->recp_room != NULL) free(valid->recp_room);
4345 if (valid->display_recp != NULL) free(valid->display_recp);
4346 if (valid->bounce_to != NULL) free(valid->bounce_to);
4347 if (valid->envelope_from != NULL) free(valid->envelope_from);
4354 * message entry - mode 0 (normal)
4356 void cmd_ent0(char *entargs)
4362 char supplied_euid[128];
4364 int format_type = 0;
4365 char newusername[256];
4366 char newuseremail[256];
4367 struct CtdlMessage *msg;
4371 struct recptypes *valid = NULL;
4372 struct recptypes *valid_to = NULL;
4373 struct recptypes *valid_cc = NULL;
4374 struct recptypes *valid_bcc = NULL;
4376 int subject_required = 0;
4381 int newuseremail_ok = 0;
4382 char references[SIZ];
4387 post = extract_int(entargs, 0);
4388 extract_token(recp, entargs, 1, '|', sizeof recp);
4389 anon_flag = extract_int(entargs, 2);
4390 format_type = extract_int(entargs, 3);
4391 extract_token(subject, entargs, 4, '|', sizeof subject);
4392 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4393 do_confirm = extract_int(entargs, 6);
4394 extract_token(cc, entargs, 7, '|', sizeof cc);
4395 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4396 switch(CC->room.QRdefaultview) {
4399 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4402 supplied_euid[0] = 0;
4405 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4406 extract_token(references, entargs, 11, '|', sizeof references);
4407 for (ptr=references; *ptr != 0; ++ptr) {
4408 if (*ptr == '!') *ptr = '|';
4411 /* first check to make sure the request is valid. */
4413 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
4416 cprintf("%d %s\n", err, errmsg);
4420 /* Check some other permission type things. */
4422 if (IsEmptyStr(newusername)) {
4423 strcpy(newusername, CC->user.fullname);
4425 if ( (CC->user.axlevel < AxAideU)
4426 && (strcasecmp(newusername, CC->user.fullname))
4427 && (strcasecmp(newusername, CC->cs_inet_fn))
4429 cprintf("%d You don't have permission to author messages as '%s'.\n",
4430 ERROR + HIGHER_ACCESS_REQUIRED,
4437 if (IsEmptyStr(newuseremail)) {
4438 newuseremail_ok = 1;
4441 if (!IsEmptyStr(newuseremail)) {
4442 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4443 newuseremail_ok = 1;
4445 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4446 j = num_tokens(CC->cs_inet_other_emails, '|');
4447 for (i=0; i<j; ++i) {
4448 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4449 if (!strcasecmp(newuseremail, buf)) {
4450 newuseremail_ok = 1;
4456 if (!newuseremail_ok) {
4457 cprintf("%d You don't have permission to author messages as '%s'.\n",
4458 ERROR + HIGHER_ACCESS_REQUIRED,
4464 CC->cs_flags |= CS_POSTING;
4466 /* In mailbox rooms we have to behave a little differently --
4467 * make sure the user has specified at least one recipient. Then
4468 * validate the recipient(s). We do this for the Mail> room, as
4469 * well as any room which has the "Mailbox" view set.
4472 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4473 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4475 if (CC->user.axlevel < AxProbU) {
4476 strcpy(recp, "sysop");
4481 valid_to = validate_recipients(recp, NULL, 0);
4482 if (valid_to->num_error > 0) {
4483 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4484 free_recipients(valid_to);
4488 valid_cc = validate_recipients(cc, NULL, 0);
4489 if (valid_cc->num_error > 0) {
4490 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4491 free_recipients(valid_to);
4492 free_recipients(valid_cc);
4496 valid_bcc = validate_recipients(bcc, NULL, 0);
4497 if (valid_bcc->num_error > 0) {
4498 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4499 free_recipients(valid_to);
4500 free_recipients(valid_cc);
4501 free_recipients(valid_bcc);
4505 /* Recipient required, but none were specified */
4506 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4507 free_recipients(valid_to);
4508 free_recipients(valid_cc);
4509 free_recipients(valid_bcc);
4510 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4514 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4515 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4516 cprintf("%d You do not have permission "
4517 "to send Internet mail.\n",
4518 ERROR + HIGHER_ACCESS_REQUIRED);
4519 free_recipients(valid_to);
4520 free_recipients(valid_cc);
4521 free_recipients(valid_bcc);
4526 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)
4527 && (CC->user.axlevel < AxNetU) ) {
4528 cprintf("%d Higher access required for network mail.\n",
4529 ERROR + HIGHER_ACCESS_REQUIRED);
4530 free_recipients(valid_to);
4531 free_recipients(valid_cc);
4532 free_recipients(valid_bcc);
4536 if ((RESTRICT_INTERNET == 1)
4537 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4538 && ((CC->user.flags & US_INTERNET) == 0)
4539 && (!CC->internal_pgm)) {
4540 cprintf("%d You don't have access to Internet mail.\n",
4541 ERROR + HIGHER_ACCESS_REQUIRED);
4542 free_recipients(valid_to);
4543 free_recipients(valid_cc);
4544 free_recipients(valid_bcc);
4550 /* Is this a room which has anonymous-only or anonymous-option? */
4551 anonymous = MES_NORMAL;
4552 if (CC->room.QRflags & QR_ANONONLY) {
4553 anonymous = MES_ANONONLY;
4555 if (CC->room.QRflags & QR_ANONOPT) {
4556 if (anon_flag == 1) { /* only if the user requested it */
4557 anonymous = MES_ANONOPT;
4561 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4565 /* Recommend to the client that the use of a message subject is
4566 * strongly recommended in this room, if either the SUBJECTREQ flag
4567 * is set, or if there is one or more Internet email recipients.
4569 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4570 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4571 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4572 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4574 /* If we're only checking the validity of the request, return
4575 * success without creating the message.
4578 cprintf("%d %s|%d\n", CIT_OK,
4579 ((valid_to != NULL) ? valid_to->display_recp : ""),
4581 free_recipients(valid_to);
4582 free_recipients(valid_cc);
4583 free_recipients(valid_bcc);
4587 /* We don't need these anymore because we'll do it differently below */
4588 free_recipients(valid_to);
4589 free_recipients(valid_cc);
4590 free_recipients(valid_bcc);
4592 /* Read in the message from the client. */
4594 cprintf("%d send message\n", START_CHAT_MODE);
4596 cprintf("%d send message\n", SEND_LISTING);
4599 msg = CtdlMakeMessage(&CC->user, recp, cc,
4600 CC->room.QRname, anonymous, format_type,
4601 newusername, newuseremail, subject,
4602 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4605 /* Put together one big recipients struct containing to/cc/bcc all in
4606 * one. This is for the envelope.
4608 char *all_recps = malloc(SIZ * 3);
4609 strcpy(all_recps, recp);
4610 if (!IsEmptyStr(cc)) {
4611 if (!IsEmptyStr(all_recps)) {
4612 strcat(all_recps, ",");
4614 strcat(all_recps, cc);
4616 if (!IsEmptyStr(bcc)) {
4617 if (!IsEmptyStr(all_recps)) {
4618 strcat(all_recps, ",");
4620 strcat(all_recps, bcc);
4622 if (!IsEmptyStr(all_recps)) {
4623 valid = validate_recipients(all_recps, NULL, 0);
4631 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4634 cprintf("%ld\n", msgnum);
4636 cprintf("Message accepted.\n");
4639 cprintf("Internal error.\n");
4641 if (msg->cm_fields['E'] != NULL) {
4642 cprintf("%s\n", msg->cm_fields['E']);
4649 CtdlFreeMessage(msg);
4651 if (valid != NULL) {
4652 free_recipients(valid);
4660 * API function to delete messages which match a set of criteria
4661 * (returns the actual number of messages deleted)
4663 int CtdlDeleteMessages(char *room_name, /* which room */
4664 long *dmsgnums, /* array of msg numbers to be deleted */
4665 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4666 char *content_type /* or "" for any. regular expressions expected. */
4669 struct ctdlroom qrbuf;
4670 struct cdbdata *cdbfr;
4671 long *msglist = NULL;
4672 long *dellist = NULL;
4675 int num_deleted = 0;
4677 struct MetaData smi;
4680 int need_to_free_re = 0;
4682 if (content_type) if (!IsEmptyStr(content_type)) {
4683 regcomp(&re, content_type, 0);
4684 need_to_free_re = 1;
4686 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4687 room_name, num_dmsgnums, content_type);
4689 /* get room record, obtaining a lock... */
4690 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4691 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4693 if (need_to_free_re) regfree(&re);
4694 return (0); /* room not found */
4696 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4698 if (cdbfr != NULL) {
4699 dellist = malloc(cdbfr->len);
4700 msglist = (long *) cdbfr->ptr;
4701 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4702 num_msgs = cdbfr->len / sizeof(long);
4706 for (i = 0; i < num_msgs; ++i) {
4709 /* Set/clear a bit for each criterion */
4711 /* 0 messages in the list or a null list means that we are
4712 * interested in deleting any messages which meet the other criteria.
4714 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4715 delete_this |= 0x01;
4718 for (j=0; j<num_dmsgnums; ++j) {
4719 if (msglist[i] == dmsgnums[j]) {
4720 delete_this |= 0x01;
4725 if (IsEmptyStr(content_type)) {
4726 delete_this |= 0x02;
4728 GetMetaData(&smi, msglist[i]);
4729 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4730 delete_this |= 0x02;
4734 /* Delete message only if all bits are set */
4735 if (delete_this == 0x03) {
4736 dellist[num_deleted++] = msglist[i];
4741 num_msgs = sort_msglist(msglist, num_msgs);
4742 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4743 msglist, (int)(num_msgs * sizeof(long)));
4746 qrbuf.QRhighest = msglist[num_msgs - 1];
4748 qrbuf.QRhighest = 0;
4750 CtdlPutRoomLock(&qrbuf);
4752 /* Go through the messages we pulled out of the index, and decrement
4753 * their reference counts by 1. If this is the only room the message
4754 * was in, the reference count will reach zero and the message will
4755 * automatically be deleted from the database. We do this in a
4756 * separate pass because there might be plug-in hooks getting called,
4757 * and we don't want that happening during an S_ROOMS critical
4760 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4761 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4762 AdjRefCount(dellist[i], -1);
4765 /* Now free the memory we used, and go away. */
4766 if (msglist != NULL) free(msglist);
4767 if (dellist != NULL) free(dellist);
4768 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4769 if (need_to_free_re) regfree(&re);
4770 return (num_deleted);
4776 * Check whether the current user has permission to delete messages from
4777 * the current room (returns 1 for yes, 0 for no)
4779 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4781 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4782 if (ra & UA_DELETEALLOWED) return(1);
4790 * Delete message from current room
4792 void cmd_dele(char *args)
4801 extract_token(msgset, args, 0, '|', sizeof msgset);
4802 num_msgs = num_tokens(msgset, ',');
4804 cprintf("%d Nothing to do.\n", CIT_OK);
4808 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4809 cprintf("%d Higher access required.\n",
4810 ERROR + HIGHER_ACCESS_REQUIRED);
4815 * Build our message set to be moved/copied
4817 msgs = malloc(num_msgs * sizeof(long));
4818 for (i=0; i<num_msgs; ++i) {
4819 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4820 msgs[i] = atol(msgtok);
4823 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4827 cprintf("%d %d message%s deleted.\n", CIT_OK,
4828 num_deleted, ((num_deleted != 1) ? "s" : ""));
4830 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4838 * move or copy a message to another room
4840 void cmd_move(char *args)
4847 char targ[ROOMNAMELEN];
4848 struct ctdlroom qtemp;
4855 extract_token(msgset, args, 0, '|', sizeof msgset);
4856 num_msgs = num_tokens(msgset, ',');
4858 cprintf("%d Nothing to do.\n", CIT_OK);
4862 extract_token(targ, args, 1, '|', sizeof targ);
4863 convert_room_name_macros(targ, sizeof targ);
4864 targ[ROOMNAMELEN - 1] = 0;
4865 is_copy = extract_int(args, 2);
4867 if (CtdlGetRoom(&qtemp, targ) != 0) {
4868 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4872 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4873 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4877 CtdlGetUser(&CC->user, CC->curr_user);
4878 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4880 /* Check for permission to perform this operation.
4881 * Remember: "CC->room" is source, "qtemp" is target.
4885 /* Aides can move/copy */
4886 if (CC->user.axlevel >= AxAideU) permit = 1;
4888 /* Room aides can move/copy */
4889 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4891 /* Permit move/copy from personal rooms */
4892 if ((CC->room.QRflags & QR_MAILBOX)
4893 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4895 /* Permit only copy from public to personal room */
4897 && (!(CC->room.QRflags & QR_MAILBOX))
4898 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4900 /* Permit message removal from collaborative delete rooms */
4901 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4903 /* Users allowed to post into the target room may move into it too. */
4904 if ((CC->room.QRflags & QR_MAILBOX) &&
4905 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4907 /* User must have access to target room */
4908 if (!(ra & UA_KNOWN)) permit = 0;
4911 cprintf("%d Higher access required.\n",
4912 ERROR + HIGHER_ACCESS_REQUIRED);
4917 * Build our message set to be moved/copied
4919 msgs = malloc(num_msgs * sizeof(long));
4920 for (i=0; i<num_msgs; ++i) {
4921 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4922 msgs[i] = atol(msgtok);
4928 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4930 cprintf("%d Cannot store message(s) in %s: error %d\n",
4936 /* Now delete the message from the source room,
4937 * if this is a 'move' rather than a 'copy' operation.
4940 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4944 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4950 * GetMetaData() - Get the supplementary record for a message
4952 void GetMetaData(struct MetaData *smibuf, long msgnum)
4955 struct cdbdata *cdbsmi;
4958 memset(smibuf, 0, sizeof(struct MetaData));
4959 smibuf->meta_msgnum = msgnum;
4960 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4962 /* Use the negative of the message number for its supp record index */
4963 TheIndex = (0L - msgnum);
4965 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4966 if (cdbsmi == NULL) {
4967 return; /* record not found; go with defaults */
4969 memcpy(smibuf, cdbsmi->ptr,
4970 ((cdbsmi->len > sizeof(struct MetaData)) ?
4971 sizeof(struct MetaData) : cdbsmi->len));
4978 * PutMetaData() - (re)write supplementary record for a message
4980 void PutMetaData(struct MetaData *smibuf)
4984 /* Use the negative of the message number for the metadata db index */
4985 TheIndex = (0L - smibuf->meta_msgnum);
4987 cdb_store(CDB_MSGMAIN,
4988 &TheIndex, (int)sizeof(long),
4989 smibuf, (int)sizeof(struct MetaData));
4994 * AdjRefCount - submit an adjustment to the reference count for a message.
4995 * (These are just queued -- we actually process them later.)
4997 void AdjRefCount(long msgnum, int incr)
4999 struct arcq new_arcq;
5002 CtdlLogPrintf(CTDL_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
5006 begin_critical_section(S_SUPPMSGMAIN);
5007 if (arcfp == NULL) {
5008 arcfp = fopen(file_arcq, "ab+");
5010 end_critical_section(S_SUPPMSGMAIN);
5012 /* msgnum < 0 means that we're trying to close the file */
5014 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
5015 begin_critical_section(S_SUPPMSGMAIN);
5016 if (arcfp != NULL) {
5020 end_critical_section(S_SUPPMSGMAIN);
5025 * If we can't open the queue, perform the operation synchronously.
5027 if (arcfp == NULL) {
5028 TDAP_AdjRefCount(msgnum, incr);
5032 new_arcq.arcq_msgnum = msgnum;
5033 new_arcq.arcq_delta = incr;
5034 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
5042 * TDAP_ProcessAdjRefCountQueue()
5044 * Process the queue of message count adjustments that was created by calls
5045 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
5046 * for each one. This should be an "off hours" operation.
5048 int TDAP_ProcessAdjRefCountQueue(void)
5050 char file_arcq_temp[PATH_MAX];
5053 struct arcq arcq_rec;
5054 int num_records_processed = 0;
5056 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
5058 begin_critical_section(S_SUPPMSGMAIN);
5059 if (arcfp != NULL) {
5064 r = link(file_arcq, file_arcq_temp);
5066 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5067 end_critical_section(S_SUPPMSGMAIN);
5068 return(num_records_processed);
5072 end_critical_section(S_SUPPMSGMAIN);
5074 fp = fopen(file_arcq_temp, "rb");
5076 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5077 return(num_records_processed);
5080 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5081 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5082 ++num_records_processed;
5086 r = unlink(file_arcq_temp);
5088 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5091 return(num_records_processed);
5097 * TDAP_AdjRefCount - adjust the reference count for a message.
5098 * This one does it "for real" because it's called by
5099 * the autopurger function that processes the queue
5100 * created by AdjRefCount(). If a message's reference
5101 * count becomes zero, we also delete the message from
5102 * disk and de-index it.
5104 void TDAP_AdjRefCount(long msgnum, int incr)
5107 struct MetaData smi;
5110 /* This is a *tight* critical section; please keep it that way, as
5111 * it may get called while nested in other critical sections.
5112 * Complicating this any further will surely cause deadlock!
5114 begin_critical_section(S_SUPPMSGMAIN);
5115 GetMetaData(&smi, msgnum);
5116 smi.meta_refcount += incr;
5118 end_critical_section(S_SUPPMSGMAIN);
5119 CtdlLogPrintf(CTDL_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5120 msgnum, incr, smi.meta_refcount
5123 /* If the reference count is now zero, delete the message
5124 * (and its supplementary record as well).
5126 if (smi.meta_refcount == 0) {
5127 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
5129 /* Call delete hooks with NULL room to show it has gone altogether */
5130 PerformDeleteHooks(NULL, msgnum);
5132 /* Remove from message base */
5134 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5135 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5137 /* Remove metadata record */
5138 delnum = (0L - msgnum);
5139 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5145 * Write a generic object to this room
5147 * Note: this could be much more efficient. Right now we use two temporary
5148 * files, and still pull the message into memory as with all others.
5150 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5151 char *content_type, /* MIME type of this object */
5152 char *raw_message, /* Data to be written */
5153 off_t raw_length, /* Size of raw_message */
5154 struct ctdluser *is_mailbox, /* Mailbox room? */
5155 int is_binary, /* Is encoding necessary? */
5156 int is_unique, /* Del others of this type? */
5157 unsigned int flags /* Internal save flags */
5161 struct ctdlroom qrbuf;
5162 char roomname[ROOMNAMELEN];
5163 struct CtdlMessage *msg;
5164 char *encoded_message = NULL;
5166 if (is_mailbox != NULL) {
5167 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5170 safestrncpy(roomname, req_room, sizeof(roomname));
5173 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
5176 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5179 encoded_message = malloc((size_t)(raw_length + 4096));
5182 sprintf(encoded_message, "Content-type: %s\n", content_type);
5185 sprintf(&encoded_message[strlen(encoded_message)],
5186 "Content-transfer-encoding: base64\n\n"
5190 sprintf(&encoded_message[strlen(encoded_message)],
5191 "Content-transfer-encoding: 7bit\n\n"
5197 &encoded_message[strlen(encoded_message)],
5205 &encoded_message[strlen(encoded_message)],
5211 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
5212 msg = malloc(sizeof(struct CtdlMessage));
5213 memset(msg, 0, sizeof(struct CtdlMessage));
5214 msg->cm_magic = CTDLMESSAGE_MAGIC;
5215 msg->cm_anon_type = MES_NORMAL;
5216 msg->cm_format_type = 4;
5217 msg->cm_fields['A'] = strdup(CC->user.fullname);
5218 msg->cm_fields['O'] = strdup(req_room);
5219 msg->cm_fields['N'] = strdup(config.c_nodename);
5220 msg->cm_fields['H'] = strdup(config.c_humannode);
5221 msg->cm_flags = flags;
5223 msg->cm_fields['M'] = encoded_message;
5225 /* Create the requested room if we have to. */
5226 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5227 CtdlCreateRoom(roomname,
5228 ( (is_mailbox != NULL) ? 5 : 3 ),
5229 "", 0, 1, 0, VIEW_BBS);
5231 /* If the caller specified this object as unique, delete all
5232 * other objects of this type that are currently in the room.
5235 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
5236 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5239 /* Now write the data */
5240 CtdlSubmitMsg(msg, NULL, roomname, 0);
5241 CtdlFreeMessage(msg);
5249 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5250 config_msgnum = msgnum;
5254 char *CtdlGetSysConfig(char *sysconfname) {
5255 char hold_rm[ROOMNAMELEN];
5258 struct CtdlMessage *msg;
5261 strcpy(hold_rm, CC->room.QRname);
5262 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5263 CtdlGetRoom(&CC->room, hold_rm);
5268 /* We want the last (and probably only) config in this room */
5269 begin_critical_section(S_CONFIG);
5270 config_msgnum = (-1L);
5271 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5272 CtdlGetSysConfigBackend, NULL);
5273 msgnum = config_msgnum;
5274 end_critical_section(S_CONFIG);
5280 msg = CtdlFetchMessage(msgnum, 1);
5282 conf = strdup(msg->cm_fields['M']);
5283 CtdlFreeMessage(msg);
5290 CtdlGetRoom(&CC->room, hold_rm);
5292 if (conf != NULL) do {
5293 extract_token(buf, conf, 0, '\n', sizeof buf);
5294 strcpy(conf, &conf[strlen(buf)+1]);
5295 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5301 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5302 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5307 * Determine whether a given Internet address belongs to the current user
5309 int CtdlIsMe(char *addr, int addr_buf_len)
5311 struct recptypes *recp;
5314 recp = validate_recipients(addr, NULL, 0);
5315 if (recp == NULL) return(0);
5317 if (recp->num_local == 0) {
5318 free_recipients(recp);
5322 for (i=0; i<recp->num_local; ++i) {
5323 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5324 if (!strcasecmp(addr, CC->user.fullname)) {
5325 free_recipients(recp);
5330 free_recipients(recp);
5336 * Citadel protocol command to do the same
5338 void cmd_isme(char *argbuf) {
5341 if (CtdlAccessCheck(ac_logged_in)) return;
5342 extract_token(addr, argbuf, 0, '|', sizeof addr);
5344 if (CtdlIsMe(addr, sizeof addr)) {
5345 cprintf("%d %s\n", CIT_OK, addr);
5348 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5354 /*****************************************************************************/
5355 /* MODULE INITIALIZATION STUFF */
5356 /*****************************************************************************/
5358 CTDL_MODULE_INIT(msgbase)
5361 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5362 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5363 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5364 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5365 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5366 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5367 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5368 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5369 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5370 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5371 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5372 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5375 /* return our Subversion id for the Log */