Close ssl_sock before returning error in connect_https_socket()
[platform/upstream/openconnect.git] / cstp.c
diff --git a/cstp.c b/cstp.c
index 20009d2..dacb2ae 100644 (file)
--- a/cstp.c
+++ b/cstp.c
@@ -1,7 +1,7 @@
 /*
  * OpenConnect (SSL + DTLS) VPN client
  *
- * Copyright © 2008-2010 Intel Corporation.
+ * Copyright © 2008-2012 Intel Corporation.
  * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
  *
  * Author: David Woodhouse <dwmw2@infradead.org>
  *   51 Franklin Street, Fifth Floor,
  *   Boston, MA 02110-1301 USA
  */
+
 #include <netdb.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <time.h>
 #include <string.h>
 #include <ctype.h>
-#include <arpa/inet.h>
-
-#include <openssl/ssl.h>
-#include <openssl/err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdarg.h>
 
-#include "openconnect.h"
+#include "openconnect-internal.h"
 
 /*
  * Data packets are encapsulated in the SSL stream as follows:
  *
  * 0000: Magic "STF\x1"
  * 0004: Big-endian 16-bit length (not including 8-byte header)
- * 0006: Byte packet type (see openconnect.h)
+ * 0006: Byte packet type (see openconnect-internal.h)
  * 0008: data payload
  */
 
@@ -63,12 +68,89 @@ static struct pkt dpd_resp_pkt = {
        .hdr = { 'S', 'T', 'F', 1, 0, 0, AC_PKT_DPD_RESP, 0 },
 };
 
+static int  __attribute__ ((format (printf, 3, 4)))
+    buf_append(char *buf, int len, const char *fmt, ...)
+{
+       int start = strlen(buf);
+       int ret;
+       va_list args;
+
+       if (start >= len)
+               return 0;
+
+       va_start(args, fmt);
+       ret = vsnprintf(buf + start, len - start, fmt, args);
+       va_end(args);
+
+       if (ret > len)
+               ret = len;
+
+       return ret;
+}
+
+/* Calculate MTU to request. Old servers simply use the X-CSTP-MTU: header,
+ * which represents the tunnel MTU, while new servers do calculations on the
+ * X-CSTP-Base-MTU: header which represents the cleartext MTU between client
+ * and server.
+ *
+ * If possible, the legacy MTU value should be the TCP MSS less 5 bytes of
+ * TLS and 8 bytes of CSTP overhead. We can get the MSS from either the
+ * TCP_INFO or TCP_MAXSEG sockopts.
+ *
+ * The base MTU comes from the TCP_INFO sockopt under Linux, but I don't know
+ * how to work it out on other systems. So leave it blank and do things the
+ * legacy way there. Contributions welcome...
+ *
+ * If we don't even have TCP_MAXSEG, then default to sending a legacy MTU of
+ * 1406 which is what we always used to do.
+ */
+static void calculate_mtu(struct openconnect_info *vpninfo, int *base_mtu, int *mtu)
+{
+       *mtu = vpninfo->reqmtu;
+       *base_mtu = vpninfo->basemtu;
+
+#if defined(__linux__) && defined(TCP_INFO)
+       if (!*mtu || !*base_mtu) {
+               struct tcp_info ti;
+               socklen_t ti_size = sizeof(ti);
+
+               if (!getsockopt(vpninfo->ssl_fd, IPPROTO_TCP, TCP_INFO,
+                               &ti, &ti_size)) {
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("TCP_INFO rcv mss %d, snd mss %d, adv mss %d, pmtu %d\n"),
+                                    ti.tcpi_rcv_mss, ti.tcpi_snd_mss, ti.tcpi_advmss, ti.tcpi_pmtu);
+                       if (!*base_mtu) *base_mtu = ti.tcpi_pmtu;
+                       if (!*mtu) {
+                               if (ti.tcpi_rcv_mss < ti.tcpi_snd_mss)
+                                       *mtu = ti.tcpi_rcv_mss - 13;
+                               else
+                                       *mtu = ti.tcpi_snd_mss - 13;
+                       }
+               }
+       }
+#endif
+#ifdef TCP_MAXSEG
+       if (!*mtu) {
+               int mss;
+               socklen_t mss_size = sizeof(mss);
+               if (!getsockopt(vpninfo->ssl_fd, IPPROTO_TCP, TCP_MAXSEG,
+                               &mss, &mss_size)) {
+                       vpn_progress(vpninfo, PRG_TRACE, _("TCP_MAXSEG %d\n"), mss);
+                       *mtu = mss - 13;
+               }
+       }
+#endif
+       if (!*mtu) {
+               /* Default */
+               *mtu = 1406;
+       }
+}
 
 static int start_cstp_connection(struct openconnect_info *vpninfo)
 {
        char buf[65536];
        int i;
-       int retried = 0;
+       int retried = 0, sessid_found = 0;
        struct vpn_option **next_dtls_option = &vpninfo->dtls_options;
        struct vpn_option **next_cstp_option = &vpninfo->cstp_options;
        struct vpn_option *old_cstp_opts = vpninfo->cstp_options;
@@ -78,6 +160,7 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
        const char *old_addr6 = vpninfo->vpn_addr6;
        const char *old_netmask6 = vpninfo->vpn_netmask6;
        struct split_include *inc;
+       int base_mtu, mtu;
 
        /* Clear old options which will be overwritten */
        vpninfo->vpn_addr = vpninfo->vpn_netmask = NULL;
@@ -99,35 +182,59 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
                free(inc);
                inc = next;
        }
