*
*/
-#ifdef DLL_EXPORT
-#define IN_LIBCIT
-#endif
-
#include "sysdep.h"
#include <stdlib.h>
#include <unistd.h>
#include "serv_fulltext.h"
#include "vcard.h"
#include "euidindex.h"
+#include "journaling.h"
+#include "citadel_dirs.h"
+#include "serv_network.h"
+
+#ifdef HAVE_LIBSIEVE
+# include "serv_sieve.h"
+#endif /* HAVE_LIBSIEVE */
long config_msgnum;
struct addresses_to_be_filed *atbf = NULL;
NULL,
"hnod",
"msgn",
- NULL, NULL, NULL,
+ "jrnl",
+ NULL, NULL,
"text",
"node",
"room",
remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
stripallbut(name, '<', '>');
- fp = fopen(
-#ifndef HAVE_ETG_DIR
- "network/"
-#else
- ETC_DIR
-#endif
- "mail.aliases", "r");
+ fp = fopen(file_mail_aliases, "r");
if (fp == NULL) {
fp = fopen("/dev/null", "r");
}
{
FILE *fp;
- fp = fopen(
-#ifndef HAVE_RUN_DIR
- "."
-#else
- RUN_DIR
-#endif
- "/citadel.control", "r");
+ fp = fopen(file_citadel_control, "r");
if (fp == NULL) {
- lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
- strerror(errno));
+ lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
+ file_citadel_control,
+ strerror(errno));
exit(errno);
}
fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
struct CtdlMessage *msg;
msg = CtdlFetchMessage(msgnum, 0);
- if (msg < 0L) {
+ if (msg == NULL) {
cprintf("%ld|0|||||\n", msgnum);
return;
}
* API function to perform an operation for each qualifying message in the
* current room. (Returns the number of messages processed.)
*/
-int CtdlForEachMessage(int mode, long ref,
+int CtdlForEachMessage(int mode, long ref, char *search_string,
char *content_type,
struct CtdlMessage *compare,
void (*CallBack) (long, void *),
void *userdata)
{
- int a;
+ int a, i, j;
struct visit vbuf;
struct cdbdata *cdbfr;
long *msglist = NULL;
int is_seen = 0;
long lastold = 0L;
int printed_lastold = 0;
+ int num_search_msgs = 0;
+ long *search_msgs = NULL;
/* Learn about the user and room in question */
get_mm();
}
}
+ /* If a search string was specified, get a message list from
+ * the full text index and remove messages which aren't on both
+ * lists.
+ *
+ * How this works:
+ * Since the lists are sorted and strictly ascending, and the
+ * output list is guaranteed to be shorter than or equal to the
+ * input list, we overwrite the bottom of the input list. This
+ * eliminates the need to memmove big chunks of the list over and
+ * over again.
+ */
+ if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
+ ft_search(&num_search_msgs, &search_msgs, search_string);
+ if (num_search_msgs > 0) {
+ int orig_num_msgs;
+
+ orig_num_msgs = num_msgs;
+ num_msgs = 0;
+ for (i=0; i<orig_num_msgs; ++i) {
+ for (j=0; j<num_search_msgs; ++j) {
+ if (msglist[i] == search_msgs[j]) {
+ msglist[num_msgs++] = msglist[i];
+ }
+ }
+ }
+ }
+ else {
+ num_msgs = 0; /* No messages qualify */
+ }
+ if (search_msgs != NULL) free(search_msgs);
+
+ /* Now that we've purged messages which don't contain the search
+ * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
+ * point on.
+ */
+ mode = MSGS_ALL;
+ }
+
/*
* Now iterate through the message list, according to the
* criteria supplied by the caller.
int with_template = 0;
struct CtdlMessage *template = NULL;
int with_headers = 0;
+ char search_string[1024];
extract_token(which, cmdbuf, 0, '|', sizeof which);
cm_ref = extract_int(cmdbuf, 1);
+ extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
with_template = extract_int(cmdbuf, 2);
with_headers = extract_int(cmdbuf, 3);
- mode = MSGS_ALL;
strcat(which, " ");
if (!strncasecmp(which, "OLD", 3))
mode = MSGS_OLD;
mode = MSGS_LAST;
else if (!strncasecmp(which, "GT", 2))
mode = MSGS_GT;
+ else if (!strncasecmp(which, "SEARCH", 6))
+ mode = MSGS_SEARCH;
+ else
+ mode = MSGS_ALL;
if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
return;
}
+ if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
+ cprintf("%d Full text index is not enabled on this server.\n",
+ ERROR + CMD_NOT_SUPPORTED);
+ return;
+ }
+
if (with_template) {
unbuffer_output();
cprintf("%d Send template then receive message list\n",
}
CtdlForEachMessage(mode,
- cm_ref,
+ ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
+ ( (mode == MSGS_SEARCH) ? search_string : NULL ),
NULL,
template,
(with_headers ? headers_listing : simple_listing),
help_subst(buffer, "^variantname", CITADEL);
snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
help_subst(buffer, "^maxsessions", buf2);
- help_subst(buffer, "^bbsdir", CTDLDIR);
+ help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
}
* to the client. The client software may reformat it again.
*/
void memfmout(
- int width, /* screen width to use */
char *mptr, /* where are we going to get our text from? */
char subst, /* nonzero if we should do substitutions */
char *nl) /* string to terminate lines with */
cit_uint8_t ch;
char aaa[140];
char buffer[SIZ];
+ static int width = 80;
strcpy(aaa, "");
old = 255;
old = real;
real = ch;
- if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
+ if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
ch = 32;
+ }
if (((old == 13) || (old == 10)) && (isspace(real))) {
cprintf("%s", nl);
c = 1;
}
- if (ch > 126)
- continue;
-
if (ch > 32) {
if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
cprintf("%s%s", nl, aaa);
}
}
if (ret->cm_fields['M'] == NULL) {
- ret->cm_fields['M'] = strdup("<no text>\n");
+ ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
}
/* Perform "before read" hooks (aborting if any return nonzero) */
if (!strcasecmp(cbtype, "multipart/alternative")) {
++ma->is_ma;
ma->did_print = 0;
- return;
+ }
+ if (!strcasecmp(cbtype, "message/rfc822")) {
+ ++ma->freeze;
}
}
if (!strcasecmp(cbtype, "multipart/alternative")) {
--ma->is_ma;
ma->did_print = 0;
- return;
+ }
+ if (!strcasecmp(cbtype, "message/rfc822")) {
+ --ma->freeze;
}
}
* If we're in the middle of a multipart/alternative scope and
* we've already printed another section, skip this one.
*/
- if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
+ if ( (ma->is_ma) && (ma->did_print) ) {
lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
partnum, cbtype);
return;
cprintf("\n");
}
}
+ return;
}
- else if (!strcasecmp(cbtype, "text/html")) {
+
+ if (!strcasecmp(cbtype, "text/html")) {
ptr = html_to_ascii(content, length, 80, 0);
wlen = strlen(ptr);
client_write(ptr, wlen);
cprintf("\n");
}
free(ptr);
+ return;
+ }
+
+ if (ma->use_fo_hooks) {
+ if (PerformFixedOutputHooks(cbtype, content, length)) {
+ /* above function returns nonzero if it handled the part */
+ return;
+ }
}
- else if (strncasecmp(cbtype, "multipart/", 10)) {
+
+ if (strncasecmp(cbtype, "multipart/", 10)) {
cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
partnum, filename, cbtype, (long)length);
+ return;
}
}
* The client is elegant and sophisticated and wants to be choosy about
* MIME content types, so figure out which multipart/alternative part
* we're going to send.
+ *
+ * We use a system of weights. When we find a part that matches one of the
+ * MIME types we've declared as preferential, we can store it in ma->chosen_part
+ * and then set ma->chosen_pref to that MIME type's position in our preference
+ * list. If we then hit another match, we only replace the first match if
+ * the preference value is lower.
*/
void choose_preferred(char *name, char *filename, char *partnum, char *disp,
void *content, char *cbtype, char *cbcharset, size_t length,
if (ma->is_ma > 0) {
for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
- if (!strcasecmp(buf, cbtype)) {
- strcpy(ma->chosen_part, partnum);
+ lprintf(CTDL_DEBUG, "Is <%s> == <%s> ??\n", buf, cbtype);
+ if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
+ if (i < ma->chosen_pref) {
+ safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
+ ma->chosen_pref = i;
+ }
}
}
}
}
+struct encapmsg {
+ char desired_section[64];
+ char *msg;
+ size_t msglen;
+};
+
+
+/*
+ * Callback function for
+ */
+void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, void *cbuserdata)
+{
+ struct encapmsg *encap;
+
+ encap = (struct encapmsg *)cbuserdata;
+
+ /* Only proceed if this is the desired section... */
+ if (!strcasecmp(encap->desired_section, partnum)) {
+ encap->msglen = length;
+ encap->msg = malloc(length + 2);
+ memcpy(encap->msg, content, length);
+ return;
+ }
+
+}
+
+
+
+
/*
* Get a message off disk. (returns om_* values found in msgbase.h)
*
int mode, /* how would you like that message? */
int headers_only, /* eschew the message body? */
int do_proto, /* do Citadel protocol responses? */
- int crlf /* Use CRLF newlines instead of LF? */
+ int crlf, /* Use CRLF newlines instead of LF? */
+ char *section /* NULL or a message/rfc822 section */
) {
struct CtdlMessage *TheMessage = NULL;
int retcode = om_no_such_msg;
+ struct encapmsg encap;
- lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
- msg_num, mode);
+ lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
+ msg_num, mode,
+ (section ? section : "<>")
+ );
if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
if (do_proto) cprintf("%d Not logged in.\n",
ERROR + MESSAGE_NOT_FOUND, msg_num);
return(om_no_such_msg);
}
-
- retcode = CtdlOutputPreLoadedMsg(
- TheMessage, mode,
- headers_only, do_proto, crlf);
+ /* Here is the weird form of this command, to process only an
+ * encapsulated message/rfc822 section.
+ */
+ if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
+ memset(&encap, 0, sizeof encap);
+ safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
+ mime_parser(TheMessage->cm_fields['M'],
+ NULL,
+ *extract_encapsulated_message,
+ NULL, NULL, (void *)&encap, 0
+ );
+ CtdlFreeMessage(TheMessage);
+ TheMessage = NULL;
+
+ if (encap.msg) {
+ encap.msg[encap.msglen] = 0;
+ TheMessage = convert_internet_message(encap.msg);
+ encap.msg = NULL; /* no free() here, TheMessage owns it now */
+
+ /* Now we let it fall through to the bottom of this
+ * function, because TheMessage now contains the
+ * encapsulated message instead of the top-level
+ * message. Isn't that neat?
+ */
+
+ }
+ else {
+ if (do_proto) cprintf("%d msg %ld has no part %s\n",
+ ERROR + MESSAGE_NOT_FOUND, msg_num, section);
+ retcode = om_no_such_msg;
+ }
+
+ }
+
+ /* Ok, output the message now */
+ retcode = CtdlOutputPreLoadedMsg(
+ TheMessage, mode,
+ headers_only, do_proto, crlf);
CtdlFreeMessage(TheMessage);
return(retcode);
else if (i == 'Y') {
cprintf("CC: %s%s", mptr, nl);
}
+ else if (i == 'P') {
+ cprintf("Return-Path: %s%s", mptr, nl);
+ }
+ else if (i == 'V') {
+ cprintf("Envelope-To: %s%s", mptr, nl);
+ }
else if (i == 'U') {
cprintf("Subject: %s%s", mptr, nl);
subject_found = 1;
if (mode == MT_MIME) {
cprintf("Content-type: text/x-citadel-variformat\n\n");
}
- memfmout(80, mptr, 0, nl);
+ memfmout(mptr, 0, nl);
}
/* If the message on disk is format 4 (MIME), we've gotta hand it
memset(&ma, 0, sizeof(struct ma_info));
if (mode == MT_MIME) {
+ ma.use_fo_hooks = 0;
strcpy(ma.chosen_part, "1");
+ ma.chosen_pref = 9999;
mime_parser(mptr, NULL,
*choose_preferred, *fixed_output_pre,
*fixed_output_post, (void *)&ma, 0);
*output_preferred, NULL, NULL, (void *)&ma, 0);
}
else {
+ ma.use_fo_hooks = 1;
mime_parser(mptr, NULL,
*fixed_output, *fixed_output_pre,
*fixed_output_post, (void *)&ma, 0);
msgid = extract_long(cmdbuf, 0);
headers_only = extract_int(cmdbuf, 1);
- CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
+ CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
return;
}
msgid = extract_long(cmdbuf, 0);
headers_only = extract_int(cmdbuf, 1);
- CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
+ CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
}
void cmd_msg4(char *cmdbuf)
{
long msgid;
+ char section[64];
msgid = extract_long(cmdbuf, 0);
- CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
+ extract_token(section, cmdbuf, 1, '|', sizeof section);
+ CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
}
extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
safestrncpy(CC->download_desired_section, desired_section,
sizeof CC->download_desired_section);
- CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
+ CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
}
-
/*
- * Save a message pointer into a specified room
+ * Save one or more message pointers into a specified room
* (Returns 0 for success, nonzero for failure)
* roomname may be NULL to use the current room
*
* Note that the 'supplied_msg' field may be set to NULL, in which case
* the message will be fetched from disk, by number, if we need to perform
* replication checks. This adds an additional database read, so if the
- * caller already has the message in memory then it should be supplied.
+ * caller already has the message in memory then it should be supplied. (Obviously
+ * this mode of operation only works if we're saving a single message.)
*/
-int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
- struct CtdlMessage *supplied_msg) {
- int i;
+int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
+ int do_repl_check, struct CtdlMessage *supplied_msg)
+{
+ int i, j, unique;
char hold_rm[ROOMNAMELEN];
struct cdbdata *cdbfr;
int num_msgs;
long *msglist;
long highest_msg = 0L;
+
+ long msgid = 0;
struct CtdlMessage *msg = NULL;
- /*lprintf(CTDL_DEBUG,
- "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
- roomname, msgid, do_repl_check);*/
+ long *msgs_to_be_merged = NULL;
+ int num_msgs_to_be_merged = 0;
+
+ lprintf(CTDL_DEBUG,
+ "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
+ roomname, num_newmsgs, do_repl_check);
strcpy(hold_rm, CC->room.QRname);
+ /* Sanity checks */
+ if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
+ if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
+ if (num_newmsgs > 1) supplied_msg = NULL;
+
/* Now the regular stuff */
if (lgetroom(&CC->room,
((roomname != NULL) ? roomname : CC->room.QRname) )
return(ERROR + ROOM_NOT_FOUND);
}
+
+ msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
+ num_msgs_to_be_merged = 0;
+
+
cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
if (cdbfr == NULL) {
msglist = NULL;
cdb_free(cdbfr);
}
- /* Make sure the message doesn't already exist in this room. It
- * is absolutely taboo to have more than one reference to the same
- * message in a room.
+
+ /* Create a list of msgid's which were supplied by the caller, but do
+ * not already exist in the target room. It is absolutely taboo to
+ * have more than one reference to the same message in a room.
*/
- if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
- if (msglist[i] == msgid) {
- lputroom(&CC->room); /* unlock the room */
- getroom(&CC->room, hold_rm);
- free(msglist);
- return(ERROR + ALREADY_EXISTS);
+ for (i=0; i<num_newmsgs; ++i) {
+ unique = 1;
+ if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
+ if (msglist[j] == newmsgidlist[i]) {
+ unique = 0;
+ }
+ }
+ if (unique) {
+ msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
}
}
- /* Now add the new message */
- ++num_msgs;
- msglist = realloc(msglist, (num_msgs * sizeof(long)));
+ lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
+ /*
+ * Now merge the new messages
+ */
+ msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
if (msglist == NULL) {
lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
}
- msglist[num_msgs - 1] = msgid;
+ memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
+ num_msgs += num_msgs_to_be_merged;
/* Sort the message list, so all the msgid's are in order */
num_msgs = sort_msglist(msglist, num_msgs);
/* Perform replication checks if necessary */
if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
- if (supplied_msg != NULL) {
- msg = supplied_msg;
- }
- else {
- msg = CtdlFetchMessage(msgid, 0);
- }
+ lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
- if (msg != NULL) {
- ReplicationChecks(msg);
- }
+ for (i=0; i<num_msgs_to_be_merged; ++i) {
+ msgid = msgs_to_be_merged[i];
+
+ if (supplied_msg != NULL) {
+ msg = supplied_msg;
+ }
+ else {
+ msg = CtdlFetchMessage(msgid, 0);
+ }
+
+ if (msg != NULL) {
+ ReplicationChecks(msg);
+
+ /* If the message has an Exclusive ID, index that... */
+ if (msg->cm_fields['E'] != NULL) {
+ index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
+ }
+ /* Free up the memory we may have allocated */
+ if (msg != supplied_msg) {
+ CtdlFreeMessage(msg);
+ }
+ }
+
+ }
}
- /* If the message has an Exclusive ID, index that... */
- if (msg != NULL) {
- if (msg->cm_fields['E'] != NULL) {
- index_message_by_euid(msg->cm_fields['E'],
- &CC->room, msgid);
- }
+ else {
+ lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
}
- /* Free up the memory we may have allocated */
- if ( (msg != NULL) && (msg != supplied_msg) ) {
- CtdlFreeMessage(msg);
+ /* Submit this room for net processing */
+ network_queue_room(&CC->room, NULL);
+
+#ifdef HAVE_LIBSIEVE
+ /* If this is someone's inbox, submit the room for sieve processing */
+ if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
+ sieve_queue_room(&CC->room);
}
+#endif /* HAVE_LIBSIEVE */
/* Go back to the room we were in before we wandered here... */
getroom(&CC->room, hold_rm);
- /* Bump the reference count for this message. */
- AdjRefCount(msgid, +1);
+ /* Bump the reference count for all messages which were merged */
+ for (i=0; i<num_msgs_to_be_merged; ++i) {
+ AdjRefCount(msgs_to_be_merged[i], +1);
+ }
+
+ /* Free up memory... */
+ if (msgs_to_be_merged != NULL) {
+ free(msgs_to_be_merged);
+ }
/* Return success. */
return (0);
}
+/*
+ * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
+ * a single message.
+ */
+int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
+ int do_repl_check, struct CtdlMessage *supplied_msg)
+{
+ return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
+}
+
+
+
/*
* Message base operation to save a new message to the message store
/* Get a new message number */
newmsgid = get_new_message_number();
- snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
+ snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
/* Generate an ID if we don't have one already */
if (msg->cm_fields['I']==NULL) {
void serialize_message(struct ser_ret *ret, /* return values */
struct CtdlMessage *msg) /* unserialized msg */
{
- size_t wlen;
+ size_t wlen, fieldlen;
int i;
static char *forder = FORDER;
- if (is_valid_message(msg) == 0) return; /* self check */
+ /*
+ * Check for valid message format
+ */
+ if (is_valid_message(msg) == 0) {
+ lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
+ ret->len = 0;
+ ret->ser = NULL;
+ return;
+ }
ret->len = 3;
for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
ret->len = ret->len +
strlen(msg->cm_fields[(int)forder[i]]) + 2;
- lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
ret->ser = malloc(ret->len);
if (ret->ser == NULL) {
+ lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
+ (long)ret->len, strerror(errno));
ret->len = 0;
+ ret->ser = NULL;
return;
}
wlen = 3;
for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
+ fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
ret->ser[wlen++] = (char)forder[i];
- strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
- wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
+ safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
+ wlen = wlen + fieldlen + 1;
}
if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
(long)ret->len, (long)wlen);
old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
if (old_msgnum > 0L) {
lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
- CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
+ CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "", 0);
}
}
char *hold_R, *hold_D;
char *collected_addresses = NULL;
struct addresses_to_be_filed *aptr = NULL;
+ char *saved_rfc822_version = NULL;
+ int qualified_for_journaling = 0;
lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
if (is_valid_message(msg) == 0) return(-1); /* self check */
safestrncpy(smi.meta_content_type, content_type,
sizeof smi.meta_content_type);
- /* As part of the new metadata record, measure how
- * big this message will be when displayed as RFC822.
- * Both POP and IMAP use this, and it's best to just take the hit now
- * instead of having to potentially measure thousands of messages when
- * a mailbox is opened later.
+ /*
+ * Measure how big this message will be when rendered as RFC822.
+ * We do this for two reasons:
+ * 1. We need the RFC822 length for the new metadata record, so the
+ * POP and IMAP services don't have to calculate message lengths
+ * while the user is waiting (multiplied by potentially hundreds
+ * or thousands of messages).
+ * 2. If journaling is enabled, we will need an RFC822 version of the
+ * message to attach to the journalized copy.
*/
-
if (CC->redirect_buffer != NULL) {
lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
abort();
CC->redirect_alloc = SIZ;
CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
smi.meta_rfc822_length = CC->redirect_len;
- free(CC->redirect_buffer);
+ saved_rfc822_version = CC->redirect_buffer;
CC->redirect_buffer = NULL;
CC->redirect_len = 0;
CC->redirect_alloc = 0;
serialize_message(&smr, msg);
if (smr.len > 0) {
snprintf(submit_filename, sizeof submit_filename,
-#ifndef HAVE_SPOOL_DIR
- "."
-#else
- SPOOL_DIR
-#endif
- "/network/spoolin/netmail.%04lx.%04x.%04x",
+ "%s/netmail.%04lx.%04x.%04x",
+ ctdl_netin_dir,
(long) getpid(), CC->cs_pid, ++seqnum);
network_fp = fopen(submit_filename, "wb+");
if (network_fp != NULL) {
imsg->cm_anon_type = MES_NORMAL;
imsg->cm_format_type = FMT_RFC822;
imsg->cm_fields['A'] = strdup("Citadel");
+ imsg->cm_fields['J'] = strdup("do not journal");
imsg->cm_fields['M'] = instr;
CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
CtdlFreeMessage(imsg);
atbf = aptr;
end_critical_section(S_ATBF);
}
- return(newmsgid);
-}
+ /*
+ * Determine whether this message qualifies for journaling.
+ */
+ if (msg->cm_fields['J'] != NULL) {
+ qualified_for_journaling = 0;
+ }
+ else {
+ if (recps == NULL) {
+ qualified_for_journaling = config.c_journal_pubmsgs;
+ }
+ else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
+ qualified_for_journaling = config.c_journal_email;
+ }
+ else {
+ qualified_for_journaling = config.c_journal_pubmsgs;
+ }
+ }
+ /*
+ * Do we have to perform journaling? If so, hand off the saved
+ * RFC822 version will be handed off to the journaler for background
+ * submit. Otherwise, we have to free the memory ourselves.
+ */
+ if (saved_rfc822_version != NULL) {
+ if (qualified_for_journaling) {
+ JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
+ }
+ else {
+ free(saved_rfc822_version);
+ }
+ }
+ /* Done. */
+ return(newmsgid);
+}
char *m;
int flushing = 0;
int finished = 0;
+ int dotdot = 0;
if (exist == NULL) {
m = malloc(4096);
}
}
+ /* Do we need to change leading ".." to "." for SMTP escaping? */
+ if (!strcmp(terminator, ".")) {
+ dotdot = 1;
+ }
+
/* flush the input if we have nowhere to store it */
if (m == NULL) {
flushing = 1;
strcat(buf, "\n");
}
+ /* Unescape SMTP-style input of two dots at the beginning of the line */
+ if (dotdot) {
+ if (!strncmp(buf, "..", 2)) {
+ strcpy(buf, &buf[1]);
+ }
+ }
+
if ( (!flushing) && (!finished) ) {
/* Measure the line */
linelen = strlen(buf);
int format_type, /* variformat, plain text, MIME... */
char *fake_name, /* who we're masquerading as */
char *subject, /* Subject (optional) */
+ char *supplied_euid, /* ...or NULL if this is irrelevant */
char *preformatted_text /* ...or NULL to read text from client */
) {
char dest_node[SIZ];
}
}
+ if (supplied_euid != NULL) {
+ msg->cm_fields['E'] = strdup(supplied_euid);
+ }
+
if (preformatted_text != NULL) {
msg->cm_fields['M'] = preformatted_text;
}
char this_recp[256];
char this_recp_cooked[256];
char append[SIZ];
- int num_recps;
+ int num_recps = 0;
int i, j;
int mailtype;
int invalid;
char recp[SIZ];
char cc[SIZ];
char bcc[SIZ];
+ char supplied_euid[128];
char masquerade_as[SIZ];
int anon_flag = 0;
int format_type = 0;
do_confirm = extract_int(entargs, 6);
extract_token(cc, entargs, 7, '|', sizeof cc);
extract_token(bcc, entargs, 8, '|', sizeof bcc);
+ switch(CC->room.QRdefaultview) {
+ case VIEW_NOTES:
+ case VIEW_WIKI:
+ extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
+ break;
+ default:
+ supplied_euid[0] = 0;
+ break;
+ }
/* first check to make sure the request is valid. */
msg = CtdlMakeMessage(&CC->user, recp, cc,
CC->room.QRname, anonymous, format_type,
- masquerade_as, subject, NULL);
+ masquerade_as, subject,
+ ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
+ NULL);
/* Put together one big recipients struct containing to/cc/bcc all in
* one. This is for the envelope.
* (returns the actual number of messages deleted)
*/
int CtdlDeleteMessages(char *room_name, /* which room */
- long dmsgnum, /* or "0" for any */
+ long *dmsgnums, /* array of msg numbers to be deleted */
+ int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
char *content_type, /* or "" for any */
int deferred /* let TDAP sweep it later */
)
long *msglist = NULL;
long *dellist = NULL;
int num_msgs = 0;
- int i;
+ int i, j;
int num_deleted = 0;
int delete_this;
struct MetaData smi;
- lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
- room_name, dmsgnum, content_type, deferred);
+ lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s, %d)\n",
+ room_name, num_dmsgnums, content_type, deferred);
/* get room record, obtaining a lock... */
if (lgetroom(&qrbuf, room_name) != 0) {
/* Set/clear a bit for each criterion */
- if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
+ /* 0 messages in the list or a null list means that we are
+ * interested in deleting any messages which meet the other criteria.
+ */
+ if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
delete_this |= 0x01;
}
+ else {
+ for (j=0; j<num_dmsgnums; ++j) {
+ if (msglist[i] == dmsgnums[j]) {
+ delete_this |= 0x01;
+ }
+ }
+ }
+
if (strlen(content_type) == 0) {
delete_this |= 0x02;
} else {
* DELETED_MSGS_ROOM. This will cause the reference count to remain
* at least 1, which will save the user from having to synchronously
* wait for various disk-intensive operations to complete.
+ *
+ * Slick -- we now use the new bulk API for moving messages.
*/
if ( (deferred) && (num_deleted) ) {
- for (i=0; i<num_deleted; ++i) {
- CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
- }
+ CtdlCopyMsgsToRoom(dellist, num_deleted, DELETED_MSGS_ROOM);
}
/* Go through the messages we pulled out of the index, and decrement
/*
* Delete message from current room
*/
-void cmd_dele(char *delstr)
+void cmd_dele(char *args)
{
- long delnum;
int num_deleted;
+ int i;
+ char msgset[SIZ];
+ char msgtok[32];
+ long *msgs;
+ int num_msgs = 0;
+
+ extract_token(msgset, args, 0, '|', sizeof msgset);
+ num_msgs = num_tokens(msgset, ',');
+ if (num_msgs < 1) {
+ cprintf("%d Nothing to do.\n", CIT_OK);
+ return;
+ }
if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
cprintf("%d Higher access required.\n",
ERROR + HIGHER_ACCESS_REQUIRED);
return;
}
- delnum = extract_long(delstr, 0);
- num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
+ /*
+ * Build our message set to be moved/copied
+ */
+ msgs = malloc(num_msgs * sizeof(long));
+ for (i=0; i<num_msgs; ++i) {
+ extract_token(msgtok, msgset, i, ',', sizeof msgtok);
+ msgs[i] = atol(msgtok);
+ }
+
+ num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 1);
+ free(msgs);
if (num_deleted) {
cprintf("%d %d message%s deleted.\n", CIT_OK,
num_deleted, ((num_deleted != 1) ? "s" : ""));
} else {
- cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
+ cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
}
}
/*
- * Back end API function for moves and deletes
+ * Back end API function for moves and deletes (multiple messages)
*/
-int CtdlCopyMsgToRoom(long msgnum, char *dest) {
+int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
int err;
- err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
+ err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
if (err != 0) return(err);
return(0);
+
/*
* move or copy a message to another room
*/
void cmd_move(char *args)
{
- long num;
+ char msgset[SIZ];
+ char msgtok[32];
+ long *msgs;
+ int num_msgs = 0;
+
char targ[ROOMNAMELEN];
struct ctdlroom qtemp;
int err;
int is_copy = 0;
int ra;
int permit = 0;
+ int i;
+
+ extract_token(msgset, args, 0, '|', sizeof msgset);
+ num_msgs = num_tokens(msgset, ',');
+ if (num_msgs < 1) {
+ cprintf("%d Nothing to do.\n", CIT_OK);
+ return;
+ }
- num = extract_long(args, 0);
extract_token(targ, args, 1, '|', sizeof targ);
+ convert_room_name_macros(targ, sizeof targ);
targ[ROOMNAMELEN - 1] = 0;
is_copy = extract_int(args, 2);
return;
}
- err = CtdlCopyMsgToRoom(num, targ);
+ /*
+ * Build our message set to be moved/copied
+ */
+ msgs = malloc(num_msgs * sizeof(long));
+ for (i=0; i<num_msgs; ++i) {
+ extract_token(msgtok, msgset, i, ',', sizeof msgtok);
+ msgs[i] = atol(msgtok);
+ }
+
+ /*
+ * Do the copy
+ */
+ err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
if (err != 0) {
- cprintf("%d Cannot store message in %s: error %d\n",
+ cprintf("%d Cannot store message(s) in %s: error %d\n",
err, targ, err);
+ free(msgs);
return;
}
* if this is a 'move' rather than a 'copy' operation.
*/
if (is_copy == 0) {
- CtdlDeleteMessages(CC->room.QRname, num, "", 0);
+ CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "", 0);
}
+ free(msgs);
- cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
+ cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
}
char *encoded_message = NULL;
off_t raw_length = 0;
- if (is_mailbox != NULL)
+ if (is_mailbox != NULL) {
MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
- else
+ }
+ else {
safestrncpy(roomname, req_room, sizeof(roomname));
- lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
-
+ }
fp = fopen(tempfilename, "rb");
if (fp == NULL) {
*/
if (is_unique) {
lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
- CtdlDeleteMessages(roomname, 0L, content_type, 0)
+ CtdlDeleteMessages(roomname, NULL, 0, content_type, 0)
);
}
/* Now write the data */
/* We want the last (and probably only) config in this room */
begin_critical_section(S_CONFIG);
config_msgnum = (-1L);
- CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
+ CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
CtdlGetSysConfigBackend, NULL);
msgnum = config_msgnum;
end_critical_section(S_CONFIG);
char temp[PATH_MAX];
FILE *fp;
- strcpy(temp, tmpnam(NULL));
+ CtdlMakeTempFileName(temp, sizeof temp);
fp = fopen(temp, "w");
if (fp == NULL) return;