From f3048c5d94635184a3f2e39a7ae87d22fca96fa0 Mon Sep 17 00:00:00 2001
From: Gu1 <aeroxteam@gmail.com>
Date: Wed, 9 Feb 2011 18:31:43 +0100
Subject: [PATCH] Fix a problem with certificate verification.

It was possible to completely bypass ssl_verify because the function
responsible for checking the certificate was only called when the
server requested a client certificate.
---
 src/core/wee-hook.c                             |   50 +++++++++++++++---
 src/core/wee-hook.h                             |   17 ++++---
 src/core/wee-network.c                          |    2 +
 src/plugins/irc/irc-server.c                    |   66 +++++++++++++++++------
 src/plugins/scripts/lua/weechat-lua-api.c       |    3 +-
 src/plugins/scripts/perl/weechat-perl-api.c     |    3 +-
 src/plugins/scripts/python/weechat-python-api.c |    3 +-
 src/plugins/scripts/ruby/weechat-ruby-api.c     |    3 +-
 src/plugins/scripts/script-api.c                |    7 ++-
 src/plugins/scripts/script-api.h                |    4 +-
 src/plugins/scripts/tcl/weechat-tcl-api.c       |    3 +-
 src/plugins/weechat-plugin.h                    |   15 +++---
 12 files changed, 129 insertions(+), 47 deletions(-)

