]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Add Pulse Connect Secure support
authorDavid Woodhouse <dwmw2@infradead.org>
Fri, 7 Jun 2019 19:32:07 +0000 (20:32 +0100)
committerDavid Woodhouse <dwmw2@infradead.org>
Fri, 7 Jun 2019 19:32:07 +0000 (20:32 +0100)
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
13 files changed:
Makefile.am
gnutls.c
http.c
library.c
oncp.c
openconnect-internal.h
openssl-dtls.c
openssl.c
pulse.c [new file with mode: 0644]
www/Makefile.am
www/changelog.xml
www/menu2-protocols.xml
www/pulse.xml [new file with mode: 0644]

index 869cc68ce6f46803be617e1e49cd5a5c5f40dfa7..f81eddb60cdb8ade5d49884c05a12a95eecbd22e 100644 (file)
@@ -27,30 +27,33 @@ openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIB
 if OPENCONNECT_WIN32
 openconnect_SOURCES += openconnect.rc
 endif
-library_srcs = ssl.c http.c http-auth.c auth-common.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c
+library_srcs = ssl.c http.c http-auth.c auth-common.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c openconnect-internal.h
 lib_srcs_cisco = auth.c cstp.c
 lib_srcs_juniper = oncp.c lzo.c auth-juniper.c
+lib_srcs_pulse = pulse.c
 lib_srcs_globalprotect = gpst.c auth-globalprotect.c
+lib_srcs_oath = oath.c
+
+library_srcs += $(lib_srcs_juniper) $(lib_srcs_cisco) $(lib_srcs_oath) \
+               $(lib_srcs_globalprotect) $(lib_srcs_pulse)
+
 lib_srcs_gnutls = gnutls.c gnutls_tpm.c gnutls_tpm2.c
 lib_srcs_openssl = openssl.c openssl-pkcs11.c
 lib_srcs_win32 = tun-win32.c sspi.c
 lib_srcs_posix = tun.c
 lib_srcs_gssapi = gssapi.c
 lib_srcs_iconv = iconv.c
-lib_srcs_oath = oath.c
 lib_srcs_yubikey = yubikey.c
 lib_srcs_stoken = stoken.c
 lib_srcs_esp = esp.c esp-seqno.c
 lib_srcs_dtls = dtls.c
 
-POTFILES = $(openconnect_SOURCES) $(lib_srcs_cisco) $(lib_srcs_juniper) $(lib_srcs_globalprotect) \
-          gnutls-esp.c gnutls-dtls.c openssl-esp.c openssl-dtls.c \
+POTFILES = $(openconnect_SOURCES) gnutls-esp.c gnutls-dtls.c openssl-esp.c openssl-dtls.c \
           $(lib_srcs_esp) $(lib_srcs_dtls) gnutls_tpm2_esys.c gnutls_tpm2_ibm.c \
           $(lib_srcs_openssl) $(lib_srcs_gnutls) $(library_srcs) \
           $(lib_srcs_win32) $(lib_srcs_posix) $(lib_srcs_gssapi) $(lib_srcs_iconv) \
-          $(lib_srcs_oath) $(lib_srcs_yubikey) $(lib_srcs_stoken) openconnect-internal.h
+          $(lib_srcs_yubikey) $(lib_srcs_stoken)
 
-library_srcs += $(lib_srcs_juniper) $(lib_srcs_cisco) $(lib_srcs_oath) $(lib_srcs_globalprotect)
 if OPENCONNECT_LIBPCSCLITE
 library_srcs += $(lib_srcs_yubikey)
 endif
index 86f1775528eab29bf4d82a58a9a9e0eb64aaf1f4..4f915d62f80fa12b7e9eb93dee5f4cd11df83d36 100644 (file)
--- a/gnutls.c
+++ b/gnutls.c
@@ -2597,3 +2597,63 @@ int hotp_hmac(struct openconnect_info *vpninfo, const void *challenge)
        hpos = hash[hpos] & 15;
        return load_be32(&hash[hpos]) & 0x7fffffff;
 }
+
+
+static int ttls_pull_timeout_func(gnutls_transport_ptr_t t, unsigned int ms)
+{
+       struct openconnect_info *vpninfo = t;
+
+       vpn_progress(vpninfo, PRG_TRACE, _("ttls_pull_timeout_func %dms\n"), ms);
+       return 0;
+}
+
+static ssize_t ttls_pull_func(gnutls_transport_ptr_t t, void *buf, size_t len)
+{
+       int ret = pulse_eap_ttls_recv(t, buf, len);
+       if (ret >= 0)
+               return ret;
+       else
+               return GNUTLS_E_PULL_ERROR;
+}
+
+static ssize_t ttls_push_func(gnutls_transport_ptr_t t, const void *buf, size_t len)
+{
+       int ret = pulse_eap_ttls_send(t, buf, len);
+       if (ret >= 0)
+               return ret;
+       else
+               return GNUTLS_E_PUSH_ERROR;
+}
+
+void *establish_eap_ttls(struct openconnect_info *vpninfo)
+{
+       gnutls_session_t ttls_sess = NULL;
+       int err;
+
+       gnutls_init(&ttls_sess, GNUTLS_CLIENT);
+       gnutls_session_set_ptr(ttls_sess, (void *) vpninfo);
+       gnutls_transport_set_ptr(ttls_sess, (void *) vpninfo);
+
+       gnutls_transport_set_push_function(ttls_sess, ttls_push_func);
+       gnutls_transport_set_pull_function(ttls_sess, ttls_pull_func);
+       gnutls_transport_set_pull_timeout_function(ttls_sess, ttls_pull_timeout_func);
+
+       gnutls_credentials_set(ttls_sess, GNUTLS_CRD_CERTIFICATE, vpninfo->https_cred);
+
+       err = gnutls_priority_set_direct(ttls_sess,
+                                  vpninfo->gnutls_prio, NULL);
+
+       err = gnutls_handshake(ttls_sess);
+       if (!err) {
+               vpn_progress(vpninfo, PRG_TRACE,
+                            _("Established EAP-TTLS session\n"));
+               return ttls_sess;
+       }
+       gnutls_deinit(ttls_sess);
+       return NULL;
+}
+
+void destroy_eap_ttls(struct openconnect_info *vpninfo, void *sess)
+{
+       gnutls_deinit(sess);
+}
diff --git a/http.c b/http.c
index 434995052b9306f567c848ee8a9b14b8058b8982..8495a48046385c464713422262c3b3604939ade7 100644 (file)
--- a/http.c
+++ b/http.c
@@ -507,8 +507,8 @@ int process_http_response(struct openconnect_info *vpninfo, int connect,
        if (result == 100)
                goto cont;
 
-       /* On successful CONNECT, there is no body. Return success */
-       if (connect && result == 200)
+       /* On successful CONNECT or upgrade, there is no body. Return success */
+       if (connect && (result == 200 || result == 101))
                return result;
 
        /* Now the body, if there is one */
index c08da876835d5a7afbe507ba26f8356f6b7c4886..629e5e1dccf6e1bf72cc9a12173e57fc98d1d51b 100644 (file)
--- a/library.c
+++ b/library.c
@@ -127,7 +127,7 @@ const struct vpn_proto openconnect_protos[] = {
        }, {
                .name = "nc",
                .pretty_name = N_("Juniper Network Connect"),
-               .description = N_("Compatible with Juniper Network Connect / Pulse Secure SSL VPN"),
+               .description = N_("Compatible with Juniper Network Connect"),
                .flags = OC_PROTO_PROXY | OC_PROTO_CSD | OC_PROTO_AUTH_CERT | OC_PROTO_AUTH_OTP,
                .vpn_close_session = oncp_bye,
                .tcp_connect = oncp_connect,
@@ -161,6 +161,25 @@ const struct vpn_proto openconnect_protos[] = {
                .udp_shutdown = esp_shutdown,
                .udp_send_probes = gpst_esp_send_probes,
                .udp_catch_probe = gpst_esp_catch_probe,
+#endif
+       }, {
+               .name = "pulse",
+               .pretty_name = N_("Pulse Connect Secure"),
+               .description = N_("Compatible with Pulse Connect Secure SSL VPN"),
+               .flags = OC_PROTO_PROXY,
+               .vpn_close_session = pulse_bye,
+               .tcp_connect = pulse_connect,
+               .tcp_mainloop = pulse_mainloop,
+               .add_http_headers = http_common_headers,
+               .obtain_cookie = pulse_obtain_cookie,
+               .udp_protocol = "ESP",
+#ifdef HAVE_ESPx
+               .udp_setup = esp_setup,
+               .udp_mainloop = esp_mainloop,
+               .udp_close = esp_close,
+               .udp_shutdown = esp_shutdown,
+               .udp_send_probes = pulse_esp_send_probes,
+               .udp_catch_probe = pulse_esp_catch_probe,
 #endif
        },
        { /* NULL */ }
@@ -363,7 +382,10 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo)
        free(vpninfo->ifname);
        free(vpninfo->dtls_cipher);
        free(vpninfo->peer_cert_hash);
-#ifdef OPENCONNECT_GNUTLS
+#if defined(OPENCONNECT_OPENSSL)
+       if (vpninfo->ttls_bio_meth)
+               BIO_meth_free(vpninfo->ttls_bio_meth);
+#elif defined(OPENCONNECT_GNUTLS)
        gnutls_free(vpninfo->cstp_cipher); /* In OpenSSL this is const */
 #ifdef HAVE_DTLS
        gnutls_free(vpninfo->gnutls_dtls_cipher);
diff --git a/oncp.c b/oncp.c
index 24b1e56cef010dd79529a5c91ca7d0abe2e043df..656118fad8014ab51ef029cf4144d067ac78dfee 100644 (file)
--- a/oncp.c
+++ b/oncp.c
@@ -1126,6 +1126,7 @@ int oncp_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable)
                /* Don't free the 'special' packets */
                if (vpninfo->current_ssl_pkt == vpninfo->deflate_pkt) {
                        free(vpninfo->pending_deflated_pkt);
+                       vpninfo->pending_deflated_pkt = NULL;
                } else if (vpninfo->current_ssl_pkt == &esp_enable_pkt) {
                        /* Only set the ESP state to connected and actually start
                           sending packets on it once the enable message has been
index b7ac3ba92313a71e50851bd8be8e4d37dea45874..75d93a79f1cba4dbe9bd080627ddd148e5a5cd3a 100644 (file)
@@ -137,6 +137,13 @@ struct pkt {
                        unsigned char pad[8];
                        unsigned char hdr[16];
                } gpst;
+               struct {
+                       unsigned char pad[8];
+                       uint32_t vendor;
+                       uint32_t type;
+                       uint32_t len;
+                       uint32_t ident;
+               } pulse;
        };
        unsigned char data[];
 };
@@ -500,8 +507,10 @@ struct openconnect_info {
        X509 *cert_x509;
        SSL_CTX *https_ctx;
        SSL *https_ssl;
+       BIO_METHOD *ttls_bio_meth;
 #elif defined(OPENCONNECT_GNUTLS)
        gnutls_session_t https_sess;
+       gnutls_session_t eap_ttls_sess;
        gnutls_certificate_credentials_t https_cred;
        gnutls_psk_client_credentials_t psk_cred;
        char local_cert_md5[MD5_SIZE * 2 + 1]; /* For CSD */
@@ -513,6 +522,12 @@ struct openconnect_info {
        struct oc_tpm2_ctx *tpm2;
 #endif
 #endif /* OPENCONNECT_GNUTLS */
+       struct oc_text_buf *ttls_pushbuf;
+       uint8_t ttls_eap_ident;
+       unsigned char *ttls_recvbuf;
+       int ttls_recvpos;
+       int ttls_recvlen;
+
        struct pin_cache *pin_cache;
        struct keepalive_info ssl_times;
        int owe_ssl_dpd_response;
@@ -560,6 +575,8 @@ struct openconnect_info {
        unsigned char dtls_app_id[32];
        unsigned dtls_app_id_size;
 
+       uint32_t ift_seq;
+
        int cisco_dtls12;
        char *dtls_cipher;
        char *vpnc_script;
@@ -830,6 +847,9 @@ int dtls_try_handshake(struct openconnect_info *vpninfo);
 unsigned dtls_set_mtu(struct openconnect_info *vpninfo, unsigned mtu);
 void dtls_ssl_free(struct openconnect_info *vpninfo);
 
+void *establish_eap_ttls(struct openconnect_info *vpninfo);
+void destroy_eap_ttls(struct openconnect_info *vpninfo, void *sess);
+
 /* dtls.c */
 int dtls_setup(struct openconnect_info *vpninfo, int dtls_attempt_period);
 int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable);
@@ -863,6 +883,15 @@ void oncp_esp_close(struct openconnect_info *vpninfo);
 int oncp_esp_send_probes(struct openconnect_info *vpninfo);
 int oncp_esp_catch_probe(struct openconnect_info *vpninfo, struct pkt *pkt);
 
+/* pulse.c */
+int pulse_obtain_cookie(struct openconnect_info *vpninfo);
+void pulse_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf);
+int pulse_connect(struct openconnect_info *vpninfo);
+int pulse_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable);
+int pulse_bye(struct openconnect_info *vpninfo, const char *reason);
+int pulse_eap_ttls_send(struct openconnect_info *vpninfo, const void *data, int len);
+int pulse_eap_ttls_recv(struct openconnect_info *vpninfo, void *data, int len);
+
 /* auth-globalprotect.c */
 int gpst_obtain_cookie(struct openconnect_info *vpninfo);
 void gpst_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf);