-       vpninfo->split_includes = vpninfo->split_excludes = NULL;
+       for (inc = vpninfo->split_dns; inc; ) {
+               struct split_include *next = inc->next;
+               free(inc);
+               inc = next;
+       }
+       vpninfo->split_dns = vpninfo->split_includes = vpninfo->split_excludes = NULL;
+
+       /* Create (new) random master key for DTLS connection, if needed */
+       if (vpninfo->dtls_times.last_rekey + vpninfo->dtls_times.rekey <
+           time(NULL) + 300 &&
+           openconnect_random(vpninfo->dtls_secret, sizeof(vpninfo->dtls_secret))) {
+               fprintf(stderr, _("Failed to initialise DTLS secret\n"));
+               exit(1);
+       }
+
  retry:
-       openconnect_SSL_printf(vpninfo->https_ssl, "CONNECT /CSCOSSLC/tunnel HTTP/1.1\r\n");
-       openconnect_SSL_printf(vpninfo->https_ssl, "Host: %s\r\n", vpninfo->hostname);
-       openconnect_SSL_printf(vpninfo->https_ssl, "User-Agent: %s\r\n", vpninfo->useragent);
-       openconnect_SSL_printf(vpninfo->https_ssl, "Cookie: webvpn=%s\r\n", vpninfo->cookie);
-       openconnect_SSL_printf(vpninfo->https_ssl, "X-CSTP-Version: 1\r\n");
-       openconnect_SSL_printf(vpninfo->https_ssl, "X-CSTP-Hostname: %s\r\n", vpninfo->localname);
-       if (vpninfo->deflate)
-               openconnect_SSL_printf(vpninfo->https_ssl, "X-CSTP-Accept-Encoding: deflate;q=1.0\r\n");
-       openconnect_SSL_printf(vpninfo->https_ssl, "X-CSTP-MTU: %d\r\n", vpninfo->mtu);
-       openconnect_SSL_printf(vpninfo->https_ssl, "X-CSTP-Address-Type: %s\r\n",
+       calculate_mtu(vpninfo, &base_mtu, &mtu);
+
+       buf[0] = 0;
+       buf_append(buf, sizeof(buf), "CONNECT /CSCOSSLC/tunnel HTTP/1.1\r\n");
+       buf_append(buf, sizeof(buf), "Host: %s\r\n", vpninfo->hostname);
+       buf_append(buf, sizeof(buf), "User-Agent: %s\r\n", vpninfo->useragent);
+       buf_append(buf, sizeof(buf), "Cookie: webvpn=%s\r\n", vpninfo->cookie);
+       buf_append(buf, sizeof(buf), "X-CSTP-Version: 1\r\n");
+       buf_append(buf, sizeof(buf), "X-CSTP-Hostname: %s\r\n", vpninfo->localname);
+       if (vpninfo->deflate && i < sizeof(buf))
+               buf_append(buf, sizeof(buf), "X-CSTP-Accept-Encoding: deflate;q=1.0\r\n");
+       if (base_mtu)
+               buf_append(buf, sizeof(buf), "X-CSTP-Base-MTU: %d\r\n", base_mtu);
+       buf_append(buf, sizeof(buf), "X-CSTP-MTU: %d\r\n", mtu);
+       buf_append(buf, sizeof(buf), "X-CSTP-Address-Type: %s\r\n",
                               vpninfo->disable_ipv6?"IPv4":"IPv6,IPv4");
-       openconnect_SSL_printf(vpninfo->https_ssl, "X-DTLS-Master-Secret: ");
+       buf_append(buf, sizeof(buf), "X-DTLS-Master-Secret: ");
        for (i = 0; i < sizeof(vpninfo->dtls_secret); i++)
-               openconnect_SSL_printf(vpninfo->https_ssl, "%02X", vpninfo->dtls_secret[i]);
-       openconnect_SSL_printf(vpninfo->https_ssl, "\r\nX-DTLS-CipherSuite: %s\r\n\r\n",
+               buf_append(buf, sizeof(buf), "%02X", vpninfo->dtls_secret[i]);
+       buf_append(buf, sizeof(buf), "\r\nX-DTLS-CipherSuite: %s\r\n\r\n",
                               vpninfo->dtls_ciphers?:"AES256-SHA:AES128-SHA:DES-CBC3-SHA:DES-CBC-SHA");
 
-       if (openconnect_SSL_gets(vpninfo->https_ssl, buf, 65536) < 0) {
-               vpninfo->progress(vpninfo, PRG_ERR, "Error fetching HTTPS response\n");
+       openconnect_SSL_write(vpninfo, buf, strlen(buf));
+
+       if ((i = openconnect_SSL_gets(vpninfo, buf, 65536) < 0)) {
+               if (i == -EINTR)
+                       return i;
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error fetching HTTPS response\n"));
                if (!retried) {
                        retried = 1;
-                       openconnect_close_https(vpninfo);
+                       openconnect_close_https(vpninfo, 0);
 
                        if (openconnect_open_https(vpninfo)) {
-                               vpninfo->progress(vpninfo, PRG_ERR,
-                                                 "Failed to open HTTPS connection to %s\n",
-                                                 vpninfo->hostname);
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Failed to open HTTPS connection to %s\n"),
+                                            vpninfo->hostname);
                                exit(1);
                        }
                        goto retry;
@@ -138,34 +245,40 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
        if (strncmp(buf, "HTTP/1.1 200 ", 13)) {
                if (!strncmp(buf, "HTTP/1.1 503 ", 13)) {
                        /* "Service Unavailable. Why? */
-                       char *reason = "<unknown>";
-                       while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
+                       const char *reason = "<unknown>";
+                       while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
                                if (!strncmp(buf, "X-Reason: ", 10)) {
                                        reason = buf + 10;
                                        break;
                                }
                        }
-                       vpninfo->progress(vpninfo, PRG_ERR, "VPN service unavailable; reason: %s\n",
-                                         reason);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("VPN service unavailable; reason: %s\n"),
+                                    reason);
                        return -EINVAL;
                }
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Got inappropriate HTTP CONNECT response: %s\n",
-                                 buf);
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Got inappropriate HTTP CONNECT response: %s\n"),
+                            buf);
                if (!strncmp(buf, "HTTP/1.1 401 ", 13))
                        exit(2);
                return -EINVAL;
        }
 
