New server command DLAT (DownLoad ATtachment) which
[citadel.git] / citadel / msgbase.c
index c91ead1c1bd3cdbc58c4d432759205d1148c9aff..2a6b775c8a0efa1e6e48058793796efcf5b6458c 100644 (file)
 #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; 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.
@@ -647,13 +692,14 @@ void cmd_msgs(char *cmdbuf)
        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;
@@ -665,12 +711,22 @@ void cmd_msgs(char *cmdbuf)
                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",
@@ -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),
@@ -912,6 +969,27 @@ void mime_download(char *name, char *filename, char *partnum, char *disp,
 
 
 
+/*
+ * Callback function for mime parser that outputs a section all at once
+ */
+void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
+                  void *content, char *cbtype, char *cbcharset, size_t length,
+                  char *encoding, void *cbuserdata)
+{
+       int *found_it = (int *)cbuserdata;
+
+       /* ...or if this is not the desired section */
+       if (strcasecmp(CC->download_desired_section, partnum))
+               return;
+
+       *found_it = 1;
+
+       cprintf("%d %d\n", BINARY_FOLLOWS, length);
+       client_write(content, length);
+}
+
+
+
 /*
  * Load a message from disk into memory.
  * This is used by CtdlOutputMsg() and other fetch functions.
@@ -1152,6 +1230,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 +1250,12 @@ void choose_preferred(char *name, char *filename, char *partnum, char *disp,
        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;
+                               }
                        }
                }
        }
@@ -1425,6 +1513,32 @@ int CtdlOutputPreLoadedMsg(
                return((CC->download_fp != NULL) ? om_ok : om_mime_error);
        }
 
+       /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
+        * in a single server operation instead of opening a download file.
+        */
+       if (mode == MT_SPEW_SECTION) {
+               if (TheMessage->cm_format_type != FMT_RFC822) {
+                       if (do_proto)
+                               cprintf("%d This is not a MIME message.\n",
+                               ERROR + ILLEGAL_VALUE);
+               } else {
+                       /* Parse the message text component */
+                       int found_it = 0;
+
+                       mptr = TheMessage->cm_fields['M'];
+                       mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
+                       /* If section wasn't found, print an error
+                        */
+                       if (!found_it) {
+                               if (do_proto) cprintf(
+                                       "%d Section %s not found.\n",
+                                       ERROR + FILE_NOT_FOUND,
+                                       CC->download_desired_section);
+                       }
+               }
+               return((CC->download_fp != NULL) ? om_ok : om_mime_error);
+       }
+
        /* now for the user-mode message reading loops */
        if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
 
@@ -1527,6 +1641,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 +1823,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,6 +1958,23 @@ void cmd_opna(char *cmdbuf)
        CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
 }                      
 
+
+/*
+ * Open a component of a MIME message and transmit it all at once
+ */
+void cmd_dlat(char *cmdbuf)
+{
+       long msgid;
+       char desired_section[128];
+
+       msgid = extract_long(cmdbuf, 0);
+       extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
+       safestrncpy(CC->download_desired_section, desired_section,
+               sizeof CC->download_desired_section);
+       CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
+}                      
+
+
 /*
  * Save one or more message pointers into a specified room
  * (Returns 0 for success, nonzero for failure)
@@ -1980,6 +2118,16 @@ int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newms
                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);
 
@@ -2563,7 +2711,7 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,       /* message to save */
 /*
  * 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;
@@ -2574,7 +2722,21 @@ void quickie_message(char *from, char *to, char *room, char *text,
        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) {
@@ -2610,6 +2772,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);
@@ -2627,6 +2790,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;
@@ -2643,6 +2811,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);
@@ -3431,25 +3606,45 @@ int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
 /*
  * 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, "", 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);
        }
 }
 
@@ -3807,7 +4002,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);