managesieve: prevent session list corruption
[claws.git] / src / plugins / managesieve / managesieve.c
index a6d7bbbd4e5fa5d1a047aa4635a4b9d0cac5e073..4e7508a764d14a5df580ee23fb4c8f815c97b4d4 100644 (file)
@@ -39,43 +39,68 @@ GSList *sessions = NULL;
 static void sieve_session_destroy(Session *session);
 static gint sieve_pop_send_queue(SieveSession *session);
 static void sieve_session_reset(SieveSession *session);
+static void command_free(SieveCommand *cmd);
+static void command_abort(SieveCommand *cmd);
+static void command_cb(SieveCommand *cmd, gpointer result);
 
 void sieve_sessions_close()
 {
        if (sessions) {
-               g_slist_free_full(sessions, (GDestroyNotify)session_destroy);
+               GSList *list = sessions;
                sessions = NULL;
+               g_slist_free_full(list, (GDestroyNotify)session_destroy);
        }
 }
 
-void noop_data_cb_fn(SieveSession *session, gpointer cb_data,
-               gpointer user_data)
-{
-       /* noop */
-}
-
 /* remove all command callbacks with a given data pointer */
 void sieve_sessions_discard_callbacks(gpointer user_data)
 {
        GSList *item;
        GSList *queue;
+       GSList *prev = NULL;
        SieveSession *session;
        SieveCommand *cmd;
 
        for (item = sessions; item; item = item->next) {
                session = (SieveSession *)item->data;
                cmd = session->current_cmd;
-               if (cmd && cmd->data == user_data)
-                       cmd->cb = noop_data_cb_fn;
+               /* abort current command handler */
+               if (cmd && cmd->data == user_data) {
+                       command_abort(cmd);
+                       session->current_cmd = NULL;
+               }
+               /* abort queued command handlers */
                for (queue = session->send_queue; queue; queue = queue->next) {
-                       cmd = (SieveCommand *)item->data;
-                       if (cmd && cmd->data == user_data)
-                               cmd->cb = noop_data_cb_fn;
+                       cmd = (SieveCommand *)queue->data;
+                       if (cmd && cmd->data == user_data) {
+                               if (prev)
+                                       prev->next = queue->next;
+                               else
+                                       session->send_queue = NULL;
+                               command_abort(cmd);
+                               g_slist_free_1(queue);
+                       } else {
+                               prev = queue;
+                       }
                }
        }
 }
 
-void command_free(SieveCommand *cmd) {
+static void command_cb(SieveCommand *cmd, gpointer result)
+{
+       if (cmd)
+               cmd->cb(cmd->session, FALSE, result, cmd->data);
+}
+
+static void command_abort(SieveCommand *cmd)
+{
+       cmd->cb(cmd->session, TRUE, NULL, cmd->data);
+       g_free(cmd->msg);
+       g_free(cmd);
+}
+
+static void command_free(SieveCommand *cmd)
+{
        g_free(cmd->msg);
        g_free(cmd);
 }
@@ -272,7 +297,7 @@ static gint sieve_auth(SieveSession *session)
        if (!session->use_auth) {
                session->state = SIEVE_READY;
                sieve_connected(session, TRUE);
-               return sieve_pop_send_queue(session);
+               return SE_OK;
        }
 
        session->state = SIEVE_AUTH;
@@ -319,7 +344,7 @@ static void sieve_session_putscript_cb(SieveSession *session, SieveResult *resul
                result->description = desc;
        }
        /* pass along the callback */
-       session->current_cmd->cb(session, result, session->current_cmd->data);
+       command_cb(session->current_cmd, result);
 }
 
 static inline gboolean response_is_ok(const char *msg)
@@ -379,6 +404,11 @@ static gint sieve_pop_send_queue(SieveSession *session)
        SieveCommand *cmd;
        GSList *send_queue = session->send_queue;
 
+       if (session->current_cmd) {
+               command_free(session->current_cmd);
+               session->current_cmd = NULL;
+       }
+
        if (!send_queue)
                return SE_OK;
 
@@ -388,8 +418,6 @@ static gint sieve_pop_send_queue(SieveSession *session)
 
        log_send(session, cmd);
        session->state = cmd->next_state;
-       if (session->current_cmd)
-               command_free(session->current_cmd);
        session->current_cmd = cmd;
        if (session_send_msg(SESSION(session), SESSION_SEND, cmd->msg) < 0)
                return SE_ERROR;