-       vpninfo->progress(vpninfo, PRG_INFO,
-                         "Got CONNECT response: %s\n", buf);
+       vpn_progress(vpninfo, PRG_INFO, _("Got CONNECT response: %s\n"), buf);
 
        /* We may have advertised it, but we only do it if the server agrees */
        vpninfo->deflate = 0;
+       mtu = 0;
 
-       while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
+       while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
                struct vpn_option *new_option;
-               char *colon = strchr(buf, ':');
+               char *colon;
+
+               if (i < 0)
+                       return i;
+
+               colon = strchr(buf, ':');
                if (!colon)
                        continue;
 
@@ -180,7 +293,7 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
 
                new_option = malloc(sizeof(*new_option));
                if (!new_option) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "No memory for options\n");
+                       vpn_progress(vpninfo, PRG_ERR, _("No memory for options\n"));
                        return -ENOMEM;
                }
                new_option->option = strdup(buf);
@@ -188,15 +301,36 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
                new_option->next = NULL;
 
                if (!new_option->option || !new_option->value) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "No memory for options\n");
+                       vpn_progress(vpninfo, PRG_ERR, _("No memory for options\n"));
+                       free(new_option->option);
+                       free(new_option->value);
+                       free(new_option);
                        return -ENOMEM;
                }
 
-               vpninfo->progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
+               vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
 
                if (!strncmp(buf, "X-DTLS-", 7)) {
                        *next_dtls_option = new_option;
                        next_dtls_option = &new_option->next;
+
+                       if (!strcmp(buf + 7, "MTU")) {
+                               int dtlsmtu = atol(colon);
+                               if (dtlsmtu > mtu)
+                                       mtu = dtlsmtu;
+                       } else if (!strcmp(buf + 7, "Session-ID")) {
+                               if (strlen(colon) != 64) {
+                                       vpn_progress(vpninfo, PRG_ERR,
+                                                    _("X-DTLS-Session-ID not 64 characters; is: \"%s\"\n"),
+                                                    colon);
+                                       vpninfo->dtls_attempt_period = 0;
+                                       return -EINVAL;
+                               }
+                               for (i = 0; i < 64; i += 2)
+                                       vpninfo->dtls_session_id[i/2] = unhex(colon + i);
+                               sessid_found = 1;
+                               time(&vpninfo->dtls_times.last_rekey);
+                       }
                        continue;
                }
                /* CSTP options... */
@@ -210,26 +344,32 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
                        int j = atol(colon);
                        if (j && (!vpninfo->ssl_times.dpd || j < vpninfo->ssl_times.dpd))
                                vpninfo->ssl_times.dpd = j;
+               } else if (!strcmp(buf + 7, "Rekey-Time")) {
+                       vpninfo->ssl_times.rekey = atol(colon);
                } else if (!strcmp(buf + 7, "Content-Encoding")) {
                        if (!strcmp(colon, "deflate"))
                                vpninfo->deflate = 1;
                        else {
-                               vpninfo->progress(vpninfo, PRG_ERR,
-                                       "Unknown CSTP-Content-Encoding %s\n",
-                                       colon);
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Unknown CSTP-Content-Encoding %s\n"),
+                                            colon);
                                return -EINVAL;
                        }
                } else if (!strcmp(buf + 7, "MTU")) {
-                       vpninfo->mtu = atol(colon);
+                       int cstpmtu = atol(colon);
+                       if (cstpmtu > mtu)
+                               mtu = cstpmtu;
                } else if (!strcmp(buf + 7, "Address")) {
-                       if (strchr(new_option->value, ':'))
-                               vpninfo->vpn_addr6 = new_option->value;
-                       else
+                       if (strchr(new_option->value, ':')) {
+                               if (!vpninfo->disable_ipv6)
+                                       vpninfo->vpn_addr6 = new_option->value;
+                       } else
                                vpninfo->vpn_addr = new_option->value;
                } else if (!strcmp(buf + 7, "Netmask")) {
-                       if (strchr(new_option->value, ':'))
-                               vpninfo->vpn_netmask6 = new_option->value;
-                       else
+                       if (strchr(new_option->value, ':')) {
+                               if (!vpninfo->disable_ipv6)
+                                       vpninfo->vpn_netmask6 = new_option->value;
+                       } else
                                vpninfo->vpn_netmask = new_option->value;
                } else if (!strcmp(buf + 7, "DNS")) {
                        int j;
@@ -253,6 +393,13 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
                        vpninfo->vpn_proxy_pac = new_option->value;
                } else if (!strcmp(buf + 7, "Banner")) {
                        vpninfo->banner = new_option->value;
+               } else if (!strcmp(buf + 7, "Split-DNS")) {
+                       struct split_include *dns = malloc(sizeof(*dns));
+                       if (!dns)
+                               continue;
+                       dns->route = new_option->value;
+                       dns->next = vpninfo->split_dns;
+                       vpninfo->split_dns = dns;
                } else if (!strcmp(buf + 7, "Split-Include")) {
                        struct split_include *inc = malloc(sizeof(*inc));
                        if (!inc)
@@ -270,35 +417,47 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
                }
        }
 
