X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmsgbase.c;h=3361b0c440c26faacf5aa9b155659fb42f38984d;hb=ef095080b26c0695b939179677fc558384b3437a;hp=eeb19d35728a7992f1300d059dafd870e3225031;hpb=5f1e4b8298b4117224248fc480e733c52c36b3c7;p=citadel.git diff --git a/citadel/msgbase.c b/citadel/msgbase.c index eeb19d357..3361b0c44 100644 --- a/citadel/msgbase.c +++ b/citadel/msgbase.c @@ -52,6 +52,11 @@ #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; @@ -504,14 +509,14 @@ void CtdlSetSeen(long *target_msgnums, int num_target_msgnums, * 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; @@ -523,6 +528,8 @@ int CtdlForEachMessage(int mode, long ref, 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(); @@ -587,7 +594,45 @@ int CtdlForEachMessage(int mode, long ref, } } + /* 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; ilogged_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", @@ -695,7 +751,8 @@ void cmd_msgs(char *cmdbuf) } CtdlForEachMessage(mode, - cm_ref, + ( (mode == MSGS_SEARCH) ? 0 : cm_ref ), + ( (mode == MSGS_SEARCH) ? search_string : NULL ), NULL, template, (with_headers ? headers_listing : simple_listing), @@ -1152,6 +1209,12 @@ void fixed_output(char *name, char *filename, char *partnum, char *disp, * 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, @@ -1166,8 +1229,12 @@ void choose_preferred(char *name, char *filename, char *partnum, char *disp, if (ma->is_ma > 0) { for (i=0; ipreferred_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; + } } } } @@ -1527,6 +1594,12 @@ int CtdlOutputPreLoadedMsg( 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; @@ -1703,6 +1776,7 @@ START_TEXT: 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); @@ -1837,33 +1911,44 @@ void cmd_opna(char *cmdbuf) 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) ) @@ -1872,6 +1957,11 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, 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; @@ -1883,27 +1973,34 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, 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; iroom); /* unlock the room */ - getroom(&CC->room, hold_rm); - free(msglist); - return(ERROR + ALREADY_EXISTS); + for (i=0; i 0) for (j=0; jroom)) && (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; icm_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; icm_fields['I']==NULL) { @@ -2116,7 +2249,7 @@ void ReplicationChecks(struct CtdlMessage *msg) { 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); } } @@ -2561,6 +2694,7 @@ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ char *m; int flushing = 0; int finished = 0; + int dotdot = 0; if (exist == NULL) { m = malloc(4096); @@ -2578,6 +2712,11 @@ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ } } + /* 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; @@ -2594,6 +2733,13 @@ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ 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); @@ -3246,7 +3392,8 @@ void cmd_ent0(char *entargs) * (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 */ ) @@ -3257,13 +3404,13 @@ int CtdlDeleteMessages(char *room_name, /* which room */ 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) { @@ -3286,9 +3433,20 @@ int CtdlDeleteMessages(char *room_name, /* which room */ /* 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; jroom.QRname, delnum, "", 1); + /* + * Build our message set to be moved/copied + */ + msgs = malloc(num_msgs * sizeof(long)); + for (i=0; iroom.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); @@ -3407,20 +3585,32 @@ int CtdlCopyMsgToRoom(long msgnum, char *dest) { + /* * 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; @@ -3464,10 +3654,23 @@ void cmd_move(char *args) return; } - err = CtdlCopyMsgToRoom(num, targ); + /* + * Build our message set to be moved/copied + */ + msgs = malloc(num_msgs * sizeof(long)); + for (i=0; iroom.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") ); } @@ -3685,7 +3889,7 @@ void CtdlWriteObject(char *req_room, /* Room to stuff it in */ */ 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 */ @@ -3720,7 +3924,7 @@ char *CtdlGetSysConfig(char *sysconfname) { /* 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);