resetting manifest requested domain to floor
[platform/upstream/openconnect.git] / dtls.c
diff --git a/dtls.c b/dtls.c
index e29eaae..9c21a3f 100644 (file)
--- a/dtls.c
+++ b/dtls.c
 #include <netdb.h>
 #include <unistd.h>
 #include <netinet/in.h>
-#include <openssl/err.h>
-#include <openssl/ssl.h>
 #include <fcntl.h>
 #include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
 
 #include "openconnect-internal.h"
 
-#ifdef HAVE_DTLS1_STOP_TIMER
-/* OpenSSL doesn't deliberately export this, but we need it to
-   workaround a DTLS bug in versions < 1.0.0e */
-extern void dtls1_stop_timer (SSL *);
-#endif
-
 static unsigned char nybble(unsigned char n)
 {
        if      (n >= '0' && n <= '9') return n - '0';
@@ -110,9 +105,16 @@ int RAND_bytes(char *buf, int len)
  * their clients use anyway.
  */
 
-#if defined (OPENCONNECT_OPENSSL)
+#if defined (DTLS_OPENSSL)
 #define DTLS_SEND SSL_write
 #define DTLS_RECV SSL_read
+
+#ifdef HAVE_DTLS1_STOP_TIMER
+/* OpenSSL doesn't deliberately export this, but we need it to
+   workaround a DTLS bug in versions < 1.0.0e */
+extern void dtls1_stop_timer (SSL *);
+#endif
+
 static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
 {
        STACK_OF(SSL_CIPHER) *ciphers;
@@ -127,6 +129,7 @@ static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
                if (!vpninfo->dtls_ctx) {
                        vpn_progress(vpninfo, PRG_ERR,
                                     _("Initialise DTLSv1 CTX failed\n"));
+                       openconnect_report_ssl_errors(vpninfo);
                        vpninfo->dtls_attempt_period = 0;
                        return -EINVAL;
                }
@@ -215,7 +218,7 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
        int ret = SSL_do_handshake(vpninfo->new_dtls_ssl);
 
        if (ret == 1) {
-               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection\n"));
+               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using OpenSSL)\n"));
 
                if (vpninfo->dtls_ssl) {
                        /* We are replacing an old connection */
@@ -328,7 +331,21 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
        return -EINVAL;
 }
 
-#elif defined (OPENCONNECT_GNUTLS)
+#elif defined (DTLS_GNUTLS)
+#include <gnutls/dtls.h>
+
+struct {
+       const char *name;
+       gnutls_cipher_algorithm_t cipher;
+       gnutls_mac_algorithm_t mac;
+       const char *prio;
+} gnutls_dtls_ciphers[] = {
+       { "AES128-SHA", GNUTLS_CIPHER_AES_128_CBC, GNUTLS_MAC_SHA1,
+         "NONE:+VERS-DTLS0.9:+COMP-NULL:+AES-128-CBC:+SHA1:+RSA:%COMPAT:%DISABLE_SAFE_RENEGOTIATION" },
+       { "DES-CBC3-SHA", GNUTLS_CIPHER_3DES_CBC, GNUTLS_MAC_SHA1,
+         "NONE:+VERS-DTLS0.9:+COMP-NULL:+3DES-CBC:+SHA1:+RSA:%COMPAT:%DISABLE_SAFE_RENEGOTIATION" },
+};
+
 #define DTLS_SEND gnutls_record_send
 #define DTLS_RECV gnutls_record_recv
 static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
@@ -336,10 +353,22 @@ static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
        gnutls_session_t dtls_ssl;
        gnutls_datum_t master_secret, session_id;
        int err;
+       int cipher;
+
+       for (cipher = 0; cipher < sizeof(gnutls_dtls_ciphers)/sizeof(gnutls_dtls_ciphers[0]); cipher++) {
+               if (!strcmp(vpninfo->dtls_cipher, gnutls_dtls_ciphers[cipher].name))
+                       goto found_cipher;
+       }
+       vpn_progress(vpninfo, PRG_ERR, _("Unknown DTLS parameters for requested CipherSuite '%s'\n"),
+                    vpninfo->dtls_cipher);
+       vpninfo->dtls_attempt_period = 0;
 
+       return -EINVAL;
+
+ found_cipher:
        gnutls_init(&dtls_ssl, GNUTLS_CLIENT|GNUTLS_DATAGRAM|GNUTLS_NONBLOCK);
        err = gnutls_priority_set_direct(dtls_ssl,
-                                        "NONE:+VERS-DTLS0.9:+COMP-NULL:+AES-128-CBC:+SHA1:+RSA:%COMPAT:%DISABLE_SAFE_RENEGOTIATION",
+                                        gnutls_dtls_ciphers[cipher].prio,
                                         NULL);
        if (err) {
                vpn_progress(vpninfo, PRG_ERR,
@@ -356,10 +385,10 @@ static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
        master_secret.size = sizeof(vpninfo->dtls_secret);
        session_id.data = vpninfo->dtls_session_id;
        session_id.size = sizeof(vpninfo->dtls_session_id);
-       err = gnutls_session_set_master(dtls_ssl, GNUTLS_CLIENT, GNUTLS_DTLS0_9,
-                                       GNUTLS_KX_RSA, GNUTLS_CIPHER_AES_128_CBC,
-                                       GNUTLS_MAC_SHA1, GNUTLS_COMP_NULL,
-                                       &master_secret, &session_id);
+       err = gnutls_session_set_premaster(dtls_ssl, GNUTLS_CLIENT, GNUTLS_DTLS0_9,
+                                          GNUTLS_KX_RSA, gnutls_dtls_ciphers[cipher].cipher,
+                                          gnutls_dtls_ciphers[cipher].mac, GNUTLS_COMP_NULL,
+                                          &master_secret, &session_id);
        if (err) {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Failed to set DTLS session parameters: %s\n"),
@@ -378,7 +407,30 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
        int err = gnutls_handshake(vpninfo->new_dtls_ssl);
 
        if (!err) {
-               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection\n"));
+#ifdef HAVE_GNUTLS_DTLS_SET_DATA_MTU
+               /* Make sure GnuTLS's idea of the MTU is sufficient to take
+                  a full VPN MTU (with 1-byte header) in a data record. */
+               err = gnutls_dtls_set_data_mtu(vpninfo->new_dtls_ssl, vpninfo->actual_mtu + 1);
+               if (err) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to set DTLS MTU: %s\n"),
+                                    gnutls_strerror(err));
+                       goto error;
+               }
+#else
+               /* If we don't have gnutls_dtls_set_data_mtu() then make sure
+                  we leave enough headroom by adding the worst-case overhead.
+                  We only support AES128-CBC and DES-CBC3-SHA anyway, so
+                  working out the worst case isn't hard. */
+               gnutls_dtls_set_mtu(vpninfo->new_dtls_ssl,
+                                   vpninfo->actual_mtu + 1 /* packet + header */
+                                   + 13 /* DTLS header */
+                                   + 20 /* biggest supported MAC (SHA1) */
+                                   + 16 /* biggest supported IV (AES-128) */
+                                   + 16 /* max padding */);
+#endif
+
+               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using GnuTLS)\n"));
 
                if (vpninfo->dtls_ssl) {
                        /* We are replacing an old connection */
@@ -409,6 +461,8 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
        vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %s\n"),
                     gnutls_strerror(err));
 
+       goto error;
+ error:
        /* Kill the new (failed) connection... */
        gnutls_deinit(vpninfo->new_dtls_ssl);
        FD_CLR(vpninfo->new_dtls_fd, &vpninfo->select_rfds);
@@ -465,6 +519,42 @@ int connect_dtls_socket(struct openconnect_info *vpninfo)
                return -EINVAL;
        }
 
+       if (vpninfo->dtls_local_port) {
+               union {
+                       struct sockaddr_in in;
+                       struct sockaddr_in6 in6;
+               } dtls_bind_addr;
+               int dtls_bind_addrlen;
+               memset(&dtls_bind_addr, 0, sizeof(dtls_bind_addr));
+
+               if (vpninfo->peer_addr->sa_family == AF_INET) {
+                       struct sockaddr_in *addr = &dtls_bind_addr.in;
+                       dtls_bind_addrlen = sizeof(*addr);
+                       addr->sin_family = AF_INET;
+                       addr->sin_addr.s_addr = INADDR_ANY;
+                       addr->sin_port = htons(vpninfo->dtls_local_port);
+               } else if (vpninfo->peer_addr->sa_family == AF_INET6) {
+                       struct sockaddr_in6 *addr = &dtls_bind_addr.in6;
+                       dtls_bind_addrlen = sizeof(*addr);
+                       addr->sin6_family = AF_INET6;
+                       addr->sin6_addr = in6addr_any;
+                       addr->sin6_port = htons(vpninfo->dtls_local_port);
+               } else {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Unknown protocol family %d. Cannot do DTLS\n"),
+                                    vpninfo->peer_addr->sa_family);
+                       vpninfo->dtls_attempt_period = 0;
+                       close(dtls_fd);
+                       return -EINVAL;
+               }
+
+               if (bind(dtls_fd, (struct sockaddr *)&dtls_bind_addr, dtls_bind_addrlen)) {
+                       perror(_("Bind UDP socket for DTLS"));
+                       close(dtls_fd);
+                       return -EINVAL;
+               }
+       }
+
        if (connect(dtls_fd, vpninfo->dtls_addr, vpninfo->peer_addrlen)) {
                perror(_("UDP (DTLS) connect:\n"));
                close(dtls_fd);
@@ -495,9 +585,9 @@ int connect_dtls_socket(struct openconnect_info *vpninfo)
 static int dtls_restart(struct openconnect_info *vpninfo)
 {
        if (vpninfo->dtls_ssl) {
-#if defined (OPENCONNECT_OPENSSL)
+#if defined (DTLS_OPENSSL)
                SSL_free(vpninfo->dtls_ssl);
-#elif defined (OPENCONNECT_GNUTLS)
+#elif defined (DTLS_GNUTLS)
                gnutls_deinit(vpninfo->dtls_ssl);
 #endif
                close(vpninfo->dtls_fd);
@@ -517,6 +607,15 @@ int setup_dtls(struct openconnect_info *vpninfo)
        struct vpn_option *dtls_opt = vpninfo->dtls_options;
        int dtls_port = 0;
 
+#if defined (OPENCONNECT_GNUTLS) && defined (DTLS_OPENSSL)
+       /* If we're using GnuTLS for authentication but OpenSSL for DTLS,
+          we'll need to initialise OpenSSL now... */
+       SSL_library_init ();
+       ERR_clear_error ();
+       SSL_load_error_strings ();
+       OpenSSL_add_all_algorithms ();
+#endif
+
        while (dtls_opt) {
                vpn_progress(vpninfo, PRG_TRACE,
                             _("DTLS option %s : %s\n"),
@@ -582,7 +681,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
        char magic_pkt;
 
        while (1) {
-               int len = vpninfo->mtu;
+               int len = vpninfo->actual_mtu;
                unsigned char *buf;
 
                if (!dtls_pkt) {
@@ -719,7 +818,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
                /* One byte of header */
                this->hdr[7] = AC_PKT_DATA;
 
-#if defined(OPENCONNECT_OPENSSL)
+#if defined(DTLS_OPENSSL)
                ret = SSL_write(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
                if (ret <= 0) {
                        ret = SSL_get_error(vpninfo->dtls_ssl, ret);
@@ -737,7 +836,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
                        }
                        return 1;
                }
-#elif defined (OPENCONNECT_GNUTLS)
+#elif defined (DTLS_GNUTLS)
                ret = gnutls_record_send(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
                if (ret <= 0) {
                        if (ret != GNUTLS_E_AGAIN) {