+       if (!mtu) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("No MTU received. Aborting\n"));
+               return -EINVAL;
+       }
+       vpninfo->actual_mtu = mtu;
+
        if (!vpninfo->vpn_addr && !vpninfo->vpn_addr6) {
-               vpninfo->progress(vpninfo, PRG_ERR, "No IP address received. Aborting\n");
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("No IP address received. Aborting\n"));
                return -EINVAL;
        }
        if (old_addr) {
                if (strcmp(old_addr, vpninfo->vpn_addr)) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "Reconnect gave different Legacy IP address (%s != %s)\n",
-                               vpninfo->vpn_addr, old_addr);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Reconnect gave different Legacy IP address (%s != %s)\n"),
+                                    vpninfo->vpn_addr, old_addr);
                        return -EINVAL;
                }
        }
        if (old_netmask) {
                if (strcmp(old_netmask, vpninfo->vpn_netmask)) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "Reconnect gave different Legacy IP netmask (%s != %s)\n",
-                               vpninfo->vpn_netmask, old_netmask);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Reconnect gave different Legacy IP netmask (%s != %s)\n"),
+                                    vpninfo->vpn_netmask, old_netmask);
                        return -EINVAL;
                }
        }
        if (old_addr6) {
                if (strcmp(old_addr6, vpninfo->vpn_addr6)) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "Reconnect gave different IPv6 address (%s != %s)\n",
-                               vpninfo->vpn_addr6, old_addr6);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Reconnect gave different IPv6 address (%s != %s)\n"),
+                                    vpninfo->vpn_addr6, old_addr6);
                        return -EINVAL;
                }
        }
        if (old_netmask6) {
                if (strcmp(old_netmask6, vpninfo->vpn_netmask6)) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "Reconnect gave different IPv6 netmask (%s != %s)\n",
-                               vpninfo->vpn_netmask6, old_netmask6);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Reconnect gave different IPv6 netmask (%s != %s)\n"),
+                                    vpninfo->vpn_netmask6, old_netmask6);
                        return -EINVAL;
                }
        }
@@ -317,20 +476,20 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
                free(tmp->option);
                free(tmp);
        }
-       vpninfo->progress(vpninfo, PRG_INFO, "CSTP connected. DPD %d, Keepalive %d\n",
-                         vpninfo->ssl_times.dpd, vpninfo->ssl_times.keepalive);
+       vpn_progress(vpninfo, PRG_INFO, _("CSTP connected. DPD %d, Keepalive %d\n"),
+                    vpninfo->ssl_times.dpd, vpninfo->ssl_times.keepalive);
 
-       BIO_set_nbio(SSL_get_rbio(vpninfo->https_ssl), 1);
-       BIO_set_nbio(SSL_get_wbio(vpninfo->https_ssl), 1);
-
-       fcntl(vpninfo->ssl_fd, F_SETFL, fcntl(vpninfo->ssl_fd, F_GETFL) | O_NONBLOCK);
        if (vpninfo->select_nfds <= vpninfo->ssl_fd)
                vpninfo->select_nfds = vpninfo->ssl_fd + 1;
 
        FD_SET(vpninfo->ssl_fd, &vpninfo->select_rfds);
        FD_SET(vpninfo->ssl_fd, &vpninfo->select_efds);
 
-       vpninfo->ssl_times.last_rx = vpninfo->ssl_times.last_tx = time(NULL);
+       if (!sessid_found)
+               vpninfo->dtls_attempt_period = 0;
+
+       vpninfo->ssl_times.last_rekey = vpninfo->ssl_times.last_rx =
+               vpninfo->ssl_times.last_tx = time(NULL);
        return 0;
 }
 
