#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;
* 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),
* 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);
+ lprintf(CTDL_DEBUG, "Is <%s> == <%s> ??\n", buf, cbtype);
if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
- safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
+ if (i < ma->chosen_pref) {
+ safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
+ ma->chosen_pref = i;
+ }
}
}
}
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) {
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);
long msgid = 0;
struct CtdlMessage *msg = NULL;
- long *msgs_to_be_merged = NULL; /* FIXME free this */
+ long *msgs_to_be_merged = NULL;
int num_msgs_to_be_merged = 0;
lprintf(CTDL_DEBUG,
lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
}
+ /* 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);
/* 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) {
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);
}
}
/*
* Convenience function for generating small administrative messages.
*/
-void quickie_message(char *from, char *to, char *room, char *text,
+void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
int format_type, char *subject)
{
struct CtdlMessage *msg;
msg->cm_magic = CTDLMESSAGE_MAGIC;
msg->cm_anon_type = MES_NORMAL;
msg->cm_format_type = format_type;
- msg->cm_fields['A'] = strdup(from);
+
+ if (from != NULL) {
+ msg->cm_fields['A'] = strdup(from);
+ }
+ else if (fromaddr != NULL) {
+ msg->cm_fields['A'] = strdup(fromaddr);
+ if (strchr(msg->cm_fields['A'], '@')) {
+ *strchr(msg->cm_fields['A'], '@') = 0;
+ }
+ }
+ else {
+ msg->cm_fields['A'] = strdup("Citadel");
+ }
+
+ if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
if (room != NULL) msg->cm_fields['O'] = strdup(room);
msg->cm_fields['N'] = strdup(NODENAME);
if (to != NULL) {
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);
* (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 (single message)
- */
-int CtdlCopyMsgToRoom(long msgnum, char *dest) {
- return CtdlCopyMsgsToRoom(&msgnum, 1, dest);
-}
-
/*
*/
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;
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") );
}
*/
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);