diff --git a/src/core/wee-hook.c b/src/core/wee-hook.c
index 67478bd..7fb1e13 100644
--- a/src/core/wee-hook.c
+++ b/src/core/wee-hook.c
@@ -1639,8 +1639,8 @@ hook_process_run (struct t_hook *hook_process)
 
 struct t_hook *
 hook_connect (struct t_weechat_plugin *plugin, const char *proxy, const char *address,
-              int port, int sock, int ipv6, void *gnutls_sess, void *gnutls_cb,
-              int gnutls_dhkey_size, const char *local_hostname,
+              int port, int sock, int ipv6, void *gnutls_sess, void *gnutls_verify_cb,
+              void *gnutls_setcert_cb, int gnutls_dhkey_size, const char *local_hostname,
               t_hook_callback_connect *callback, void *callback_data)
 {
     struct t_hook *new_hook;
@@ -1649,7 +1649,8 @@ hook_connect (struct t_weechat_plugin *plugin, const char *proxy, const char *ad
 #ifndef HAVE_GNUTLS
     /* make C compiler happy */
     (void) gnutls_sess;
-    (void) gnutls_cb;
+    (void) gnutls_verify_cb;
+    (void) gnutls_setcert_cb;
     (void) gnutls_dhkey_size;
 #endif
     
@@ -1678,7 +1679,8 @@ hook_connect (struct t_weechat_plugin *plugin, const char *proxy, const char *ad
     new_hook_connect->ipv6 = ipv6;
 #ifdef HAVE_GNUTLS
     new_hook_connect->gnutls_sess = gnutls_sess;
-    new_hook_connect->gnutls_cb = gnutls_cb;
+    new_hook_connect->gnutls_verify_cb = gnutls_verify_cb;
+    new_hook_connect->gnutls_setcert_cb = gnutls_setcert_cb;
     new_hook_connect->gnutls_dhkey_size = gnutls_dhkey_size;
 #endif
     new_hook_connect->local_hostname = (local_hostname) ?
@@ -1700,6 +1702,37 @@ hook_connect (struct t_weechat_plugin *plugin, const char *proxy, const char *ad
 }
 
 /*
+ * hook_connect_gnutls_verify_certificates: verify server certificate
+ */
+
+#ifdef HAVE_GNUTLS
+int
+hook_connect_gnutls_verify_certificates (gnutls_session_t tls_session)
+{
+    struct t_hook *ptr_hook;
+    int rc;
+    
+    rc = -1;
+    ptr_hook = weechat_hooks[HOOK_TYPE_CONNECT];
+    while (ptr_hook)
+    {
+        /* looking for the right hook using to the gnutls session pointer */
+        if (!ptr_hook->deleted
+            && HOOK_CONNECT(ptr_hook, gnutls_sess)
+            && (*(HOOK_CONNECT(ptr_hook, gnutls_sess)) == tls_session))
+        {
+            rc = (int) (HOOK_CONNECT(ptr_hook, gnutls_verify_cb))
+                (ptr_hook->callback_data, tls_session);
+            break;
+        }
+        ptr_hook = ptr_hook->next_hook;
+    }
+    
+    return rc;
+}
+#endif
+
+/*
  * hook_connect_gnutls_set_certificates: set gnutls
  */
 
@@ -1723,7 +1756,7 @@ hook_connect_gnutls_set_certificates (gnutls_session_t tls_session,
             && HOOK_CONNECT(ptr_hook, gnutls_sess)
             && (*(HOOK_CONNECT(ptr_hook, gnutls_sess)) == tls_session))
         {
-            rc = (int) (HOOK_CONNECT(ptr_hook, gnutls_cb))
+            rc = (int) (HOOK_CONNECT(ptr_hook, gnutls_setcert_cb))
                 (ptr_hook->callback_data, tls_session, req_ca, nreq,
                  pk_algos, pk_algos_len, answer);
             break;
@@ -2999,7 +3032,9 @@ hook_add_to_infolist_type (struct t_infolist *infolist,
 #ifdef HAVE_GNUTLS
                     if (!infolist_new_var_pointer (ptr_item, "gnutls_sess", HOOK_CONNECT(ptr_hook, gnutls_sess)))
                         return 0;
-                    if (!infolist_new_var_pointer (ptr_item, "gnutls_cb", HOOK_CONNECT(ptr_hook, gnutls_cb)))
+                    if (!infolist_new_var_pointer (ptr_item, "gnutls_verify_cb", HOOK_CONNECT(ptr_hook, gnutls_verify_cb)))
+                        return 0;
+                    if (!infolist_new_var_pointer (ptr_item, "gnutls_setcert_cb", HOOK_CONNECT(ptr_hook, gnutls_setcert_cb)))
                         return 0;
                     if (!infolist_new_var_integer (ptr_item, "gnutls_dhkey_size", HOOK_CONNECT(ptr_hook, gnutls_dhkey_size)))
                         return 0;
@@ -3345,7 +3380,8 @@ hook_print_log ()
                         log_printf ("    ipv6. . . . . . . . . : %d",    HOOK_CONNECT(ptr_hook, ipv6));
 #ifdef HAVE_GNUTLS
                         log_printf ("    gnutls_sess . . . . . : 0x%lx", HOOK_CONNECT(ptr_hook, gnutls_sess));
-                        log_printf ("    gnutls_cb . . . . . . : 0x%lx", HOOK_CONNECT(ptr_hook, gnutls_cb));
+                        log_printf ("    gnutls_verify_cb. . . : 0x%lx", HOOK_CONNECT(ptr_hook, gnutls_verify_cb));
+                        log_printf ("    gnutls_setcert_cb . . : 0x%lx", HOOK_CONNECT(ptr_hook, gnutls_setcert_cb));
                         log_printf ("    gnutls_dhkey_size . . : %d",    HOOK_CONNECT(ptr_hook, gnutls_dhkey_size));
 #endif
                         log_printf ("    local_hostname. . . . : '%s'",  HOOK_CONNECT(ptr_hook, local_hostname));
diff --git a/src/core/wee-hook.h b/src/core/wee-hook.h
index 1c2e357..139922a 100644
--- a/src/core/wee-hook.h
+++ b/src/core/wee-hook.h
@@ -202,10 +202,11 @@ typedef int (t_hook_callback_connect)(void *data, int status,
                                       const char *ip_address);
 
 #ifdef HAVE_GNUTLS
-typedef int (gnutls_callback_t)(void *data, gnutls_session_t tls_session,
-                                const gnutls_datum_t *req_ca, int nreq,
-                                const gnutls_pk_algorithm_t *pk_algos,
-                                int pk_algos_len, gnutls_retr_st *answer);
+typedef int (gnutls_verify_cb_t)(void *data, gnutls_session_t tls_session);
+typedef int (gnutls_setcert_cb_t)(void *data, gnutls_session_t tls_session,
+                                  const gnutls_datum_t *req_ca, int nreq,
+                                  const gnutls_pk_algorithm_t *pk_algos,
+                                  int pk_algos_len, gnutls_retr_st *answer);
 #endif
 
 struct t_hook_connect
@@ -218,7 +219,8 @@ struct t_hook_connect
     int ipv6;                          /* IPv6 connection ?                 */
 #ifdef HAVE_GNUTLS
     gnutls_session_t *gnutls_sess;     /* GnuTLS session (SSL connection)   */
-    gnutls_callback_t *gnutls_cb;      /* GnuTLS callback during handshake  */
+    gnutls_verify_cb_t *gnutls_verify_cb; /* GnuTLS callback during handshake  */
+    gnutls_setcert_cb_t *gnutls_setcert_cb; /* GnuTLS callback during handshake  */
     int gnutls_dhkey_size;             /* Diffie Hellman Key Exchange size  */
 #endif
     char *local_hostname;              /* force local hostname (optional)   */
@@ -404,8 +406,8 @@ extern struct t_hook *hook_process (struct t_weechat_plugin *plugin,
                                     void *callback_data);
 extern struct t_hook *hook_connect (struct t_weechat_plugin *plugin,
                                     const char *proxy, const char *address,
-                                    int port, int sock, int ipv6,
-                                    void *gnutls_session, void *gnutls_cb,
+                                    int port, int sock, int ipv6, void *gnutls_sess,
+                                    void *gnutls_verify_cb, void *gnutls_setcert_cb,
                                     int gnutls_dhkey_size,
                                     const char *local_hostname,
                                     t_hook_callback_connect *callback,
@@ -416,6 +418,7 @@ extern int hook_connect_gnutls_set_certificates (gnutls_session_t tls_session,
                                                  const gnutls_pk_algorithm_t *pk_algos,
                                                  int pk_algos_len,
                                                  gnutls_retr_st *answer);
+extern int hook_connect_gnutls_verify_certificates (gnutls_session_t tls_session);
 #endif
 extern struct t_hook *hook_print (struct t_weechat_plugin *plugin,
                                   struct t_gui_buffer *buffer,
diff --git a/src/core/wee-network.c b/src/core/wee-network.c
index f74a90b..08fa691 100644
--- a/src/core/wee-network.c
+++ b/src/core/wee-network.c
@@ -94,6 +94,8 @@ network_init ()
         }
         free (ca_path);
     }
+    gnutls_certificate_set_verify_function (gnutls_xcred,
+                                            &hook_connect_gnutls_verify_certificates);
     gnutls_certificate_client_set_retrieve_function (gnutls_xcred,
                                                      &hook_connect_gnutls_set_certificates);
     network_init_ok = 1;
diff --git a/src/plugins/irc/irc-server.c b/src/plugins/irc/irc-server.c
index acbc1ca..a3fb90a 100644
--- a/src/plugins/irc/irc-server.c
+++ b/src/plugins/irc/irc-server.c
@@ -2798,35 +2798,23 @@ irc_server_create_buffer (struct t_irc_server *server)
 
 #ifdef HAVE_GNUTLS
 /*
- * irc_server_gnutls_callback: gnutls callback called during handshake
+ * irc_server_gnutls_verify_callback: gnutls callback for certificate verification
  *
  */
 int
-irc_server_gnutls_callback (void *data, gnutls_session_t tls_session,
-                            const gnutls_datum_t *req_ca, int nreq,
-                            const gnutls_pk_algorithm_t *pk_algos,
-                            int pk_algos_len, gnutls_retr_st *answer)
+irc_server_gnutls_verify_callback (void *data, gnutls_session_t tls_session)
 {
     struct t_irc_server *server;
-    gnutls_retr_st tls_struct;
     gnutls_x509_crt_t cert_temp;
     const gnutls_datum_t *cert_list;
-    gnutls_datum_t filedatum;
     unsigned int cert_list_len, status;
     time_t cert_time;
-    char *cert_path0, *cert_path1, *cert_path2, *cert_str, *hostname;
-    const char *weechat_dir;
-    int rc, ret, i, j, hostname_match;
+    char *hostname;
+    int rc, i, j, hostname_match;
 #if LIBGNUTLS_VERSION_NUMBER >= 0x010706
     gnutls_datum_t cinfo;
     int rinfo;
 #endif
-
-    /* make C compiler happy */
-    (void) req_ca;
-    (void) nreq;
-    (void) pk_algos;
-    (void) pk_algos_len;
     
     rc = 0;
     
@@ -2947,6 +2935,49 @@ irc_server_gnutls_callback (void *data, gnutls_session_t tls_session,
         }
     }
     
+    /* an error should stop the handshake unless the user doesn't care */
+    if ((rc == -1)
+        && (IRC_SERVER_OPTION_BOOLEAN(server, IRC_SERVER_OPTION_SSL_VERIFY) == 0))
+    {
+        rc = 0;
+    }
+    
+    return rc;
+}
+#endif
+
+#ifdef HAVE_GNUTLS
+/*
+ * irc_server_gnutls_setcert_callback: gnutls callback setting the client certificate
+ *
+ */
+int
+irc_server_gnutls_setcert_callback (void *data, gnutls_session_t tls_session,
+                                    const gnutls_datum_t *req_ca, int nreq,
+                                    const gnutls_pk_algorithm_t *pk_algos,
+                                    int pk_algos_len, gnutls_retr_st *answer)
+{
+    struct t_irc_server *server;
+    gnutls_retr_st tls_struct;
+    gnutls_datum_t filedatum;
+    char *cert_path0, *cert_path1, *cert_path2, *cert_str;
+    const char *weechat_dir;
+    int rc, ret;
+#if LIBGNUTLS_VERSION_NUMBER >= 0x010706
+    gnutls_datum_t cinfo;
+    int rinfo;
+#endif
+
+    /* make C compiler happy */
+    (void) data;
+    (void) tls_session;
+    (void) req_ca;
+    (void) nreq;
+    (void) pk_algos;
+    (void) pk_algos_len;
+    
+    rc = 0;
+    
     /* using client certificate if it exists */
     cert_path0 = (char *) IRC_SERVER_OPTION_STRING(server,
                                                    IRC_SERVER_OPTION_SSL_CERT);
@@ -3282,7 +3313,8 @@ irc_server_connect (struct t_irc_server *server)
                                                  server->sock,
                                                  IRC_SERVER_OPTION_BOOLEAN(server, IRC_SERVER_OPTION_IPV6),
                                                  (server->ssl_connected) ? &server->gnutls_sess : NULL,
-                                                 (server->ssl_connected) ? irc_server_gnutls_callback : NULL,
+                                                 (server->ssl_connected) ? irc_server_gnutls_verify_callback : NULL,
+                                                 (server->ssl_connected) ? irc_server_gnutls_setcert_callback : NULL,
                                                  IRC_SERVER_OPTION_INTEGER(server, IRC_SERVER_OPTION_SSL_DHKEY_SIZE),
                                                  IRC_SERVER_OPTION_STRING(server, IRC_SERVER_OPTION_LOCAL_HOSTNAME),
                                                  &irc_server_connect_cb,
diff --git a/src/plugins/scripts/lua/weechat-lua-api.c b/src/plugins/scripts/lua/weechat-lua-api.c
index 038d1cf..f76ed30 100644
--- a/src/plugins/scripts/lua/weechat-lua-api.c
+++ b/src/plugins/scripts/lua/weechat-lua-api.c
@@ -3690,7 +3690,8 @@ weechat_lua_api_hook_connect (lua_State *L)
                                                       sock,
                                                       ipv6,
                                                       NULL, /* gnutls session */
-                                                      NULL, /* gnutls callback */
+                                                      NULL, /* gnutls verify callback */
+                                                      NULL, /* gnutls setcert callback */
                                                       0,    /* gnutls DH key size */
                                                       local_hostname,
                                                       &weechat_lua_api_hook_connect_cb,
diff --git a/src/plugins/scripts/perl/weechat-perl-api.c b/src/plugins/scripts/perl/weechat-perl-api.c
index c652aab..f7d2168 100644
--- a/src/plugins/scripts/perl/weechat-perl-api.c
+++ b/src/plugins/scripts/perl/weechat-perl-api.c
@@ -3318,7 +3318,8 @@ XS (XS_weechat_api_hook_connect)
                                                       SvIV (ST (3)), /* sock */
                                                       SvIV (ST (4)), /* ipv6 */
                                                       NULL, /* gnutls session */
-                                                      NULL, /* gnutls callback */
+                                                      NULL, /* gnutls verify callback */
+                                                      NULL, /* gnutls setcert callback */
                                                       0,    /* gnutls DH key size */
                                                       local_hostname,
                                                       &weechat_perl_api_hook_connect_cb,
diff --git a/src/plugins/scripts/python/weechat-python-api.c b/src/plugins/scripts/python/weechat-python-api.c
index e306d00..fc4c47c 100644
--- a/src/plugins/scripts/python/weechat-python-api.c
+++ b/src/plugins/scripts/python/weechat-python-api.c
@@ -3510,7 +3510,8 @@ weechat_python_api_hook_connect (PyObject *self, PyObject *args)
                                                       sock,
                                                       ipv6,
                                                       NULL, /* gnutls session */
-                                                      NULL, /* gnutls callback */
+                                                      NULL, /* gnutls verify callback */
+                                                      NULL, /* gnutls setcert callback */
                                                       0,    /* gnutls DH key size */
                                                       local_hostname,
                                                       &weechat_python_api_hook_connect_cb,
diff --git a/src/plugins/scripts/ruby/weechat-ruby-api.c b/src/plugins/scripts/ruby/weechat-ruby-api.c
index cc97b57..4a2a771 100644
--- a/src/plugins/scripts/ruby/weechat-ruby-api.c
+++ b/src/plugins/scripts/ruby/weechat-ruby-api.c
@@ -3815,7 +3815,8 @@ weechat_ruby_api_hook_connect (VALUE class, VALUE proxy, VALUE address,
                                                       c_sock,
                                                       c_ipv6,
                                                       NULL, /* gnutls session */
-                                                      NULL, /* gnutls callback */
+                                                      NULL, /* gnutls verify callback */
+                                                      NULL, /* gnutls setcert callback */
                                                       0,    /* gnutls DH key size */
                                                       c_local_hostname,
                                                       &weechat_ruby_api_hook_connect_cb,
diff --git a/src/plugins/scripts/script-api.c b/src/plugins/scripts/script-api.c
index 4fb0cd0..6ee6cc1 100644
--- a/src/plugins/scripts/script-api.c
+++ b/src/plugins/scripts/script-api.c
@@ -928,8 +928,8 @@ script_api_hook_connect (struct t_weechat_plugin *weechat_plugin,
                          struct t_plugin_script *script,
                          const char *proxy, const char *address, int port,
                          int sock, int ipv6, void *gnutls_sess,
-                         void *gnutls_cb, int gnutls_dhkey_size,
-                         const char *local_hostname,
+                         void *gnutls_verify_cb, void *gnutls_setcert_cb,
+                         int gnutls_dhkey_size, const char *local_hostname,
                          int (*callback)(void *data, int status,
                                          int gnutls_rc,
                                          const char *error,
@@ -945,7 +945,8 @@ script_api_hook_connect (struct t_weechat_plugin *weechat_plugin,
         return NULL;
     
     new_hook = weechat_hook_connect (proxy, address, port, sock, ipv6,
-                                     gnutls_sess, gnutls_cb, gnutls_dhkey_size,
+                                     gnutls_sess, gnutls_verify_cb,
+                                     gnutls_setcert_cb, gnutls_dhkey_size,
                                      local_hostname, callback,
                                      new_script_callback);
     if (!new_hook)
diff --git a/src/plugins/scripts/script-api.h b/src/plugins/scripts/script-api.h
index 73f419b..d451a7e 100644
--- a/src/plugins/scripts/script-api.h
+++ b/src/plugins/scripts/script-api.h
@@ -170,7 +170,9 @@ extern struct t_hook *script_api_hook_connect (struct t_weechat_plugin *weechat_
                                                int port,
                                                int sock,
                                                int ipv6,
-                                               void *gnutls_sess, void *gnutls_cb,
+                                               void *gnutls_sess,
+                                               void *gnutls_verify_cb,
+                                               void *gnutls_setcert_cb,
                                                int gnutls_dhkey_size,
                                                const char *local_hostname,
                                                int (*callback)(void *data,
diff --git a/src/plugins/scripts/tcl/weechat-tcl-api.c b/src/plugins/scripts/tcl/weechat-tcl-api.c
index f5db112..f922616 100644
--- a/src/plugins/scripts/tcl/weechat-tcl-api.c
+++ b/src/plugins/scripts/tcl/weechat-tcl-api.c
@@ -3774,7 +3774,8 @@ weechat_tcl_api_hook_connect (ClientData clientData, Tcl_Interp *interp,
                                                       sock,
                                                       ipv6,
                                                       NULL, /* gnutls session */
-                                                      NULL, /* gnutls callback */
+                                                      NULL, /* gnutls verify callback */
+                                                      NULL, /* gnutls setcert callback */
                                                       0,    /* gnutls DH key size */
                                                       local_hostname,
                                                       &weechat_tcl_api_hook_connect_cb,
diff --git a/src/plugins/weechat-plugin.h b/src/plugins/weechat-plugin.h
index 8f45504..f98dc0c 100644
--- a/src/plugins/weechat-plugin.h
+++ b/src/plugins/weechat-plugin.h
@@ -462,8 +462,8 @@ struct t_weechat_plugin
                                     int port,
                                     int sock,
                                     int ipv6,
-                                    void *gnutls_sess, void *gnutls_cb,
-                                    int gnutls_dhkey_size,
+                                    void *gnutls_sess, void *gnutls_verify_cb,
+                                    void *gnutls_setcert_cb, int gnutls_dhkey_size,
                                     const char *local_hostname,
                                     int (*callback)(void *data,
                                                     int status,
@@ -1146,13 +1146,14 @@ extern int weechat_plugin_end (struct t_weechat_plugin *plugin);
     weechat_plugin->hook_process(weechat_plugin, __command, __timeout,  \
                                  __callback, __callback_data)
 #define weechat_hook_connect(__proxy, __address, __port, __sock,        \
-                            __ipv6, __gnutls_sess, __gnutls_cb,         \
-                             __gnutls_dhkey_size, __local_hostname,     \
-                             __callback, __data)                        \
+                            __ipv6, __gnutls_sess, __gnutls_verify_cb,  \
+                             __gnutls_setcert_cb, __gnutls_dhkey_size,  \
+                             __local_hostname, __callback, __data)      \
     weechat_plugin->hook_connect(weechat_plugin, __proxy, __address,    \
                                  __port, __sock, __ipv6, __gnutls_sess, \
-                                 __gnutls_cb, __gnutls_dhkey_size,      \
-                                 __local_hostname, __callback, __data)
+                               __gnutls_verify_cb, __gnutls_setcert_cb, \
+                                 __gnutls_dhkey_size, __local_hostname, \
+                                 __callback, __data)
 #define weechat_hook_print(__buffer, __tags, __msg, __strip__colors,    \
                            __callback, __data)                          \
     weechat_plugin->hook_print(weechat_plugin, __buffer, __tags,        \
-- 
1.7.4