@@ -339,7 +498,8 @@ int make_cstp_connection(struct openconnect_info *vpninfo)
 {
        int ret;
 
-       if (!vpninfo->https_ssl && (ret = openconnect_open_https(vpninfo)))
+       ret = openconnect_open_https(vpninfo);
+       if (ret)
                return ret;
 
        if (vpninfo->deflate) {
@@ -349,40 +509,56 @@ int make_cstp_connection(struct openconnect_info *vpninfo)
                if (inflateInit2(&vpninfo->inflate_strm, -12) ||
                    deflateInit2(&vpninfo->deflate_strm, Z_DEFAULT_COMPRESSION,
                                 Z_DEFLATED, -12, 9, Z_DEFAULT_STRATEGY)) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "Compression setup failed\n");
+                       vpn_progress(vpninfo, PRG_ERR, _("Compression setup failed\n"));
                        vpninfo->deflate = 0;
                }
 
                if (!vpninfo->deflate_pkt) {
                        vpninfo->deflate_pkt = malloc(sizeof(struct pkt) + 2048);
                        if (!vpninfo->deflate_pkt) {
-                               vpninfo->progress(vpninfo, PRG_ERR, "Allocation of deflate buffer failed\n");
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Allocation of deflate buffer failed\n"));
+                               inflateEnd(&vpninfo->inflate_strm);
+                               deflateEnd(&vpninfo->deflate_strm);
                                vpninfo->deflate = 0;
+                       } else {
+                               memset(vpninfo->deflate_pkt, 0, sizeof(struct pkt));
+                               memcpy(vpninfo->deflate_pkt->hdr, data_hdr, 8);
+                               vpninfo->deflate_pkt->hdr[6] = AC_PKT_COMPRESSED;
                        }
-                       memset(vpninfo->deflate_pkt, 0, sizeof(struct pkt));
-                       memcpy(vpninfo->deflate_pkt->hdr, data_hdr, 8);
-                       vpninfo->deflate_pkt->hdr[6] = AC_PKT_COMPRESSED;
                }
        }
 
        return start_cstp_connection(vpninfo);
 }
 
-static int cstp_reconnect(struct openconnect_info *vpninfo)
+int cstp_reconnect(struct openconnect_info *vpninfo)
 {
        int ret;
        int timeout;
        int interval;
 
+       openconnect_close_https(vpninfo, 0);
+
+       if (vpninfo->deflate) {
+               /* Requeue the original packet that was deflated */
+               if (vpninfo->current_ssl_pkt == vpninfo->deflate_pkt) {
+                       vpninfo->current_ssl_pkt = NULL;
+                       queue_packet(&vpninfo->outgoing_queue, vpninfo->pending_deflated_pkt);
+                       vpninfo->pending_deflated_pkt = NULL;
+               }
+               inflateEnd(&vpninfo->inflate_strm);
+               deflateEnd(&vpninfo->deflate_strm);
+       }
        timeout = vpninfo->reconnect_timeout;
        interval = vpninfo->reconnect_interval;
 
        while ((ret = make_cstp_connection(vpninfo))) {
                if (timeout <= 0)
                        return ret;
-               vpninfo->progress(vpninfo, PRG_INFO,
-                                 "sleep %ds, remaining timeout %ds\n",
-                                 interval, timeout);
+               vpn_progress(vpninfo, PRG_INFO,
+                            _("sleep %ds, remaining timeout %ds\n"),
+                            interval, timeout);
                sleep(interval);
                if (killed)
                        return 1;
@@ -391,12 +567,15 @@ static int cstp_reconnect(struct openconnect_info *vpninfo)
                if (interval > RECONNECT_INTERVAL_MAX)
                        interval = RECONNECT_INTERVAL_MAX;
        }
+       script_config_tun(vpninfo, "reconnect");
        return 0;
 }
 