index 8eab13b370573401b38b1476f843e5a236e0fbd6..5086440f223712f304ee0a0b4771dcb3b67cc966 100644 (file)
@@ -524,7 +524,7 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
                        /* For PSK-NEGOTIATE, we have to determine the tunnel MTU
                         * for ourselves based on the base MTU */
                        int data_mtu = vpninfo->cstp_basemtu;
-                       if (vpninfo->peer_addr->sa_family == IPPROTO_IPV6)
+                       if (vpninfo->peer_addr->sa_family == AF_INET6)
                                data_mtu -= 40; /* IPv6 header */
                        else
                                data_mtu -= 20; /* Legacy IP header */
index 0cc6779210176da9ea235957c7f7ff2c3975c33b..2b1f07bdec384d213de916a935bfe843e7118957 100644 (file)
--- a/openssl.c
+++ b/openssl.c
@@ -1978,3 +1978,81 @@ int hotp_hmac(struct openconnect_info *vpninfo, const void *challenge)
        hashlen = hash[hashlen - 1] & 15;
        return load_be32(&hash[hashlen]) & 0x7fffffff;
 }
+
+static int ttls_push_func(BIO *b, const char *buf, int len)
+{
+       struct openconnect_info *vpninfo = BIO_get_data(b);
+       int ret = pulse_eap_ttls_send(vpninfo, buf, len);
+       if (ret >= 0)
+               return ret;
+
+       return 0;
+}
+
+static int ttls_pull_func(BIO *b, char *buf, int len)
+{
+       struct openconnect_info *vpninfo = BIO_get_data(b);
+       int ret = pulse_eap_ttls_recv(vpninfo, buf, len);
+       if (ret >= 0)
+               return ret;
+
+       return 0;
+}
+
+static long ttls_ctrl_func(BIO *b, int cmd, long larg, void *iarg)
+{
+       switch(cmd) {
+       case BIO_CTRL_FLUSH:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+void *establish_eap_ttls(struct openconnect_info *vpninfo)
+{
+       SSL *ttls_ssl = NULL;
+       BIO *bio;
+       int err;
+
+
+       if (!vpninfo->ttls_bio_meth) {
+               vpninfo->ttls_bio_meth = BIO_meth_new(BIO_get_new_index(), "EAP-TTLS");
+               BIO_meth_set_write(vpninfo->ttls_bio_meth, ttls_push_func);
+               BIO_meth_set_read(vpninfo->ttls_bio_meth, ttls_pull_func);
+               BIO_meth_set_ctrl(vpninfo->ttls_bio_meth, ttls_ctrl_func);
+       }
+
+       bio = BIO_new(vpninfo->ttls_bio_meth);
+       BIO_set_data(bio, vpninfo);
+       BIO_set_init(bio, 1);
+       ttls_ssl = SSL_new(vpninfo->https_ctx);
+       workaround_openssl_certchain_bug(vpninfo, ttls_ssl);
+
+       SSL_set_bio(ttls_ssl, bio, bio);
+
+       SSL_set_verify(ttls_ssl, SSL_VERIFY_PEER, NULL);
+
+       vpn_progress(vpninfo, PRG_INFO, _("EAP-TTLS negotiation with %s\n"),
+                    vpninfo->hostname);
+
+       err = SSL_connect(ttls_ssl);
+       if (err == 1) {
+               vpn_progress(vpninfo, PRG_TRACE,
+                            _("Established EAP-TTLS session\n"));
+               return ttls_ssl;
+       }
+
+       err = SSL_get_error(ttls_ssl, err);
+       vpn_progress(vpninfo, PRG_ERR, _("EAP-TTLS connection failure %d\n"), err);
+       openconnect_report_ssl_errors(vpninfo);
+       SSL_free(ttls_ssl);
+       return NULL;
+}
+
+void destroy_eap_ttls(struct openconnect_info *vpninfo, void *ttls)
+{
+       SSL_free(ttls);
+       /* Leave the BIO_METH for now. It may get reused and we don't want to
+        * have to call BIO_get_new_index() more times than is necessary */
+}
diff --git a/pulse.c b/pulse.c
new file mode 100644 (file)
index 0000000..fbd351b
--- /dev/null
+++ b/pulse.c
@@ -0,0 +1,2039 @@
+/*
+ * OpenConnect (SSL + DTLS) VPN client
+ *
+ * Copyright Â© 2019 David Woodhouse.
+ *
+ * Author: David Woodhouse <dwmw2@infradead.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdarg.h>
+#include <sys/types.h>
+
+#include "openconnect-internal.h"
+
+#define VENDOR_JUNIPER 0xa4c
+#define VENDOR_JUNIPER2 0x583
+#define VENDOR_TCG 0x5597
+
+#define IFT_VERSION_REQUEST 1
+#define IFT_VERSION_RESPONSE 2
+#define IFT_CLIENT_AUTH_REQUEST 3
+#define IFT_CLIENT_AUTH_SELECTION 4
+#define IFT_CLIENT_AUTH_CHALLENGE 5
+#define IFT_CLIENT_AUTH_RESPONSE 6
+#define IFT_CLIENT_AUTH_SUCCESS 7
+
+/* IF-T/TLS v1 authentication messages all start
+ * with the Auth Type Vendor (Juniper) + Type (1) */
+#define JUNIPER_1 ((VENDOR_JUNIPER << 8) | 1)
+
+#define AVP_VENDOR 0x80
+#define AVP_MANDATORY 0x40
+
+#define EAP_REQUEST 1
+#define EAP_RESPONSE 2
+#define EAP_SUCCESS 3
+#define EAP_FAILURE 4
+
+#define EAP_TYPE_IDENTITY 1
+#define EAP_TYPE_TTLS 0x15
+#define EAP_TYPE_EXPANDED 0xfe
+
+#define EXPANDED_JUNIPER ((EAP_TYPE_EXPANDED << 24) | VENDOR_JUNIPER)
+
+#define AVP_CODE_EAP_MESSAGE 79
+
+#if defined(OPENCONNECT_OPENSSL)
+#define TTLS_SEND SSL_write
+#define TTLS_RECV SSL_read
+#elif defined(OPENCONNECT_GNUTLS)
+#define TTLS_SEND gnutls_record_send
+#define TTLS_RECV gnutls_record_recv
+#endif
+
+static void buf_append_be16(struct oc_text_buf *buf, uint16_t val)
+{
+       unsigned char b[2];
+
+       store_be16(b, val);
+
+       buf_append_bytes(buf, b, 2);
+}
+
+static void buf_append_be32(struct oc_text_buf *buf, uint32_t val)
+{
+       unsigned char b[4];
+
+       store_be32(b, val);
+
+       buf_append_bytes(buf, b, 4);
+}
+
+static void buf_append_ift_hdr(struct oc_text_buf *buf, uint32_t vendor, uint32_t type)
+{
+       uint32_t b[4];
+
+       store_be32(&b[0], vendor);
+       store_be32(&b[1], type);
+       b[2] = 0; /* Length will be filled in later. */
+       b[3] = 0;
+       buf_append_bytes(buf, b, 16);
+}
+
+/* Append EAP header, using VENDOR_JUNIPER and the given subtype if
+ * the main type is EAP_TYPE_EXPANDED */
+static int buf_append_eap_hdr(struct oc_text_buf *buf, uint8_t code, uint8_t ident, uint8_t type,
+                              uint32_t subtype)
+{
+       unsigned char b[24];
+       int len_ofs = -1;
+
+       if (!buf_error(buf))
+               len_ofs = buf->pos;
+
+       b[0] = code;
+       b[1] = ident;
+       b[2] = b[3] = 0; /* Length is filled in later. */
+       if (type == EAP_TYPE_EXPANDED) {
+               store_be32(b + 4, EXPANDED_JUNIPER);
+               store_be32(b + 8, subtype);
+               buf_append_bytes(buf, b, 12);
+       } else {
+               b[4] = type;
+               buf_append_bytes(buf, b, 5);
+       }
+       return len_ofs;
+}
+
+/* For an IF-T/TLS auth frame containing the Juniper/1 Auth Type,
+ * the EAP header is at offset 0x14. Fill in the length field,
+ * based on the current length of the buf */
+static void buf_fill_eap_len(struct oc_text_buf *buf, int ofs)
+{
+       /* EAP length word is always at 0x16, and counts bytes from 0x14 */
+       if (ofs >= 0 && !buf_error(buf) && buf->pos > ofs + 8)
+               store_be16(buf->data + ofs + 2, buf->pos - ofs);
+}
+
+static void buf_append_avp(struct oc_text_buf *buf, uint32_t type, const void *bytes, int len)
+{
+       buf_append_be32(buf, type);
+       buf_append_be16(buf, 0x8000);
+       buf_append_be16(buf, len + 12);
+       buf_append_be32(buf, VENDOR_JUNIPER2);
+       buf_append_bytes(buf, bytes, len);
+       if (len & 3) {
+               uint32_t pad = 0;
+               buf_append_bytes(buf, &pad, 4 - ( len & 3 ));
+       }
+}
+
+static void buf_append_avp_string(struct oc_text_buf *buf, uint32_t type, const char *str)
+{
+       buf_append_avp(buf, type, str, strlen(str));
+}
+
+static int valid_ift_success(unsigned char *bytes, int len)
+{
+       if (len != 0x18 || (load_be32(bytes) & 0xffffff) != VENDOR_TCG ||
+           load_be32(bytes + 4) != IFT_CLIENT_AUTH_SUCCESS ||
+           load_be32(bytes + 8) != len ||
+           load_be32(bytes + 0x10) != JUNIPER_1 ||
+           bytes[0x14] != EAP_SUCCESS ||
+           load_be16(bytes + 0x16) != len - 0x14)
+               return 0;
+
+       return 1;
+}
+
+/* Check for a valid IF-T/TLS auth challenge of the Juniper/1 Auth Type */
+static int valid_ift_auth(unsigned char *bytes, int len)
+{
+       if (len < 0x14 || (load_be32(bytes) & 0xffffff) != VENDOR_TCG ||
+           load_be32(bytes + 4) != IFT_CLIENT_AUTH_CHALLENGE ||
+           load_be32(bytes + 8) != len ||
+           load_be32(bytes + 0x10) != JUNIPER_1)
+               return 0;
+
+       return 1;
+}
+
+
+static int valid_ift_auth_eap(unsigned char *bytes, int len)
+{
+       /* Needs to be a valid IF-T/TLS auth challenge with the
+        * expect Auth Type, *and* the payload has to be a valid
+        * EAP request with correct length field. */
+       if (!valid_ift_auth(bytes, len) || len < 0x19 ||
+           bytes[0x14] != EAP_REQUEST ||
+           load_be16(bytes + 0x16) != len - 0x14)
+               return 0;
+
+       return 1;
+}
+
+static int valid_ift_auth_eap_exj1(unsigned char *bytes, int len)
+{
+       /* Also needs to be the Expanded Juniper/1 EAP Type */
+       if (!valid_ift_auth_eap(bytes, len) || len < 0x20 ||
+           load_be32(bytes + 0x18) != EXPANDED_JUNIPER ||
+           load_be32(bytes + 0x1c) != 1)
+               return 0;
+
+       return 1;
+}
+
+/* We behave like CSTP â€” create a linked list in vpninfo->cstp_options
+ * with the strings containing the information we got from the server,
+ * and oc_ip_info contains const copies of those pointers. */
+
+static const char *add_option(struct openconnect_info *vpninfo, const char *opt,
+                             const char *val, int val_len)
+{
+       struct oc_vpn_option *new = malloc(sizeof(*new));
+       if (!new)
+               return NULL;
+
+       new->option = strdup(opt);
+       if (!new->option) {
+               free(new);
+               return NULL;
+       }
+       if (val_len >= 0)
+               new->value = strndup(val, val_len);
+       else
+               new->value = strdup(val);
+       if (!new->value) {
+               free(new->option);
+               free(new);
+               return NULL;
+       }
+       new->next = vpninfo->cstp_options;
+       vpninfo->cstp_options = new;
+
+       return new->value;
+}
+
+static int process_attr(struct openconnect_info *vpninfo, uint16_t type,
+                       unsigned char *data, int attrlen)
+{
+       char buf[80];
+       int i;
+
+       switch (type) {
+
+       case 0x0001:
+               if (attrlen != 4)
+                       goto badlen;
+               snprintf(buf, sizeof(buf), "%d.%d.%d.%d", data[0], data[1], data[2], data[3]);
+
+               vpn_progress(vpninfo, PRG_DEBUG, _("Received internal Legacy IP address %s\n"), buf);
+               vpninfo->ip_info.addr = add_option(vpninfo, "ipaddr", buf, -1);
+               break;
+
+       case 0x0002:
+               if (attrlen != 4)
+                       goto badlen;
+               snprintf(buf, sizeof(buf), "%d.%d.%d.%d", data[0], data[1], data[2], data[3]);
+
+               vpn_progress(vpninfo, PRG_DEBUG, _("Received netmask %s\n"), buf);
+               vpninfo->ip_info.netmask = add_option(vpninfo, "netmask", buf, -1);
+               break;
+
+       case 0x0003:
+               if (attrlen != 4)
+                       goto badlen;
+               snprintf(buf, sizeof(buf), "%d.%d.%d.%d", data[0], data[1], data[2], data[3]);
+
+               vpn_progress(vpninfo, PRG_DEBUG, _("Received DNS server %s\n"), buf);
+
+               for (i = 0; i < 3; i++) {
+                       if (!vpninfo->ip_info.dns[i]) {
+                               vpninfo->ip_info.dns[i] = add_option(vpninfo, "DNS", buf, -1);
+                               break;
+                       }
+               }
+               break;
+
+       case 0x0004:
+               if (attrlen != 4)
+                       goto badlen;
+               snprintf(buf, sizeof(buf), "%d.%d.%d.%d", data[0], data[1], data[2], data[3]);
+
+               vpn_progress(vpninfo, PRG_DEBUG, _("Received WINS server %s\n"), buf);
+
+               for (i = 0; i < 3; i++) {
+                       if (!vpninfo->ip_info.nbns[i]) {
+                               vpninfo->ip_info.nbns[i] = add_option(vpninfo, "WINS", buf, -1);
+                               break;
+                       }
+               }
+               break;
+
+       case 0x0008:
+               if (attrlen != 17)
+                       goto badlen;
+               if (!inet_ntop(AF_INET6, data, buf, sizeof(buf))) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to handle IPv6 address\n"));
+                       return -EINVAL;
+               }
+               i = strlen(buf);
+               snprintf(buf + i, sizeof(buf) - i, "/%d", data[16]);
+               vpn_progress(vpninfo, PRG_DEBUG, _("Received internal IPv6 address %s\n"), buf);
+               vpninfo->ip_info.addr6 = add_option(vpninfo, "ip6addr", buf, -1);
+               break;
+
+       case 0x4005:
+               if (attrlen != 4) {
+               badlen:
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Unexpected length %d for attr 0x%x\n"),
+                                    attrlen, type);
+                       return -EINVAL;
+               }
+               vpninfo->ip_info.mtu = load_be32(data);
+               vpn_progress(vpninfo, PRG_DEBUG,
+                            _("Received MTU %d from server\n"),
+                            vpninfo->ip_info.mtu);
+               break;
+
+       case 0x4006:
+               if (!attrlen)
+                       goto badlen;
+               if (!data[attrlen-1])
+                   attrlen--;
+               vpn_progress(vpninfo, PRG_DEBUG, _("Received DNS search domain %.*s\n"),
+                            attrlen, (char *)data);
+               vpninfo->ip_info.domain = add_option(vpninfo, "search", (char *)data, attrlen);
+               if (vpninfo->ip_info.domain) {
+                       char *p = (char *)vpninfo->ip_info.domain;
+                       while ((p = strchr(p, ',')))
+                               *p = ' ';
+               }
+               break;
+
+       case 0x400b:
+               if (attrlen != 4)
+                       goto badlen;
+               snprintf(buf, sizeof(buf), "%d.%d.%d.%d", data[0], data[1], data[2], data[3]);
+
+               vpn_progress(vpninfo, PRG_DEBUG, _("Received internal gateway address %s\n"), buf);
+               /* Hm, what are we supposed to do with this? It's a tunnel;
+                  having a gateway is meaningless. */
+               add_option(vpninfo, "ipaddr", buf, -1);
+               break;
+
+       case 0x4010: {
+               const char *enctype;
+               uint16_t val;
+
+               if (attrlen != 2)
+                       goto badlen;
+               val = load_be16(data);
+               if (val == ENC_AES_128_CBC) {
+                       enctype = "AES-128";
+                       vpninfo->enc_key_len = 16;
+               } else if (val == ENC_AES_256_CBC) {
+                       enctype = "AES-256";
+                       vpninfo->enc_key_len = 32;
+               } else
+                       enctype = "unknown";
+               vpn_progress(vpninfo, PRG_DEBUG, _("ESP encryption: 0x%04x (%s)\n"),
+                             val, enctype);
+               vpninfo->esp_enc = val;
+               break;
+       }
+
+       case 0x4011: {
+               const char *mactype;
+               uint16_t val;
+
+               if (attrlen != 2)
+                       goto badlen;
+               val = load_be16(data);
+               if (val == HMAC_MD5) {
+                       mactype = "MD5";
+                       vpninfo->hmac_key_len = 16;
+               } else if (val == HMAC_SHA1) {
+                       mactype = "SHA1";
+                       vpninfo->hmac_key_len = 20;
+               } else
+                       mactype = "unknown";
+               vpn_progress(vpninfo, PRG_DEBUG, _("ESP HMAC: 0x%04x (%s)\n"),
+                             val, mactype);
+               vpninfo->esp_hmac = val;
+               break;
+       }
+
+       case 0x4012:
+               if (attrlen != 4)
+                       goto badlen;
+               vpninfo->esp_lifetime_seconds = load_be32(data);
+               vpn_progress(vpninfo, PRG_DEBUG, _("ESP key lifetime: %u seconds\n"),
+                            vpninfo->esp_lifetime_seconds);
+               break;
+
+       case 0x4013:
+               if (attrlen != 4)
+                       goto badlen;
+               vpninfo->esp_lifetime_bytes = load_be32(data);
+               vpn_progress(vpninfo, PRG_DEBUG, _("ESP key lifetime: %u bytes\n"),
+                            vpninfo->esp_lifetime_bytes);
+               break;
+
+       case 0x4014:
+               if (attrlen != 4)
+                       goto badlen;
+               vpninfo->esp_replay_protect = load_be32(data);
+               vpn_progress(vpninfo, PRG_DEBUG, _("ESP replay protection: %d\n"),
+                            load_be32(data));
+               break;
+
+       case 0x4016:
+               if (attrlen != 2)
+                       goto badlen;
+               i = load_be16(data);
+               udp_sockaddr(vpninfo, i);
+               vpn_progress(vpninfo, PRG_DEBUG, _("ESP port: %d\n"), i);
+               break;
+
+       case 0x4017:
+               if (attrlen != 4)
+                       goto badlen;
+               vpninfo->esp_ssl_fallback = load_be32(data);
+               vpn_progress(vpninfo, PRG_DEBUG, _("ESP to SSL fallback: %u seconds\n"),
+                            vpninfo->esp_ssl_fallback);
+               break;
+
+       case 0x401a:
+               if (attrlen != 1)
+                       goto badlen;
+               /* Amusingly, this isn't enforced. It's client-only */
+               vpn_progress(vpninfo, PRG_DEBUG, _("ESP only: %d\n"),
+                            data[0]);
+               break;
+#if 0
+       case GRP_ATTR(7, 1):
+               if (attrlen != 4)
+                       goto badlen;
+               memcpy(&vpninfo->esp_out.spi, data, 4);
+               vpn_progress(vpninfo, PRG_DEBUG, _("ESP SPI (outbound): %x\n"),
+                            load_be32(data));
+               break;
+
+       case GRP_ATTR(7, 2):
+               if (attrlen != 0x40)
+                       goto badlen;
+               /* data contains enc_key and hmac_key concatenated */
+               memcpy(vpninfo->esp_out.enc_key, data, 0x40);
+               vpn_progress(vpninfo, PRG_DEBUG, _("%d bytes of ESP secrets\n"),
+                            attrlen);
+               break;
+#endif
+       /* 0x4022: disable proxy
+          0x400a: preserve proxy
+          0x4008: proxy (string)
+          0x4000: disconnect when routes changed
+          0x4015: tos copy
+          0x4001:  tunnel routes take precedence
+          0x401f:  tunnel routes with subnet access (also 4001 set)
+          0x4020: Enforce IPv4
+          0x4021: Enforce IPv6
+          0x401e: Server IPv6 address
+          0x000f: IPv6 netmask?
+       */
+
+       default:
+               buf[0] = 0;
+               for (i=0; i < 16 && i < attrlen; i++)
+                       sprintf(buf + strlen(buf), " %02x", data[i]);
+               if (attrlen > 16)
+                       sprintf(buf + strlen(buf), "...");
+
+               vpn_progress(vpninfo, PRG_DEBUG,
+                            _("Unknown attr 0x%x len %d:%s\n"),
+                            type, attrlen, buf);
+       }
+       return 0;
+}
+
+static int recv_ift_packet(struct openconnect_info *vpninfo, void *buf, int len)
+{
+       int ret = vpninfo->ssl_read(vpninfo, buf, len);
+       if (ret > 0 && vpninfo->dump_http_traffic) {
+               vpn_progress(vpninfo, PRG_TRACE,
+                            _("Read %d bytes of IF-T/TLS record\n"), ret);
+               dump_buf_hex(vpninfo, PRG_TRACE, '<', buf, ret);
+       }
+       return ret;
+}
+
+static int send_ift_packet(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
+{
+       int ret;
+
+       if (buf_error(buf) || buf->pos < 16) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error creating IF-T packet\n"));
+               return buf_error(buf);
+       }
+
+       /* Fill in the length word in the header with the full length of the buffer.
+        * Also populate the sequence number. */
+       store_be32(buf->data + 8, buf->pos);
+       store_be32(buf->data + 12, vpninfo->ift_seq++);
+
+       dump_buf_hex(vpninfo, PRG_DEBUG, '>', (void *)buf->data, buf->pos);
+       ret = vpninfo->ssl_write(vpninfo, buf->data, buf->pos);
+       if (ret != buf->pos) {
+               if (ret >= 0) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Short write to IF-T/TLS\n"));
+                       ret = -EIO;
+               }
+               return ret;
+       }
+       return 0;
+}
+
+/* We create packets with IF-T/TLS headers prepended because that's the
+ * larger header. In the case where they need to be sent over EAP-TTLS,
+ * convert the header to the EAP-Message AVP instead. */
+static int send_eap_packet(struct openconnect_info *vpninfo, void *ttls, struct oc_text_buf *buf)
+{
+       int ret;
+
+       if (buf_error(buf) || buf->pos < 16) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error creating EAP packet\n"));
+               return buf_error(buf);
+       }
+
+       if (!ttls)
+               return send_ift_packet(vpninfo, buf);
+
+       /* AVP EAP-Message header */
+       store_be32(buf->data + 0x0c, AVP_CODE_EAP_MESSAGE);
+       store_be32(buf->data + 0x10, buf->pos - 0xc);
+       dump_buf_hex(vpninfo, PRG_DEBUG, '.', (void *)(buf->data + 0x0c), buf->pos - 0x0c);
+       ret = TTLS_SEND(ttls, buf->data + 0x0c, buf->pos - 0x0c);
+       if (ret != buf->pos - 0x0c)
+               return -EIO;
+       return 0;
+}
+
+
+/*
+ * Using the given buffer, receive and validate an EAP request of the
+ * Expanded Juniper/1 type, either natively over IF-T/TLS or by EAP-TTLS
+ * over IF-T/TLS. Return a pointer to the EAP header, with its length and
+ * type already validated.
+ */
+static void *recv_eap_packet(struct openconnect_info *vpninfo, void *ttls, void *buf, int len)
+{
+       unsigned char *cbuf = buf;
+       int ret;
+
+       if (!ttls) {
+               ret = recv_ift_packet(vpninfo, buf, len);
+               if (ret < 0)
+                       return NULL;
+               if (!valid_ift_auth_eap_exj1(buf, ret)) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Unexpected IF-T/TLS authentication challenge:\n"));
+                       dump_buf_hex(vpninfo, PRG_ERR, '<', (void *)buf, ret);
+                       return NULL;
+               }
+               return cbuf + 0x14;
+       } else {
+               ret = TTLS_RECV(ttls, buf, len);
+               if (ret <= 8)
+                       return NULL;
+               if (/* EAP-Message AVP */
+                   load_be32(cbuf) != AVP_CODE_EAP_MESSAGE ||
+                   /* Ignore the mandatory bit */
+                   (load_be32(cbuf+0x04) & ~0x40000000) != ret ||
+                   cbuf[0x08] != EAP_REQUEST ||
+                   load_be16(cbuf+0x0a) != ret - 8 ||
+                   load_be32(cbuf+0x0c) != EXPANDED_JUNIPER ||
+                   load_be32(cbuf+0x10) != 1) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Unexpected EAP-TTLS payload:\n"));
+                       dump_buf_hex(vpninfo, PRG_ERR, '<', buf, ret);
+                       return NULL;
+               }
+               return cbuf + 0x08;
+       }
+}
+
+static void dump_avp(struct openconnect_info *vpninfo, uint8_t flags,
+                    uint32_t vendor, uint32_t code, void *p, int len)
+{
+       struct oc_text_buf *buf = buf_alloc();
+       const char *pretty;
+       int i;
+
+       for (i = 0; i < len; i++)
+               if (!isprint( ((char *)p)[i] ))
+                       break;
+
+       if (i == len) {
+               buf_append(buf, " '");
+               buf_append_bytes(buf, p, len);
+               buf_append(buf, "'");
+       } else {
+               for (i = 0; i < len; i++)
+                       buf_append(buf, " %02x", ((unsigned char *)p)[i]);
+       }
+       if (buf_error(buf))
+               pretty = " <error>";
+       else
+               pretty = buf->data;
+
+       if (flags & AVP_VENDOR)
+               vpn_progress(vpninfo, PRG_TRACE, _("AVP 0x%x/0x%x:%s\n"), vendor, code, pretty);
+       else
+               vpn_progress(vpninfo, PRG_TRACE, _("AVP %d:%s\n"), code, pretty);
+       buf_free(buf);
+}
+
+/* RFC5281 Â§10 */
+static int parse_avp(struct openconnect_info *vpninfo, void **pkt, int *pkt_len,
+                   void **avp_out, int *avp_len, uint8_t *avp_flags,
+                   uint32_t *avp_vendor, uint32_t *avp_code)
+{
+       unsigned char *p = *pkt;
+       int l = *pkt_len;
+       uint32_t code, len, vendor = 0;
+       uint8_t flags;
+
+       if (l < 8)
+               return -EINVAL;
+
+       code = load_be32(p);
+       len = load_be32(p + 4) & 0xffffff;
+       flags = p[4];
+
+       if (len > l || len < 8)
+               return -EINVAL;
+
+       p += 8;
+       l -= 8;
+       len -= 8;
+
+       /* Vendor field is optional. */
+       if (flags & AVP_VENDOR) {
+               if (l < 4)
+                       return -EINVAL;
+               vendor = load_be32(p);
+               p += 4;
+               l -= 4;
+               len -= 4;
+       }
+
+       *avp_vendor = vendor;
+       *avp_flags = flags;
+       *avp_code = code;
+       *avp_out = p;
+       *avp_len = len;
+
+       /* Now set up packet pointer and length for next AVP,
+        * aligned to 4 octets (if they exist in the packet) */
+       len = (len + 3) & ~3;
+       if (len > l)
+               len = l;
+       *pkt = p + len;
+       *pkt_len = l - len;
+
+       return 0;
+}
+
+
+static int pulse_request_realm_entry(struct openconnect_info *vpninfo, struct oc_text_buf *reqbuf)
+{
+       struct oc_auth_form f;
+       struct oc_form_opt o;
+       int ret;
+
+       memset(&f, 0, sizeof(f));
+       memset(&o, 0, sizeof(o));
+        f.auth_id = (char *)"pulse_realm_entry";
+        f.opts = &o;
+
+       f.message = _("Enter Pulse user realm:");
+
+       o.next = NULL;
+       o.type = OC_FORM_OPT_TEXT;
+       o.name = (char *)"realm";
+       o.label = (char *)_("Realm:");
+
+       ret = process_auth_form(vpninfo, &f);
+       if (ret)
+               return ret;
+
+       if (o._value) {
+               buf_append_avp_string(reqbuf, 0xd50, o._value);
+               free_pass(&o._value);
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static int pulse_request_realm_choice(struct openconnect_info *vpninfo, struct oc_text_buf *reqbuf,
+                                     int realms, unsigned char *eap)
+{
+       uint8_t avp_flags;
+       uint32_t avp_code;
+       uint32_t avp_vendor;
+       int avp_len;
+       void *avp_p;
+       struct oc_auth_form f;
+       struct oc_form_opt_select o;
+       int i = 0, ret;
+       void *p;
+       int l;
+
+       l = load_be16(eap + 2) - 0x0c; /* Already validated */
+       p = eap + 0x0c;
+
+       memset(&f, 0, sizeof(f));
+       memset(&o, 0, sizeof(o));
+       f.auth_id = (char *)"pulse_realm_choice";
+       f.opts = &o.form;
+       f.authgroup_opt = &o;
+       f.authgroup_selection = 1;
+       f.message = _("Choose Pulse user realm:");
+
+       o.form.next = NULL;
+       o.form.type = OC_FORM_OPT_SELECT;
+       o.form.name = (char *)"realm_choice";
+       o.form.label = (char *)_("Realm:");
+
+       o.nr_choices = realms;
+       o.choices = calloc(realms, sizeof(*o.choices));
+       if (!o.choices)
+               return -ENOMEM;
+
+       while (l) {
+               if (parse_avp(vpninfo, &p, &l, &avp_p, &avp_len, &avp_flags,
+                             &avp_vendor, &avp_code)) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to parse AVP\n"));
+                       ret = -EINVAL;
+                       goto out;
+               }
+               if (avp_vendor != VENDOR_JUNIPER2 || avp_code != 0xd4e)
+                       continue;
+
+               o.choices[i] = malloc(sizeof(struct oc_choice));
+               if (!o.choices[i]) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+               o.choices[i]->name = o.choices[i]->label = strndup(avp_p, avp_len);
+               if (!o.choices[i]->name) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+
+               i++;
+       }
+
+
+       /* We don't need to do anything on group changes. */
+       do {
+               ret = process_auth_form(vpninfo, &f);
+       } while (ret == OC_FORM_RESULT_NEWGROUP);
+
+       if (!ret)
+               buf_append_avp_string(reqbuf, 0xd50, o.form._value);
+ out:
+       if (o.choices) {
+               for (i = 0; i < realms; i++) {
+                       if (o.choices[i]) {
+                               free(o.choices[i]->name);
+                               free(o.choices[i]);
+                       }
+               }
+               free(o.choices);
+       }
+       return ret;
+}
+
+static int pulse_request_session_kill(struct openconnect_info *vpninfo, struct oc_text_buf *reqbuf,
+                                     int sessions, unsigned char *eap)
+{
+       uint8_t avp_flags;
+       uint32_t avp_code;
+       uint32_t avp_vendor;
+       int avp_len, avp_len2;
+       void *avp_p, *avp_p2;
+       struct oc_auth_form f;
+       struct oc_form_opt_select o;
+       int i = 0, ret;
+       void *p;
+       int l;
+       struct oc_text_buf *form_msg = buf_alloc();
+       char tmbuf[80];
+       struct tm tm;
+
+       l = load_be16(eap + 2) - 0x0c; /* Already validated */
+       p = eap + 0x0c;
+
+       memset(&f, 0, sizeof(f));
+       memset(&o, 0, sizeof(o));
+       f.auth_id = (char *)"pulse_session_kill";
+       f.opts = &o.form;
+
+       buf_append(form_msg, _("Session limit reached. Choose session to kill:\n"));
+
+       o.form.next = NULL;
+       o.form.type = OC_FORM_OPT_SELECT;
+       o.form.name = (char *)"session_choice";
+       o.form.label = (char *)_("Session:");
+
+       o.nr_choices = sessions;
+       o.choices = calloc(sessions, sizeof(*o.choices));
+       if (!o.choices)
+               return -ENOMEM;
+
+       while (l) {
+               char *from = NULL;
+               time_t when = 0;
+               char *sessid = NULL;
+
+               if (parse_avp(vpninfo, &p, &l, &avp_p, &avp_len, &avp_flags,
+                             &avp_vendor, &avp_code)) {
+               badlist:
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to parse session list\n"));
+                       ret = -EINVAL;
+                       goto out;
+               }
+
+               if (avp_vendor != VENDOR_JUNIPER2 || avp_code != 0xd65)
+                       continue;
+
+               while (avp_len) {
+                       if (parse_avp(vpninfo, &avp_p, &avp_len, &avp_p2, &avp_len2,
+                                     &avp_flags, &avp_vendor, &avp_code))
+                               goto badlist;
+
+                       dump_avp(vpninfo, avp_flags, avp_vendor, avp_code, avp_p2, avp_len2);
+
+                       if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd66) {
+                               sessid = strndup(avp_p2, avp_len2);
+                       } else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd67) {
+                               from = strndup(avp_p2, avp_len2);
+                       } else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd68 &&
+                                  avp_len2 == 8) {
+                               when = load_be32((char *)avp_p2 + 4);
+                               if (sizeof(time_t) > 4)
+                                       when |= ((uint64_t)load_be32(avp_p2)) << 32;
+                       }
+               }
+
+               if (!from || !sessid || !when) {
+                       free(from);
+                       free(sessid);
+                       goto badlist;
+               }
+
+               localtime_r(&when, &tm);
+               strftime(tmbuf, 80, "%a, %d %b %Y %H:%M:%S %Z", &tm);
+               buf_append(form_msg, " - %s from %s at %s\n", sessid, from, tmbuf);
+               free(from);
+               o.choices[i] = malloc(sizeof(struct oc_choice));
+               if (!o.choices[i]) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+               o.choices[i]->name = o.choices[i]->label = sessid;
+               if (!o.choices[i]->name) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+               i++;
+       }
+       ret = buf_error(form_msg);
+       if (ret)
+               goto out;
+
+       f.message = form_msg->data;
+
+       ret = process_auth_form(vpninfo, &f);
+       if (!ret)
+               buf_append_avp_string(reqbuf, 0xd69, o.form._value);
+ out:
+       if (o.choices) {
+               for (i = 0; i < sessions; i++) {
+                       if (o.choices[i]) {
+                               free(o.choices[i]->name);
+                               free(o.choices[i]);
+                       }
+               }
+               free(o.choices);
+       }
+       buf_free(form_msg);
+       return ret;
+}
+
+static int pulse_request_user_auth(struct openconnect_info *vpninfo, struct oc_text_buf *reqbuf,
+                                  uint8_t eap_ident, char *user_prompt, char *pass_prompt)
+{
+       struct oc_auth_form f;
+       struct oc_form_opt o[2];
+       int ret;
+
+       memset(&f, 0, sizeof(f));
+       memset(o, 0, sizeof(o));
+        f.auth_id = (char *)"pulse_user";
+        f.opts = &o[0];
+
+       f.message = _("Enter user credentials:");
+
+       o[0].next = &o[1];
+       o[0].type = OC_FORM_OPT_TEXT;
+       o[0].name = (char *)"username";
+       if (!user_prompt || asprintf(&o[0].label, "%s:", user_prompt) < 0) {
+               user_prompt = NULL;
+               o[0].label = (char *)_("Username:");
+       }
+
+       o[1].type = OC_FORM_OPT_PASSWORD;
+       o[1].name = (char *)"password";
+       if (!pass_prompt || asprintf(&o[1].label, "%s:", pass_prompt) < 0) {
+               pass_prompt = NULL;
+               o[1].label = (char *)_("Password:");
+       }
+
+       ret = process_auth_form(vpninfo, &f);
+       if (ret)
+               goto out;
+
+       if (o[0]._value) {
+               buf_append_avp_string(reqbuf, 0xd6d, o[0]._value);
+               free_pass(&o[0]._value);
+       }
+       if (o[1]._value) {
+               unsigned char eap_avp[23];
+               int l = strlen(o[1]._value);
+               if (l > 253) {
+                       free_pass(&o[1]._value);
+                       return -EINVAL;
+               }
+
+               /* AVP flags+mandatory+length */
+               store_be32(eap_avp, AVP_CODE_EAP_MESSAGE);
+               store_be32(eap_avp + 4, (AVP_MANDATORY << 24) + sizeof(eap_avp) + l);
+
+               /* EAP header: code/ident/len */
+               eap_avp[8] = EAP_RESPONSE;
+               eap_avp[9] = eap_ident;
+               store_be16(eap_avp + 10, l + 15); /* EAP length */
+               store_be32(eap_avp + 12, EXPANDED_JUNIPER);
+               store_be32(eap_avp + 16, 2);
+
+               /* EAP Juniper/2 payload: 02 02 <len> <password> */
+               eap_avp[20] = eap_avp[21] = 0x02;
+               eap_avp[22] = l + 2; /* Why 2? */
+               buf_append_bytes(reqbuf, eap_avp, sizeof(eap_avp));
+               buf_append_bytes(reqbuf, o[1]._value, l);
+
+               /* Padding */
+               if ((sizeof(eap_avp) + l) & 3) {
+                       uint32_t pad = 0;
+
+                       buf_append_bytes(reqbuf, &pad,
+                                        4 - ((sizeof(eap_avp) + l) & 3));
+               }
+               free_pass(&o[1]._value);
+       }
+       ret = 0;
+ out:
+       if (user_prompt)
+               free(o[0].label);
+       if (pass_prompt)
+               free(o[1].label);
+
+       return ret;
+}
+
+/* IF-T/TLS session establishment is the same for both pulse_obtain_cookie() and
+ * pulse_connect(). We have to go through the EAP phase of the connection either
+ * way; it's just that we might do it with just the cookie, or we might need to
+ * use the password/cert etc. */
+static int pulse_authenticate(struct openconnect_info *vpninfo, int connecting)
+{
+       int ret;
+       struct oc_text_buf *reqbuf;
+       unsigned char bytes[16384];
+       int eap_ofs;
+       uint8_t eap_ident, eap2_ident = 0;
+       uint8_t avp_flags;
+       uint32_t avp_code;
+       uint32_t avp_vendor;
+       int avp_len, l;
+       void *avp_p, *p;
+       unsigned char *eap;
+       int cookie_found = 0;
+       int j2_found = 0, realms_found = 0, realm_entry = 0, old_sessions = 0;
+       uint8_t j2_code = 0;
+       void *ttls = NULL;
+       char *user_prompt = NULL, *pass_prompt = NULL;
+
+       /* XXX: We should do what cstp_connect() does to check that configuration
+          hasn't changed on a reconnect. */
+
+       ret = openconnect_open_https(vpninfo);
+       if (ret)
+               return ret;
+
+       reqbuf = buf_alloc();
+
+       buf_append(reqbuf, "GET /%s HTTP/1.1\r\n", vpninfo->urlpath ?: "");
+       http_common_headers(vpninfo, reqbuf);
+       buf_append(reqbuf, "Content-Type: EAP\r\n");
+       buf_append(reqbuf, "Upgrade: IF-T/TLS 1.0\r\n");
+       buf_append(reqbuf, "Content-Length: 0\r\n");
+       buf_append(reqbuf, "\r\n");
+
+       if (buf_error(reqbuf)) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error creating Pulse connection request\n"));
+               ret = buf_error(reqbuf);
+               goto out;
+       }
+       if (vpninfo->dump_http_traffic)
+               dump_buf(vpninfo, '>', reqbuf->data);
+
+       ret = vpninfo->ssl_write(vpninfo, reqbuf->data, reqbuf->pos);
+       if (ret < 0)
+               goto out;
+
+       ret = process_http_response(vpninfo, 1, NULL, reqbuf);
+       if (ret < 0)
+               goto out;
+
+       if (ret != 101) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unexpected %d result from server\n"),
+                            ret);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       vpninfo->ift_seq = 0;
+       /* IF-T version request. */
+       buf_truncate(reqbuf);
+       buf_append_ift_hdr(reqbuf, VENDOR_TCG, IFT_VERSION_REQUEST);
+       /* min=1, max=1, preferred version=1 */
+       buf_append_be32(reqbuf, 0x00010101);
+       ret = send_ift_packet(vpninfo, reqbuf);
+       if (ret)
+               goto out;
+
+       ret = recv_ift_packet(vpninfo, (void *)bytes, sizeof(bytes));
+       if (ret < 0)
+               goto out;
+
+       if (ret != 0x14 || (load_be32(bytes) & 0xffffff) != VENDOR_TCG ||
+           load_be32(bytes + 4) != IFT_VERSION_RESPONSE ||
+           load_be32(bytes + 8) != 0x14) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unexpected response to IF-T/TLS version negotiation:\n"));
+               dump_buf_hex(vpninfo, PRG_ERR, '<', (void *)bytes, ret);
+               ret = -EINVAL;
+               goto out;
+       }
+       vpn_progress(vpninfo, PRG_TRACE, _("IF-T/TLS version from server: %d\n"),
+                    bytes[0x13]);
+
+       /* Client information packet over IF-T/TLS */
+       buf_truncate(reqbuf);
+       buf_append_ift_hdr(reqbuf, VENDOR_JUNIPER, 0x88);
+       buf_append(reqbuf, "clientHostName=%s", vpninfo->localname);
+       bytes[0] = 0;
+       if (vpninfo->peer_addr && vpninfo->peer_addr->sa_family == AF_INET6) {
+               struct sockaddr_in6 a;
+               socklen_t l = sizeof(a);
+               if (!getsockname(vpninfo->ssl_fd, (void *)&a, &l))
+                       inet_ntop(AF_INET6, &a.sin6_addr, (void *)bytes, sizeof(bytes));
+       } else if (vpninfo->peer_addr && vpninfo->peer_addr->sa_family == AF_INET) {
+               struct sockaddr_in a;
+               socklen_t l = sizeof(a);
+               if (!getsockname(vpninfo->ssl_fd, (void *)&a, &l))
+                       inet_ntop(AF_INET, &a.sin_addr, (void *)bytes, sizeof(bytes));
+       }
+       if (bytes[0])
+               buf_append(reqbuf, " clientIp=%s", bytes);
+       buf_append(reqbuf, "\n%c", 0);
+       ret = send_ift_packet(vpninfo, reqbuf);
+       if (ret)
+               goto out;
+
+       /* Await start of auth negotiations */
+       ret = recv_ift_packet(vpninfo, (void *)bytes, sizeof(bytes));
+       if (ret < 0)
+               goto out;
+
+       /* Basically an empty IF-T/TLS auth challenge packet of type Juniper/1,
+        * without even an EAP header in the payload. */
+       if (!valid_ift_auth(bytes, ret) || ret != 0x14) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unexpected IF-T/TLS authentication challenge:\n"));
+               dump_buf_hex(vpninfo, PRG_ERR, '<', (void *)bytes, ret);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /* Start by sending an EAP Identity of 'anonymous'. At this point we
+        * aren't yet very far down the rabbithole...
+        *
+        *     --------------------------------------
+        *     |                TCP/IP              |
+        *     |------------------------------------|
+        *     |                 TLS                |
+        *     |------------------------------------|
+        *     |               IF-T/TLS             |
+        *     |------------------------------------|
+        *     | EAP (IF-T/TLS Auth Type Juniper/1) |
+        *     |------------------------------------|
+        *     |             EAP-Identity           |
+        *     --------------------------------------
+        */
+       buf_truncate(reqbuf);
+       buf_append_ift_hdr(reqbuf, VENDOR_TCG, IFT_CLIENT_AUTH_RESPONSE);
+       buf_append_be32(reqbuf, JUNIPER_1); /* IF-T/TLS Auth Type */
+       eap_ofs = buf_append_eap_hdr(reqbuf, EAP_RESPONSE, 1, EAP_TYPE_IDENTITY, 0);
+       buf_append(reqbuf, "anonymous");
+       buf_fill_eap_len(reqbuf, eap_ofs);
+       ret = send_ift_packet(vpninfo, reqbuf);
+       if (ret)
+               goto out;
+
+       /*
+        * Phase 2 may continue directly with EAP within IF-T/TLS, or if certificate
+        * auth is enabled, the server may use EAP-TTLS. In that case, we end up
+        * with EAP within EAP-Message AVPs within EAP-TTLS within IF-T/TLS.
+        * The send_eap_packet() and recv_eap_packet() functions cope with both
+        * formats. The buffers have 0x14 bytes of header space, to allow for
+        * the IF-T/TLS header which is the larger of the two.
+        *
+        *     --------------------------------------
+        *     |                TCP/IP              |
+        *     |------------------------------------|
+        *     |                 TLS                |
+        *     |------------------------------------|
+        *     |               IF-T/TLS             |
+        *     |------------------------------------|
+        *     | EAP (IF-T/TLS Auth Type Juniper/1) |
+        *     |------------------                  |
+        *     |     EAP-TTLS    |                  |
+        *     |-----------------|  (or directly)   |
+        *     | EAP-Message AVP |                  |
+        *     |-----------------|------------------|
+        *     |            EAP-Juniper-1           |
+        *     --------------------------------------
+        */
+       ret = recv_ift_packet(vpninfo, (void *)bytes, sizeof(bytes));
+       if (ret < 0)
+               goto out;
+
+       /* Check EAP header and length */
+       if (!valid_ift_auth_eap(bytes, ret)) {
+       bad_ift:
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unexpected IF-T/TLS authentication challenge:\n"));
+               dump_buf_hex(vpninfo, PRG_ERR, '<', (void *)bytes, ret);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /*
+        * We know the packet is valid at least down to the first layer of
+        * EAP in the diagram above, directly within the IF-T/TLS Auth Type
+        * of Juniper/1. Now, disambiguate between the two cases where the
+        * diagram diverges. Is it EAP-TTLS or is it EAP-Juniper-1 directly?
+        */
+       if (valid_ift_auth_eap_exj1(bytes, ret)) {
+               eap = bytes + 0x14;
+       } else {
+               /* If it isn't that, it'd better be EAP-TTLS... */
+               if (bytes[0x18] != EAP_TYPE_TTLS)
+                       goto bad_ift;
+
+               vpninfo->ttls_eap_ident = bytes[0x15];
+               vpninfo->ttls_recvbuf = malloc(16384);
+               if (!vpninfo->ttls_recvbuf)
+                       return -ENOMEM;
+               vpninfo->ttls_recvlen = 0;
+               vpninfo->ttls_recvpos = 0;
+               ttls = establish_eap_ttls(vpninfo);
+               if (!ttls) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to establish EAP-TTLS session\n"));
+                       ret = -EINVAL;
+                       goto out;
+               }
+               /* Resend the EAP Identity 'anonymous' packet within EAP-TTLS */
+               ret = send_eap_packet(vpninfo, ttls, reqbuf);
+               if (ret)
+                       goto out;
+
+               /*
+                * The recv_eap_packet() function receives and validates the EAP
+                * packet of type Extended Juniper-1, either natively or within
+                * EAP-TTLS according to whether 'ttls' is set.
+                */
+               eap = recv_eap_packet(vpninfo, ttls, bytes, sizeof(bytes));
+               if (!eap) {
+                       ret = -EIO;
+                       goto out;
+               }
+       }
+
+       /* Now we (hopefully) have the server information packet, in an EAP request
+        * from the server. Either it was received directly in IF-T/TLS, or within
+        * an EAP-Message within EAP-TTLS. Either way, the EAP message we're
+        * interested in will be at offset 0x14 in the packet, its header will
+        * have been checked, and is Expanded Juniper/1, and its payload thus
+        * starts at 0x20. And its length is sufficient that we won't underflow */
+       eap_ident = eap[1];
+       l = load_be16(eap + 2) - 0x0c; /* Already validated */
+       p = eap + 0x0c;
+
+       /* We don't actually use anything we get here. Typically it
+        * contains Juniper/0xd49 and Juniper/0xd4a word AVPs, and
+        * a Juniper/0xd56 AVP with server licensing information. */
+       while (l) {
+               if (parse_avp(vpninfo, &p, &l, &avp_p, &avp_len, &avp_flags,
+                             &avp_vendor, &avp_code)) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to parse AVP\n"));
+               bad_eap:
+                       dump_buf_hex(vpninfo, PRG_ERR, 'E', eap, load_be16(eap + 2));
+                       ret = -EINVAL;
+                       goto out;
+               }
+               dump_avp(vpninfo, avp_flags, avp_vendor, avp_code, avp_p, avp_len);
+       }
+
+       /* Present the client information and auth cookie */
+       buf_truncate(reqbuf);
+       buf_append_ift_hdr(reqbuf, VENDOR_TCG, IFT_CLIENT_AUTH_RESPONSE);
+       buf_append_be32(reqbuf, JUNIPER_1); /* IF-T/TLS Auth Type */
+       eap_ofs = buf_append_eap_hdr(reqbuf, EAP_RESPONSE, eap_ident, EAP_TYPE_EXPANDED, 1);
+       /* Their client sends a lot of other stuff here, which we don't
+        * understand and which doesn't appear to be mandatory. So leave
+        * it out for now until/unless it becomes necessary. */
+       buf_append_avp_string(reqbuf, 0xd70, vpninfo->useragent);
+       if (vpninfo->cookie)
+               buf_append_avp_string(reqbuf, 0xd53, vpninfo->cookie);
+       buf_fill_eap_len(reqbuf, eap_ofs);
+       ret = send_eap_packet(vpninfo, ttls, reqbuf);
+       if (ret)
+               goto out;
+
+
+       /* Await start of auth negotiations */
+ auth_response:
+       realm_entry = realms_found = j2_found = old_sessions = 0;
+       eap = recv_eap_packet(vpninfo, ttls, (void *)bytes, sizeof(bytes));
+       if (!eap) {
+               ret = -EIO;
+               goto out;
+       }
+
+       eap_ident = eap[1];
+       l = load_be16(eap + 2) - 0x0c; /* Already validated */
+       p = eap + 0x0c;
+
+       while (l) {
+               if (parse_avp(vpninfo, &p, &l, &avp_p, &avp_len, &avp_flags,
+                             &avp_vendor, &avp_code)) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to parse AVP\n"));
+                       goto bad_eap;
+               }
+               dump_avp(vpninfo, avp_flags, avp_vendor, avp_code, avp_p, avp_len);
+
+               /* It's a bit late for this given that we don't get it until after
+                * we provide the password. */
+               if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd55) {
+                       char md5buf[MD5_SIZE * 2 + 1];
+                       get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, md5buf);
+                       if (avp_len != MD5_SIZE * 2 || strncasecmp(avp_p, md5buf, MD5_SIZE * 2)) {
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Server certificate mismatch. Aborting due to suspected MITM attack\n"));
+                               ret = -EPERM;
+                               goto out;
+                       }
+               }
+               if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd65) {
+                       old_sessions++;
+               } else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd80) {
+                       free(user_prompt);
+                       user_prompt = strndup(avp_p, avp_len);
+               } else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd81) {
+                       free(pass_prompt);
+                       pass_prompt = strndup(avp_p, avp_len);
+               } else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd4e) {
+                       realms_found++;
+               } else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd4f) {
+                       realm_entry++;
+               } else if (avp_vendor == VENDOR_JUNIPER2 && avp_code == 0xd53) {
+                       free(vpninfo->cookie);
+                       vpninfo->cookie = strndup(avp_p, avp_len);
+                       cookie_found = 1;
+               } else if (!avp_vendor && avp_code == AVP_CODE_EAP_MESSAGE) {
+                       char *avp_c = avp_p;
+
+                       /* EAP within AVP within EAP within IF-T/TLS.
+                        * The only thing we understand here is another form of Expanded EAP,
+                        * this time with the type Juniper/2. */
+                       if (avp_len != 13 || avp_c[0] != EAP_REQUEST ||
+                           load_be16(avp_c + 2) != avp_len ||
+                           load_be32(avp_c + 4) != EXPANDED_JUNIPER ||
+                           load_be32(avp_c + 8) != 2)
+                               goto auth_unknown;
+
+                       j2_found = 1;
+                       j2_code = avp_c[12];
+                       eap2_ident = avp_c[1];
+               } else if (avp_flags & AVP_MANDATORY)
+                       goto auth_unknown;
+       }
+
+       /* We want it to be precisely one type of request, not a mixture. */
+       if (realm_entry + !!realms_found + j2_found + cookie_found + !!old_sessions != 1) {
+       auth_unknown:
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unhandled Pulse authenticationb packet, or authentication failure\n"));
+               goto bad_eap;
+       }
+
+       /* Prepare next response packet */
+       buf_truncate(reqbuf);
+       buf_append_ift_hdr(reqbuf, VENDOR_TCG, IFT_CLIENT_AUTH_RESPONSE);
+       buf_append_be32(reqbuf, JUNIPER_1); /* IF-T/TLS Auth Type */
+       eap_ofs = buf_append_eap_hdr(reqbuf, EAP_RESPONSE, eap_ident, EAP_TYPE_EXPANDED, 1);
+
+       if (!cookie_found) {
+
+               /* No user interaction when called from pulse_connect().
+                * We expect the cookie to work. */
+               if (connecting) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Pulse authentication cookie not accepted\n"));
+                       ret = -EPERM;
+                       goto out;
+               }
+
+               if (realm_entry) {
+                       vpn_progress(vpninfo, PRG_TRACE, _("Pulse realm entry\n"));
+
+                       ret = pulse_request_realm_entry(vpninfo, reqbuf);
+                       if (ret)
+                               goto out;
+               } else if (realms_found) {
+                       vpn_progress(vpninfo, PRG_TRACE, _("Pulse realm choice\n"));
+
+                       ret = pulse_request_realm_choice(vpninfo, reqbuf, realms_found, eap);
+                       if (ret)
+                               goto out;
+               } else if (j2_found) {
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("Pulse password auth request, code 0x%02x\n"),
+                                    j2_code);
+
+                       /* Present user/password form to user */
+                       ret = pulse_request_user_auth(vpninfo, reqbuf, eap2_ident,
+                                                     user_prompt, pass_prompt);
+                       if (ret)
+                               goto out;
+               } else if (old_sessions) {
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("Pulse session limit, %d sessions\n"),
+                                    old_sessions);
+                       ret = pulse_request_session_kill(vpninfo, reqbuf, old_sessions, eap);
+                       if (ret)
+                               goto out;
+               } else {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Unhandled Pulse auth request\n"));
+                       goto bad_eap;
+               }
+
+               /* If we get here, something has filled in the next response */
+               buf_fill_eap_len(reqbuf, eap_ofs);
+               ret = send_eap_packet(vpninfo, ttls, reqbuf);
+               if (ret)
+                       goto out;
+
+               goto auth_response;
+       }
+
+       /* We're done, but need to send an empty response to the above information
+        * in order that the EAP session can complete with 'success'. Not quite
+        * sure why they didn't send it as payload on the success frame, mind you. */
+       buf_fill_eap_len(reqbuf, eap_ofs);
+       ret = send_eap_packet(vpninfo, ttls, reqbuf);
+       if (ret)
+               goto out;
+
+       if (ttls) {
+               /* Normally we don't actually send the EAP-TTLS frame until
+                * we're waiting for a response, which allows us to coalesce.
+                * This time, we need to flush the outbound frames. The empty
+                * EAP response (within EAP-TTLS) causes the server to close
+                * the EAP-TTLS session and the next response is plain IF-T/TLS
+                * IFT_CLIENT_AUTH_SUCCESS just like the non-certificate mode. */
+               pulse_eap_ttls_recv(vpninfo, NULL, 0);
+       }
+
+       ret = recv_ift_packet(vpninfo, (void *)bytes, sizeof(bytes));
+       if (ret < 0)
+               goto out;
+
+       if (!valid_ift_success(bytes, ret)) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unexpected response instead of IF-T/TLS auth success:\n"));
+               dump_buf_hex(vpninfo, PRG_ERR, '<', (void *)bytes, ret);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       ret = 0;
+ out:
+       if (ret)
+               openconnect_close_https(vpninfo, 0);
+
+       buf_free(reqbuf);
+       if (ttls)
+               destroy_eap_ttls(vpninfo, ttls);
+       buf_free(vpninfo->ttls_pushbuf);
+       vpninfo->ttls_pushbuf = NULL;
+       free(vpninfo->ttls_recvbuf);
+       vpninfo->ttls_recvbuf = NULL;
+       free(user_prompt);
+       free(pass_prompt);
+       return ret;
+}
+
+int pulse_eap_ttls_send(struct openconnect_info *vpninfo, const void *data, int len)
+{
+       struct oc_text_buf *buf = vpninfo->ttls_pushbuf;
+
+       if (!buf) {
+               buf = vpninfo->ttls_pushbuf = buf_alloc();
+               if (!buf)
+                       return -ENOMEM;
+       }
+
+       /* We concatenate sent data into a single EAP-TTLS frame which is
+        * sent just before we actually need to read something. */
+       if (!buf->pos) {
+               buf_append_ift_hdr(buf, VENDOR_TCG, IFT_CLIENT_AUTH_RESPONSE);
+               buf_append_be32(buf, JUNIPER_1); /* IF-T/TLS Auth Type */
+               buf_append_eap_hdr(buf, EAP_RESPONSE, vpninfo->ttls_eap_ident,
+                                  EAP_TYPE_TTLS, 0);
+               /* Flags byte for EAP-TTLS */
+               buf_append_bytes(buf, "\0", 1);
+       }
+       buf_append_bytes(buf, data, len);
+       return len;
+}
+
+int pulse_eap_ttls_recv(struct openconnect_info *vpninfo, void *data, int len)
+{
+       struct oc_text_buf *pushbuf= vpninfo->ttls_pushbuf;
+       int ret;
+
+       if (!vpninfo->ttls_recvlen) {
+               uint8_t flags;
+
+               if (pushbuf && !buf_error(pushbuf) && pushbuf->pos) {
+                       buf_fill_eap_len(pushbuf, 0x14);
+                       ret = send_ift_packet(vpninfo, pushbuf);
+                       if (ret)
+                               return ret;
+                       buf_truncate(pushbuf);
+               } /* else send a continue? */
+               if (!len)
+                       return 0;
+
+               vpninfo->ttls_recvlen = vpninfo->ssl_read(vpninfo, (void *)vpninfo->ttls_recvbuf,
+                                                         16384);
+               if (vpninfo->ttls_recvlen > 0 && vpninfo->dump_http_traffic) {
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("Read %d bytes of IF-T/TLS EAP-TTLS record\n"),
+                                    vpninfo->ttls_recvlen);
+                       dump_buf_hex(vpninfo, PRG_TRACE, '<',
+                                    (void *)vpninfo->ttls_recvbuf,
+                                    vpninfo->ttls_recvlen);
+               }
+               if (!valid_ift_auth_eap(vpninfo->ttls_recvbuf, vpninfo->ttls_recvlen) ||
+                   vpninfo->ttls_recvlen < 0x1a ||
+                   vpninfo->ttls_recvbuf[0x18] != EAP_TYPE_TTLS) {
+               bad_pkt:
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Bad EAP-TTLS packet\n"));
+                       return -EIO;
+               }
+               vpninfo->ttls_eap_ident = vpninfo->ttls_recvbuf[0x15];
+               flags = vpninfo->ttls_recvbuf[0x19];
+               if (flags & 0x7f)
+                       goto bad_pkt;
+               if (flags & 0x80) {
+                       /* Length bit. */
+                       if (vpninfo->ttls_recvlen < 0x1e ||
+                           load_be32(vpninfo->ttls_recvbuf + 0x1a) != vpninfo->ttls_recvlen - 0x1e)
+                               goto bad_pkt;
+                       vpninfo->ttls_recvpos = 0x1e;
+                       vpninfo->ttls_recvlen -= 0x1e;
+               } else {
+                       vpninfo->ttls_recvpos = 0x1a;
+                       vpninfo->ttls_recvlen -= 0x1a;
+               }
+       }
+
+       if (len > vpninfo->ttls_recvlen) {
+               memcpy(data, vpninfo->ttls_recvbuf + vpninfo->ttls_recvpos,
+                      vpninfo->ttls_recvlen);
+               len = vpninfo->ttls_recvlen;
+               vpninfo->ttls_recvlen = 0;
+               return len;
+       }
+       memcpy(data, vpninfo->ttls_recvbuf + vpninfo->ttls_recvpos, len);
+       vpninfo->ttls_recvpos += len;
+       vpninfo->ttls_recvlen -= len;
+       return len;
+
+}
+
+int pulse_obtain_cookie(struct openconnect_info *vpninfo)
+{
+       return pulse_authenticate(vpninfo, 0);
+}
+
+int pulse_connect(struct openconnect_info *vpninfo)
+{
+       unsigned char bytes[16384];
+       int ret = 0, l;
+       unsigned char *p;
+       int routes_len = 0;
+
+       /* If we already have a channel open, it's because we have just
+        * successfully authenticated on it from pulse_obtain_cookie(). */
+       if (vpninfo->ssl_fd == -1) {
+               ret = pulse_authenticate(vpninfo, 1);
+               if (ret)
+                       return ret;
+       }
+
+       ret = recv_ift_packet(vpninfo, (void *)bytes, sizeof(bytes));
+       if (ret < 0)
+               return ret;
+
+
+       /* Example config packet:
+
+          < 0000: 00 00 0a 4c 00 00 00 01  00 00 01 80 00 00 01 fb  |...L............|
+          < 0010: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
+          < 0020: 2c 20 f0 00 00 00 00 00  00 00 01 70 2e 00 00 78  |, .........p...x|
+          < 0030: 07 00 00 00 07 00 00 10  00 00 ff ff 05 05 00 00  |................|
+          < 0040: 05 05 ff ff 07 00 00 10  00 00 ff ff 07 00 00 00  |................|
+          < 0050: 07 00 00 ff 07 00 00 10  00 00 ff ff 08 08 08 08  |................|
+          < 0060: 08 08 08 08 f1 00 00 10  00 00 ff ff 06 06 06 06  |................|
+          < 0070: 06 06 06 07 f1 00 00 10  00 00 ff ff 09 09 09 09  |................|
+          < 0080: 09 09 09 09 f1 00 00 10  00 00 ff ff 0a 0a 0a 0a  |................|
+          < 0090: 0a 0a 0a 0a f1 00 00 10  00 00 ff ff 0b 0b 0b 0b  |................|
+          < 00a0: 0b 0b 0b 0b 00 00 00 dc  03 00 00 00 40 00 00 01  |............@...|
+          < 00b0: 00 40 01 00 01 00 40 1f  00 01 00 40 20 00 01 00  |.@....@....@ ...|
+          < 00c0: 40 21 00 01 00 40 05 00  04 00 00 05 78 00 03 00  |@!...@......x...|
+          < 00d0: 04 08 08 08 08 00 03 00  04 08 08 04 04 40 06 00  |.............@..|
+          < 00e0: 0c 70 73 65 63 75 72 65  2e 6e 65 74 00 40 07 00  |.psecure.net.@..|
+          < 00f0: 04 00 00 00 00 00 04 00  04 01 01 01 01 40 19 00  |.............@..|
+          < 0100: 01 01 40 1a 00 01 00 40  0f 00 02 00 00 40 10 00  |..@....@.....@..|
+          < 0110: 02 00 05 40 11 00 02 00  02 40 12 00 04 00 00 04  |...@.....@......|
+          < 0120: b0 40 13 00 04 00 00 00  00 40 14 00 04 00 00 00  |.@.......@......|
+          < 0130: 01 40 15 00 04 00 00 00  00 40 16 00 02 11 94 40  |.@.......@.....@|
+          < 0140: 17 00 04 00 00 00 0f 40  18 00 04 00 00 00 3c 00  |.......@......<.|
+          < 0150: 01 00 04 0a 14 03 01 00  02 00 04 ff ff ff ff 40  |...............@|
+          < 0160: 0b 00 04 0a c8 c8 c8 40  0c 00 01 00 40 0d 00 01  |.......@....@...|
+          < 0170: 00 40 0e 00 01 00 40 1b  00 01 00 40 1c 00 01 00  |.@....@....@....|
+
+          It starts as an IF-T/TLS packet of type Juniper/1.
+
+          Lots of zeroes at the start, and at 0x20 there is a distinctive 0x2c20f000
+          signature which appears to be in all config packets.
+
+          At 0x28 it has the payload length (0x10 less than the full IF-T length).
+          0x2c is the start of the routing information. The 0x2e byte always
+          seems to be there, and in this example 0x78 is the length of the
+          routing information block. The number of entries is in byte 0x30.
+          In the absence of IPv6 perhaps, the length at 0x2c seems always to be
+          the number of entries (in 0x30) * 0x10 + 8.
+
+          Routing entries are 0x10 bytes each, starting at 0x34. The ones starting
+          with 0x07 are include, with 0xf1 are exclude. No idea what the following 7
+          bytes 0f 00 00 10 00 00 ff ff mean; perhaps the 0010 is a length? The IP
+          address range is in bytes 8-11 (starting address) and the highest address
+          of the range (traditionally a broadcast address) is in bytes 12-15.
+
+          After the routing inforamation (in this example at 0xa4) comes another
+          length field, this time for the information elements which comprise
+          the rest of the packet. Not sure what the 03 00 00 00 at 0xa8 means;
+          it *could* be an element type 0x3000 with payload length zero but if it
+          is, we don't know what it means. Following that, the elements all have
+          two bytes of type followed by two bytes length, then their payload.
+
+          There follows an attempt to parse the packet based on the above
+          understanding. Having more examples, especially with IPv6 split includes
+          and excludes, would be useful...
+       */
+
+       if (ret < 0x50 ||
+           /* IF-T/TLS header */
+           load_be32(bytes) != VENDOR_JUNIPER ||
+           load_be32(bytes + 4) != 1 ||
+           load_be32(bytes + 8) != ret ||
+           /* This appears to indicate the packet type (vs. ESP config) */
+           load_be32(bytes + 0x20) != 0x2c20f000 ||
+           /* A length field */
+           load_be32(bytes + 0x28) != ret - 0x10 ||
+           /* Start of routing information */
+           load_be16(bytes + 0x2c) != 0x2e00 ||
+           /* Routing length makes sense */
+           (routes_len = load_be16(bytes + 0x2e)) != ((int)bytes[0x30] * 0x10 + 8) ||
+           /* Make sure the next length field is actually present... */
+           ret < 0x34 + 4 + routes_len ||
+           /* Another length field (and maybe some adjacent zeroes) */
+           load_be32(bytes + 0x2c + routes_len) + routes_len + 0x2c != ret) {
+       bad_config:
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unexpected Pulse config packet:\n"));
+               dump_buf_hex(vpninfo, PRG_ERR, '<', (void *)bytes, ret);
+               return -EINVAL;
+       }
+       p = bytes + 0x34;
+       routes_len -= 8;
+       /* We know it's a multiple of 0x10 now. We checked. */
+       while (routes_len) {
+               char buf[80];
+               /* Probably not a whole be32 but let's see if anything ever changes */
+               uint32_t type = load_be32(p);
+               uint32_t ffff = load_be32(p+4);
+
+               if (ffff != 0xffff)
+                       goto bad_config;
+
+               /* Convert the range end into a netmask by xor. Mask out the
+                * bits in the network address, leaving only the low bits set,
+                * then invert what's left so that only the high bits are set
+                * as in a normal netmask.
+                *
+                * e.g.
+                * 10.0.0.0-10.0.63.255 becomes 0.0.63.255 becomes 255.255.192.0
+               */
+               snprintf(buf, sizeof(buf), "%d.%d.%d.%d/%d.%d.%d.%d",
+                        p[8], p[9], p[10], p[11],
+                        255 ^ (p[8] ^ p[12]),  255 ^ (p[9] ^ p[13]),
+                        255 ^ (p[10] ^ p[14]),  255 ^ (p[11] ^ p[15]));
+
+               if (type == 0x07000010) {
+                       struct oc_split_include *inc;
+
+                       vpn_progress(vpninfo, PRG_DEBUG, _("Received split include route %s\n"), buf);
+                       inc = malloc(sizeof(*inc));
+                       if (inc) {
+                               inc->route = add_option(vpninfo, "split-include", buf, -1);
+                               if (inc->route) {
+                                       inc->next = vpninfo->ip_info.split_includes;
+                                       vpninfo->ip_info.split_includes = inc;
+                               } else
+                                       free(inc);
+                       }
+               } else if (type == 0xf1000010) {
+                       struct oc_split_include *exc;
+
+                       vpn_progress(vpninfo, PRG_DEBUG, _("Received split exclude route %s\n"), buf);
+                       exc = malloc(sizeof(*exc));
+                       if (exc) {
+                               exc->route = add_option(vpninfo, "split-exclude", buf, -1);
+                               if (exc->route) {
+                                       exc->next = vpninfo->ip_info.split_excludes;
+                                       vpninfo->ip_info.split_excludes = exc;
+                               } else
+                                       free(exc);
+                       }
+               } else
+                       goto bad_config;
+
+               p += 0x10;
+               routes_len -= 0x10;
+       }
+
+       /* p now points at the length field of the final elements, which
+          was already checked. */
+       l = load_be32(p);
+       /* No idea what this is */
+       if (l < 8 || load_be32(p + 4) != 0x03000000)
+               goto bad_config;
+       p += 8;
+       l -= 8;
+
+       while (l) {
+               uint16_t type = load_be16(p);
+               uint16_t len = load_be16(p+2);
+
+               if (len + 4 > l)
+                       goto bad_config;
+
+               p += 4;
+               l -= 4;
+               process_attr(vpninfo, type, p, len);
+               p += len;
+               l -= len;
+               if (l && l < 4)
+                       goto bad_config;
+       }
+
+       if (!vpninfo->ip_info.mtu ||
+           (!vpninfo->ip_info.addr && !vpninfo->ip_info.addr6)) {
+               vpn_progress(vpninfo, PRG_ERR, "Insufficient configuration found\n");
+               goto bad_config;
+       }
+
+       ret = 0;
+       monitor_fd_new(vpninfo, ssl);
+       monitor_read_fd(vpninfo, ssl);
+       monitor_except_fd(vpninfo, ssl);
+
+       free(vpninfo->cstp_pkt);
+       vpninfo->cstp_pkt = NULL;
+
+       return ret;
+}
+
+
+int pulse_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable)
+{
+       int ret;
+       int work_done = 0;
+
+       if (vpninfo->ssl_fd == -1)
+               goto do_reconnect;
+
+       /* FIXME: The poll() handling here is fairly simplistic. Actually,
+          if the SSL connection stalls it could return a WANT_WRITE error
+          on _either_ of the SSL_read() or SSL_write() calls. In that case,
+          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 (readable) {
+               /* Some servers send us packets that are larger than
+                  negotiated MTU. We reserve some extra space to
+                  handle that */
+               int receive_mtu = MAX(16384, vpninfo->deflate_pkt_size ? : vpninfo->ip_info.mtu);
+               int len, payload_len;
+
+               if (!vpninfo->cstp_pkt) {
+                       vpninfo->cstp_pkt = malloc(sizeof(struct pkt) + receive_mtu);
+                       if (!vpninfo->cstp_pkt) {
+                               vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n"));
+                               break;
+                       }
+               }
+
+               len = ssl_nonblock_read(vpninfo, &vpninfo->cstp_pkt->pulse.vendor, receive_mtu + 16);
+               if (!len)
+                       break;
+               if (len < 0)
+                       goto do_reconnect;
+               if (len < 16) {
+                       vpn_progress(vpninfo, PRG_ERR, _("Short packet received (%d bytes)\n"), len);
+                       vpninfo->quit_reason = "Short packet received";
+                       return 1;
+               }
+
+               if (load_be32(&vpninfo->cstp_pkt->pulse.vendor) != VENDOR_JUNIPER ||
+                   load_be32(&vpninfo->cstp_pkt->pulse.len) != len)
+                       goto unknown_pkt;
+
+               vpninfo->ssl_times.last_rx = time(NULL);
+
+               switch(load_be32(&vpninfo->cstp_pkt->pulse.type)) {
+               case 4:
+                       payload_len = len - 16;
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("Received data packet of %d bytes\n"),
+                                    payload_len);
+                       vpninfo->cstp_pkt->len = payload_len;
+                       queue_packet(&vpninfo->incoming_queue, vpninfo->cstp_pkt);
+                       vpninfo->cstp_pkt = NULL;
+                       work_done = 1;
+                       continue;
+
+               default:
+               unknown_pkt:
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Unknown Pulse packet\n"));
+                       dump_buf_hex(vpninfo, PRG_TRACE, '<', (void *)&vpninfo->cstp_pkt->pulse.vendor, len);
+                       continue;
+               }
+       }
+
+
+       /* If SSL_write() fails we are expected to try again. With exactly
+          the same data, at exactly the same location. So we keep the
+          packet we had before.... */
+       if (vpninfo->current_ssl_pkt) {
+       handle_outgoing:
+               vpninfo->ssl_times.last_tx = time(NULL);
+               unmonitor_write_fd(vpninfo, ssl);
+
+
+               vpn_progress(vpninfo, PRG_TRACE, _("Packet outgoing:\n"));
+               dump_buf_hex(vpninfo, PRG_TRACE, '>',
+                            (void *)&vpninfo->current_ssl_pkt->pulse.vendor,
+                            vpninfo->current_ssl_pkt->len + 16);
+
+               ret = ssl_nonblock_write(vpninfo,
+                                        &vpninfo->current_ssl_pkt->pulse.vendor,
+                                        vpninfo->current_ssl_pkt->len + 16);
+               if (ret < 0) {
+                       do_reconnect:
+                       /* XXX: Do we have to do this or can we leave it open?
+                        * Perhaps we could even reconnect asynchronously while
+                        * the ESP is still running? */
+#ifdef HAVE_ESP
+                       esp_shutdown(vpninfo);
+#endif
+                       ret = ssl_reconnect(vpninfo);
+                       if (ret) {
+                               vpn_progress(vpninfo, PRG_ERR, _("Reconnect failed\n"));
+                               vpninfo->quit_reason = "Pulse reconnect failed";
+                               return ret;
+                       }
+                       vpninfo->dtls_need_reconnect = 1;
+                       return 1;
+               } else if (!ret) {
+#if 0 /* Not for Pulse yet */
+                       /* -EAGAIN: ssl_nonblock_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:
+                               /* This should never happen */
+                               ;
+                       }
+#else
+                       return work_done;
+#endif
+               }
+
+               if (ret != vpninfo->current_ssl_pkt->len + 16) {
+                       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) {
+                       free(vpninfo->pending_deflated_pkt);
+                       vpninfo->pending_deflated_pkt = NULL;
+               } else
+                       free(vpninfo->current_ssl_pkt);
+
+               vpninfo->current_ssl_pkt = NULL;
+       }
+
+#if 0 /* Not understood for Pulse yet */
+       if (vpninfo->owe_ssl_dpd_response) {
+               vpninfo->owe_ssl_dpd_response = 0;
+               vpninfo->current_ssl_pkt = (struct pkt *)&dpd_resp_pkt;
+               goto handle_outgoing;
+       }
+
+       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. */
+               vpn_progress(vpninfo, PRG_INFO, _("CSTP rekey due\n"));
+               if (vpninfo->ssl_times.rekey_method == REKEY_TUNNEL)
+                       goto do_reconnect;
+               else if (vpninfo->ssl_times.rekey_method == REKEY_SSL) {
+                       ret = cstp_handshake(vpninfo, 0);
+                       if (ret) {
+                               /* if we failed rehandshake try establishing a new-tunnel instead of failing */
+                               vpn_progress(vpninfo, PRG_ERR, _("Rehandshake failed; attempting new-tunnel\n"));
+                               goto do_reconnect;
+                       }
+
+                       goto do_dtls_reconnect;
+               }
+               break;
+
+       case KA_DPD_DEAD:
+       peer_dead:
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("CSTP Dead Peer Detection detected dead peer!\n"));
+               goto do_reconnect;
+       do_reconnect:
+               ret = cstp_reconnect(vpninfo);
+               if (ret) {
+                       vpn_progress(vpninfo, PRG_ERR, _("Reconnect failed\n"));
+                       vpninfo->quit_reason = "CSTP reconnect failed";
+                       return ret;
+               }
+
+       do_dtls_reconnect:
+               /* succeeded, let's rekey DTLS, if it is not rekeying
+                * itself. */
+               if (vpninfo->dtls_state > DTLS_SLEEPING &&
+                   vpninfo->dtls_times.rekey_method == REKEY_NONE) {
+                       vpninfo->dtls_need_reconnect = 1;
+               }
+
+               return 1;
+
+       case KA_DPD:
+               vpn_progress(vpninfo, PRG_DEBUG, _("Send CSTP DPD\n"));
+
+               vpninfo->current_ssl_pkt = (struct pkt *)&dpd_pkt;
+               goto handle_outgoing;
+
+       case KA_KEEPALIVE:
+               /* No need to send an explicit keepalive
+                  if we have real data to send */
+               if (vpninfo->dtls_state != DTLS_CONNECTED &&
+                   vpninfo->outgoing_queue.head)
+                       break;
+
+               vpn_progress(vpninfo, PRG_DEBUG, _("Send CSTP Keepalive\n"));
+
+               vpninfo->current_ssl_pkt = (struct pkt *)&keepalive_pkt;
+               goto handle_outgoing;
+
+       case KA_NONE:
+               ;
+       }
+#endif
+       /* Service outgoing packet queue, if no DTLS */
+       while (vpninfo->dtls_state != DTLS_CONNECTED &&
+              (vpninfo->current_ssl_pkt = dequeue_packet(&vpninfo->outgoing_queue))) {
+               struct pkt *this = vpninfo->current_ssl_pkt;
+
+               store_be32(&this->pulse.vendor, VENDOR_JUNIPER);
+               store_be32(&this->pulse.type, 4);
+               store_be32(&this->pulse.len, this->len + 16);
+               store_be32(&this->pulse.ident, vpninfo->ift_seq++);
+
+               vpn_progress(vpninfo, PRG_TRACE,
+                            _("Sending IF-T/TLS data packet of %d bytes\n"),
+                            this->len);
+
+               vpninfo->current_ssl_pkt = this;
+               goto handle_outgoing;
+       }
+
+       /* Work is not done if we just got rid of packets off the queue */
+       return work_done;
+}
+
+int pulse_bye(struct openconnect_info *vpninfo, const char *reason)
+{
+       if (vpninfo->ssl_fd != -1) {
+               struct oc_text_buf *buf = buf_alloc();
+               buf_append_ift_hdr(buf, VENDOR_JUNIPER, 0x89);
+               if (!buf_error(buf))
+                       send_ift_packet(vpninfo, buf);
+               buf_free(buf);
+
+               openconnect_close_https(vpninfo, 0);
+       }
+       return 0;
+}
+
+#ifdef HAVE_ESPx
+void pulse_esp_close(struct openconnect_info *vpninfo)
+{
+       /* Tell server to stop sending on ESP channel */
+       queue_esp_control(vpninfo, 0);
+       esp_close(vpninfo);
+}
+
+int pulse_esp_send_probes(struct openconnect_info *vpninfo)
+{
+       struct pkt *pkt;
+       int pktlen, seq;
+
+       if (vpninfo->dtls_fd == -1) {
+               int fd = udp_connect(vpninfo);
+               if (fd < 0)
+                       return fd;
+
+               /* We are not connected until we get an ESP packet back */
+               vpninfo->dtls_state = DTLS_SLEEPING;
+               vpninfo->dtls_fd = fd;
+               monitor_fd_new(vpninfo, dtls);
+               monitor_read_fd(vpninfo, dtls);
+               monitor_except_fd(vpninfo, dtls);
+       }
+
+       pkt = malloc(sizeof(*pkt) + 1 + vpninfo->pkt_trailer);
+       if (!pkt)
+               return -ENOMEM;
+
+       for (seq=1; seq <= (vpninfo->dtls_state==DTLS_CONNECTED ? 1 : 2); seq++) {
+               pkt->len = 1;
+               pkt->data[0] = 0;
+               pktlen = encrypt_esp_packet(vpninfo, pkt);
+               if (pktlen >= 0)
+                       send(vpninfo->dtls_fd, (void *)&pkt->esp, pktlen, 0);
+       }
+       free(pkt);
+
+       vpninfo->dtls_times.last_tx = time(&vpninfo->new_dtls_started);
+
+       return 0;
+};
+
+int pulse_esp_catch_probe(struct openconnect_info *vpninfo, struct pkt *pkt)
+{
+       return (pkt->len == 1 && pkt->data[0] == 0);
+}
+#endif /* HAVE_ESP */
index 680c6c2a36e7860258fb268c2332162d4f0437ea..5674e4edc5f5019a76a48a6545a07d06d5c5642d 100644 (file)
@@ -6,7 +6,7 @@ CONV    = "$(srcdir)/html.py"
 FTR_PAGES = csd.html charset.html token.html pkcs11.html tpm.html features.html gui.html nonroot.html hip.html tncc.html
 START_PAGES = building.html connecting.html manual.html vpnc-script.html 
 INDEX_PAGES = changelog.html download.html index.html packages.html platforms.html licence.html
