/* * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client * Copyright (C) 1999-2015 the Claws Mail Team * Copyright (C) 2014-2015 Charles Lehner * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include "claws.h" #include "account.h" #include "passwordstore.h" #include "gtk/inputdialog.h" #include "md5.h" #include "utils.h" #include "log.h" #include "session.h" #include "prefs_common.h" #include "managesieve.h" #include "sieve_editor.h" #include "sieve_prefs.h" 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); 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) { GSList *list = sessions; sessions = NULL; g_slist_free_full(list, (GDestroyNotify)session_destroy); } } /* 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; /* 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 *)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; } } } } 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); } void sieve_session_handle_status(SieveSession *session, sieve_session_error_cb_fn on_error, sieve_session_connected_cb_fn on_connected, gpointer data) { session->on_error = on_error; session->on_connected = on_connected; session->cb_data = data; } static void sieve_error(SieveSession *session, const gchar *msg) { if (session->on_error) session->on_error(session, msg, session->cb_data); } static void sieve_connected(SieveSession *session, gboolean connected) { if (session->on_connected) 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; switch (session->auth_type) { case SIEVEAUTH_LOGIN: session->state = SIEVE_AUTH_LOGIN_USER; if (strstr(msg, "VXNlcm5hbWU6")) { tmp = g_base64_encode(session->user, strlen(session->user)); g_snprintf(buf, sizeof(buf), "\"%s\"", tmp); if (session_send_msg(SESSION(session), buf) < 0) { g_free(tmp); return SE_ERROR; } g_free(tmp); log_print(LOG_PROTOCOL, "Sieve> [USERID]\n"); } else { /* Server rejects AUTH */ if (session_send_msg(SESSION(session), "\"*\"") < 0) return SE_ERROR; log_print(LOG_PROTOCOL, "Sieve> *\n"); } break; case SIEVEAUTH_CRAM_MD5: session->state = SIEVE_AUTH_CRAM_MD5; if (msg[0] == '"') { gchar *response; gchar *response64; gchar *challenge, *tmp; gsize challengelen; guchar hexdigest[33]; tmp = g_base64_decode(msg + 1, &challengelen); challenge = g_strndup(tmp, challengelen); g_free(tmp); log_print(LOG_PROTOCOL, "Sieve< [Decoded: %s]\n", challenge); g_snprintf(buf, sizeof(buf), "%s", session->pass); md5_hex_hmac(hexdigest, challenge, challengelen, buf, strlen(session->pass)); g_free(challenge); response = g_strdup_printf ("%s %s", session->user, hexdigest); log_print(LOG_PROTOCOL, "Sieve> [Encoded: %s]\n", response); response64 = g_base64_encode(response, strlen(response)); g_free(response); response = g_strdup_printf("\"%s\"", response64); g_free(response64); if (session_send_msg(SESSION(session), response) < 0) { g_free(response); return SE_ERROR; } log_print(LOG_PROTOCOL, "Sieve> %s\n", response); g_free(response); } else { /* Server rejects AUTH */ if (session_send_msg(SESSION(session), "\"*\"") < 0) return SE_ERROR; log_print(LOG_PROTOCOL, "Sieve> *\n"); } break; default: /* stop sieve_auth when no correct authtype */ if (session_send_msg(SESSION(session), "*") < 0) return SE_ERROR; log_print(LOG_PROTOCOL, "Sieve> *\n"); break; } return SE_OK; } static gint sieve_auth_login_user_recv(SieveSession *session, const gchar *msg) { gchar *tmp, *tmp2; session->state = SIEVE_AUTH_LOGIN_PASS; if (strstr(msg, "UGFzc3dvcmQ6")) { tmp2 = g_base64_encode(session->pass, strlen(session->pass)); tmp = g_strdup_printf("\"%s\"", tmp2); g_free(tmp2); } else { /* Server rejects AUTH */ tmp = g_strdup("\"*\""); } if (session_send_msg(SESSION(session), tmp) < 0) { g_free(tmp); return SE_ERROR; } g_free(tmp); log_print(LOG_PROTOCOL, "Sieve> [PASSWORD]\n"); return SE_OK; } static gint sieve_auth_cram_md5(SieveSession *session) { session->state = SIEVE_AUTH; session->auth_type = SIEVEAUTH_CRAM_MD5; if (session_send_msg(SESSION(session), "Authenticate \"CRAM-MD5\"") < 0) return SE_ERROR; log_print(LOG_PROTOCOL, "Sieve> Authenticate CRAM-MD5\n"); return SE_OK; } static gint sieve_auth_plain(SieveSession *session) { gchar buf[MESSAGEBUFSIZE], *b64buf, *out; gint len; session->state = SIEVE_AUTH_PLAIN; session->auth_type = SIEVEAUTH_PLAIN; memset(buf, 0, sizeof buf); /* "\0user\0password" */ len = sprintf(buf, "%c%s%c%s", '\0', session->user, '\0', session->pass); b64buf = g_base64_encode(buf, len); out = g_strconcat("Authenticate \"PLAIN\" \"", b64buf, "\"", NULL); g_free(b64buf); if (session_send_msg(SESSION(session), out) < 0) { g_free(out); return SE_ERROR; } g_free(out); log_print(LOG_PROTOCOL, "Sieve> [Authenticate PLAIN]\n"); return SE_OK; } static gint sieve_auth_login(SieveSession *session) { session->state = SIEVE_AUTH; session->auth_type = SIEVEAUTH_LOGIN; if (session_send_msg(SESSION(session), "Authenticate \"LOGIN\"") < 0) return SE_ERROR; log_print(LOG_PROTOCOL, "Sieve> Authenticate LOGIN\n"); return SE_OK; } static gint sieve_auth(SieveSession *session) { SieveAuthType forced_auth_type = session->forced_auth_type; if (!session->use_auth) { session->state = SIEVE_READY; sieve_connected(session, TRUE); return SE_OK; } session->state = SIEVE_AUTH; sieve_error(session, _("Authenticating...")); if ((forced_auth_type == SIEVEAUTH_CRAM_MD5 || forced_auth_type == 0) && (session->avail_auth_type & SIEVEAUTH_CRAM_MD5) != 0) return sieve_auth_cram_md5(session); else if ((forced_auth_type == SIEVEAUTH_LOGIN || forced_auth_type == 0) && (session->avail_auth_type & SIEVEAUTH_LOGIN) != 0) return sieve_auth_login(session); else if ((forced_auth_type == SIEVEAUTH_PLAIN || forced_auth_type == 0) && (session->avail_auth_type & SIEVEAUTH_PLAIN) != 0) return sieve_auth_plain(session); else if (forced_auth_type == 0) { log_warning(LOG_PROTOCOL, _("No Sieve auth method available\n")); session->state = SIEVE_RETRY_AUTH; return SE_AUTHFAIL; } else { log_warning(LOG_PROTOCOL, _("Selected Sieve auth method not available\n")); session->state = SIEVE_RETRY_AUTH; return SE_AUTHFAIL; } return SE_OK; } static void sieve_session_putscript_cb(SieveSession *session, SieveResult *result) { /* Remove script name from the beginning the response, * which is added by Dovecot/Pigeonhole */ gchar *start, *desc = result->description; 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++; /* TODO: match against known script name, in case it contains * weird text like ": line " */ } else if ((start = strstr(desc, ": line ")) || (start = strstr(desc, ": error"))) { desc = start+2; } result->description = desc; command_cb(session->current_cmd, result); desc = end; } } static inline gboolean response_is_ok(const char *msg) { return !strncmp(msg, "OK", 2) && (!msg[2] || msg[2] == ' '); } static inline gboolean response_is_no(const char *msg) { return !strncmp(msg, "NO", 2) && (!msg[2] || msg[2] == ' '); } static inline gboolean response_is_bye(const char *msg) { return !strncmp(msg, "BYE", 3) && (!msg[3] || msg[3] == ' '); } static void sieve_got_capability(SieveSession *session, gchar *cap_name, gchar *cap_value) { if (strcmp(cap_name, "SASL") == 0) { SieveAuthType auth_type = 0; gchar *auth, *end; for (auth = cap_value; auth && auth[0]; auth = end) { if ((end = strchr(auth, ' '))) *end++ = '\0'; if (strcmp(auth, "PLAIN") == 0) { auth_type |= SIEVEAUTH_PLAIN; } else if (strcmp(auth, "CRAM-MD5") == 0) { auth_type |= SIEVEAUTH_CRAM_MD5; } else if (strcmp(auth, "LOGIN") == 0) { auth_type |= SIEVEAUTH_LOGIN; } } session->avail_auth_type = auth_type; } else if (strcmp(cap_name, "STARTTLS") == 0) { session->capability.starttls = TRUE; } } static void log_send(SieveSession *session, SieveCommand *cmd) { gchar *end, *msg = cmd->msg; if (cmd->next_state == SIEVE_PUTSCRIPT && (end = strchr(msg, '\n'))) { /* Don't log the script data */ msg = g_strndup(msg, end - msg); log_print(LOG_PROTOCOL, "Sieve> %s\n", msg); g_free(msg); msg = "[Data]"; } log_print(LOG_PROTOCOL, "Sieve> %s\n", msg); } 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; cmd = (SieveCommand *)send_queue->data; session->send_queue = g_slist_next(send_queue); g_slist_free_1(send_queue); log_send(session, cmd); session->state = cmd->next_state; session->current_cmd = cmd; if (session_send_msg(SESSION(session), cmd->msg) < 0) return SE_ERROR; return SE_OK; } static void parse_split(gchar *line, gchar **first_word, gchar **second_word) { gchar *first = line; gchar *second; gchar *end; /* get first */ if (line[0] == '"' && ((second = strchr(line + 1, '"')))) { *second++ = '\0'; first++; if (second[0] == ' ') second++; } else if ((second = strchr(line, ' '))) { *second++ = '\0'; } /* unquote second */ if (second && second[0] == '"' && ((end = strchr(second + 1, '"')))) { second++; *end = '\0'; } *first_word = first; *second_word = second; } static void unquote_inplace(gchar *str) { gchar *src, *dest; if (*str != '"') return; for (src = str+1, dest = str; src && *src && *src != '"'; src++) { if (*src == '\\') { src++; } *dest++ = *src; } *dest = '\0'; } 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, ' '); if (end) { *end++ = '\0'; while (*end == ' ') end++; } result->success = strcmp(msg, "OK") == 0; result->has_status = TRUE; msg = end; } else { result->has_status = FALSE; } /* response code */ if (msg && msg[0] == '(' && (end = strchr(msg, ')'))) { msg++; *end++ = '\0'; result->code = strcmp(msg, "WARNINGS") == 0 ? SIEVE_CODE_WARNINGS : strcmp(msg, "TRYLATER") == 0 ? SIEVE_CODE_TRYLATER : SIEVE_CODE_UNKNOWN; while (*end == ' ') end++; msg = end; } else { result->code = SIEVE_CODE_NONE; } /* s2c octets */ if (msg && msg[0] == '{' && (end = strchr(msg, '}'))) { msg++; *end++ = '\0'; if (msg[0] == '0' && msg+1 == end) { result->has_octets = TRUE; result->octets = 0; } else { result->has_octets = (result->octets = g_ascii_strtoll(msg, NULL, 10)) != 0; } while (*end == ' ') end++; msg = end; } else { result->has_octets = FALSE; result->octets = 0; } /* text */ if (msg && *msg) { unquote_inplace(msg); result->description = msg; } else { result->description = NULL; } } static gint sieve_session_recv_msg(Session *session, const gchar *msg) { SieveSession *sieve_session = SIEVE_SESSION(session); SieveResult result; gint ret = SE_OK; 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) { case SIEVE_CAPABILITIES: if (response_is_ok(msg)) { /* capabilities list done */ #ifdef USE_GNUTLS if (sieve_session->tls_init_done == FALSE && sieve_session->config->tls_type != SIEVE_TLS_NO) { if (sieve_session->capability.starttls) { if (session_send_msg(session, "STARTTLS") < 0) sieve_session->state = SIEVE_ERROR; else sieve_session->state = SIEVE_STARTTLS; } else if (sieve_session->config->tls_type == SIEVE_TLS_YES) { log_warning(LOG_PROTOCOL, "Sieve: does not support STARTTLS\n"); sieve_session->state = SIEVE_ERROR; } else { log_warning(LOG_PROTOCOL, "Sieve: continuing unencrypted\n"); sieve_session->state = SIEVE_READY; } break; } #endif /* authenticate after getting capabilities */ if (!sieve_session->authenticated) { ret = sieve_auth(sieve_session); } else { sieve_session->state = SIEVE_READY; sieve_connected(sieve_session, TRUE); } } else { /* got a capability */ gchar *cap_name, *cap_value; parse_split((gchar *)msg, &cap_name, &cap_value); sieve_got_capability(sieve_session, cap_name, cap_value); } break; case SIEVE_READY: if (!msg[0]) break; log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %s\n"), msg); break; case SIEVE_STARTTLS: #ifdef USE_GNUTLS if (session_start_tls(session) < 0) { sieve_session->state = SIEVE_ERROR; sieve_session->error = SE_ERROR; sieve_error(sieve_session, _("STARTTLS failed")); return -1; } sieve_session->tls_init_done = TRUE; sieve_session->state = SIEVE_CAPABILITIES; #endif break; case SIEVE_AUTH: ret = sieve_auth_recv(sieve_session, msg); break; case SIEVE_AUTH_LOGIN_USER: ret = sieve_auth_login_user_recv(sieve_session, msg); break; case SIEVE_AUTH_PLAIN: case SIEVE_AUTH_LOGIN_PASS: case SIEVE_AUTH_CRAM_MD5: if (response_is_no(msg)) { log_print(LOG_PROTOCOL, "Sieve auth failed\n"); sieve_session->state = SIEVE_RETRY_AUTH; ret = SE_AUTHFAIL; } else if (response_is_ok(msg)) { log_print(LOG_PROTOCOL, "Sieve auth completed\n"); sieve_error(sieve_session, ""); sieve_session->authenticated = TRUE; sieve_session->state = SIEVE_READY; sieve_connected(sieve_session, TRUE); } break; case SIEVE_NOOP: if (!response_is_ok(msg)) { sieve_session->state = SIEVE_ERROR; } sieve_session->state = SIEVE_READY; break; case SIEVE_LISTSCRIPTS: if (response_is_no(msg)) { /* got an error. probably not authenticated. */ command_cb(sieve_session->current_cmd, NULL); sieve_session->state = SIEVE_READY; } else if (response_is_ok(msg)) { /* end of list */ sieve_session->state = SIEVE_READY; sieve_session->error = SE_OK; command_cb(sieve_session->current_cmd, (gpointer)&(SieveScript){0}); } else { /* got a script name */ SieveScript script; gchar *script_status; parse_split((gchar *)msg, &script.name, &script_status); script.active = (script_status && strcasecmp(script_status, "active") == 0); command_cb(sieve_session->current_cmd, (gpointer)&script); } break; case SIEVE_RENAMESCRIPT: if (response_is_no(msg)) { /* error */ command_cb(sieve_session->current_cmd, NULL); } else if (response_is_ok(msg)) { 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: 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")); } 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)) { 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); } break; case SIEVE_GETSCRIPT_DATA: if (!msg[0]) break; sieve_session->state = SIEVE_READY; if (response_is_ok(msg)) { command_cb(sieve_session->current_cmd, NULL); } else if (msg[0]) { log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n")); } break; case SIEVE_PUTSCRIPT: if (!msg[0]) break; parse_response((gchar *)msg, &result); sieve_session_putscript_cb(sieve_session, &result); if (result.has_octets) { return sieve_session_recv_chunk(sieve_session, result.octets); } else { sieve_session->state = SIEVE_READY; } break; case SIEVE_DELETESCRIPT: parse_response((gchar *)msg, &result); if (!result.success) { command_cb(sieve_session->current_cmd, result.description); } else { command_cb(sieve_session->current_cmd, NULL); } sieve_session->state = SIEVE_READY; break; case SIEVE_ERROR: log_warning(LOG_PROTOCOL, _("error occurred on Sieve session. data: %s\n"), msg); sieve_session->error = SE_ERROR; break; case SIEVE_RETRY_AUTH: log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %s\n"), msg); ret = sieve_auth(sieve_session); break; default: log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %d\n"), sieve_session->state); sieve_session->error = SE_ERROR; return -1; } 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) { sieve_error(sieve_session, _("Auth failed")); sieve_session->state = SIEVE_ERROR; sieve_session->error = SE_ERROR; } return 0; } static gint sieve_recv_message(Session *session, const gchar *msg, gpointer user_data) { 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"); session->state = SIEVE_NOOP; if (session_send_msg(SESSION(session), "NOOP") < 0) { session->state = SIEVE_ERROR; session->error = SE_ERROR; return 1; } return 0; } static gboolean sieve_ping(gpointer data) { Session *session = SESSION(data); SieveSession *sieve_session = SIEVE_SESSION(session); if (sieve_session->state == SIEVE_ERROR || session->state == SESSION_ERROR) return FALSE; if (sieve_session->state != SIEVE_READY) return TRUE; return sieve_cmd_noop(sieve_session) == 0; } static void sieve_session_destroy(Session *session) { SieveSession *sieve_session = SIEVE_SESSION(session); g_free(sieve_session->pass); if (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) { if (!success) { sieve_connected(SIEVE_SESSION(session), FALSE); } } static gint sieve_session_connect(SieveSession *session) { PrefsAccount *ac = session->account; ProxyInfo *proxy_info = NULL; session->state = SIEVE_CAPABILITIES; session->authenticated = FALSE; #ifdef USE_GNUTLS session->tls_init_done = FALSE; #endif if (ac->use_proxy) { if (ac->use_default_proxy) { proxy_info = (ProxyInfo *)&(prefs_common_get_prefs()->proxy_info); if (proxy_info->use_proxy_auth) proxy_info->proxy_pass = passwd_store_get(PWS_CORE, PWS_CORE_PROXY, PWS_CORE_PROXY_PASS); } else { proxy_info = (ProxyInfo *)&(ac->proxy_info); if (proxy_info->use_proxy_auth) proxy_info->proxy_pass = passwd_store_get_account(ac->account_id, PWS_ACCOUNT_PROXY_PASS); } } SESSION(session)->proxy_info = proxy_info; return session_connect(SESSION(session), session->host, session->port); } static SieveSession *sieve_session_new(PrefsAccount *account) { SieveSession *session; session = g_new0(SieveSession, 1); session_init(SESSION(session), account, FALSE); session->account = account; SESSION(session)->recv_msg = sieve_session_recv_msg; SESSION(session)->destroy = sieve_session_destroy; SESSION(session)->connect_finished = sieve_connect_finished; session_set_recv_message_notify(SESSION(session), sieve_recv_message, NULL); sieve_session_reset(session); return session; } static void sieve_session_reset(SieveSession *session) { PrefsAccount *account = session->account; SieveAccountConfig *config = sieve_prefs_account_get_config(account); gboolean reuse_auth = (config->auth == SIEVEAUTH_REUSE); g_slist_free_full(session->send_queue, (GDestroyNotify)command_abort); session_disconnect(SESSION(session)); SESSION(session)->ssl_cert_auto_accept = account->ssl_certs_auto_accept; SESSION(session)->nonblocking = account->use_nonblocking_ssl; session->authenticated = FALSE; session->current_cmd = NULL; session->send_queue = NULL; session->state = SIEVE_CAPABILITIES; #ifdef USE_GNUTLS session->tls_init_done = FALSE; SESSION(session)->use_tls_sni = account->use_tls_sni; #endif session->avail_auth_type = 0; session->auth_type = 0; session->config = config; session->host = config->use_host ? config->host : account->recv_server; session->port = config->use_port ? config->port : SIEVE_PORT; session->user = reuse_auth ? account->userid : session->config->userid; session->forced_auth_type = config->auth_type; session_register_ping(SESSION(session), sieve_ping); if (session->pass) g_free(session->pass); if (config->auth == SIEVEAUTH_NONE) { session->pass = NULL; } 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 { session->pass = input_dialog_query_password_keep(session->host, session->user, &(session->pass)); } if (!session->pass) { session->pass = g_strdup(""); session->use_auth = FALSE; } else { session->use_auth = TRUE; } #ifdef USE_GNUTLS SESSION(session)->ssl_type = (config->tls_type == SIEVE_TLS_NO) ? SSL_NONE : SSL_STARTTLS; #endif } /* When an account config is changed, reset associated sessions. */ void sieve_account_prefs_updated(PrefsAccount *account) { GSList *item; SieveSession *session; for (item = sessions; item; item = item->next) { session = (SieveSession *)item->data; if (session->account == account) { log_print(LOG_PROTOCOL, "Sieve: resetting session\n"); sieve_session_reset(session); } } } SieveSession *sieve_session_get_for_account(PrefsAccount *account) { SieveSession *session; GSList *item; /* find existing */ for (item = sessions; item; item = item->next) { session = (SieveSession *)item->data; if (session->account == account) { return session; } } /* create new */ session = sieve_session_new(account); sessions = g_slist_prepend(sessions, session); return session; } static void sieve_queue_send(SieveSession *session, SieveState next_state, gchar *msg, sieve_session_data_cb_fn cb, gpointer data) { gboolean queue = FALSE; SieveCommand *cmd = g_new0(SieveCommand, 1); cmd->session = session; cmd->next_state = next_state; cmd->msg = msg; cmd->data = data; cmd->cb = cb; if (!session_is_connected(SESSION(session))) { log_print(LOG_PROTOCOL, "Sieve: connecting to %s:%hu\n", session->host, session->port); if (sieve_session_connect(session) < 0) { sieve_connect_finished(SESSION(session), FALSE); } queue = TRUE; } else if (session->state == SIEVE_RETRY_AUTH) { log_print(LOG_PROTOCOL, _("Sieve: retrying auth\n")); if (sieve_auth(session) == SE_AUTHFAIL) sieve_error(session, _("Auth method not available")); queue = TRUE; } else if (session->state != SIEVE_READY) { log_print(LOG_PROTOCOL, "Sieve: in state %d\n", session->state); queue = TRUE; } if (queue) { session->send_queue = g_slist_prepend(session->send_queue, cmd); } else { if (session->current_cmd) command_free(session->current_cmd); session->current_cmd = cmd; session->state = next_state; log_send(session, cmd); if (session_send_msg(SESSION(session), cmd->msg) < 0) { log_warning(LOG_PROTOCOL, _("sending error on Sieve session: %s\n"), cmd->msg); } } } void sieve_session_list_scripts(SieveSession *session, 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, FALSE, (void*)FALSE, data); return; } sieve_queue_send(session, SIEVE_SETACTIVE, msg, cb, data); } void sieve_session_rename_script(SieveSession *session, const gchar *name_old, const char *name_new, sieve_session_data_cb_fn cb, gpointer data) { gchar *msg = g_strdup_printf("RENAMESCRIPT \"%s\" \"%s\"", name_old, name_new); sieve_queue_send(session, SIEVE_RENAMESCRIPT, msg, cb, data); } void sieve_session_get_script(SieveSession *session, const gchar *filter_name, sieve_session_data_cb_fn cb, gpointer data) { gchar *msg = g_strdup_printf("GETSCRIPT \"%s\"", filter_name); sieve_queue_send(session, SIEVE_GETSCRIPT, msg, cb, data); } void sieve_session_put_script(SieveSession *session, const gchar *filter_name, gint len, const gchar *script_contents, 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+}%s%s", filter_name, len, len > 0 ? "\r\n" : "", script_contents); sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data); } 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+}%s%s", len, len > 0 ? "\r\n" : "", script_contents); sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data); } void sieve_session_delete_script(SieveSession *session, const gchar *filter_name, sieve_session_data_cb_fn cb, gpointer data) { gchar *msg = g_strdup_printf("DELETESCRIPT \"%s\"", filter_name); sieve_queue_send(session, SIEVE_DELETESCRIPT, msg, cb, data); }