@@ -442,6 +470,8 @@ static void parse_response(gchar *msg, SieveResult *result)
 {
        gchar *end;
 
+       cm_return_if_fail(msg != NULL);
+
        /* response status */
        if (isalpha(msg[0])) {
                end = strchr(msg, ' ');
@@ -504,7 +534,7 @@ static gint sieve_session_recv_msg(Session *session, const gchar *msg)
 {
        SieveSession *sieve_session = SIEVE_SESSION(session);
        SieveResult result;
-       gint ret = 0;
+       gint ret = SE_OK;
 
        switch (sieve_session->state) {
        case SIEVE_GETSCRIPT_DATA:
@@ -546,7 +576,7 @@ static gint sieve_session_recv_msg(Session *session, const gchar *msg)
                                        sieve_session->state = SIEVE_ERROR;
                                } else {
                                        log_warning(LOG_PROTOCOL, "Sieve: continuing without TLS\n");
-                                       sieve_session->state = SIEVE_CAPABILITIES;
+                                       sieve_session->state = SIEVE_READY;
                                }
                                break;
                        }
@@ -557,7 +587,6 @@ static gint sieve_session_recv_msg(Session *session, const gchar *msg)
                        } else {
                                sieve_session->state = SIEVE_READY;
                                sieve_connected(sieve_session, TRUE);
-                               ret = sieve_pop_send_queue(sieve_session);
                        }
                } else {
                        /* got a capability */
@@ -580,7 +609,6 @@ static gint sieve_session_recv_msg(Session *session, const gchar *msg)
                }
                sieve_session->tls_init_done = TRUE;
                sieve_session->state = SIEVE_CAPABILITIES;
-               ret = SE_OK;
 #endif
                break;
        case SIEVE_AUTH:
@@ -602,7 +630,6 @@ static gint sieve_session_recv_msg(Session *session, const gchar *msg)
                        sieve_session->authenticated = TRUE;
                        sieve_session->state = SIEVE_READY;
                        sieve_connected(sieve_session, TRUE);
-                       ret = sieve_pop_send_queue(sieve_session);
                }
                break;
        case SIEVE_NOOP:
@@ -614,18 +641,14 @@ static gint sieve_session_recv_msg(Session *session, const gchar *msg)
        case SIEVE_LISTSCRIPTS:
                if (response_is_no(msg)) {
                        /* got an error. probably not authenticated. */
-                       sieve_session->current_cmd->cb(sieve_session, NULL,
-                                       sieve_session->current_cmd->data);
+                       command_cb(sieve_session->current_cmd, NULL);
                        sieve_session->state = SIEVE_READY;
-                       ret = sieve_pop_send_queue(sieve_session);
                } else if (response_is_ok(msg)) {
                        /* end of list */
                        sieve_session->state = SIEVE_READY;
                        sieve_session->error = SE_OK;
-                       sieve_session->current_cmd->cb(sieve_session,
-                                       (gpointer)&(SieveScript){0},
-                                       sieve_session->current_cmd->data);
-                       ret = sieve_pop_send_queue(sieve_session);
+                       command_cb(sieve_session->current_cmd,
+                                       (gpointer)&(SieveScript){0});
                } else {
                        /* got a script name */
                        SieveScript script;
@@ -635,41 +658,55 @@ static gint sieve_session_recv_msg(Session *session, const gchar *msg)
                        script.active = (script_status &&
                                        strcasecmp(script_status, "active") == 0);
 
-                       sieve_session->current_cmd->cb(sieve_session, (gpointer)&script,
-                                       sieve_session->current_cmd->data);
-                       ret = SE_OK;
+                       command_cb(sieve_session->current_cmd,
+                                       (gpointer)&script);
                }
                break;
        case SIEVE_RENAMESCRIPT:
                if (response_is_no(msg)) {
                        /* error */
-                       sieve_session->current_cmd->cb(sieve_session, NULL,
-                                       sieve_session->current_cmd->data);
+                       command_cb(sieve_session->current_cmd, NULL);
                } else if (response_is_ok(msg)) {
-                       sieve_session->current_cmd->cb(sieve_session, (void*)TRUE,
-                                       sieve_session->current_cmd->data);
+                       command_cb(sieve_session->current_cmd, (void*)TRUE);
                } else {
                        log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
                }
                sieve_session->state = SIEVE_READY;
                break;
        case SIEVE_SETACTIVE:
-               if (response_is_no(msg)) {
-                       /* error */
-                       sieve_session->current_cmd->cb(sieve_session, NULL,
-                                       sieve_session->current_cmd->data);
-               } else if (response_is_ok(msg)) {
-                       sieve_session->current_cmd->cb(sieve_session, (void*)TRUE,
-                                       sieve_session->current_cmd->data);
+               parse_response((gchar *)msg, &result);
+               if (result.success) {
+                       /* clear status possibly set when setting another
+                        * script active. TODO: give textual feedback */
+                       sieve_error(sieve_session, "");
+
+                       command_cb(sieve_session->current_cmd, NULL);
+               } else if (result.description) {
+                       command_cb(sieve_session->current_cmd,
+                                       result.description);
                } else {
                        log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
                }
-               sieve_session->state = SIEVE_READY;
+               if (result.has_octets) {
+                       sieve_session->octets_remaining = result.octets;
+                       sieve_session->state = SIEVE_SETACTIVE_DATA;
+               } else {
+                       sieve_session->state = SIEVE_READY;
+               }
+               break;
+       case SIEVE_SETACTIVE_DATA:
+               /* Dovecot shows a script's warnings when making it active */
+               sieve_session->octets_remaining -= strlen(msg) + 1;
+               if (sieve_session->octets_remaining > 0) {
+                       /* TODO: buffer multi-line message */
+                       sieve_error(sieve_session, msg);
+               } else {
+                       sieve_session->state = SIEVE_READY;
+               }
                break;
        case SIEVE_GETSCRIPT:
                if (response_is_no(msg)) {
-                       sieve_session->current_cmd->cb(sieve_session, (void *)-1,
-                                       sieve_session->current_cmd->data);
+                       command_cb(sieve_session->current_cmd, (void *)-1);
                        sieve_session->state = SIEVE_READY;
                } else {
                        parse_response((gchar *)msg, &result);
@@ -677,22 +714,17 @@ static gint sieve_session_recv_msg(Session *session, const gchar *msg)
                        /* account for newline */
                        sieve_session->octets_remaining = result.octets + 1;
                }
-               ret = SE_OK;
                break;
        case SIEVE_GETSCRIPT_DATA:
                if (sieve_session->octets_remaining > 0) {
-                       sieve_session->current_cmd->cb(sieve_session,
-                                       (gchar *)msg,
-                                       sieve_session->current_cmd->data);
+                       command_cb(sieve_session->current_cmd, (gchar *)msg);
                        sieve_session->octets_remaining -= strlen(msg) + 1;
                } else if (response_is_ok(msg)) {
                        sieve_session->state = SIEVE_READY;
-                       sieve_session->current_cmd->cb(sieve_session, NULL,
-                                       sieve_session->current_cmd->data);
+                       command_cb(sieve_session->current_cmd, NULL);
                } else {
                        log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
                }
-               ret = SE_OK;
                break;
        case SIEVE_PUTSCRIPT:
                parse_response((gchar *)msg, &result);
@@ -702,7 +734,6 @@ static gint sieve_session_recv_msg(Session *session, const gchar *msg)
                        sieve_session->state = SIEVE_READY;
                }
                sieve_session_putscript_cb(sieve_session, &result);
-               ret = SE_OK;
                break;
        case SIEVE_PUTSCRIPT_DATA:
                if (!msg[0]) {
@@ -715,16 +746,14 @@ static gint sieve_session_recv_msg(Session *session, const gchar *msg)
                        result.description = (gchar *)msg;
                        sieve_session_putscript_cb(sieve_session, &result);
                }
-               ret = SE_OK;
                break;
        case SIEVE_DELETESCRIPT:
                parse_response((gchar *)msg, &result);
                if (!result.success) {
-                       sieve_session->current_cmd->cb(sieve_session, result.description,
-                                       sieve_session->current_cmd->data);
+                       command_cb(sieve_session->current_cmd,
+                                       result.description);
                } else {
-                       sieve_session->current_cmd->cb(sieve_session, NULL,
-                                       sieve_session->current_cmd->data);
+                       command_cb(sieve_session->current_cmd, NULL);
                }
                sieve_session->state = SIEVE_READY;
                break;