-static int inflate_and_queue_packet(struct openconnect_info *vpninfo, void *buf, int len)
+static int inflate_and_queue_packet(struct openconnect_info *vpninfo,
+                                   unsigned char *buf, int len)
 {
-       struct pkt *new = malloc(sizeof(struct pkt) + vpninfo->mtu);
+       struct pkt *new = malloc(sizeof(struct pkt) + vpninfo->actual_mtu);
+       uint32_t pkt_sum;
 
        if (!new)
                return -ENOMEM;
@@ -407,11 +586,11 @@ static int inflate_and_queue_packet(struct openconnect_info *vpninfo, void *buf,
        vpninfo->inflate_strm.avail_in = len - 4;
 
        vpninfo->inflate_strm.next_out = new->data;
-       vpninfo->inflate_strm.avail_out = vpninfo->mtu;
+       vpninfo->inflate_strm.avail_out = vpninfo->actual_mtu;
        vpninfo->inflate_strm.total_out = 0;
 
        if (inflate(&vpninfo->inflate_strm, Z_SYNC_FLUSH)) {
-               vpninfo->progress(vpninfo, PRG_ERR, "inflate failed\n");
+               vpn_progress(vpninfo, PRG_ERR, _("inflate failed\n"));
                free(new);
                return -EINVAL;
        }
@@ -421,18 +600,103 @@ static int inflate_and_queue_packet(struct openconnect_info *vpninfo, void *buf,
        vpninfo->inflate_adler32 = adler32(vpninfo->inflate_adler32,
                                           new->data, new->len);
 
-       if (vpninfo->inflate_adler32 != ntohl( *(uint32_t *) (buf + len - 4) )) {
+       pkt_sum = buf[len - 1] | (buf[len - 2] << 8) |
+               (buf[len - 3] << 16) | (buf[len - 4] << 24);
+
+       if (vpninfo->inflate_adler32 != pkt_sum) {
                vpninfo->quit_reason = "Compression (inflate) adler32 failure";
        }
 
-       vpninfo->progress(vpninfo, PRG_TRACE,
-                         "Received compressed data packet of %ld bytes\n",
-                         vpninfo->inflate_strm.total_out);
+       vpn_progress(vpninfo, PRG_TRACE,
+                    _("Received compressed data packet of %ld bytes\n"),
+                    (long)vpninfo->inflate_strm.total_out);
 
        queue_packet(&vpninfo->incoming_queue, new);
        return 0;
 }
 
+#if defined (OPENCONNECT_OPENSSL)
+static int cstp_read(struct openconnect_info *vpninfo, void *buf, int maxlen)
+{
+       int len, ret;
+
+       len = SSL_read(vpninfo->https_ssl, buf, maxlen);
+       if (len > 0)
+               return len;
+
+       ret = SSL_get_error(vpninfo->https_ssl, len);
+       if (ret == SSL_ERROR_SYSCALL || ret == SSL_ERROR_ZERO_RETURN) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("SSL read error %d (server probably closed connection); reconnecting.\n"),
+                            ret);
+               return -EIO;
+       }
+       return 0;
+}
+
+static int cstp_write(struct openconnect_info *vpninfo, void *buf, int buflen)
+{
+       int ret;
+
+       ret = SSL_write(vpninfo->https_ssl, buf, buflen);
+       if (ret > 0)
+               return ret;
+
+       ret = SSL_get_error(vpninfo->https_ssl, ret);
+       switch (ret) {
+       case SSL_ERROR_WANT_WRITE:
+               /* Waiting for the socket to become writable -- it's
+                  probably stalled, and/or the buffers are full */
+               FD_SET(vpninfo->ssl_fd, &vpninfo->select_wfds);
+       case SSL_ERROR_WANT_READ:
+               return 0;
+
+       default:
+               vpn_progress(vpninfo, PRG_ERR, _("SSL_write failed: %d\n"), ret);
+               openconnect_report_ssl_errors(vpninfo);
+               return -1;
+       }
+}
+#elif defined (OPENCONNECT_GNUTLS)
+static int cstp_read(struct openconnect_info *vpninfo, void *buf, int maxlen)
+{
+       int ret;
+
+       ret = gnutls_record_recv(vpninfo->https_sess, buf, maxlen);
+       if (ret > 0)
+               return ret;
+
+       if (ret != GNUTLS_E_AGAIN) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("SSL read error: %s; reconnecting.\n"),
+                            gnutls_strerror(ret));
+               return -EIO;
+       }
+       return 0;
+}
+
+static int cstp_write(struct openconnect_info *vpninfo, void *buf, int buflen)
+{
+       int ret;
+
+       ret = gnutls_record_send(vpninfo->https_sess, buf, buflen);
+       if (ret > 0)
+               return ret;
+
+       if (ret == GNUTLS_E_AGAIN) {
+               if (gnutls_record_get_direction(vpninfo->https_sess)) {
+                       /* Waiting for the socket to become writable -- it's
+                          probably stalled, and/or the buffers are full */
+                       FD_SET(vpninfo->ssl_fd, &vpninfo->select_wfds);
+               }
+               return 0;
+       }
+       vpn_progress(vpninfo, PRG_ERR, _("SSL send failed: %s\n"),
+                    gnutls_strerror(ret));
+       return -1;
+}
+#endif
+
 int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
 {
        unsigned char buf[16384];
@@ -445,7 +709,7 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
           we should probably remove POLLIN from the events we're looking for,
           and add POLLOUT. As it is, though, it'll just chew CPU time in that
           fairly unlikely situation, until the write backlog clears. */
-       while ( (len = SSL_read(vpninfo->https_ssl, buf, sizeof(buf))) > 0) {
+       while ( (len = cstp_read(vpninfo, buf, sizeof(buf))) > 0) {
                int payload_len;
 
                if (buf[0] != 'S' || buf[1] != 'T' ||
@@ -454,37 +718,37 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
 
                payload_len = (buf[4] << 8) + buf[5];
                if (len != 8 + payload_len) {
-                       vpninfo->progress(vpninfo, PRG_ERR,
-                                         "Unexpected packet length. SSL_read returned %d but packet is\n",
-                                         len);
-                       vpninfo->progress(vpninfo, PRG_ERR,
-                                         "%02x %02x %02x %02x %02x %02x %02x %02x\n",
-                                         buf[0], buf[1], buf[2], buf[3],
-                                         buf[4], buf[5], buf[6], buf[7]);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Unexpected packet length. SSL_read returned %d but packet is\n"),
+                                    len);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    "%02x %02x %02x %02x %02x %02x %02x %02x\n",
+                                    buf[0], buf[1], buf[2], buf[3],
+                                    buf[4], buf[5], buf[6], buf[7]);
                        continue;
                }
                vpninfo->ssl_times.last_rx = time(NULL);
                switch(buf[6]) {
                case AC_PKT_DPD_OUT:
-                       vpninfo->progress(vpninfo, PRG_TRACE,
-                                         "Got CSTP DPD request\n");
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("Got CSTP DPD request\n"));
                        vpninfo->owe_ssl_dpd_response = 1;
                        continue;
 
                case AC_PKT_DPD_RESP:
-                       vpninfo->progress(vpninfo, PRG_TRACE,
-                                         "Got CSTP DPD response\n");
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("Got CSTP DPD response\n"));
                        continue;
 
                case AC_PKT_KEEPALIVE:
-                       vpninfo->progress(vpninfo, PRG_TRACE,
-                                         "Got CSTP Keepalive\n");
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("Got CSTP Keepalive\n"));
                        continue;
 
                case AC_PKT_DATA:
-                       vpninfo->progress(vpninfo, PRG_TRACE,
-                                         "Received uncompressed data packet of %d bytes\n",
-                                         payload_len);
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("Received uncompressed data packet of %d bytes\n"),
+                                    payload_len);
                        queue_new_packet(&vpninfo->incoming_queue, buf + 8,
                                         payload_len);
                        work_done = 1;
