talons

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

commit 7e5114d12fed0a029ce62db08ca74529b1a00387
parent bf9ad025b9071ec287c9460f9a5f9df4181310a4
Author: Colin Leroy <colin@colino.net>
Date:   Thu,  3 Jul 2008 20:31:33 +0000

2008-07-03 [colin]	3.5.0cvs2

	* src/imap.c
	* src/news.c
	* src/pop.c
	* src/prefs_account.c
	* src/prefs_account.h
	* src/prefs_common.c
	* src/send_message.c
	* src/common/session.c
	* src/common/session.h
	* src/common/smtp.c
	* src/common/smtp.h
	* src/common/socket.h
	* src/common/ssl.c
	* src/common/ssl.h
	* src/common/ssl_certificate.c
	* src/common/ssl_certificate.h
	* src/etpan/imap-thread.c
	* src/etpan/nntp-thread.c
	* src/gtk/inputdialog.c
		Add support for SSL client certificates (either PEM files
		with certificate and private key, either PKCS12 files)

Diffstat:
MChangeLog | 24++++++++++++++++++++++++
MPATCHSETS | 1+
Mconfigure.ac | 2+-
Msrc/common/session.c | 8++++++--
Msrc/common/session.h | 7++++++-
Msrc/common/smtp.c | 4++--
Msrc/common/smtp.h | 2+-
Msrc/common/socket.h | 5+++++
Msrc/common/ssl.c | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/common/ssl.h | 10++++++++++
Msrc/common/ssl_certificate.c | 382++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/common/ssl_certificate.h | 16++++++++++++++++
Msrc/etpan/imap-thread.c | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/etpan/nntp-thread.c | 58+++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/gtk/inputdialog.c | 11++++++++++-
Msrc/imap.c | 6+-----
Msrc/news.c | 2+-
Msrc/pop.c | 2+-
Msrc/prefs_account.c | 217++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/prefs_account.h | 6++++++
Msrc/prefs_common.c | 2+-
Msrc/send_message.c | 2+-
22 files changed, 893 insertions(+), 36 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,27 @@ +2008-07-03 [colin] 3.5.0cvs2 + + * src/imap.c + * src/news.c + * src/pop.c + * src/prefs_account.c + * src/prefs_account.h + * src/prefs_common.c + * src/send_message.c + * src/common/session.c + * src/common/session.h + * src/common/smtp.c + * src/common/smtp.h + * src/common/socket.h + * src/common/ssl.c + * src/common/ssl.h + * src/common/ssl_certificate.c + * src/common/ssl_certificate.h + * src/etpan/imap-thread.c + * src/etpan/nntp-thread.c + * src/gtk/inputdialog.c + Add support for SSL client certificates (either PEM files + with certificate and private key, either PKCS12 files) + 2008-06-28 [colin] 3.5.0cvs1 * src/foldersel.c diff --git a/PATCHSETS b/PATCHSETS @@ -3413,3 +3413,4 @@ ( cvs diff -u -r 1.654.2.3471 -r 1.654.2.3472 configure.ac; cvs diff -u -r 1.1.2.47 -r 1.1.2.48 src/gtk/authors.h; ) > 3.4.0cvs115.patchset ( cvs diff -u -r 1.1.2.48 -r 1.1.2.49 src/gtk/authors.h; ) > 3.4.0cvs116.patchset ( cvs diff -u -r 1.26.2.36 -r 1.26.2.37 src/foldersel.c; ) > 3.5.0cvs1.patchset +( cvs diff -u -r 1.179.2.225 -r 1.179.2.226 src/imap.c; cvs diff -u -r 1.101.2.50 -r 1.101.2.51 src/news.c; cvs diff -u -r 1.56.2.61 -r 1.56.2.62 src/pop.c; cvs diff -u -r 1.105.2.132 -r 1.105.2.133 src/prefs_account.c; cvs diff -u -r 1.49.2.36 -r 1.49.2.37 src/prefs_account.h; cvs diff -u -r 1.204.2.172 -r 1.204.2.173 src/prefs_common.c; cvs diff -u -r 1.17.2.47 -r 1.17.2.48 src/send_message.c; cvs diff -u -r 1.23.2.17 -r 1.23.2.18 src/common/session.c; cvs diff -u -r 1.8.2.11 -r 1.8.2.12 src/common/session.h; cvs diff -u -r 1.11.2.24 -r 1.11.2.25 src/common/smtp.c; cvs diff -u -r 1.6.2.13 -r 1.6.2.14 src/common/smtp.h; cvs diff -u -r 1.13.2.18 -r 1.13.2.19 src/common/socket.h; cvs diff -u -r 1.9.2.28 -r 1.9.2.29 src/common/ssl.c; cvs diff -u -r 1.2.2.7 -r 1.2.2.8 src/common/ssl.h; cvs diff -u -r 1.4.2.27 -r 1.4.2.28 src/common/ssl_certificate.c; cvs diff -u -r 1.1.4.10 -r 1.1.4.11 src/common/ssl_certificate.h; cvs diff -u -r 1.1.4.101 -r 1.1.4.102 src/etpan/imap-thread.c; cvs diff -u -r 1.1.2.6 -r 1.1.2.7 src/etpan/nntp-thread.c; cvs diff -u -r 1.2.2.29 -r 1.2.2.30 src/gtk/inputdialog.c; ) > 3.5.0cvs2.patchset diff --git a/configure.ac b/configure.ac @@ -11,7 +11,7 @@ MINOR_VERSION=5 MICRO_VERSION=0 INTERFACE_AGE=0 BINARY_AGE=0 -EXTRA_VERSION=1 +EXTRA_VERSION=2 EXTRA_RELEASE= EXTRA_GTK2_VERSION= diff --git a/src/common/session.c b/src/common/session.c @@ -60,7 +60,7 @@ static gboolean session_write_data_cb (SockInfo *source, gpointer data); -void session_init(Session *session) +void session_init(Session *session, const void *prefs_account, gboolean is_smtp) { session->type = SESSION_UNKNOWN; session->sock = NULL; @@ -97,6 +97,8 @@ void session_init(Session *session) session->timeout_interval = 0; session->data = NULL; + session->account = prefs_account; + session->is_smtp = is_smtp; } /*! @@ -134,6 +136,7 @@ gint session_connect(Session *session, const gchar *server, gushort port) session->port = port; sock = sock_connect(server, port); + sock->is_smtp = session->is_smtp; if (sock == NULL) { g_warning("can't connect to server."); session_close(session); @@ -157,7 +160,8 @@ static gint session_connect_cb(SockInfo *sock, gpointer data) } session->sock = sock; - + sock->account = session->account; + sock->is_smtp = session->is_smtp; #if (defined(USE_OPENSSL) || defined (USE_GNUTLS)) if (session->ssl_type == SSL_TUNNEL) { sock_set_nonblocking_mode(sock, FALSE); diff --git a/src/common/session.h b/src/common/session.h @@ -154,9 +154,14 @@ struct _Session gpointer recv_data_notify_data; gpointer send_data_progressive_notify_data; gpointer send_data_notify_data; + + void *account; + gboolean is_smtp; }; -void session_init (Session *session); +void session_init (Session *session, + const void *prefs_account, + gboolean is_smtp); gint session_connect (Session *session, const gchar *server, gushort port); diff --git a/src/common/smtp.c b/src/common/smtp.c @@ -61,13 +61,13 @@ static gint smtp_session_recv_msg(Session *session, const gchar *msg); static gint smtp_session_send_data_finished(Session *session, guint len); -Session *smtp_session_new(void) +Session *smtp_session_new(void *prefs_account) { SMTPSession *session; session = g_new0(SMTPSession, 1); - session_init(SESSION(session)); + session_init(SESSION(session), prefs_account, TRUE); SESSION(session)->type = SESSION_SMTP; diff --git a/src/common/smtp.h b/src/common/smtp.h @@ -120,7 +120,7 @@ struct _SMTPSession void *dialog; }; -Session *smtp_session_new (void); +Session *smtp_session_new (void *prefs_account); gint smtp_from(SMTPSession *session); gint smtp_quit(SMTPSession *session); diff --git a/src/common/socket.h b/src/common/socket.h @@ -63,6 +63,8 @@ struct _SockInfo #elif USE_GNUTLS gnutls_session ssl; gnutls_certificate_credentials_t xcred; + gnutls_x509_crt client_crt; + gnutls_x509_privkey client_key; #endif guint g_source; GIOChannel *sock_ch; @@ -75,6 +77,9 @@ struct _SockInfo SockFunc callback; GIOCondition condition; gchar *canonical_name; + + void *account; + gboolean is_smtp; }; void refresh_resolvers (void); diff --git a/src/common/ssl.c b/src/common/ssl.c @@ -31,6 +31,7 @@ #include "utils.h" #include "ssl.h" #include "ssl_certificate.h" +#include "hooks.h" #ifdef HAVE_LIBETPAN #include <libetpan/mailstream_ssl.h> @@ -56,6 +57,84 @@ typedef struct _thread_data { static SSL_CTX *ssl_ctx; #endif +#ifdef USE_OPENSSL +static int openssl_client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey) +{ + SSLClientCertHookData hookdata; + SockInfo *sockinfo = (SockInfo *)SSL_CTX_get_app_data(ssl->ctx); + + if (x509 == NULL || pkey == NULL) { + return 0; + } + + if (sockinfo == NULL) + return 0; + + hookdata.account = sockinfo->account; + hookdata.cert_path = NULL; + hookdata.password = NULL; + hookdata.is_smtp = sockinfo->is_smtp; + hooks_invoke(SSLCERT_GET_CLIENT_CERT_HOOKLIST, &hookdata); + + if (hookdata.cert_path == NULL) + return 0; + + *x509 = ssl_certificate_get_x509_from_pem_file(hookdata.cert_path); + *pkey = ssl_certificate_get_pkey_from_pem_file(hookdata.cert_path); + if (!(*x509 && *pkey)) { + /* try pkcs12 format */ + ssl_certificate_get_x509_and_pkey_from_p12_file(hookdata.cert_path, hookdata.password, x509, pkey); + } + if (*x509 && *pkey) + return 1; + else + return 0; +} +#endif +#ifdef USE_GNUTLS +static int gnutls_client_cert_cb(gnutls_session session, + const gnutls_datum *req_ca_rdn, int nreqs, + const gnutls_pk_algorithm *sign_algos, + int sign_algos_length, gnutls_retr_st *st) +{ + SSLClientCertHookData hookdata; + SockInfo *sockinfo = (SockInfo *)gnutls_session_get_ptr(session); + gnutls_certificate_type type = gnutls_certificate_type_get(session); + gnutls_x509_crt crt; + gnutls_x509_privkey key; + + st->ncerts = 0; + + hookdata.account = sockinfo->account; + hookdata.cert_path = NULL; + hookdata.password = NULL; + hookdata.is_smtp = sockinfo->is_smtp; + hooks_invoke(SSLCERT_GET_CLIENT_CERT_HOOKLIST, &hookdata); + + if (hookdata.cert_path == NULL) + return 0; + + sockinfo->client_crt = ssl_certificate_get_x509_from_pem_file(hookdata.cert_path); + sockinfo->client_key = ssl_certificate_get_pkey_from_pem_file(hookdata.cert_path); + if (!(sockinfo->client_crt && sockinfo->client_key)) { + /* try pkcs12 format */ + ssl_certificate_get_x509_and_pkey_from_p12_file(hookdata.cert_path, hookdata.password, + &crt, &key); + sockinfo->client_crt = crt; + sockinfo->client_key = key; + } + + if (type == GNUTLS_CRT_X509 && sockinfo->client_crt && sockinfo->client_key) { + st->ncerts = 1; + st->type = type; + st->cert.x509 = &(sockinfo->client_crt); + st->key.x509 = sockinfo->client_key; + st->deinit_all = 0; + return 0; + } + return -1; +} +#endif void ssl_init(void) { #ifdef USE_OPENSSL @@ -64,6 +143,9 @@ void ssl_init(void) /* Global system initialization*/ SSL_library_init(); SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); #ifdef HAVE_LIBETPAN mailstream_openssl_init_not_required(); @@ -73,6 +155,9 @@ void ssl_init(void) meth = SSLv23_client_method(); ssl_ctx = SSL_CTX_new(meth); + + SSL_CTX_set_client_cert_cb(ssl_ctx, openssl_client_cert_cb); + /* Set default certificate paths */ SSL_CTX_set_default_verify_paths(ssl_ctx); @@ -228,6 +313,7 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method) break; } + SSL_CTX_set_app_data(ssl_ctx, sockinfo); SSL_set_fd(ssl, sockinfo->sock); if (SSL_connect_nb(ssl) == -1) { g_warning(_("SSL connect failed (%s)\n"), @@ -257,6 +343,7 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method) X509_free(server_cert); sockinfo->ssl = ssl; + #else gnutls_session session; int r; @@ -300,8 +387,9 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method) gnutls_certificate_set_verify_flags (xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); - gnutls_transport_set_ptr(session, (gnutls_transport_ptr) - sockinfo->sock); + gnutls_transport_set_ptr(session, (gnutls_transport_ptr) sockinfo->sock); + gnutls_session_set_ptr(session, sockinfo); + gnutls_certificate_client_set_retrieve_function(xcred, gnutls_client_cert_cb); gnutls_dh_set_prime_bits(session, 512); @@ -350,6 +438,12 @@ void ssl_done_socket(SockInfo *sockinfo) #else gnutls_certificate_free_credentials(sockinfo->xcred); gnutls_deinit(sockinfo->ssl); + if (sockinfo->client_crt) + gnutls_x509_crt_deinit(sockinfo->client_crt); + if (sockinfo->client_key) + gnutls_x509_privkey_deinit(sockinfo->client_key); + sockinfo->client_key = NULL; + sockinfo->client_crt = NULL; #endif sockinfo->ssl = NULL; } diff --git a/src/common/ssl.h b/src/common/ssl.h @@ -40,6 +40,7 @@ typedef enum { #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/evp.h> +#include <openssl/pkcs12.h> #else #include <gnutls/gnutls.h> #include <gnutls/x509.h> @@ -58,6 +59,15 @@ gboolean ssl_init_socket_with_method (SockInfo *sockinfo, SSLMethod method); void ssl_done_socket (SockInfo *sockinfo); +typedef struct _SSLClientCertHookData SSLClientCertHookData; +struct _SSLClientCertHookData +{ + void *account; + const gchar *cert_path; + const gchar *password; + gboolean is_smtp; +}; + #endif /* USE_OPENSSL */ #endif /* __SSL_H__ */ diff --git a/src/common/ssl_certificate.c b/src/common/ssl_certificate.c @@ -28,6 +28,7 @@ #else #include <gnutls/gnutls.h> #include <gnutls/x509.h> +#include <gnutls/pkcs12.h> #include <sys/stat.h> #include <unistd.h> #include <string.h> @@ -281,7 +282,46 @@ static void i2d_X509_fp(FILE *fp, gnutls_x509_crt x509_cert) g_warning("failed to write cert\n"); } } -static gnutls_x509_crt d2i_X509_fp(FILE *fp, int unused) + +size_t i2d_X509(gnutls_x509_crt x509_cert, unsigned char **output) +{ + size_t cert_size = 10*1024; + int r; + + if (output == NULL) + return 0; + + *output = malloc(cert_size); + + if ((r = gnutls_x509_crt_export(x509_cert, GNUTLS_X509_FMT_DER, *output, &cert_size)) < 0) { + g_warning("couldn't export cert %s (%d)\n", gnutls_strerror(r), cert_size); + free(*output); + *output = NULL; + return 0; + } + return cert_size; +} + +size_t i2d_PrivateKey(gnutls_x509_privkey pkey, unsigned char **output) +{ + size_t key_size = 10*1024; + int r; + + if (output == NULL) + return 0; + + *output = malloc(key_size); + + if ((r = gnutls_x509_privkey_export(pkey, GNUTLS_X509_FMT_DER, *output, &key_size)) < 0) { + g_warning("couldn't export key %s (%d)\n", gnutls_strerror(r), key_size); + free(*output); + *output = NULL; + return 0; + } + return key_size; +} + +static gnutls_x509_crt d2i_X509_fp(FILE *fp, int format) { gnutls_x509_crt cert = NULL; gnutls_datum tmp; @@ -296,18 +336,82 @@ static gnutls_x509_crt d2i_X509_fp(FILE *fp, int unused) tmp.size = s.st_size; if (fread (tmp.data, 1, s.st_size, fp) < s.st_size) { perror("fread"); + free(tmp.data); return NULL; } gnutls_x509_crt_init(&cert); - if ((r = gnutls_x509_crt_import(cert, &tmp, GNUTLS_X509_FMT_DER)) < 0) { - g_warning("import failed: %s\n", gnutls_strerror(r)); + if ((r = gnutls_x509_crt_import(cert, &tmp, (format == 0)?GNUTLS_X509_FMT_DER:GNUTLS_X509_FMT_PEM)) < 0) { + debug_print("cert import failed: %s\n", gnutls_strerror(r)); gnutls_x509_crt_deinit(cert); cert = NULL; } + free(tmp.data); debug_print("got cert! %p\n", cert); return cert; } + +static gnutls_x509_privkey d2i_key_fp(FILE *fp, int format) +{ + gnutls_x509_privkey key = NULL; + gnutls_datum tmp; + struct stat s; + int r; + if (fstat(fileno(fp), &s) < 0) { + perror("fstat"); + return NULL; + } + tmp.data = malloc(s.st_size); + memset(tmp.data, 0, s.st_size); + tmp.size = s.st_size; + if (fread (tmp.data, 1, s.st_size, fp) < s.st_size) { + perror("fread"); + free(tmp.data); + return NULL; + } + + gnutls_x509_privkey_init(&key); + if ((r = gnutls_x509_privkey_import(key, &tmp, (format == 0)?GNUTLS_X509_FMT_DER:GNUTLS_X509_FMT_PEM)) < 0) { + debug_print("key import failed: %s\n", gnutls_strerror(r)); + gnutls_x509_privkey_deinit(key); + key = NULL; + } + free(tmp.data); + debug_print("got key! %p\n", key); + return key; +} + +static gnutls_pkcs12_t d2i_PKCS12_fp(FILE *fp, int format) +{ + gnutls_pkcs12_t p12 = NULL; + gnutls_datum tmp; + struct stat s; + int r; + if (fstat(fileno(fp), &s) < 0) { + perror("fstat"); + return NULL; + } + tmp.data = malloc(s.st_size); + memset(tmp.data, 0, s.st_size); + tmp.size = s.st_size; + if (fread (tmp.data, 1, s.st_size, fp) < s.st_size) { + perror("fread"); + free(tmp.data); + return NULL; + } + + gnutls_pkcs12_init(&p12); + + if ((r = gnutls_pkcs12_import(p12, &tmp, (format == 0)?GNUTLS_X509_FMT_DER:GNUTLS_X509_FMT_PEM,0)) < 0) { + g_warning("p12 import failed: %s\n", gnutls_strerror(r)); + gnutls_pkcs12_deinit(p12); + p12 = NULL; + } + free(tmp.data); + debug_print("got p12! %p\n", p12); + return p12; +} + #endif static void ssl_certificate_save (SSLCertificate *cert) @@ -675,4 +779,276 @@ gboolean ssl_certificate_check (gnutls_x509_crt x509_cert, guint status, gchar * return TRUE; } +#if USE_OPENSSL +X509 *ssl_certificate_get_x509_from_pem_file(const gchar *file) +{ + X509 *x509 = NULL; + if (!file) + return NULL; + if (is_file_exist(file)) { + FILE *fp = g_fopen(file, "r"); + if (fp) { + x509 = PEM_read_X509(fp, NULL, NULL, NULL); + fclose(fp); + return x509; + } + } else { + log_error(LOG_PROTOCOL, "Can not open certificate file %s\n", file); + } + return NULL; +} + +static int ssl_pkey_password_cb(char *buf, int max_len, int flag, void *pwd) +{ + return 0; +} + +EVP_PKEY *ssl_certificate_get_pkey_from_pem_file(const gchar *file) +{ + EVP_PKEY *pkey = NULL; + if (!file) + return NULL; + if (is_file_exist(file)) { + FILE *fp = g_fopen(file, "r"); + if (fp) { + pkey = PEM_read_PrivateKey(fp, NULL, ssl_pkey_password_cb, NULL); + fclose(fp); + return pkey; + } + } else { + log_error(LOG_PROTOCOL, "Can not open private key file %s\n", file); + } + return NULL; +} + +void ssl_certificate_get_x509_and_pkey_from_p12_file(const gchar *file, const gchar *password, + X509 **x509, EVP_PKEY **pkey) +{ + PKCS12 *p12 = NULL; + *x509 = NULL; + *pkey = NULL; + + if (!file) + return; + + if (is_file_exist(file)) { + FILE *fp = g_fopen(file, "r"); + if (fp) { + p12 = d2i_PKCS12_fp(fp, NULL); + fclose(fp); + } + } else { + log_error(LOG_PROTOCOL, "Can not open certificate file %s\n", file); + } + if (p12 != NULL) { + if (PKCS12_parse(p12, password, pkey, x509, NULL) == 1) { + /* we got the correct password */ + } else { + gchar *tmp = NULL; + hooks_invoke(SSL_CERT_GET_PASSWORD, &tmp); + if (PKCS12_parse(p12, tmp, pkey, x509, NULL) == 1) { + debug_print("got p12\n"); + } else { + log_error(LOG_PROTOCOL, "%s\n", ERR_error_string(ERR_get_error(),NULL)); + } + } + PKCS12_free(p12); + } +} +#endif + +#ifdef USE_GNUTLS +gnutls_x509_crt ssl_certificate_get_x509_from_pem_file(const gchar *file) +{ + gnutls_x509_crt x509 = NULL; + if (!file) + return NULL; + + if (is_file_exist(file)) { + FILE *fp = g_fopen(file, "r"); + if (fp) { + x509 = d2i_X509_fp(fp, 1); + fclose(fp); + return x509; + } + } else { + log_error(LOG_PROTOCOL, "Can not open certificate file %s\n", file); + } + return NULL; +} + +gnutls_x509_privkey ssl_certificate_get_pkey_from_pem_file(const gchar *file) +{ + gnutls_x509_privkey key = NULL; + if (!file) + return NULL; + + if (is_file_exist(file)) { + FILE *fp = g_fopen(file, "r"); + if (fp) { + key = d2i_key_fp(fp, 1); + fclose(fp); + return key; + } + } else { + log_error(LOG_PROTOCOL, "Can not open key file %s\n", file); + } + return NULL; +} + +/* From GnuTLS lib/gnutls_x509.c */ +static int +parse_pkcs12 (gnutls_pkcs12_t p12, + const char *password, + gnutls_x509_privkey * key, + gnutls_x509_crt_t * cert) +{ + gnutls_pkcs12_bag bag = NULL; + int index = 0; + int ret; + + for (;;) + { + int elements_in_bag; + int i; + + ret = gnutls_pkcs12_bag_init (&bag); + if (ret < 0) + { + bag = NULL; + goto done; + } + + ret = gnutls_pkcs12_get_bag (p12, index, bag); + if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + break; + if (ret < 0) + { + goto done; + } + + ret = gnutls_pkcs12_bag_get_type (bag, 0); + if (ret < 0) + { + goto done; + } + + if (ret == GNUTLS_BAG_ENCRYPTED) + { + ret = gnutls_pkcs12_bag_decrypt (bag, password); + if (ret < 0) + { + goto done; + } + } + + elements_in_bag = gnutls_pkcs12_bag_get_count (bag); + if (elements_in_bag < 0) + { + goto done; + } + + for (i = 0; i < elements_in_bag; i++) + { + int type; + gnutls_datum data; + + type = gnutls_pkcs12_bag_get_type (bag, i); + if (type < 0) + { + goto done; + } + + ret = gnutls_pkcs12_bag_get_data (bag, i, &data); + if (ret < 0) + { + goto done; + } + + switch (type) + { + case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY: + case GNUTLS_BAG_PKCS8_KEY: + ret = gnutls_x509_privkey_init (key); + if (ret < 0) + { + goto done; + } + + ret = gnutls_x509_privkey_import_pkcs8 + (*key, &data, GNUTLS_X509_FMT_DER, password, + type == GNUTLS_BAG_PKCS8_KEY ? GNUTLS_PKCS_PLAIN : 0); + if (ret < 0) + { + goto done; + } + break; + + case GNUTLS_BAG_CERTIFICATE: + ret = gnutls_x509_crt_init (cert); + if (ret < 0) + { + goto done; + } + + ret = + gnutls_x509_crt_import (*cert, &data, GNUTLS_X509_FMT_DER); + if (ret < 0) + { + goto done; + } + break; + + case GNUTLS_BAG_ENCRYPTED: + /* XXX Bother to recurse one level down? Unlikely to + use the same password anyway. */ + case GNUTLS_BAG_EMPTY: + default: + break; + } + } + + index++; + gnutls_pkcs12_bag_deinit (bag); + } + + ret = 0; + +done: + if (bag) + gnutls_pkcs12_bag_deinit (bag); + + return ret; +} +void ssl_certificate_get_x509_and_pkey_from_p12_file(const gchar *file, const gchar *password, + gnutls_x509_crt *x509, gnutls_x509_privkey *pkey) +{ + gnutls_pkcs12_t p12 = NULL; + + int r; + + *x509 = NULL; + *pkey = NULL; + if (!file) + return; + + if (is_file_exist(file)) { + FILE *fp = g_fopen(file, "r"); + if (fp) { + p12 = d2i_PKCS12_fp(fp, 0); + fclose(fp); + } + } else { + log_error(LOG_PROTOCOL, "Can not open certificate file %s\n", file); + } + if (p12 != NULL) { + if ((r = parse_pkcs12(p12, password, pkey, x509)) == 0) { + debug_print("got p12\n"); + } else { + log_error(LOG_PROTOCOL, "%s\n", gnutls_strerror(r)); + } + gnutls_pkcs12_deinit(p12); + } +} +#endif #endif /* USE_OPENSSL */ diff --git a/src/common/ssl_certificate.h b/src/common/ssl_certificate.h @@ -37,6 +37,8 @@ #include <glib.h> #define SSLCERT_ASK_HOOKLIST "sslcert_ask" +#define SSLCERT_GET_CLIENT_CERT_HOOKLIST "sslcert_get_client_cert" +#define SSL_CERT_GET_PASSWORD "sslcert_get_password" typedef struct _SSLCertificate SSLCertificate; @@ -82,5 +84,19 @@ time_t asn1toTime(ASN1_TIME *asn1Time); char *ssl_certificate_check_signer (gnutls_x509_crt cert, guint status); #endif +#if USE_OPENSSL +X509 *ssl_certificate_get_x509_from_pem_file(const gchar *file); +EVP_PKEY *ssl_certificate_get_pkey_from_pem_file(const gchar *file); +void ssl_certificate_get_x509_and_pkey_from_p12_file(const gchar *file, + const gchar *password, X509 **x509, EVP_PKEY **pkey); +#endif +#ifdef USE_GNUTLS +gnutls_x509_crt ssl_certificate_get_x509_from_pem_file(const gchar *file); +gnutls_x509_privkey ssl_certificate_get_pkey_from_pem_file(const gchar *file); +void ssl_certificate_get_x509_and_pkey_from_p12_file(const gchar *file, + const gchar *password, gnutls_x509_crt *crt, gnutls_x509_privkey *key); +size_t i2d_X509(gnutls_x509_crt x509_cert, unsigned char **output); +size_t i2d_PrivateKey(gnutls_x509_privkey pkey, unsigned char **output); +#endif #endif /* USE_OPENSSL */ #endif /* SSL_CERTIFICATE_H */ diff --git a/src/etpan/imap-thread.c b/src/etpan/imap-thread.c @@ -40,6 +40,7 @@ #include "etpan-thread-manager.h" #include "utils.h" #include "mainwindow.h" +#include "ssl.h" #include "ssl_certificate.h" #include "socket.h" #include "remotefolder.h" @@ -438,6 +439,7 @@ static void threaded_run(Folder * folder, void * param, void * result, struct connect_param { mailimap * imap; + PrefsAccount *account; const char * server; int port; }; @@ -557,6 +559,55 @@ static int etpan_certificate_check(const unsigned char *certificate, int len, vo return 0; } +static void connect_ssl_context_cb(struct mailstream_ssl_context * ssl_context, void * data) +{ +#if (defined(USE_OPENSSL) || defined(USE_GNUTLS)) + PrefsAccount *account = (PrefsAccount *)data; + const gchar *cert_path = NULL; + const gchar *password = NULL; +#ifdef USE_OPENSSL + X509 *x509 = NULL; + EVP_PKEY *pkey = NULL; +#else + gnutls_x509_crt x509 = NULL; + gnutls_x509_privkey pkey = NULL; +#endif + + if (account->in_ssl_client_cert_file && *account->in_ssl_client_cert_file) + cert_path = account->in_ssl_client_cert_file; + if (account->in_ssl_client_cert_pass && *account->in_ssl_client_cert_pass) + password = account->in_ssl_client_cert_pass; + + if (mailstream_ssl_set_client_certificate_data(ssl_context, NULL, 0) < 0 || + mailstream_ssl_set_client_private_key_data(ssl_context, NULL, 0) < 0) + debug_print("Impossible to set the client certificate.\n"); + x509 = ssl_certificate_get_x509_from_pem_file(cert_path); + pkey = ssl_certificate_get_pkey_from_pem_file(cert_path); + if (!(x509 && pkey)) { + /* try pkcs12 format */ + ssl_certificate_get_x509_and_pkey_from_p12_file(cert_path, password, &x509, &pkey); + } + if (x509 && pkey) { + unsigned char *x509_der = NULL, *pkey_der = NULL; + size_t x509_len, pkey_len; + + x509_len = (size_t)i2d_X509(x509, &x509_der); + pkey_len = (size_t)i2d_PrivateKey(pkey, &pkey_der); + if (x509_len > 0 && pkey_len > 0) { + if (mailstream_ssl_set_client_certificate_data(ssl_context, x509_der, x509_len) < 0 || + mailstream_ssl_set_client_private_key_data(ssl_context, pkey_der, pkey_len) < 0) + log_error(LOG_PROTOCOL, "Impossible to set the client certificate.\n"); + g_free(x509_der); + g_free(pkey_der); + } +#ifdef USE_GNUTLS + gnutls_x509_crt_deinit(x509); + gnutls_x509_privkey_deinit(pkey); +#endif + } +#endif +} + static void connect_ssl_run(struct etpan_thread_op * op) { int r; @@ -568,8 +619,9 @@ static void connect_ssl_run(struct etpan_thread_op * op) CHECK_IMAP(); - r = mailimap_ssl_connect(param->imap, - param->server, param->port); + r = mailimap_ssl_connect_with_callback(param->imap, + param->server, param->port, + connect_ssl_context_cb, param->account); result->error = r; } @@ -601,7 +653,8 @@ int imap_threaded_connect_ssl(Folder * folder, const char * server, int port) param.imap = imap; param.server = server; param.port = port; - + param.account = folder->account; + refresh_resolvers(); threaded_run(folder, &param, &result, connect_ssl_run); @@ -1119,7 +1172,7 @@ static void starttls_run(struct etpan_thread_op * op) return; } - tls_low = mailstream_low_tls_open(fd); + tls_low = mailstream_low_tls_open_with_callback(fd, connect_ssl_context_cb, param->account); if (tls_low == NULL) { debug_print("imap starttls run - can't tls_open\n"); result->error = MAILIMAP_ERROR_STREAM; @@ -1142,7 +1195,8 @@ int imap_threaded_starttls(Folder * folder, const gchar *host, int port) param.imap = get_imap(folder); param.server = host; param.port = port; - + param.account = folder->account; + threaded_run(folder, &param, &result, starttls_run); debug_print("imap starttls - end\n"); diff --git a/src/etpan/nntp-thread.c b/src/etpan/nntp-thread.c @@ -290,6 +290,7 @@ static void threaded_run(Folder * folder, void * param, void * result, struct connect_param { newsnntp * nntp; + PrefsAccount *account; const char * server; int port; }; @@ -409,6 +410,55 @@ static int etpan_certificate_check(const unsigned char *certificate, int len, vo return 0; } +static void connect_ssl_context_cb(struct mailstream_ssl_context * ssl_context, void * data) +{ +#if (defined(USE_OPENSSL) || defined(USE_GNUTLS)) + PrefsAccount *account = (PrefsAccount *)data; + const gchar *cert_path = NULL; + const gchar *password = NULL; +#ifdef USE_OPENSSL + X509 *x509 = NULL; + EVP_PKEY *pkey = NULL; +#else + gnutls_x509_crt x509 = NULL; + gnutls_x509_privkey pkey = NULL; +#endif + + if (account->in_ssl_client_cert_file && *account->in_ssl_client_cert_file) + cert_path = account->in_ssl_client_cert_file; + if (account->in_ssl_client_cert_pass && *account->in_ssl_client_cert_pass) + password = account->in_ssl_client_cert_pass; + + if (mailstream_ssl_set_client_certificate_data(ssl_context, NULL, 0) < 0 || + mailstream_ssl_set_client_private_key_data(ssl_context, NULL, 0) < 0) + debug_print("Impossible to set the client certificate.\n"); + x509 = ssl_certificate_get_x509_from_pem_file(cert_path); + pkey = ssl_certificate_get_pkey_from_pem_file(cert_path); + if (!(x509 && pkey)) { + /* try pkcs12 format */ + ssl_certificate_get_x509_and_pkey_from_p12_file(cert_path, password, &x509, &pkey); + } + if (x509 && pkey) { + unsigned char *x509_der = NULL, *pkey_der = NULL; + size_t x509_len, pkey_len; + + x509_len = (size_t)i2d_X509(x509, &x509_der); + pkey_len = (size_t)i2d_PrivateKey(pkey, &pkey_der); + if (x509_len > 0 && pkey_len > 0) { + if (mailstream_ssl_set_client_certificate_data(ssl_context, x509_der, x509_len) < 0 || + mailstream_ssl_set_client_private_key_data(ssl_context, pkey_der, pkey_len) < 0) + log_error(LOG_PROTOCOL, "Impossible to set the client certificate.\n"); + g_free(x509_der); + g_free(pkey_der); + } +#ifdef USE_GNUTLS + gnutls_x509_crt_deinit(x509); + gnutls_x509_privkey_deinit(pkey); +#endif + } +#endif +} + static void connect_ssl_run(struct etpan_thread_op * op) { int r; @@ -420,8 +470,9 @@ static void connect_ssl_run(struct etpan_thread_op * op) CHECK_NNTP(); - r = newsnntp_ssl_connect(param->nntp, - param->server, param->port); + r = newsnntp_ssl_connect_with_callback(param->nntp, + param->server, param->port, + connect_ssl_context_cb, param->account); result->error = r; } @@ -453,7 +504,8 @@ int nntp_threaded_connect_ssl(Folder * folder, const char * server, int port) param.nntp = nntp; param.server = server; param.port = port; - + param.account = folder->account; + refresh_resolvers(); threaded_run(folder, &param, &result, connect_ssl_run); diff --git a/src/gtk/inputdialog.c b/src/gtk/inputdialog.c @@ -222,8 +222,17 @@ gchar *input_dialog_query_password(const gchar *server, const gchar *user) gchar *message; gchar *pass; - message = g_strdup_printf(_("Input password for %s on %s:"), + if (server && user) + message = g_strdup_printf(_("Input password for %s on %s:"), user, server); + else if (server) + message = g_strdup_printf(_("Input password for %s:"), + server); + else if (user) + message = g_strdup_printf(_("Input password for %s:"), + user); + else + message = g_strdup_printf(_("Input password:")); pass = input_dialog_with_invisible(_("Input password"), message, NULL); g_free(message); diff --git a/src/imap.c b/src/imap.c @@ -680,13 +680,11 @@ static void imap_handle_error(Session *session, int libetpan_errcode) case MAILIMAP_ERROR_SASL: log_warning(LOG_PROTOCOL, _("IMAP error: SASL error\n")); break; -#if (LIBETPAN_VERSION_MAJOR > 0 || LIBETPAN_VERSION_MINOR > 48) #if (defined(USE_OPENSSL) || defined (USE_GNUTLS)) case MAILIMAP_ERROR_SSL: log_warning(LOG_PROTOCOL, _("IMAP error: SSL error\n")); break; #endif -#endif default: log_warning(LOG_PROTOCOL, _("IMAP error: Unknown error [%d]\n"), libetpan_errcode); @@ -1088,13 +1086,11 @@ static IMAPSession *imap_session_new(Folder * folder, authenticated = FALSE; } else { -#if (LIBETPAN_VERSION_MAJOR > 0 || LIBETPAN_VERSION_MINOR > 48) #if (defined(USE_OPENSSL) || defined (USE_GNUTLS)) if (r == MAILIMAP_ERROR_SSL) log_error(LOG_PROTOCOL, _("SSL handshake failed\n")); else #endif -#endif imap_handle_error(NULL, r); if(!prefs_common.no_recv_err_panel) { @@ -1109,7 +1105,7 @@ static IMAPSession *imap_session_new(Folder * folder, } session = g_new0(IMAPSession, 1); - session_init(SESSION(session)); + session_init(SESSION(session), account, FALSE); SESSION(session)->type = SESSION_IMAP; SESSION(session)->server = g_strdup(account->recv_server); SESSION(session)->port = port; diff --git a/src/news.c b/src/news.c @@ -294,7 +294,7 @@ static Session *news_session_new(Folder *folder, const gchar *server, gushort po log_message(LOG_PROTOCOL, _("creating NNTP connection to %s:%d ...\n"), server, port); session = g_new0(NewsSession, 1); - session_init(SESSION(session)); + session_init(SESSION(session), folder->account, FALSE); SESSION(session)->type = SESSION_NEWS; SESSION(session)->server = g_strdup(server); SESSION(session)->sock = NULL; diff --git a/src/pop.c b/src/pop.c @@ -520,7 +520,7 @@ Session *pop3_session_new(PrefsAccount *account) session = g_new0(Pop3Session, 1); - session_init(SESSION(session)); + session_init(SESSION(session), account, FALSE); SESSION(session)->type = SESSION_POP3; diff --git a/src/prefs_account.c b/src/prefs_account.c @@ -57,6 +57,9 @@ #include "combobox.h" #include "setup.h" #include "quote_fmt.h" +#include "hooks.h" +#include "inputdialog.h" +#include "ssl_certificate.h" static gboolean cancelled; static gboolean new_account; @@ -69,6 +72,13 @@ static GtkWidget *entry_sigpath; static GtkWidget *signature_browse_button; static GtkWidget *signature_edit_button; +#if (defined(USE_OPENSSL) || defined (USE_GNUTLS)) +static GtkWidget *entry_in_cert_file; +static GtkWidget *entry_out_cert_file; +static GtkWidget *in_ssl_cert_browse_button; +static GtkWidget *out_ssl_cert_browse_button; +#endif + static GSList *prefs_pages = NULL; typedef struct BasicPage @@ -242,6 +252,11 @@ typedef struct SSLPage GtkWidget *smtp_ssltunnel_radiobtn; GtkWidget *smtp_starttls_radiobtn; + GtkWidget *entry_in_cert_file; + GtkWidget *entry_in_cert_pass; + GtkWidget *entry_out_cert_file; + GtkWidget *entry_out_cert_pass; + GtkWidget *use_nonblocking_ssl_checkbtn; } SSLPage; @@ -680,6 +695,18 @@ static PrefParam ssl_param[] = { {"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}, + + {"in_ssl_client_cert_file", "", &tmp_ac_prefs.in_ssl_client_cert_file, P_STRING, + &ssl_page.entry_in_cert_file, prefs_set_data_from_entry, prefs_set_entry}, + + {"in_ssl_client_cert_pass", "", &tmp_ac_prefs.in_ssl_client_cert_pass, P_PASSWORD, + &ssl_page.entry_in_cert_pass, prefs_set_data_from_entry, prefs_set_entry}, + + {"out_ssl_client_cert_file", "", &tmp_ac_prefs.out_ssl_client_cert_file, P_STRING, + &ssl_page.entry_out_cert_file, prefs_set_data_from_entry, prefs_set_entry}, + + {"out_ssl_client_cert_pass", "", &tmp_ac_prefs.out_ssl_client_cert_pass, P_PASSWORD, + &ssl_page.entry_out_cert_pass, prefs_set_data_from_entry, prefs_set_entry}, #else {"ssl_pop", "0", &tmp_ac_prefs.ssl_pop, P_ENUM, NULL, NULL, NULL}, @@ -693,6 +720,18 @@ static PrefParam ssl_param[] = { {"ssl_smtp", "0", &tmp_ac_prefs.ssl_smtp, P_ENUM, NULL, NULL, NULL}, + {"in_ssl_client_cert_file", "", &tmp_ac_prefs.in_ssl_client_cert_file, P_STRING, + NULL, NULL, NULL}, + + {"in_ssl_client_cert_pass", "", &tmp_ac_prefs.in_ssl_client_cert_pass, P_PASSWORD, + NULL, NULL, NULL}, + + {"out_ssl_client_cert_file", "", &tmp_ac_prefs.out_ssl_client_cert_file, P_STRING, + NULL, NULL, NULL}, + + {"out_ssl_client_cert_pass", "", &tmp_ac_prefs.out_ssl_client_cert_pass, P_PASSWORD, + NULL, NULL, NULL}, + {"use_nonblocking_ssl", "1", &tmp_ac_prefs.use_nonblocking_ssl, P_BOOL, NULL, NULL, NULL}, #endif /* USE_OPENSSL */ @@ -803,6 +842,12 @@ static void prefs_account_sigcmd_radiobtn_cb (GtkWidget *widget, static void prefs_account_signature_browse_cb (GtkWidget *widget, gpointer data); +static void prefs_account_in_cert_browse_cb (GtkWidget *widget, + gpointer data); + +static void prefs_account_out_cert_browse_cb (GtkWidget *widget, + gpointer data); + static void prefs_account_signature_edit_cb (GtkWidget *widget, gpointer data); @@ -2254,11 +2299,18 @@ static void ssl_create_widget_func(PrefsPage * _page, GtkWidget *smtp_ssltunnel_radiobtn; GtkWidget *smtp_starttls_radiobtn; + GtkWidget *cert_frame; GtkWidget *vbox6; + GtkWidget *cert_table; + GtkWidget *entry_in_cert_pass; + GtkWidget *entry_out_cert_pass; + + GtkWidget *vbox7; GtkWidget *use_nonblocking_ssl_checkbtn; GtkWidget *hbox; GtkWidget *hbox_spc; GtkWidget *label; + GtkTooltips *tips = gtk_tooltips_new(); vbox1 = gtk_vbox_new (FALSE, VSPACING); gtk_widget_show (vbox1); @@ -2276,7 +2328,7 @@ static void ssl_create_widget_func(PrefsPage * _page, pop_starttls_radiobtn, _("Use STARTTLS command to start SSL session"), SSL_STARTTLS); - + vbox3 = gtkut_get_options_frame(vbox1, &imap_frame, _("IMAP4")); CREATE_RADIO_BUTTONS(vbox3, @@ -2317,16 +2369,63 @@ static void ssl_create_widget_func(PrefsPage * _page, _("Use STARTTLS command to start SSL session"), SSL_STARTTLS); - vbox6 = gtk_vbox_new (FALSE, 0); - gtk_widget_show (vbox6); - gtk_box_pack_start (GTK_BOX (vbox1), vbox6, FALSE, FALSE, 0); + vbox6 = gtkut_get_options_frame(vbox1, &cert_frame, _("Client certificates")); + cert_table = gtk_table_new(4,3, FALSE); + + label = gtk_label_new("Reception certificate"); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + entry_in_cert_file = gtk_entry_new(); + in_ssl_cert_browse_button = gtkut_get_browse_file_btn(_("Browse")); + gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), label, _("Client certificate file as a PKCS12 or PEM file."), NULL); + gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), entry_in_cert_file, _("Client certificate file as a PKCS12 or PEM file."), NULL); + gtk_table_attach (GTK_TABLE (cert_table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_table_attach (GTK_TABLE (cert_table), entry_in_cert_file, 1, 2, 0, 1, GTK_EXPAND|GTK_FILL, 0, 0, 0); + gtk_table_attach (GTK_TABLE (cert_table), in_ssl_cert_browse_button, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + + label = gtk_label_new("Certificate password"); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + entry_in_cert_pass = gtk_entry_new(); + gtk_entry_set_visibility(GTK_ENTRY(entry_in_cert_pass), FALSE); + gtk_table_attach (GTK_TABLE (cert_table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + gtk_table_attach (GTK_TABLE (cert_table), entry_in_cert_pass, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + + label = gtk_label_new("Send certificate"); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + entry_out_cert_file = gtk_entry_new(); + out_ssl_cert_browse_button = gtkut_get_browse_file_btn(_("Browse")); + gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), label, _("Client certificate file as a PKCS12 or PEM file."), NULL); + gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), entry_out_cert_file, _("Client certificate file as a PKCS12 or PEM file."), NULL); + gtk_table_attach (GTK_TABLE (cert_table), label, 0, 1, 2, 3, GTK_FILL, 0, 0, 0); + gtk_table_attach (GTK_TABLE (cert_table), entry_out_cert_file, 1, 2, 2, 3, GTK_EXPAND|GTK_FILL, 0, 0, 0); + gtk_table_attach (GTK_TABLE (cert_table), out_ssl_cert_browse_button, 2, 3, 2, 3, GTK_FILL, 0, 0, 0); + + label = gtk_label_new("Certificate password"); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + entry_out_cert_pass = gtk_entry_new(); + gtk_entry_set_visibility(GTK_ENTRY(entry_out_cert_pass), FALSE); + gtk_table_attach (GTK_TABLE (cert_table), label, 0, 1, 3, 4, GTK_FILL, 0, 0, 0); + gtk_table_attach (GTK_TABLE (cert_table), entry_out_cert_pass, 1, 2, 3, 4, GTK_FILL, 0, 0, 0); + hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox6), hbox, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (hbox), cert_table, TRUE, TRUE, 0); + gtk_widget_show_all(vbox6); + + g_signal_connect(G_OBJECT(in_ssl_cert_browse_button), "clicked", + G_CALLBACK(prefs_account_in_cert_browse_cb), NULL); + g_signal_connect(G_OBJECT(out_ssl_cert_browse_button), "clicked", + G_CALLBACK(prefs_account_out_cert_browse_cb), NULL); + + vbox7 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox7); + gtk_box_pack_start (GTK_BOX (vbox1), vbox7, FALSE, FALSE, 0); - PACK_CHECK_BUTTON(vbox6, use_nonblocking_ssl_checkbtn, + PACK_CHECK_BUTTON(vbox7, use_nonblocking_ssl_checkbtn, _("Use non-blocking SSL")); hbox = gtk_hbox_new (FALSE, 0); gtk_widget_show (hbox); - gtk_box_pack_start (GTK_BOX (vbox6), hbox, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox7), hbox, FALSE, FALSE, 0); hbox_spc = gtk_hbox_new (FALSE, 0); gtk_widget_show (hbox_spc); @@ -2358,6 +2457,11 @@ static void ssl_create_widget_func(PrefsPage * _page, page->smtp_ssltunnel_radiobtn = smtp_ssltunnel_radiobtn; page->smtp_starttls_radiobtn = smtp_starttls_radiobtn; + page->entry_in_cert_file = entry_in_cert_file; + page->entry_in_cert_pass = entry_in_cert_pass; + page->entry_out_cert_file = entry_out_cert_file; + page->entry_out_cert_pass = entry_out_cert_pass; + page->use_nonblocking_ssl_checkbtn = use_nonblocking_ssl_checkbtn; tmp_ac_prefs = *ac_prefs; @@ -3102,6 +3206,69 @@ static void register_ssl_page(void) } #endif +static gboolean sslcert_get_client_cert_hook(gpointer source, gpointer data) +{ + SSLClientCertHookData *hookdata = (SSLClientCertHookData *)source; + PrefsAccount *account = (PrefsAccount *)hookdata->account; + + hookdata->cert_path = NULL; + hookdata->password = NULL; + + if (!g_list_find(account_get_list(), account)) { + g_warning("can't find sock account\n"); + return TRUE; + } + + if (hookdata->is_smtp) { + if (account->out_ssl_client_cert_file && *account->out_ssl_client_cert_file) + hookdata->cert_path = account->out_ssl_client_cert_file; + if (account->out_ssl_client_cert_pass && *account->out_ssl_client_cert_pass) + hookdata->password = account->out_ssl_client_cert_pass; + } else { + if (account->in_ssl_client_cert_file && *account->in_ssl_client_cert_file) + hookdata->cert_path = account->in_ssl_client_cert_file; + if (account->in_ssl_client_cert_pass && *account->in_ssl_client_cert_pass) + hookdata->password = account->in_ssl_client_cert_pass; + } + return TRUE; +} + +struct GetPassData { + GCond *cond; + GMutex* mutex; + gchar **pass; +}; + + +static gboolean do_get_pass(gpointer data) +{ + struct GetPassData *pass_data = (struct GetPassData *)data; + g_mutex_lock(pass_data->mutex); + *(pass_data->pass) = input_dialog_query_password("the PKCS12 client certificate", NULL); + g_cond_signal(pass_data->cond); + g_mutex_unlock(pass_data->mutex); + return FALSE; +} +static gboolean sslcert_get_password(gpointer source, gpointer data) +{ + struct GetPassData pass_data; + /* do complicated stuff to be able to call GTK from the mainloop */ + pass_data.cond = g_cond_new(); + pass_data.mutex = g_mutex_new(); + pass_data.pass = (gchar **)source; + + g_mutex_lock(pass_data.mutex); + + g_idle_add(do_get_pass, &pass_data); + + g_cond_wait(pass_data.cond, pass_data.mutex); + g_cond_free(pass_data.cond); + g_mutex_unlock(pass_data.mutex); + g_mutex_free(pass_data.mutex); + + return TRUE; +} + static void register_advanced_page(void) { static gchar *path[3]; @@ -3130,6 +3297,8 @@ void prefs_account_init() register_privacy_page(); #if (defined(USE_OPENSSL) || defined (USE_GNUTLS)) register_ssl_page(); + hooks_register_hook(SSLCERT_GET_CLIENT_CERT_HOOKLIST, sslcert_get_client_cert_hook, NULL); + hooks_register_hook(SSL_CERT_GET_PASSWORD, sslcert_get_password, NULL); #endif register_advanced_page(); } @@ -3503,6 +3672,42 @@ static void prefs_account_signature_browse_cb(GtkWidget *widget, gpointer data) g_free(utf8_filename); } +#if (defined(USE_OPENSSL) || defined (USE_GNUTLS)) +static void prefs_account_in_cert_browse_cb(GtkWidget *widget, gpointer data) +{ + gchar *filename; + gchar *utf8_filename; + + filename = filesel_select_file_open(_("Select certificate file"), NULL); + if (!filename) return; + + utf8_filename = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + if (!utf8_filename) { + g_warning("prefs_account_cert_browse_cb(): failed to convert character set."); + utf8_filename = g_strdup(filename); + } + gtk_entry_set_text(GTK_ENTRY(entry_in_cert_file), utf8_filename); + g_free(utf8_filename); +} + +static void prefs_account_out_cert_browse_cb(GtkWidget *widget, gpointer data) +{ + gchar *filename; + gchar *utf8_filename; + + filename = filesel_select_file_open(_("Select certificate file"), NULL); + if (!filename) return; + + utf8_filename = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + if (!utf8_filename) { + g_warning("prefs_account_cert_browse_cb(): failed to convert character set."); + utf8_filename = g_strdup(filename); + } + gtk_entry_set_text(GTK_ENTRY(entry_out_cert_file), utf8_filename); + g_free(utf8_filename); +} +#endif + static void prefs_account_signature_edit_cb(GtkWidget *widget, gpointer data) { const gchar *sigpath = gtk_entry_get_text(GTK_ENTRY(data)); diff --git a/src/prefs_account.h b/src/prefs_account.h @@ -78,6 +78,12 @@ struct _PrefsAccount SSLType ssl_imap; SSLType ssl_nntp; SSLType ssl_smtp; + + gchar *out_ssl_client_cert_file; + gchar *out_ssl_client_cert_pass; + gchar *in_ssl_client_cert_file; + gchar *in_ssl_client_cert_pass; + gboolean use_nonblocking_ssl; /* Temporarily preserved password */ diff --git a/src/prefs_common.c b/src/prefs_common.c @@ -295,7 +295,7 @@ static PrefParam param[] = { /* Quote */ {"reply_quote_mark", "> ", &prefs_common.quotemark, P_STRING, NULL, NULL, NULL}, - {"reply_quote_format", N_("On %d\\n%f wrote:\\n\\n%q"), + {"reply_quote_format", N_("On %d\\n%f wrote:\\n\\n%q\\n%X"), &prefs_common.quotefmt, P_STRING, NULL, NULL, NULL}, {"forward_quote_mark", "> ", &prefs_common.fw_quotemark, P_STRING, diff --git a/src/send_message.c b/src/send_message.c @@ -232,7 +232,7 @@ 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(); + session = smtp_session_new(ac_prefs); smtp_session = SMTP_SESSION(session); if (ac_prefs->set_domain && ac_prefs->domain && strlen(ac_prefs->domain)) {