#include <glib.h>
#include <glib/gi18n.h>
+#include <ctype.h>
+#include <errno.h>
#include "claws.h"
#include "account.h"
+#include "passwordstore.h"
#include "gtk/inputdialog.h"
#include "md5.h"
#include "utils.h"
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);
+static gint sieve_session_recv_chunk(SieveSession *, guint len);
+static void sieve_read_chunk(SieveSession *, gchar *data, guint len);
+static gint sieve_read_chunk_done(SieveSession *session);
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);
}
session->on_connected(session, connected, session->cb_data);
}
+static gboolean sieve_read_chunk_cb(SockInfo *source,
+ GIOCondition condition, gpointer data)
+{
+ SieveSession *sieve_session = SIEVE_SESSION(data);
+ Session *session = &sieve_session->session;
+ gint data_len;
+ gint ret;
+
+ cm_return_val_if_fail(condition == G_IO_IN, FALSE);
+
+ session_set_timeout(session, session->timeout_interval);
+
+ if (session->read_buf_len == 0) {
+ gint read_len = -1;
+
+ if (session->sock)
+ read_len = sock_read(session->sock,
+ session->read_buf,
+ SESSION_BUFFSIZE - 1);
+
+ if (read_len == -1 &&
+ session->state == SESSION_DISCONNECTED) {
+ g_warning ("sock_read: session disconnected");
+ if (session->io_tag > 0) {
+ g_source_remove(session->io_tag);
+ session->io_tag = 0;
+ }
+ return FALSE;
+ }
+
+ if (read_len == 0) {
+ g_warning("sock_read: received EOF");
+ session->state = SESSION_EOF;
+ return FALSE;
+ }
+
+ if (read_len < 0) {
+ switch (errno) {
+ case EAGAIN:
+ return TRUE;
+ default:
+ g_warning("sock_read: %s",
+ g_strerror(errno));
+ session->state = SESSION_ERROR;
+ return FALSE;
+ }
+ }
+
+ session->read_buf_len = read_len;
+ }
+
+ data_len = MIN(session->read_buf_len,
+ sieve_session->octets_remaining);
+ sieve_session->octets_remaining -= data_len;
+ session->read_buf_len -= data_len;
+ session->read_buf_p[data_len] = '\0';
+
+ /* progress callback */
+ sieve_read_chunk(sieve_session, session->read_buf_p, data_len);
+
+ if (session->read_buf_len == 0) {
+ session->read_buf_p = session->read_buf;
+ } else {
+ session->read_buf_p += data_len;
+ }
+
+ /* incomplete read */
+ if (sieve_session->octets_remaining > 0)
+ return TRUE;
+
+ /* complete */
+ if (session->io_tag > 0) {
+ g_source_remove(session->io_tag);
+ session->io_tag = 0;
+ }
+
+ /* completion callback */
+ ret = sieve_read_chunk_done(sieve_session);
+
+ if (ret < 0)
+ session->state = SESSION_ERROR;
+
+ return FALSE;
+}
+
+static gboolean sieve_read_chunk_idle_cb(gpointer data)
+{
+ Session *session = SESSION(data);
+ gboolean ret;
+
+ ret = sieve_read_chunk_cb(session->sock, G_IO_IN, session);
+
+ if (ret == TRUE)
+ session->io_tag = sock_add_watch(session->sock, G_IO_IN,
+ sieve_read_chunk_cb, session);
+
+ return FALSE;
+}
+
+/* Get data of specified length.
+ * If needed elsewhere, this should be put in session.c */
+static gint sieve_session_recv_chunk(SieveSession *sieve_session,
+ guint bytes)
+{
+ Session *session = &sieve_session->session;
+ cm_return_val_if_fail(session->read_msg_buf->len == 0, -1);
+
+ session->state = SESSION_RECV;
+ sieve_session->octets_remaining = bytes;
+
+ if (session->read_buf_len > 0)
+ g_idle_add(sieve_read_chunk_idle_cb, session);
+ else
+ session->io_tag = sock_add_watch(session->sock, G_IO_IN,
+ sieve_read_chunk_cb, session);
+ return 0;
+}
+
+
static gint sieve_auth_recv(SieveSession *session, const gchar *msg)
{
gchar buf[MESSAGEBUFSIZE], *tmp;
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;
static void sieve_session_putscript_cb(SieveSession *session, SieveResult *result)
{
/* Remove script name from the beginning the response,
- * which are added by Dovecot/Pigeonhole */
+ * which is added by Dovecot/Pigeonhole */
gchar *start, *desc = result->description;
- if (desc) {
+ gchar *end = NULL;
+ if (!desc) {
+ /* callback just for the status */
+ command_cb(session->current_cmd, result);
+ }
+ while (desc && desc[0]) {
+ if ((end = strchr(desc, '\r')) ||
+ (end = strchr(desc, '\n')))
+ while (*end == '\n' || *end == '\r')
+ *end++ = '\0';
if (g_str_has_prefix(desc, "NULL_") && (start = strchr(desc+5, ':'))) {
desc = start+1;
while (*desc == ' ')
desc = start+2;
}
result->description = desc;
+ command_cb(session->current_cmd, result);
+ desc = end;
}
- /* pass along the callback */
- session->current_cmd->cb(session, result, session->current_cmd->data);
}
static inline gboolean response_is_ok(const char *msg)
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;
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;
static void parse_response(gchar *msg, SieveResult *result)
{
+ gchar *end;
+
+ cm_return_if_fail(msg != NULL);
+
/* response status */
- gchar *end = strchr(msg, ' ');
- if (end)
- *end++ = '\0';
- result->success = strcmp(msg, "OK") == 0;
- result->has_status = TRUE;
- if (!end) {
- result->code = SIEVE_CODE_NONE;
- result->description = NULL;
- result->has_octets = FALSE;
- result->octets = 0;
- return;
+ if (isalpha(msg[0])) {
+ end = strchr(msg, ' ');
+ if (end) {
+ *end++ = '\0';
+ while (*end == ' ')
+ end++;
+ }
+ result->success = strcmp(msg, "OK") == 0;
+ result->has_status = TRUE;
+ msg = end;
+ } else {
+ result->has_status = FALSE;
}
- while (*end == ' ')
- end++;
- msg = end;
/* response code */
- if (msg[0] == '(' && (end = strchr(msg, ')'))) {
+ if (msg && msg[0] == '(' && (end = strchr(msg, ')'))) {
msg++;
*end++ = '\0';
result->code =
}
/* s2c octets */
- if (msg[0] == '{' && (end = strchr(msg, '}'))) {
+ if (msg && msg[0] == '{' && (end = strchr(msg, '}'))) {
msg++;
*end++ = '\0';
if (msg[0] == '0' && msg+1 == end) {
}
/* text */
- if (*msg) {
+ if (msg && *msg) {
unquote_inplace(msg);
result->description = msg;
} else {
{
SieveSession *sieve_session = SIEVE_SESSION(session);
SieveResult result;
- gint ret = 0;
+ gint ret = SE_OK;
- switch (sieve_session->state) {
- case SIEVE_GETSCRIPT_DATA:
- log_print(LOG_PROTOCOL, "Sieve< [GETSCRIPT data]\n");
- break;
- default:
- log_print(LOG_PROTOCOL, "Sieve< %s\n", msg);
- if (response_is_bye(msg)) {
- gchar *status;
- parse_response((gchar *)msg, &result);
- if (!result.description)
- status = g_strdup(_("Disconnected"));
- else if (g_str_has_prefix(result.description, "Disconnected"))
- status = g_strdup(result.description);
- else
- status = g_strdup_printf(_("Disconnected: %s"), result.description);
- sieve_session->error = SE_ERROR;
- sieve_error(sieve_session, status);
- sieve_session->state = SIEVE_DISCONNECTED;
- g_free(status);
- return -1;
- }
+ log_print(LOG_PROTOCOL, "Sieve< %s\n", msg);
+ if (response_is_bye(msg)) {
+ gchar *status;
+ parse_response((gchar *)msg, &result);
+ if (!result.description)
+ status = g_strdup(_("Disconnected"));
+ else if (g_str_has_prefix(result.description, "Disconnected"))
+ status = g_strdup(result.description);
+ else
+ status = g_strdup_printf(_("Disconnected: %s"), result.description);
+ sieve_session->error = SE_ERROR;
+ sieve_error(sieve_session, status);
+ sieve_session->state = SIEVE_DISCONNECTED;
+ g_free(status);
+ return -1;
}
switch (sieve_session->state) {
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;
}
} else {
sieve_session->state = SIEVE_READY;
sieve_connected(sieve_session, TRUE);
- ret = sieve_pop_send_queue(sieve_session);
}
} else {
/* got a capability */
}
break;
case SIEVE_READY:
+ if (!msg[0])
+ break;
log_warning(LOG_PROTOCOL,
_("unhandled message on Sieve session: %s\n"), msg);
break;
}
sieve_session->tls_init_done = TRUE;
sieve_session->state = SIEVE_CAPABILITIES;
- ret = SE_OK;
#endif
break;
case SIEVE_AUTH:
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:
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;
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) {
+ return sieve_session_recv_chunk(sieve_session,
+ result.octets);
+ } 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);
sieve_session->state = SIEVE_GETSCRIPT_DATA;
+ return sieve_session_recv_chunk(sieve_session,
+ result.octets);
}
- ret = SE_OK;
break;
case SIEVE_GETSCRIPT_DATA:
+ if (!msg[0])
+ break;
+ sieve_session->state = SIEVE_READY;
if (response_is_ok(msg)) {
- sieve_session->state = SIEVE_READY;
- sieve_session->current_cmd->cb(sieve_session, NULL,
- sieve_session->current_cmd->data);
- } else {
- sieve_session->current_cmd->cb(sieve_session, (gchar *)msg,
- sieve_session->current_cmd->data);
+ command_cb(sieve_session->current_cmd, NULL);
+ } else if (msg[0]) {
+ log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
}
- ret = SE_OK;
break;
case SIEVE_PUTSCRIPT:
+ if (!msg[0])
+ break;
parse_response((gchar *)msg, &result);
+ sieve_session_putscript_cb(sieve_session, &result);
if (result.has_octets) {
- sieve_session->state = SIEVE_PUTSCRIPT_DATA;
+ return sieve_session_recv_chunk(sieve_session,
+ result.octets);
} else {
sieve_session->state = SIEVE_READY;
}
- sieve_session_putscript_cb(sieve_session, &result);
- ret = SE_OK;
- break;
- case SIEVE_PUTSCRIPT_DATA:
- if (!msg[0]) {
- sieve_session->state = SIEVE_READY;
- } else {
- result.has_status = FALSE;
- result.has_octets = FALSE;
- result.success = -1;
- result.code = SIEVE_CODE_NONE;
- 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;
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;
return 0;
}
+static void sieve_read_chunk(SieveSession *session, gchar *data, guint len)
+{
+ log_print(LOG_PROTOCOL, "Sieve< [%u bytes]\n", len);
+
+ switch (session->state) {
+ case SIEVE_GETSCRIPT_DATA:
+ command_cb(session->current_cmd, (gchar *)data);
+ break;
+ case SIEVE_SETACTIVE:
+ /* Dovecot shows a script's warnings when making it active */
+ /* TODO: append message in case it is very long*/
+ strretchomp(data);
+ sieve_error(session, data);
+ break;
+ case SIEVE_PUTSCRIPT: {
+ SieveResult result = {.description = (gchar *)data};
+ sieve_session_putscript_cb(session, &result);
+ break;
+ }
+ default:
+ log_warning(LOG_PROTOCOL,
+ _("error occurred on SIEVE session\n"));
+ }
+}
+
+static gint sieve_read_chunk_done(SieveSession *session)
+{
+ gint ret = SE_OK;
+
+ switch (session->state) {
+ case SIEVE_GETSCRIPT_DATA:
+ /* wait for ending "OK" response */
+ break;
+ case SIEVE_SETACTIVE:
+ case SIEVE_PUTSCRIPT:
+ session->state = SIEVE_READY;
+ break;
+ default:
+ log_warning(LOG_PROTOCOL,
+ _("error occurred on SIEVE session\n"));
+ }
+
+ if (ret == SE_OK && session->state == SIEVE_READY)
+ ret = sieve_pop_send_queue(session);
+
+ if (ret == SE_OK)
+ return session_recv_msg(SESSION(session));
+
+ return 0;
+}
+
static gint sieve_cmd_noop(SieveSession *session)
{
log_print(LOG_PROTOCOL, "Sieve> NOOP\n");
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)
{
session->state = SIEVE_CAPABILITIES;
session->authenticated = FALSE;
+#ifdef USE_GNUTLS
session->tls_init_done = FALSE;
+#endif
return session_connect(SESSION(session), session->host,
session->port);
}
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));
session->current_cmd = NULL;
session->send_queue = NULL;
session->state = SIEVE_CAPABILITIES;
+#ifdef USE_GNUTLS
session->tls_init_done = FALSE;
+#endif
session->avail_auth_type = 0;
session->auth_type = 0;
session->config = config;
g_free(session->pass);
if (config->auth == SIEVEAUTH_NONE) {
session->pass = NULL;
- } else if (reuse_auth && account->passwd) {
- session->pass = g_strdup(account->passwd);
- } else if (config->passwd && config->passwd[0]) {
- session->pass = g_strdup(config->passwd);
+ } else if (reuse_auth && (session->pass = passwd_store_get_account(
+ account->account_id, PWS_ACCOUNT_RECV))) {
+ } else if ((session->pass = passwd_store_get_account(
+ account->account_id, "sieve"))) {
} else if (password_get(session->user, session->host, "sieve",
session->port, &session->pass)) {
} else {
{
gboolean queue = FALSE;
SieveCommand *cmd = g_new0(SieveCommand, 1);
+ cmd->session = session;
cmd->next_state = next_state;
cmd->msg = msg;
cmd->data = data;
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)
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;
}
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);
}
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);
}