2008-07-03 [colin] 3.5.0cvs2
authorColin Leroy <colin@colino.net>
Thu, 3 Jul 2008 20:31:33 +0000 (20:31 +0000)
committerColin Leroy <colin@colino.net>
Thu, 3 Jul 2008 20:31:33 +0000 (20:31 +0000)
* 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)

22 files changed:
ChangeLog
PATCHSETS
configure.ac
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
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

index 2463b5f..a5efa03 100644 (file)
--- 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
index 1943201..c2e8520 100644 (file)
--- a/PATCHSETS
+++ b/PATCHSETS
 ( 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
index a4ef3f0..9f82860 100644 (file)
@@ -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=
 
index 8391ba0..b366e5a 100644 (file)
@@ -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);
index f192721..cc8aa37 100644 (file)
@@ -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);
index 940778c..9877e51 100644 (file)
@@ -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;
 
index 216fb61..b2b9cc7 100644 (file)
@@ -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);
 
index 7a37647..86aeffd 100644 (file)
@@ -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);
index 09c14d0..83efa00 100644 (file)
@@ -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;
        }
index e016b6d..686740b 100644 (file)
@@ -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__ */
index a48a7cd..ed44cf0 100644 (file)
@@ -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 */
index 932affe..9a57fb3 100644 (file)
@@ -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 */
index 6c86513..bae2e4f 100644 (file)
@@ -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");
index d20ac04..21b9b0f 100644 (file)
@@ -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);
 
index 6c54c6e..127bfe3 100644 (file)
@@ -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);
 
index f25b87e..5947a0f 100644 (file)
@@ -680,12 +680,10 @@ 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"),
@@ -1088,12 +1086,10 @@ 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);
 
@@ -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;
index 473f043..edee3ec 100644 (file)
@@ -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;
index fdb6ff0..3780fec 100644 (file)
--- 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;
 
index 84c7b8f..81243db 100644 (file)
@@ -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));
index e8e793b..54103d2 100644 (file)
@@ -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 */
index 9879927..2c9207e 100644 (file)
@@ -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,
index 96ef37a..ded4b6e 100644 (file)
@@ -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)) {