@@ -744,9 +773,12 @@ static gint sieve_session_recv_msg(Session *session, const gchar *msg)
                return -1;
        }
 
-       if (ret == SE_OK)
+       if (ret == SE_OK && sieve_session->state == SIEVE_READY)
+               ret = sieve_pop_send_queue(sieve_session);
+
+       if (ret == SE_OK) {
                return session_recv_msg(session);
-       else if (ret == SE_AUTHFAIL) {
+       else if (ret == SE_AUTHFAIL) {
                sieve_error(sieve_session, _("Auth failed"));
                sieve_session->state = SIEVE_ERROR;
                sieve_session->error = SE_ERROR;
@@ -791,8 +823,10 @@ static void sieve_session_destroy(Session *session)
        SieveSession *sieve_session = SIEVE_SESSION(session);
        g_free(sieve_session->pass);
        if (sieve_session->current_cmd)
-               command_free(sieve_session->current_cmd);
+               command_abort(sieve_session->current_cmd);
        sessions = g_slist_remove(sessions, (gconstpointer)session);
+       g_slist_free_full(sieve_session->send_queue,
+                       (GDestroyNotify)command_abort);
 }
 
 static void sieve_connect_finished(Session *session, gboolean success)
@@ -834,7 +868,7 @@ static void sieve_session_reset(SieveSession *session)
        SieveAccountConfig *config = sieve_prefs_account_get_config(account);
        gboolean reuse_auth = (config->auth == SIEVEAUTH_REUSE);
 
-       g_slist_free_full(session->send_queue, (GDestroyNotify)command_free);
+       g_slist_free_full(session->send_queue, (GDestroyNotify)command_abort);
 
        session_disconnect(SESSION(session));
 
@@ -921,6 +955,7 @@ static void sieve_queue_send(SieveSession *session, SieveState next_state,
 {
        gboolean queue = FALSE;
        SieveCommand *cmd = g_new0(SieveCommand, 1);
+       cmd->session = session;
        cmd->next_state = next_state;
        cmd->msg = msg;
        cmd->data = data;
@@ -964,15 +999,6 @@ void sieve_session_list_scripts(SieveSession *session,
        sieve_queue_send(session, SIEVE_LISTSCRIPTS, msg, cb, data);
 }
 
-void sieve_session_add_script(SieveSession *session, const gchar *filter_name,
-               sieve_session_data_cb_fn cb, gpointer data)
-{
-/*
-       gchar *msg = g_strdup("LISTSCRIPTS");
-       sieve_queue_send(session, SIEVE_LISTSCRIPTS, msg, cb, data);
-*/
-}
-
 void sieve_session_set_active_script(SieveSession *session,
                const gchar *filter_name,
                sieve_session_data_cb_fn cb, gpointer data)
@@ -980,7 +1006,7 @@ void sieve_session_set_active_script(SieveSession *session,
        gchar *msg = g_strdup_printf("SETACTIVE \"%s\"",
                        filter_name ? filter_name : "");
        if (!msg) {
-               cb(session, (void*)FALSE, data);
+               cb(session, FALSE, (void*)FALSE, data);
                return;
        }
 
@@ -1011,8 +1037,9 @@ void sieve_session_put_script(SieveSession *session, const gchar *filter_name,
                sieve_session_data_cb_fn cb, gpointer data)
 {
        /* TODO: refactor so don't have to copy the whole script here */
-       gchar *msg = g_strdup_printf("PUTSCRIPT \"%s\" {%u+}\r\n%s",
-                       filter_name, len, script_contents);
+       gchar *msg = g_strdup_printf("PUTSCRIPT \"%s\" {%u+}%s%s",
+                       filter_name, len, len > 0 ? "\r\n" : "",
+                       script_contents);
 
        sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
 }
@@ -1021,8 +1048,8 @@ void sieve_session_check_script(SieveSession *session,
                gint len, const gchar *script_contents,
                sieve_session_data_cb_fn cb, gpointer data)
 {
-       gchar *msg = g_strdup_printf("CHECKSCRIPT {%u+}\r\n%s",
-                       len, script_contents);
+       gchar *msg = g_strdup_printf("CHECKSCRIPT {%u+}%s%s",
+                       len, len > 0 ? "\r\n" : "", script_contents);
 
        sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
 }