-PROTO_PAGES = anyconnect.html juniper.html globalprotect.html
+PROTO_PAGES = anyconnect.html juniper.html globalprotect.html pulse.html
 TOPLEVEL_PAGES = contribute.html mail.html
 
 ALL_PAGES = $(FTR_PAGES) $(START_PAGES) $(INDEX_PAGES) $(TOPLEVEL_PAGES) $(PROTO_PAGES)
index b5fc071566d5a7736b9006b2876f6cc6d8890a12..2a4887bd38d4ba9f71b6ff1e439723b6d4e1bdd9 100644 (file)
@@ -16,6 +16,7 @@
    <li><b>OpenConnect HEAD</b>
      <ul>
        <li>Rework DTLS MTU detection. (<a href="https://gitlab.com/openconnect/openconnect/issues/10">#10</a>)</li>
+       <li>Add Pulse Connect Secure support.</li>
      </ul><br/>
   </li>
   <li><b><a href="ftp://ftp.infradead.org/pub/openconnect/openconnect-8.03.tar.gz">OpenConnect v8.03</a></b>
index 2e51cb5c3c94cb872ea1ebd53dc7509a4404bb2a..8f4d82f173bb6294189200cb1cbcac737791c306 100644 (file)
@@ -3,5 +3,6 @@
         <MENU topic="AnyConnect" link="anyconnect.html" mode="VAR_SEL_ANYCONNECT" />
         <MENU topic="Juniper" link="juniper.html" mode="VAR_SEL_JUNIPER" />
         <MENU topic="GlobalProtect" link="globalprotect.html" mode="VAR_SEL_GLOBALPROTECT" />
+        <MENU topic="Pulse Secure" link="pulse.html" mode="VAR_SEL_PULSE" />
        <ENDMENU />
 </PAGE>
diff --git a/www/pulse.xml b/www/pulse.xml
new file mode 100644 (file)
index 0000000..94b15ba
--- /dev/null
@@ -0,0 +1,51 @@
+<PAGE>
+       <INCLUDE file="inc/header.tmpl" />
+
+       <VAR match="VAR_SEL_PROTOCOLS" replace="selected" />
+       <VAR match="VAR_SEL_PULSE" replace="selected" />
+       <PARSE file="menu1.xml" />
+       <PARSE file="menu2-protocols.xml" />
+       
+       <INCLUDE file="inc/content.tmpl" />
+
+<h1>Pulse Connect Secure</h1>
+
+<p>Support for Pulse Connect Secure was added to OpenConnect in June 2019,
+for the 8.04 release. In most cases it supersedes the older Juniper Network
+Connect support. It is a much saner protocol.</p>
+
+<p>Pulse mode is requested by adding <tt>--protocol=pulse</tt>
+to the command line:
+<pre>
+  openconnect --protocol=pulse vpn.example.com
+</pre></p>
+
+<p>The TCP transport for Pulse Connect Secure works over
+<a href="https://trustedcomputinggroup.org/resource/tnc-if-t-binding-to-tls/">IF-T/TLS</a>,
+first using EAP (and EAP-TTLS if certificates are being used) for
+authentication and then passing traffic over IF-T messages over
+the same transport. Just as with the older Juniper protocol, the UDP
+transport is <a href="https://tools.ietf.org/html/rfc3948">ESP</a>.</p>
+
+<h2>Authentication</h2>
+
+<p>The authentication cookies are compatible with the
+<a href="juniper.html">Juniper</a> mode, which means that external
+tools like <a href="https://github.com/russdill/juniper-vpn-py">juniper-vpn-py</a>
+should be usable with OpenConnect in Pulse mode too.</p>
+
+<h3>Host Checker</h3>
+
+<p>Not yet investigated and implemented for Pulse mode. The Juniper support may
+suffice for some users.</p>
+
+<h2>Connectivity</h2>
+
+<p>Once authentication is complete, the VPN connection can be
+established. Both Legacy IP and IPv6 should be working, although
+test reports from someone with an IPv6-capable server would be greatly
+appreciated as the freely available demo Virtual Appliance does not
+support IPv6.</p>
+
+       <INCLUDE file="inc/footer.tmpl" />
+</PAGE>