@@ -497,14 +761,16 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
                                        buf[payload_len + 8 + i] = '.';
                        }
                        buf[payload_len + 8] = 0;
-                       vpninfo->progress(vpninfo, PRG_ERR,
-                                         "Received server disconnect: %02x '%s'\n", buf[8], buf + 9);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Received server disconnect: %02x '%s'\n"),
+                                    buf[8], buf + 9);
                        vpninfo->quit_reason = "Server request";
                        return 1;
                }
                case AC_PKT_COMPRESSED:
                        if (!vpninfo->deflate) {
-                               vpninfo->progress(vpninfo, PRG_ERR, "Compressed packet received in !deflate mode\n");
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Compressed packet received in !deflate mode\n"));
                                goto unknown_pkt;
                        }
                        inflate_and_queue_packet(vpninfo, buf + 8, payload_len);
@@ -512,27 +778,21 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
                        continue;
 
                case AC_PKT_TERM_SERVER:
-                       vpninfo->progress(vpninfo, PRG_ERR, "received server terminate packet\n");
+                       vpn_progress(vpninfo, PRG_ERR, _("received server terminate packet\n"));
                        vpninfo->quit_reason = "Server request";
                        return 1;
                }
 
        unknown_pkt:
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Unknown packet %02x %02x %02x %02x %02x %02x %02x %02x\n",
-                                 buf[0], buf[1], buf[2], buf[3],
-                                 buf[4], buf[5], buf[6], buf[7]);
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unknown packet %02x %02x %02x %02x %02x %02x %02x %02x\n"),
+                            buf[0], buf[1], buf[2], buf[3],
+                            buf[4], buf[5], buf[6], buf[7]);
                vpninfo->quit_reason = "Unknown packet received";
                return 1;
        }
-
-       ret = SSL_get_error(vpninfo->https_ssl, len);
-       if (ret == SSL_ERROR_SYSCALL || ret == SSL_ERROR_ZERO_RETURN) {
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "SSL read error %d (server probably closed connection); reconnecting.\n",
-                                 ret);
-                       goto do_reconnect;
-       }
+       if (len < 0)
+               goto do_reconnect;
 
 
        /* If SSL_write() fails we are expected to try again. With exactly
@@ -542,38 +802,43 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
        handle_outgoing:
                vpninfo->ssl_times.last_tx = time(NULL);
                FD_CLR(vpninfo->ssl_fd, &vpninfo->select_wfds);
-               ret = SSL_write(vpninfo->https_ssl,
-                               vpninfo->current_ssl_pkt->hdr,
-                               vpninfo->current_ssl_pkt->len + 8);
-               if (ret <= 0) {
-                       ret = SSL_get_error(vpninfo->https_ssl, ret);
-                       switch (ret) {
-                       case SSL_ERROR_WANT_WRITE:
-                               /* Waiting for the socket to become writable -- it's
-                                  probably stalled, and/or the buffers are full */
-                               FD_SET(vpninfo->ssl_fd, &vpninfo->select_wfds);
-
-                       case SSL_ERROR_WANT_READ:
-                               if (ka_stalled_dpd_time(&vpninfo->ssl_times, timeout))
-                                       goto peer_dead;
+
+               ret = cstp_write(vpninfo,
+                                vpninfo->current_ssl_pkt->hdr,
+                                vpninfo->current_ssl_pkt->len + 8);
+               if (ret < 0)
+                       goto do_reconnect;
+               else if (!ret) {
+                       /* -EAGAIN: cstp_write() will have added the SSL fd to
+                          ->select_wfds if appropriate, so we can just return
+                          and wait. Unless it's been stalled for so long that
+                          DPD kicks in and we kill the connection. */
+                       switch (ka_stalled_action(&vpninfo->ssl_times, timeout)) {
+                       case KA_DPD_DEAD:
+                               goto peer_dead;
+                       case KA_REKEY:
+                               goto do_rekey;
+                       case KA_NONE:
                                return work_done;
                        default:
-                               vpninfo->progress(vpninfo, PRG_ERR, "SSL_write failed: %d\n", ret);
-                               report_ssl_errors(vpninfo);
-                               goto do_reconnect;
+                               /* This should never happen */
+                               ;
                        }
                }
+
                if (ret != vpninfo->current_ssl_pkt->len + 8) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "SSL wrote too few bytes! Asked for %d, sent %d\n",
-                               vpninfo->current_ssl_pkt->len + 8, ret);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("SSL wrote too few bytes! Asked for %d, sent %d\n"),
+                                    vpninfo->current_ssl_pkt->len + 8, ret);
                        vpninfo->quit_reason = "Internal error";
                        return 1;
                }
                /* Don't free the 'special' packets */
-               if (vpninfo->current_ssl_pkt != vpninfo->deflate_pkt &&
-                   vpninfo->current_ssl_pkt != &dpd_pkt &&
-                   vpninfo->current_ssl_pkt != &dpd_resp_pkt &&
-                   vpninfo->current_ssl_pkt != &keepalive_pkt)
+               if (vpninfo->current_ssl_pkt == vpninfo->deflate_pkt)
+                       free(vpninfo->pending_deflated_pkt);
+               else if (vpninfo->current_ssl_pkt != &dpd_pkt &&
+                        vpninfo->current_ssl_pkt != &dpd_resp_pkt &&
+                        vpninfo->current_ssl_pkt != &keepalive_pkt)
                        free(vpninfo->current_ssl_pkt);
 
                vpninfo->current_ssl_pkt = NULL;
