talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

commit 78965aa3b773324d9c1102d686e533379debd149
parent 7c1bb13b902e8974b364a6e92d88e48c3a2fdcd0
Author: Colin Leroy <colin@colino.net>
Date:   Wed, 28 May 2014 21:18:34 +0200

Add a per-account preference to allow automatically accepting unknown
and changed SSL certificates, if they're valid (that is, if the root CA
is trusted by the distro).

Diffstat:
Msrc/common/session.c | 4++++
Msrc/common/session.h | 4++--
Msrc/common/socket.h | 3++-
Msrc/common/ssl.c | 3++-
Msrc/common/ssl_certificate.c | 59+++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/common/ssl_certificate.h | 4++--
Msrc/compose.c | 2+-
Msrc/etpan/etpan-ssl.c | 8+++++---
Msrc/etpan/etpan-ssl.h | 2+-
Msrc/etpan/imap-thread.c | 28+++++++++++++++++++---------
Msrc/etpan/nntp-thread.c | 13+++++++++----
Msrc/pop.c | 2+-
Msrc/prefs_account.c | 13+++++++++++++
Msrc/prefs_account.h | 1+
Msrc/send_message.c | 2++
15 files changed, 111 insertions(+), 37 deletions(-)

diff --git a/src/common/session.c b/src/common/session.c @@ -165,6 +165,8 @@ static gint session_connect_cb(SockInfo *sock, gpointer data) session->sock = sock; sock->account = session->account; sock->is_smtp = session->is_smtp; + sock->ssl_cert_auto_accept = session->ssl_cert_auto_accept; + #ifdef USE_GNUTLS sock->gnutls_priority = session->gnutls_priority; @@ -373,6 +375,8 @@ gint session_start_tls(Session *session) nb_mode = sock_is_nonblocking_mode(session->sock); + session->sock->ssl_cert_auto_accept = session->ssl_cert_auto_accept; + if (nb_mode) sock_set_nonblocking_mode(session->sock, FALSE); diff --git a/src/common/session.h b/src/common/session.h @@ -150,10 +150,10 @@ struct _Session gpointer recv_data_notify_data; gpointer send_data_progressive_notify_data; gpointer send_data_notify_data; - + const void *account; gboolean is_smtp; - + gboolean ssl_cert_auto_accept; gint ping_tag; #ifdef USE_GNUTLS diff --git a/src/common/socket.h b/src/common/socket.h @@ -76,9 +76,10 @@ struct _SockInfo SockFunc callback; GIOCondition condition; gchar *canonical_name; - + const void *account; gboolean is_smtp; + gboolean ssl_cert_auto_accept; }; void refresh_resolvers (void); diff --git a/src/common/ssl.c b/src/common/ssl.c @@ -371,7 +371,8 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method) return FALSE; } - if (!ssl_certificate_check_chain(certs, cert_list_length, sockinfo->hostname, sockinfo->port)) { + if (!ssl_certificate_check_chain(certs, cert_list_length, sockinfo->hostname, sockinfo->port, + sockinfo->ssl_cert_auto_accept)) { for (i = 0; i < cert_list_length; i++) gnutls_x509_crt_deinit(certs[i]); g_free(certs); diff --git a/src/common/ssl_certificate.c b/src/common/ssl_certificate.c @@ -603,6 +603,17 @@ static guint check_cert(SSLCertificate *cert) } +static gboolean ssl_certificate_is_valid(SSLCertificate *cert, guint status) +{ + gchar *str_status = ssl_certificate_check_signer(cert, status); + + if (str_status != NULL) { + g_free(str_status); + return FALSE; + } + return ssl_certificate_check_subject_cn(cert); +} + char *ssl_certificate_check_signer (SSLCertificate *cert, guint status) { gnutls_x509_crt_t x509_cert = cert ? cert->x509_cert : NULL; @@ -667,17 +678,20 @@ static void ssl_certificate_save_chain(gnutls_x509_crt_t *certs, gint len, const fclose(fp); } -gboolean ssl_certificate_check (gnutls_x509_crt_t x509_cert, guint status, const gchar *host, gushort port) +gboolean ssl_certificate_check (gnutls_x509_crt_t x509_cert, guint status, + const gchar *host, gushort port, + gboolean accept_if_valid) { SSLCertificate *current_cert = NULL; SSLCertificate *known_cert; SSLCertHookData cert_hook_data; gchar *fingerprint; size_t n; - unsigned char md[128]; + unsigned char md[128]; + gboolean valid = FALSE; current_cert = ssl_certificate_new(x509_cert, host, port); - + if (current_cert == NULL) { debug_print("Buggy certificate !\n"); return FALSE; @@ -693,14 +707,25 @@ gboolean ssl_certificate_check (gnutls_x509_crt_t x509_cert, guint status, const g_free(fingerprint); + if (accept_if_valid) + valid = ssl_certificate_is_valid(current_cert, status); + else + valid = FALSE; /* Force check */ + if (known_cert == NULL) { + if (valid) { + ssl_certificate_save(current_cert); + ssl_certificate_destroy(current_cert); + return TRUE; + } + cert_hook_data.cert = current_cert; cert_hook_data.old_cert = NULL; cert_hook_data.expired = FALSE; cert_hook_data.accept = FALSE; - + hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data); - + if (!cert_hook_data.accept) { ssl_certificate_destroy(current_cert); return FALSE; @@ -710,11 +735,18 @@ gboolean ssl_certificate_check (gnutls_x509_crt_t x509_cert, guint status, const return TRUE; } } else if (!ssl_certificate_compare (current_cert, known_cert)) { + if (valid) { + ssl_certificate_save(current_cert); + ssl_certificate_destroy(current_cert); + ssl_certificate_destroy(known_cert); + return TRUE; + } + cert_hook_data.cert = current_cert; cert_hook_data.old_cert = known_cert; cert_hook_data.expired = FALSE; cert_hook_data.accept = FALSE; - + hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data); if (!cert_hook_data.accept) { @@ -729,22 +761,22 @@ gboolean ssl_certificate_check (gnutls_x509_crt_t x509_cert, guint status, const } } else if (gnutls_x509_crt_get_expiration_time(current_cert->x509_cert) < time(NULL)) { gchar *tmp = g_strdup_printf("%s:%d", current_cert->host, current_cert->port); - + if (warned_expired == NULL) warned_expired = g_hash_table_new(g_str_hash, g_str_equal); - + if (g_hash_table_lookup(warned_expired, tmp)) { g_free(tmp); ssl_certificate_destroy(current_cert); ssl_certificate_destroy(known_cert); return TRUE; } - + cert_hook_data.cert = current_cert; cert_hook_data.old_cert = NULL; cert_hook_data.expired = TRUE; cert_hook_data.accept = FALSE; - + hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data); if (!cert_hook_data.accept) { @@ -765,7 +797,9 @@ gboolean ssl_certificate_check (gnutls_x509_crt_t x509_cert, guint status, const return TRUE; } -gboolean ssl_certificate_check_chain(gnutls_x509_crt_t *certs, gint chain_len, const gchar *host, gushort port) +gboolean ssl_certificate_check_chain(gnutls_x509_crt_t *certs, gint chain_len, + const gchar *host, gushort port, + gboolean accept_if_valid) { int ncas = 0; gnutls_x509_crt_t *cas = NULL; @@ -798,7 +832,8 @@ gboolean ssl_certificate_check_chain(gnutls_x509_crt_t *certs, gint chain_len, c GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT, &status); - result = ssl_certificate_check(certs[0], status, host, port); + result = ssl_certificate_check(certs[0], status, host, port, + accept_if_valid); if (result == TRUE) { ssl_certificate_save_chain(certs, chain_len, host, port); diff --git a/src/common/ssl_certificate.h b/src/common/ssl_certificate.h @@ -57,8 +57,8 @@ struct _SSLCertHookData }; SSLCertificate *ssl_certificate_find (const gchar *host, gushort port, const gchar *fingerprint); -gboolean ssl_certificate_check (gnutls_x509_crt_t x509_cert, guint status, const gchar *host, gushort port); -gboolean ssl_certificate_check_chain(gnutls_x509_crt_t *certs, gint chain_len, const gchar *host, gushort port); +gboolean ssl_certificate_check (gnutls_x509_crt_t x509_cert, guint status, const gchar *host, gushort port, gboolean accept_if_valid); +gboolean ssl_certificate_check_chain(gnutls_x509_crt_t *certs, gint chain_len, const gchar *host, gushort port, gboolean accept_if_valid); void ssl_certificate_destroy(SSLCertificate *cert); void ssl_certificate_delete_from_disk(SSLCertificate *cert); char * readable_fingerprint(unsigned char *src, int len); diff --git a/src/compose.c b/src/compose.c @@ -11565,7 +11565,7 @@ gboolean compose_close(Compose *compose) } return TRUE; } - + if (compose->draft_timeout_tag >= 0) { g_source_remove(compose->draft_timeout_tag); compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; diff --git a/src/etpan/etpan-ssl.c b/src/etpan/etpan-ssl.c @@ -40,7 +40,8 @@ #include "log.h" #include "prefs_account.h" -gboolean etpan_certificate_check(mailstream *stream, const char *host, gint port) +gboolean etpan_certificate_check(mailstream *stream, const char *host, gint port, + gboolean accept_if_valid) { #if (!defined LIBETPAN_API_CURRENT || LIBETPAN_API_CURRENT < 18) unsigned char *cert_der = NULL; @@ -69,7 +70,7 @@ gboolean etpan_certificate_check(mailstream *stream, const char *host, gint port free(tmp.data); g_warning("IMAP: can't get cert\n"); return FALSE; - } else if (ssl_certificate_check(cert, (guint)-1, host, port) == TRUE) { + } else if (ssl_certificate_check(cert, (guint)-1, host, port, accept_if_valid) == TRUE) { free(tmp.data); gnutls_x509_crt_deinit(cert); return TRUE; @@ -121,7 +122,8 @@ gboolean etpan_certificate_check(mailstream *stream, const char *host, gint port carray_free(certs_der); if (result == TRUE) - result = ssl_certificate_check_chain(certs, chain_len, host, port); + result = ssl_certificate_check_chain(certs, chain_len, host, port, + accept_if_valid); for (i = 0; i < chain_len; i++) gnutls_x509_crt_deinit(certs[i]); diff --git a/src/etpan/etpan-ssl.h b/src/etpan/etpan-ssl.h @@ -31,7 +31,7 @@ #include <libetpan/libetpan.h> -gboolean etpan_certificate_check(mailstream *imap_stream, const char *host, gint port); +gboolean etpan_certificate_check(mailstream *imap_stream, const char *host, gint port, gboolean accept_if_valid); void etpan_connect_ssl_context_cb(struct mailstream_ssl_context * ssl_context, void * data); #endif /* USE_GNUTLS */ diff --git a/src/etpan/imap-thread.c b/src/etpan/imap-thread.c @@ -544,11 +544,12 @@ int imap_threaded_connect_ssl(Folder * folder, const char * server, int port) chashdatum key; chashdatum value; mailimap * imap, * oldimap; - + gboolean accept_if_valid = FALSE; + oldimap = get_imap(folder); imap = mailimap_new(0, NULL); - + if (oldimap) { debug_print("deleting old imap %p\n", oldimap); delete_imap(folder, oldimap); @@ -559,22 +560,26 @@ int imap_threaded_connect_ssl(Folder * folder, const char * server, int port) value.data = imap; value.len = 0; chash_set(session_hash, &key, &value, NULL); - + param.imap = imap; param.server = server; param.port = port; param.account = folder->account; + if (folder->account) + accept_if_valid = folder->account->ssl_certs_auto_accept; + refresh_resolvers(); threaded_run(folder, &param, &result, connect_ssl_run); if ((result.error == MAILIMAP_NO_ERROR_AUTHENTICATED || result.error == MAILIMAP_NO_ERROR_NON_AUTHENTICATED) && !etpan_skip_ssl_cert_check) { - if (etpan_certificate_check(imap->imap_stream, server, port) != TRUE) + if (etpan_certificate_check(imap->imap_stream, server, port, + accept_if_valid) != TRUE) result.error = MAILIMAP_ERROR_SSL; } debug_print("connect %d with imap %p\n", result.error, imap); - + return result.error; } @@ -1094,20 +1099,25 @@ int imap_threaded_starttls(Folder * folder, const gchar *host, int port) { struct connect_param param; struct starttls_result result; - + gboolean accept_if_valid = FALSE; + debug_print("imap starttls - begin\n"); - + param.imap = get_imap(folder); param.server = host; param.port = port; param.account = folder->account; + if (folder->account) + accept_if_valid = folder->account->ssl_certs_auto_accept; + threaded_run(folder, &param, &result, starttls_run); - + debug_print("imap starttls - end\n"); if (result.error == 0 && param.imap && !etpan_skip_ssl_cert_check) { - if (etpan_certificate_check(param.imap->imap_stream, host, port) != TRUE) + if (etpan_certificate_check(param.imap->imap_stream, host, port, + accept_if_valid) != TRUE) return MAILIMAP_ERROR_SSL; } return result.error; diff --git a/src/etpan/nntp-thread.c b/src/etpan/nntp-thread.c @@ -398,11 +398,12 @@ int nntp_threaded_connect_ssl(Folder * folder, const char * server, int port) chashdatum key; chashdatum value; newsnntp * nntp, * oldnntp; - + gboolean accept_if_valid = FALSE; + oldnntp = get_nntp(folder); nntp = newsnntp_new(0, NULL); - + if (oldnntp) { debug_print("deleting old nntp %p\n", oldnntp); delete_nntp(folder, oldnntp); @@ -413,17 +414,21 @@ int nntp_threaded_connect_ssl(Folder * folder, const char * server, int port) value.data = nntp; value.len = 0; chash_set(session_hash, &key, &value, NULL); - + param.nntp = nntp; param.server = server; param.port = port; param.account = folder->account; + if (folder->account) + accept_if_valid = folder->account->ssl_certs_auto_accept; + refresh_resolvers(); threaded_run(folder, &param, &result, connect_ssl_run); if (result.error == NEWSNNTP_NO_ERROR && !etpan_skip_ssl_cert_check) { - if (etpan_certificate_check(nntp->nntp_stream, server, port) != TRUE) + if (etpan_certificate_check(nntp->nntp_stream, server, port, + accept_if_valid) != TRUE) return -1; } debug_print("connect %d with nntp %p\n", result.error, nntp); diff --git a/src/pop.c b/src/pop.c @@ -530,7 +530,7 @@ Session *pop3_session_new(PrefsAccount *account) SESSION(session)->recv_msg = pop3_session_recv_msg; SESSION(session)->recv_data_finished = pop3_session_recv_data_finished; SESSION(session)->send_data_finished = NULL; - + SESSION(session)->ssl_cert_auto_accept = account->ssl_certs_auto_accept; SESSION(session)->destroy = pop3_session_destroy; session->state = POP3_READY; diff --git a/src/prefs_account.c b/src/prefs_account.c @@ -269,6 +269,7 @@ typedef struct SSLPage GtkWidget *entry_out_cert_file; GtkWidget *entry_out_cert_pass; + GtkWidget *ssl_certs_auto_accept_checkbtn; GtkWidget *use_nonblocking_ssl_checkbtn; } SSLPage; @@ -729,6 +730,10 @@ static PrefParam ssl_param[] = { prefs_account_enum_set_data_from_radiobtn, prefs_account_enum_set_radiobtn}, + {"ssl_certs_auto_accept", "0", &tmp_ac_prefs.ssl_certs_auto_accept, P_BOOL, + &ssl_page.ssl_certs_auto_accept_checkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"use_nonblocking_ssl", "1", &tmp_ac_prefs.use_nonblocking_ssl, P_BOOL, &ssl_page.use_nonblocking_ssl_checkbtn, prefs_set_data_from_toggle, prefs_set_toggle}, @@ -769,6 +774,9 @@ static PrefParam ssl_param[] = { {"out_ssl_client_cert_pass", "", &tmp_ac_prefs.out_ssl_client_cert_pass, P_PASSWORD, NULL, NULL, NULL}, + {"ssl_certs_auto_accept", "0", &tmp_ac_prefs.ssl_certs_auto_accept, P_BOOL, + NULL, NULL, NULL}, + {"use_nonblocking_ssl", "1", &tmp_ac_prefs.use_nonblocking_ssl, P_BOOL, NULL, NULL, NULL}, #endif /* USE_GNUTLS */ @@ -2413,6 +2421,7 @@ static void ssl_create_widget_func(PrefsPage * _page, GtkWidget *entry_out_cert_pass; GtkWidget *vbox7; + GtkWidget *ssl_certs_auto_accept_checkbtn; GtkWidget *use_nonblocking_ssl_checkbtn; GtkWidget *hbox; GtkWidget *hbox_spc; @@ -2545,6 +2554,9 @@ static void ssl_create_widget_func(PrefsPage * _page, gtk_widget_show (vbox7); gtk_box_pack_start (GTK_BOX (vbox1), vbox7, FALSE, FALSE, 0); + PACK_CHECK_BUTTON(vbox7, ssl_certs_auto_accept_checkbtn, + _("Automatically accept unknown valid SSL certificates")); + PACK_CHECK_BUTTON(vbox7, use_nonblocking_ssl_checkbtn, _("Use non-blocking SSL")); @@ -2587,6 +2599,7 @@ static void ssl_create_widget_func(PrefsPage * _page, page->entry_out_cert_file = entry_out_cert_file; page->entry_out_cert_pass = entry_out_cert_pass; + page->ssl_certs_auto_accept_checkbtn = ssl_certs_auto_accept_checkbtn; page->use_nonblocking_ssl_checkbtn = use_nonblocking_ssl_checkbtn; tmp_ac_prefs = *ac_prefs; diff --git a/src/prefs_account.h b/src/prefs_account.h @@ -86,6 +86,7 @@ struct _PrefsAccount gchar *in_ssl_client_cert_file; gchar *in_ssl_client_cert_pass; + gboolean ssl_certs_auto_accept; gboolean use_nonblocking_ssl; /* Receive */ diff --git a/src/send_message.c b/src/send_message.c @@ -250,6 +250,8 @@ gint send_message_smtp_full(PrefsAccount *ac_prefs, GSList *to_list, FILE *fp, g if (!ac_prefs->session) { /* we can't reuse a previously initialised session */ session = smtp_session_new(ac_prefs); + session->ssl_cert_auto_accept = ac_prefs->ssl_certs_auto_accept; + smtp_session = SMTP_SESSION(session); if (ac_prefs->set_domain && ac_prefs->domain && strlen(ac_prefs->domain)) {