@@ -587,26 +852,20 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
 
        switch (keepalive_action(&vpninfo->ssl_times, timeout)) {
        case KA_REKEY:
+       do_rekey:
                /* Not that this will ever happen; we don't even process
                   the setting when we're asked for it. */
-               vpninfo->progress(vpninfo, PRG_ERR, "CSTP rekey due but we don't know how\n");
-               time(&vpninfo->ssl_times.last_rekey);
-               work_done = 1;
+               vpn_progress(vpninfo, PRG_INFO, _("CSTP rekey due\n"));
+               goto do_reconnect;
                break;
 
        case KA_DPD_DEAD:
        peer_dead:
-               vpninfo->progress(vpninfo, PRG_ERR, "CSTP Dead Peer Detection detected dead peer!\n");
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("CSTP Dead Peer Detection detected dead peer!\n"));
        do_reconnect:
-               openconnect_close_https(vpninfo);
-
-               /* It's already deflated in the old stream. Extremely
-                  non-trivial to reconstitute it; just throw it away */
-               if (vpninfo->current_ssl_pkt == vpninfo->deflate_pkt)
-                       vpninfo->current_ssl_pkt = NULL;
-
                if (cstp_reconnect(vpninfo)) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "Reconnect failed\n");
+                       vpn_progress(vpninfo, PRG_ERR, _("Reconnect failed\n"));
                        vpninfo->quit_reason = "CSTP reconnect failed";
                        return 1;
                }
@@ -615,7 +874,7 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
                return 1;
 
        case KA_DPD:
-               vpninfo->progress(vpninfo, PRG_TRACE, "Send CSTP DPD\n");
+               vpn_progress(vpninfo, PRG_TRACE, _("Send CSTP DPD\n"));
 
                vpninfo->current_ssl_pkt = &dpd_pkt;
                goto handle_outgoing;
@@ -626,7 +885,7 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
                if (vpninfo->dtls_fd == -1 && vpninfo->outgoing_queue)
                        break;
 
-               vpninfo->progress(vpninfo, PRG_TRACE, "Send CSTP Keepalive\n");
+               vpn_progress(vpninfo, PRG_TRACE, _("Send CSTP Keepalive\n"));
 
                vpninfo->current_ssl_pkt = &keepalive_pkt;
                goto handle_outgoing;
@@ -653,7 +912,7 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
 
                        ret = deflate(&vpninfo->deflate_strm, Z_SYNC_FLUSH);
                        if (ret) {
-                               vpninfo->progress(vpninfo, PRG_ERR, "deflate failed %d\n", ret);
+                               vpn_progress(vpninfo, PRG_ERR, _("deflate failed %d\n"), ret);
                                goto uncompr;
                        }
 
@@ -672,10 +931,11 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
 
                        vpninfo->deflate_pkt->len = vpninfo->deflate_strm.total_out + 4;
 
-                       vpninfo->progress(vpninfo, PRG_TRACE,
-                                         "Sending compressed data packet of %d bytes\n",
-                                         this->len);
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("Sending compressed data packet of %d bytes\n"),
+                                    this->len);
 
+                       vpninfo->pending_deflated_pkt = this;
                        vpninfo->current_ssl_pkt = vpninfo->deflate_pkt;
                } else {
                uncompr:
@@ -683,9 +943,9 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
                        this->hdr[4] = this->len >> 8;
                        this->hdr[5] = this->len & 0xff;
 
-                       vpninfo->progress(vpninfo, PRG_TRACE,
-                                         "Sending uncompressed data packet of %d bytes\n",
-                                         this->len);
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("Sending uncompressed data packet of %d bytes\n"),
+                                    this->len);
 
                        vpninfo->current_ssl_pkt = this;
                }
@@ -696,14 +956,19 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
        return work_done;
 }
 
-int cstp_bye(struct openconnect_info *vpninfo, char *reason)
+int cstp_bye(struct openconnect_info *vpninfo, const char *reason)
 {
        unsigned char *bye_pkt;
        int reason_len;
 
        /* already lost connection? */
+#if defined (OPENCONNECT_OPENSSL)
        if (!vpninfo->https_ssl)
                return 0;
+#elif defined (OPENCONNECT_GNUTLS)
+       if (!vpninfo->https_sess)
+               return 0;
+#endif
 
        reason_len = strlen(reason);
        bye_pkt = malloc(reason_len + 9);
@@ -718,11 +983,11 @@ int cstp_bye(struct openconnect_info *vpninfo, char *reason)
        bye_pkt[6] = AC_PKT_DISCONN;
        bye_pkt[8] = 0xb0;
 
-       SSL_write(vpninfo->https_ssl, bye_pkt, reason_len + 9);
-       free(bye_pkt);
+       vpn_progress(vpninfo, PRG_INFO,
+                    _("Send BYE packet: %s\n"), reason);
 
-       vpninfo->progress(vpninfo, PRG_INFO,
-                         "Send BYE packet: %s\n", reason);
+       cstp_write(vpninfo, bye_pkt, reason_len + 9);
+       free(bye_pkt);
 
        return 0;
 }