resetting manifest requested domain to floor
[platform/upstream/openconnect.git] / http.c
diff --git a/http.c b/http.c
index 28cdf64..bcc4e98 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1,7 +1,7 @@
 /*
  * OpenConnect (SSL + DTLS) VPN client
  *
- * Copyright © 2008-2010 Intel Corporation.
+ * Copyright © 2008-2012 Intel Corporation.
  * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
  *
  * Author: David Woodhouse <dwmw2@infradead.org>
@@ -23,7 +23,6 @@
  *   Boston, MA 02110-1301 USA
  */
 
-#define _GNU_SOURCE
 #include <netdb.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <pwd.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
 
-#include <openssl/ssl.h>
-#include <openssl/err.h>
-#include <openssl/engine.h>
+#include "openconnect-internal.h"
 
-#include "openconnect.h"
-
-static int proxy_write(int fd, unsigned char *buf, size_t len);
+static int proxy_write(struct openconnect_info *vpninfo, int fd,
+                      unsigned char *buf, size_t len);
+static int proxy_read(struct openconnect_info *vpninfo, int fd,
+                     unsigned char *buf, size_t len);
 
 #define MAX_BUF_LEN 131072
+#define BUF_CHUNK_SIZE 4096
+
+struct oc_text_buf {
+       char *data;
+       int pos;
+       int buf_len;
+       int error;
+};
+
+static struct oc_text_buf *buf_alloc(void)
+{
+       return calloc(1, sizeof(struct oc_text_buf));
+}
+
+static void buf_append(struct oc_text_buf *buf, const char *fmt, ...)
+{
+       va_list ap;
+
+       if (!buf || buf->error)
+               return;
+
+       if (!buf->data) {
+               buf->data = malloc(BUF_CHUNK_SIZE);
+               if (!buf->data) {
+                       buf->error = -ENOMEM;
+                       return;
+               }
+               buf->buf_len = BUF_CHUNK_SIZE;
+       }
+
+       while (1) {
+               int max_len = buf->buf_len - buf->pos, ret;
+
+               va_start(ap, fmt);
+               ret = vsnprintf(buf->data + buf->pos, max_len, fmt, ap);
+               va_end(ap);
+               if (ret < 0) {
+                       buf->error = -EIO;
+                       break;
+               } else if (ret < max_len) {
+                       buf->pos += ret;
+                       break;
+               } else {
+                       int new_buf_len = buf->buf_len + BUF_CHUNK_SIZE;
+
+                       if (new_buf_len > MAX_BUF_LEN) {
+                               /* probably means somebody is messing with us */
+                               buf->error = -E2BIG;
+                               break;
+                       }
+
+                       buf->data = realloc(buf->data, new_buf_len);
+                       if (!buf->data) {
+                               buf->error = -ENOMEM;
+                               break;
+                       }
+                       buf->buf_len = new_buf_len;
+               }
+       }
+}
+
+static int buf_error(struct oc_text_buf *buf)
+{
+       return buf ? buf->error : -ENOMEM;
+}
+
+static int buf_free(struct oc_text_buf *buf)
+{
+       int error = buf_error(buf);
+
+       if (buf) {
+               if (buf->data)
+                       free(buf->data);
+               free(buf);
+       }
+
+       return error;
+}
+
 /*
  * We didn't really want to have to do this for ourselves -- one might have
  * thought that it would be available in a library somewhere. But neither
@@ -51,14 +132,16 @@ static int proxy_write(int fd, unsigned char *buf, size_t len);
  * provided by their caller.
  */
 
-int http_add_cookie(struct openconnect_info *vpninfo, const char *option, const char *value)
+static int http_add_cookie(struct openconnect_info *vpninfo,
+                          const char *option, const char *value)
 {
        struct vpn_option *new, **this;
 
        if (*value) {
                new = malloc(sizeof(*new));
                if (!new) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "No memory for allocating cookies\n");
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("No memory for allocating cookies\n"));
                        return -ENOMEM;
                }
                new->next = NULL;
@@ -111,8 +194,9 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
        int i;
 
  cont:
-       if (openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)) < 0) {
-               vpninfo->progress(vpninfo, PRG_ERR, "Error fetching HTTPS response\n");
+       if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error fetching HTTPS response\n"));
                return -EINVAL;
        }
 
@@ -120,52 +204,38 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
                closeconn = 1;
        
        if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
-               vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse HTTP response '%s'\n", buf);
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to parse HTTP response '%s'\n"), buf);
                return -EINVAL;
        }
 
-       vpninfo->progress(vpninfo, PRG_TRACE, "Got HTTP response: %s\n", buf);
+       vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
+                    _("Got HTTP response: %s\n"), buf);
 
        /* Eat headers... */
-       while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
+       while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
                char *colon;
 
-               vpninfo->progress(vpninfo, PRG_TRACE, "%s\n", buf);
-
                if (i < 0) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "Error processing HTTP response\n");
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Error processing HTTP response\n"));
                        return -EINVAL;
                }
                colon = strchr(buf, ':');
                if (!colon) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "Ignoring unknown HTTP response line '%s'\n", buf);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Ignoring unknown HTTP response line '%s'\n"), buf);
                        continue;
                }
                *(colon++) = 0;
                if (*colon == ' ')
                        colon++;
 
-               if (!strcasecmp(buf, "Connection")) {
-                       if (!strcasecmp(colon, "Close"))
-                               closeconn = 1;
-                       else if (!strcasecmp(colon, "Keep-Alive"))
-                               closeconn = 0;
-               }
-               if (!strcasecmp(buf, "Location")) {
-                       vpninfo->redirect_url = strdup(colon);
-                       if (!vpninfo->redirect_url)
-                               return -ENOMEM;
-               }
-               if (!strcasecmp(buf, "Content-Length")) {
-                       bodylen = atoi(colon);
-                       if (bodylen < 0) {
-                               vpninfo->progress(vpninfo, PRG_ERR, "Response body has negative size (%d)\n",
-                                                 bodylen);
-                               return -EINVAL;
-                       }
-               }
+               /* Handle Set-Cookie first so that we can avoid printing the
+                  webvpn cookie in the verbose debug output */
                if (!strcasecmp(buf, "Set-Cookie")) {
                        char *semicolon = strchr(colon, ';');
+                       const char *print_equals;
                        char *equals = strchr(colon, '=');
                        int ret;
 
@@ -173,20 +243,69 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
                                *semicolon = 0;
 
                        if (!equals) {
-                               vpninfo->progress(vpninfo, PRG_ERR, "Invalid cookie offered: %s\n", buf);
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Invalid cookie offered: %s\n"), buf);
                                return -EINVAL;
                        }
                        *(equals++) = 0;
 
+                       print_equals = equals;
+                       /* Don't print the webvpn cookie unless it's empty; we don't
+                          want people posting it in public with debugging output */
+                       if (!strcmp(colon, "webvpn") && *equals)
+                               print_equals = _("<elided>");
+                       vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
+                                    buf, colon, print_equals, semicolon?";":"",
+                                    semicolon?(semicolon+1):"");
+
+                       /* The server tends to ask for the username and password as
+                          usual, even if we've already failed because it didn't like
+                          our cert. Thankfully it does give us this hint... */
+                       if (!strcmp(colon, "ClientCertAuthFailed"))
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("SSL certificate authentication failed\n"));
+
                        ret = http_add_cookie(vpninfo, colon, equals);
                        if (ret)
                                return ret;
+               } else {
+                       vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
+               }
+
+               if (!strcasecmp(buf, "Connection")) {
+                       if (!strcasecmp(colon, "Close"))
+                               closeconn = 1;
+#if 0
+                       /* This might seem reasonable, but in fact it breaks
+                          certificate authentication with some servers. If
+                          they give an HTTP/1.0 response, even if they
+                          explicitly give a Connection: Keep-Alive header,
+                          just close the connection. */
+                       else if (!strcasecmp(colon, "Keep-Alive"))
+                               closeconn = 0;
+#endif
+               }
+               if (!strcasecmp(buf, "Location")) {
+                       vpninfo->redirect_url = strdup(colon);
+                       if (!vpninfo->redirect_url)
+                               return -ENOMEM;
+               }
+               if (!strcasecmp(buf, "Content-Length")) {
+                       bodylen = atoi(colon);
+                       if (bodylen < 0) {
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Response body has negative size (%d)\n"),
+                                            bodylen);
+                               return -EINVAL;
+                       }
                }
                if (!strcasecmp(buf, "Transfer-Encoding")) {
                        if (!strcasecmp(colon, "chunked"))
                                bodylen = BODY_CHUNKED;
                        else {
-                               vpninfo->progress(vpninfo, PRG_ERR, "Unknown Transfer-Encoding: %s\n", colon);
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Unknown Transfer-Encoding: %s\n"),
+                                            colon);
                                return -EINVAL;
                        }
                }
@@ -199,10 +318,10 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
                goto cont;
 
        /* Now the body, if there is one */
-       vpninfo->progress(vpninfo, PRG_TRACE, "HTTP body %s (%d)\n"
-                         bodylen==BODY_HTTP10?"http 1.0" :
-                         bodylen==BODY_CHUNKED?"chunked" : "length: ",
-                         bodylen);
+       vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n")
+                    bodylen==BODY_HTTP10?"http 1.0" :
+                    bodylen==BODY_CHUNKED?"chunked" : "length: ",
+                    bodylen);
 
        /* If we were given Content-Length, it's nice and easy... */
        if (bodylen > 0) {
@@ -210,9 +329,10 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
                if (!body)
                        return -ENOMEM;
                while (done < bodylen) {
-                       i = SSL_read(vpninfo->https_ssl, body + done, bodylen - done);
+                       i = openconnect_SSL_read(vpninfo, body + done, bodylen - done);
                        if (i < 0) {
-                               vpninfo->progress(vpninfo, PRG_ERR, "Error reading HTTP response body\n");
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Error reading HTTP response body\n"));
                                 free(body);
                                return -EINVAL;
                        }
@@ -220,12 +340,13 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
                }
        } else if (bodylen == BODY_CHUNKED) {
                /* ... else, chunked */
-               while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
+               while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
                        int chunklen, lastchunk = 0;
 
                        if (i < 0) {
-                               vpninfo->progress(vpninfo, PRG_ERR, "Error fetching chunk header\n");
-                               exit(1);
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Error fetching chunk header\n"));
+                               return i;
                        }
                        chunklen = strtol(buf, NULL, 16);
                        if (!chunklen) {
@@ -236,9 +357,10 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
                        if (!body)
                                return -ENOMEM;
                        while (chunklen) {
-                               i = SSL_read(vpninfo->https_ssl, body + done, chunklen);
+                               i = openconnect_SSL_read(vpninfo, body + done, chunklen);
                                if (i < 0) {
-                                       vpninfo->progress(vpninfo, PRG_ERR, "Error reading HTTP response body\n");
+                                       vpn_progress(vpninfo, PRG_ERR,
+                                                    _("Error reading HTTP response body\n"));
                                         free(body);
                                        return -EINVAL;
                                }
@@ -246,12 +368,14 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
                                done += i;
                        }
                skip:
-                       if ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
+                       if ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
                                if (i < 0) {
-                                       vpninfo->progress(vpninfo, PRG_ERR, "Error fetching HTTP response body\n");
+                                       vpn_progress(vpninfo, PRG_ERR,
+                                                    _("Error fetching HTTP response body\n"));
                                } else {
-                                       vpninfo->progress(vpninfo, PRG_ERR, "Error in chunked decoding. Expected '', got: '%s'",
-                                                         buf);
+                                       vpn_progress(vpninfo, PRG_ERR,
+                                                    _("Error in chunked decoding. Expected '', got: '%s'"),
+                                                    buf);
                                }
                                 free(body);
                                return -EINVAL;
@@ -262,7 +386,9 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
                }
        } else if (bodylen == BODY_HTTP10) {
                if (!closeconn) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "Cannot receive HTTP 1.0 body without closing connection\n");
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Cannot receive HTTP 1.0 body without closing connection\n"));
+                       openconnect_close_https(vpninfo, 0);
                        return -EINVAL;
                }
 
@@ -271,23 +397,26 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
                        body = realloc(body, done + 16384);
                        if (!body)
                                return -ENOMEM;
-                       i = SSL_read(vpninfo->https_ssl, body + done, 16384);
-                       if (i <= 0) {
+                       i = openconnect_SSL_read(vpninfo, body + done, 16384);
+                       if (i > 0) {
+                               /* Got more data */
+                               done += i;
+                       } else if (i < 0) {
+                               /* Error */
+                               free(body);
+                               return i;
+                       } else {
+                               /* Connection closed. Reduce allocation to just what we need */
                                body = realloc(body, done + 1);
                                if (!body)
                                        return -ENOMEM;
                                break;
                         }
-                       done += i;
                }
        }
 
-       if (closeconn) {
-               SSL_free(vpninfo->https_ssl);
-               vpninfo->https_ssl = NULL;
-               close(vpninfo->ssl_fd);
-               vpninfo->ssl_fd = -1;
-       }
+       if (closeconn || vpninfo->no_http_keepalive)
+               openconnect_close_https(vpninfo, 0);
 
        if (body)
                body[done] = 0;
@@ -295,33 +424,58 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
        return done;
 }
 
-static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
-                       char *server_sha1)
+static void add_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
 {
        struct vpn_option *opt;
-       char buf[MAX_BUF_LEN];
-       char *config_buf = NULL;
-       int result, buflen;
-       unsigned char local_sha1_bin[SHA_DIGEST_LENGTH];
-       char local_sha1_ascii[(SHA_DIGEST_LENGTH * 2)+1];
-       EVP_MD_CTX c;
-       int i;
 
-       sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
-       sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
-       sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
-       sprintf(buf + strlen(buf),  "Accept: */*\r\n");
-       sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
+       buf_append(buf, "Host: %s\r\n", vpninfo->hostname);
+       buf_append(buf, "User-Agent: %s\r\n", vpninfo->useragent);
+       buf_append(buf, "Accept: */*\r\n");
+       buf_append(buf, "Accept-Encoding: identity\r\n");
 
        if (vpninfo->cookies) {
-               sprintf(buf + strlen(buf),  "Cookie: ");
+               buf_append(buf, "Cookie: ");
                for (opt = vpninfo->cookies; opt; opt = opt->next)
-                       sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
+                       buf_append(buf, "%s=%s%s", opt->option,
                                      opt->value, opt->next ? "; " : "\r\n");
        }
-       sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
+       buf_append(buf, "X-Transcend-Version: 1\r\n");
+       buf_append(buf, "X-Aggregate-Auth: 1\r\n");
+       buf_append(buf, "X-AnyConnect-Platform: %s\r\n", vpninfo->platname);
+}
 
-       SSL_write(vpninfo->https_ssl, buf, strlen(buf));
+static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
+                       char *server_sha1)
+{
+       struct oc_text_buf *buf;
+       char *config_buf = NULL;
+       int result, buflen;
+       unsigned char local_sha1_bin[SHA1_SIZE];
+       char local_sha1_ascii[(SHA1_SIZE * 2)+1];
+       int i;
+
+       if (openconnect_open_https(vpninfo)) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to open HTTPS connection to %s\n"),
+                            vpninfo->hostname);
+               return -EINVAL;
+       }
+
+       buf = buf_alloc();
+       buf_append(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
+       add_common_headers(vpninfo, buf);
+       buf_append(buf, "\r\n");
+
+       if (buf_error(buf))
+               return buf_free(buf);
+
+       if (openconnect_SSL_write(vpninfo, buf->data, buf->pos) != buf->pos) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to send GET request for new config\n"));
+               buf_free(buf);
+               return -EIO;
+       }
+       buf_free(buf);
 
        buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
        if (buflen < 0) {
@@ -334,20 +488,19 @@ static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
                return -EINVAL;
        }
 
-       EVP_MD_CTX_init(&c);
-       EVP_Digest(config_buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
-       EVP_MD_CTX_cleanup(&c);
+       openconnect_sha1(local_sha1_bin, config_buf, buflen);
 
-       for (i = 0; i < SHA_DIGEST_LENGTH; i++)
+       for (i = 0; i < SHA1_SIZE; i++)
                sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
 
        if (strcasecmp(server_sha1, local_sha1_ascii)) {
-               vpninfo->progress(vpninfo, PRG_ERR, "Downloaded config file did not match intended SHA1\n");
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Downloaded config file did not match intended SHA1\n"));
                free(config_buf);
                return -EINVAL;
        }
 
-       result = vpninfo->write_new_config(vpninfo, config_buf, buflen);
+       result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
        free(config_buf);
        return result;
 }
@@ -357,113 +510,119 @@ static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int bufle
        char fname[16];
        int fd, ret;
 
-       if (!vpninfo->uid_csd_given) {
-               vpninfo->progress(vpninfo, PRG_ERR, "Error: You are trying to "
-                                 "run insecure CSD code without specifying the CSD user.\n"
-                                 "       Use command line option \"--csd-user\"\n");
-               exit(1);
+       if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
+                              "This facility is disabled by default for security reasons, so you may wish to enable it."));
+               return -EPERM;
        }
 
 #ifndef __linux__
-       vpninfo->progress(vpninfo, PRG_INFO,
-                         "Trying to run Linux CSD trojan script.");
+       vpn_progress(vpninfo, PRG_INFO,
+                    _("Trying to run Linux CSD trojan script."));
 #endif
 
        sprintf(fname, "/tmp/csdXXXXXX");
        fd = mkstemp(fname);
        if (fd < 0) {
                int err = -errno;
-               vpninfo->progress(vpninfo, PRG_ERR, "Failed to open temporary CSD script file: %s\n",
-                                 strerror(errno));
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to open temporary CSD script file: %s\n"),
+                            strerror(errno));
                return err;
        }
 
-       ret = proxy_write(fd, (void *)buf, buflen);
+       ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
        if (ret) {
-               vpninfo->progress(vpninfo, PRG_ERR, "Failed to write temporary CSD script file: %s\n",
-                                 strerror(ret));
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to write temporary CSD script file: %s\n"),
+                            strerror(-ret));
                return ret;
        }
        fchmod(fd, 0755);
        close(fd);
 
        if (!fork()) {
-               X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
-               X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
-               char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
-               char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
+               char scertbuf[MD5_SIZE * 2 + 1];
+               char ccertbuf[MD5_SIZE * 2 + 1];
                char *csd_argv[32];
                int i = 0;
 
-               if (vpninfo->uid_csd != getuid()) {
+               if (vpninfo->uid_csd_given && vpninfo->uid_csd != getuid()) {
                        struct passwd *pw;
 
                        if (setuid(vpninfo->uid_csd)) {
-                               fprintf(stderr, "Failed to set uid %d\n",
-                                       vpninfo->uid_csd);
+                               fprintf(stderr, _("Failed to set uid %ld\n"),
+                                       (long)vpninfo->uid_csd);
                                exit(1);
                        }
                        if (!(pw = getpwuid(vpninfo->uid_csd))) {
-                               fprintf(stderr, "Invalid user uid=%d\n",
-                                       vpninfo->uid_csd);
+                               fprintf(stderr, _("Invalid user uid=%ld\n"),
+                                       (long)vpninfo->uid_csd);
                                exit(1);
                        }
                        setenv("HOME", pw->pw_dir, 1);
                        if (chdir(pw->pw_dir)) {
-                               fprintf(stderr, "Failed to change to CSD home directory '%s': %s\n",
+                               fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
                                        pw->pw_dir, strerror(errno));
                                exit(1);
                        }
                }
-               if (vpninfo->uid_csd == 0) {
-                       fprintf(stderr, "Warning: you are running insecure "
-                               "CSD code with root privileges\n"
-                               "\t Use command line option \"--csd-user\"\n");
+               if (getuid() == 0 && !vpninfo->csd_wrapper) {
+                       fprintf(stderr, _("Warning: you are running insecure "
+                                         "CSD code with root privileges\n"
+                                         "\t Use command line option \"--csd-user\"\n"));
                }
-
+               if (vpninfo->uid_csd_given == 2) {
+                       /* The NM tool really needs not to get spurious output
+                          on stdout, which the CSD trojan spews. */
+                       dup2(2, 1);
+               }
+               if (vpninfo->csd_wrapper)
+                       csd_argv[i++] = vpninfo->csd_wrapper;
                csd_argv[i++] = fname;
-               csd_argv[i++] = "-ticket";
+               csd_argv[i++]= (char *)"-ticket";
                if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
-                       return -ENOMEM;
-               csd_argv[i++] = "-stub";
-               csd_argv[i++] = "\"0\"";
-               csd_argv[i++] = "-group";
+                       goto out;
+               csd_argv[i++]= (char *)"-stub";
+               csd_argv[i++]= (char *)"\"0\"";
+               csd_argv[i++]= (char *)"-group";
                if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
-                       return -ENOMEM;
-
-               get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
-               if (ccert)
-                       get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
-               else
-                       ccertbuf[0] = 0;
+                       goto out;
 
-               csd_argv[i++] = "-certhash";
+               openconnect_local_cert_md5(vpninfo, ccertbuf);
+               scertbuf[0] = 0;
+               get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
+               csd_argv[i++]= (char *)"-certhash";
                if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
-                       return -ENOMEM;
-               csd_argv[i++] = "-url";
+                       goto out;
+
+               csd_argv[i++]= (char *)"-url";
                if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
-                       return -ENOMEM;
-               /* WTF would it want to know this for? */
-               csd_argv[i++] = "-vpnclient";
-               csd_argv[i++] = "\"/opt/cisco/vpn/bin/vpnui";
-               csd_argv[i++] = "-connect";
-               if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
-                       return -ENOMEM;
-               csd_argv[i++] = "-connectparam";
-               if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
-                       return -ENOMEM;
-               csd_argv[i++] = "-langselen";
+                       goto out;
+
+               csd_argv[i++]= (char *)"-langselen";
                csd_argv[i++] = NULL;
 
-               execv(fname, csd_argv);
-               vpninfo->progress(vpninfo, PRG_ERR, "Failed to exec CSD script %s\n", fname);
+               if (setenv("CSD_TOKEN", vpninfo->csd_token, 1))
+                       goto out;
+               if (setenv("CSD_HOSTNAME", vpninfo->hostname, 1))
+                       goto out;
+
+               execv(csd_argv[0], csd_argv);
+
+out:
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to exec CSD script %s\n"), csd_argv[0]);
                exit(1);
        }
 
        free(vpninfo->csd_stuburl);
        vpninfo->csd_stuburl = NULL;
+       free(vpninfo->urlpath);
        vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
                                  (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
+       free(vpninfo->csd_waiturl);
        vpninfo->csd_waiturl = NULL;
        vpninfo->csd_scriptname = strdup(fname);
 
@@ -472,29 +631,8 @@ static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int bufle
        return 0;
 }
 
-#ifdef __sun__
-char *local_strcasestr(const char *haystack, const char *needle)
-{
-       int hlen = strlen(haystack);
-       int nlen = strlen(needle);
-       int i, j;
-
-       for (i = 0; i < hlen - nlen + 1; i++) {
-               for (j = 0; j < nlen; j++) {
-                       if (tolower(haystack[i + j]) != 
-                           tolower(needle[j]))
-                               break;
-               }
-               if (j == nlen)
-                       return (char *)haystack + i;
-       }
-       return NULL;
-}
-#define strcasestr local_strcasestr
-#endif
-
-int parse_url(char *url, char **res_proto, char **res_host, int *res_port,
-             char **res_path, int default_port)
+int internal_parse_url(char *url, char **res_proto, char **res_host,
+                      int *res_port, char **res_path, int default_port)
 {
        char *proto = url;
        char *host, *path, *port_str;
@@ -525,11 +663,8 @@ int parse_url(char *url, char **res_proto, char **res_host, int *res_port,
        }
 
        path = strchr(host, '/');
-       if (path) {
+       if (path)
                *(path++) = 0;
-               if (!*path)
-                       path = NULL;
-       }
 
        port_str = strrchr(host, ':');
        if (port_str) {
@@ -549,29 +684,150 @@ int parse_url(char *url, char **res_proto, char **res_host, int *res_port,
        if (res_port)
                *res_port = port;
        if (res_path)
-               *res_path = path ? strdup(path) : NULL;
+               *res_path = (path && *path) ? strdup(path) : NULL;
+
+       /* Undo the damage we did to the original string */
+       if (port_str)
+               *(port_str) = ':';
+       if (path)
+               *(path - 1) = '/';
+       if (proto)
+               *(host - 3) = ':';
        return 0;
 }
 
+static void clear_cookies(struct openconnect_info *vpninfo)
+{
+       struct vpn_option *opt, *next;
+
+       for (opt = vpninfo->cookies; opt; opt = next) {
+               next = opt->next;
+
+               free(opt->option);
+               free(opt->value);
+               free(opt);
+       }
+       vpninfo->cookies = NULL;
+}
+
 /* Return value:
  *  < 0, on error
- *  = 0, no cookie (user cancel)
- *  = 1, obtained cookie
+ *  = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
  */
-int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
+static int handle_redirect(struct openconnect_info *vpninfo)
 {
-       struct vpn_option *opt, *next;
-       char buf[MAX_BUF_LEN];
-       char *form_buf = NULL;
+       vpninfo->redirect_type = REDIR_TYPE_LOCAL;
+
+       if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
+               /* New host. Tear down the existing connection and make a new one */
+               char *host;
+               int port;
+               int ret;
+
+               free(vpninfo->urlpath);
+               vpninfo->urlpath = NULL;
+
+               ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
+               if (ret) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to parse redirected URL '%s': %s\n"),
+                                    vpninfo->redirect_url, strerror(-ret));
+                       free(vpninfo->redirect_url);
+                       vpninfo->redirect_url = NULL;
+                       return ret;
+               }
+
+               if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
+                       free(vpninfo->hostname);
+                       vpninfo->hostname = host;
+                       vpninfo->port = port;
+
+                       /* Kill the existing connection, and a new one will happen */
+                       free(vpninfo->peer_addr);
+                       vpninfo->peer_addr = NULL;
+                       openconnect_close_https(vpninfo, 0);
+                       clear_cookies(vpninfo);
+                       vpninfo->redirect_type = REDIR_TYPE_NEWHOST;
+               } else
+                       free(host);
+
+               free(vpninfo->redirect_url);
+               vpninfo->redirect_url = NULL;
+
+               return 0;
+       } else if (strstr(vpninfo->redirect_url, "://")) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Cannot follow redirection to non-https URL '%s'\n"),
+                            vpninfo->redirect_url);
+               free(vpninfo->redirect_url);
+               vpninfo->redirect_url = NULL;
+               return -EINVAL;
+       } else if (vpninfo->redirect_url[0] == '/') {
+               /* Absolute redirect within same host */
+               free(vpninfo->urlpath);
+               vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
+               free(vpninfo->redirect_url);
+               vpninfo->redirect_url = NULL;
+               return 0;
+       } else {
+               char *lastslash = NULL;
+               if (vpninfo->urlpath)
+                       lastslash = strrchr(vpninfo->urlpath, '/');
+               if (!lastslash) {
+                       free(vpninfo->urlpath);
+                       vpninfo->urlpath = vpninfo->redirect_url;
+                       vpninfo->redirect_url = NULL;
+               } else {
+                       char *oldurl = vpninfo->urlpath;
+                       *lastslash = 0;
+                       vpninfo->urlpath = NULL;
+                       if (asprintf(&vpninfo->urlpath, "%s/%s",
+                                    oldurl, vpninfo->redirect_url) == -1) {
+                               int err = -errno;
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Allocating new path for relative redirect failed: %s\n"),
+                                            strerror(-err));
+                               return err;
+                       }
+                       free(oldurl);
+                       free(vpninfo->redirect_url);
+                       vpninfo->redirect_url = NULL;
+               }
+               return 0;
+       }
+}
+
+/* Inputs:
+ *  method:             GET or POST
+ *  vpninfo->hostname:  Host DNS name
+ *  vpninfo->port:      TCP port, typically 443
+ *  vpninfo->urlpath:   Relative path, e.g. /+webvpn+/foo.html
+ *  request_body_type:  Content type for a POST (e.g. text/html).  Can be NULL.
+ *  request_body:       POST content
+ *  form_buf:           Callee-allocated buffer for server content
+ *
+ * Return value:
+ *  < 0, on error
+ *  >=0, on success, indicating the length of the data in *form_buf
+ */
+static int do_https_request(struct openconnect_info *vpninfo, const char *method,
+                           const char *request_body_type, const char *request_body,
+                           char **form_buf, int fetch_redirect)
+{
+       struct oc_text_buf *buf;
        int result, buflen;
-       char request_body[2048];
-       char *request_body_type = NULL;
-       char *method = "GET";
 
  retry:
-       if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
-               vpninfo->progress(vpninfo, PRG_ERR, "Failed to open HTTPS connection to %s\n",
-                       vpninfo->hostname);
+       vpninfo->redirect_type = REDIR_TYPE_NONE;
+
+       if (*form_buf) {
+               free(*form_buf);
+               *form_buf = NULL;
+       }
+       if (openconnect_open_https(vpninfo)) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to open HTTPS connection to %s\n"),
+                            vpninfo->hostname);
                return -EINVAL;
        }
 
@@ -584,162 +840,239 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
         *
         * So we process the HTTP for ourselves...
         */
-       sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
-       sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
-       sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
-       sprintf(buf + strlen(buf),  "Accept: */*\r\n");
-       sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
+       buf = buf_alloc();
+       buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
+       add_common_headers(vpninfo, buf);
 
-       if (vpninfo->cookies) {
-               sprintf(buf + strlen(buf),  "Cookie: ");
-               for (opt = vpninfo->cookies; opt; opt = opt->next)
-                       sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
-                                     opt->value, opt->next ? "; " : "\r\n");
-       }
        if (request_body_type) {
-               sprintf(buf + strlen(buf),  "Content-Type: %s\r\n",
-                             request_body_type);
-               sprintf(buf + strlen(buf),  "Content-Length: %zd\r\n",
-                             strlen(request_body));
+               buf_append(buf, "Content-Type: %s\r\n", request_body_type);
+               buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
        }
-       sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
+       buf_append(buf, "\r\n");
+
        if (request_body_type)
-               sprintf(buf + strlen(buf), "%s", request_body);
+               buf_append(buf, "%s", request_body);
 
        if (vpninfo->port == 443)
-               vpninfo->progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
-                                 method, vpninfo->hostname,
-                                 vpninfo->urlpath ?: "");
+               vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
+                            method, vpninfo->hostname,
+                            vpninfo->urlpath ?: "");
        else
-               vpninfo->progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
-                                 method, vpninfo->hostname, vpninfo->port,
-                                 vpninfo->urlpath ?: "");
+               vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
+                            method, vpninfo->hostname, vpninfo->port,
+                            vpninfo->urlpath ?: "");
 
-       SSL_write(vpninfo->https_ssl, buf, strlen(buf));
+       if (buf_error(buf))
+               return buf_free(buf);
+
+       result = openconnect_SSL_write(vpninfo, buf->data, buf->pos);
+       buf_free(buf);
+       if (result < 0)
+               return result;
 
-       buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
+       buflen = process_http_response(vpninfo, &result, NULL, form_buf);
        if (buflen < 0) {
                /* We'll already have complained about whatever offended us */
-               exit(1);
+               return buflen;
        }
 
        if (result != 200 && vpninfo->redirect_url) {
-       redirect:
-               if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
-                       /* New host. Tear down the existing connection and make a new one */
-                       char *host;
-                       int port;
-                       int ret;
+               result = handle_redirect(vpninfo);
+               if (result == 0) {
+                       if (!fetch_redirect)
+                               return 0;
+                       goto retry;
+               }
+               goto out;
+       }
+       if (!*form_buf || result != 200) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unexpected %d result from server\n"),
+                            result);
+               result = -EINVAL;
+               goto out;
+       }
 
-                       free(vpninfo->urlpath);
-                       vpninfo->urlpath = NULL;
+       return buflen;
 
-                       ret = parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
-                       if (ret) {
-                               vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse redirected URL '%s': %s\n",
-                                                 vpninfo->redirect_url, strerror(-ret));
-                               free(vpninfo->redirect_url);
-                               free(form_buf);
-                               return ret;
-                       }
+ out:
+       free(*form_buf);
+       *form_buf = NULL;
+       return result;
+}
 
-                       if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
-                               free(vpninfo->hostname);
-                               vpninfo->hostname = host;
-                               vpninfo->port = port;
-
-                               /* Kill the existing connection, and a new one will happen */
-                               free(vpninfo->peer_addr);
-                               vpninfo->peer_addr = NULL;
-                               if (vpninfo->https_ssl) {
-                                       SSL_free(vpninfo->https_ssl);
-                                       vpninfo->https_ssl = NULL;
-                                       close(vpninfo->ssl_fd);
-                                       vpninfo->ssl_fd = -1;
-                               }
+/* Return value:
+ *  < 0, if the data is unrecognized
+ *  = 0, if the page contains an XML document
+ *  = 1, if the page is a wait/refresh HTML page
+ */
+static int check_response_type(struct openconnect_info *vpninfo, char *form_buf)
+{
+       if (strncmp(form_buf, "<?xml", 5)) {
+               /* Not XML? Perhaps it's HTML with a refresh... */
+               if (strcasestr(form_buf, "http-equiv=\"refresh\""))
+                       return 1;
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unknown response from server\n"));
+               return -EINVAL;
+       }
+       return 0;
+}
 
-                               for (opt = vpninfo->cookies; opt; opt = next) {
-                                       next = opt->next;
+/* Return value:
+ *  < 0, on error
+ *  > 0, no cookie (user cancel)
+ *  = 0, obtained cookie
+ */
+int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
+{
+       struct vpn_option *opt;
+       char *form_buf = NULL;
+       struct oc_auth_form *form = NULL;
+       int result, buflen, tries;
+       char request_body[2048];
+       const char *request_body_type = "application/x-www-form-urlencoded";
+       const char *method = "POST";
+       int xmlpost = 0;
+
+       /* Step 1: Unlock software token (if applicable) */
+       if (vpninfo->use_stoken) {
+               result = prepare_stoken(vpninfo);
+               if (result)
+                       return result;
+       }
 
-                                       free(opt->option);
-                                       free(opt->value);
-                                       free(opt);
-                               }
-                               vpninfo->cookies = NULL;
-                       } else
-                               free(host);
+       /*
+        * Step 2: Probe for XML POST compatibility
+        *
+        * This can get stuck in a redirect loop, so give up after any of:
+        *
+        * a) HTTP error (e.g. 400 Bad Request)
+        * b) Same-host redirect (e.g. Location: /foo/bar)
+        * c) Three redirects without seeing a plausible login form
+        */
+       result = xmlpost_initial_req(vpninfo, request_body, sizeof(request_body));
+       if (result < 0)
+               return result;
 
-                       free(vpninfo->redirect_url);
-                       vpninfo->redirect_url = NULL;
+       for (tries = 0; ; tries++) {
+               if (tries == 3)
+                       break;
+               buflen = do_https_request(vpninfo, method, request_body_type, request_body,
+                                         &form_buf, 0);
+               if (buflen == -EINVAL)
+                       break;
+               if (buflen < 0)
+                       return buflen;
 
-                       goto retry;
-               } else if (vpninfo->redirect_url[0] == '/') {
-                       /* Absolute redirect within same host */
-                       free(vpninfo->urlpath);
-                       vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
-                       free(vpninfo->redirect_url);
-                       vpninfo->redirect_url = NULL;
-                       goto retry;
-               } else {
-                       char *lastslash = strrchr(vpninfo->urlpath, '/');
-                       if (!lastslash) {
-                               free(vpninfo->urlpath);
-                               vpninfo->urlpath = vpninfo->redirect_url;
-                               vpninfo->redirect_url = NULL;
-                       } else {
-                               char *oldurl = vpninfo->urlpath;
-                               *lastslash = 0;
-                               vpninfo->urlpath = NULL;
-                               if (asprintf(&vpninfo->urlpath, "%s/%s",
-                                            oldurl, vpninfo->redirect_url) == -1) {
-                                       int err = -errno;
-                                       vpninfo->progress(vpninfo, PRG_ERR,
-                                                         "Allocating new path for relative redirect failed: %s\n",
-                                                         strerror(-err));
-                                       return err;
-                               }
-                               free(oldurl);
-                               free(vpninfo->redirect_url);
-                               vpninfo->redirect_url = NULL;
-                       }
-                       goto retry;
-               }
+               if (vpninfo->redirect_type == REDIR_TYPE_LOCAL)
+                       break;
+               else if (vpninfo->redirect_type != REDIR_TYPE_NONE)
+                       continue;
+
+               result = parse_xml_response(vpninfo, form_buf, &form);
+               if (result < 0)
+                       break;
+
+               xmlpost = 1;
+               vpn_progress(vpninfo, PRG_INFO, _("XML POST enabled\n"));
+               break;
        }
 
-       if (vpninfo->csd_stuburl) {
-               /* This is the CSD stub script, which we now need to run */
-               result = run_csd_script(vpninfo, form_buf, buflen);
-               if (result) {
+       /* Step 3: Fetch and parse the login form, if not using XML POST */
+       if (!xmlpost) {
+               buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
+               if (buflen < 0)
+                       return buflen;
+
+               result = parse_xml_response(vpninfo, form_buf, &form);
+               if (result < 0) {
                        free(form_buf);
                        return result;
                }
-
-               /* Now we'll be redirected to the waiturl */
-               goto retry;
+               if (form->action) {
+                       vpninfo->redirect_url = strdup(form->action);
+                       handle_redirect(vpninfo);
+               }
        }
-       if (strncmp(form_buf, "<?xml", 5)) {
-               /* Not XML? Perhaps it's HTML with a refresh... */
-               if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
-                       vpninfo->progress(vpninfo, PRG_INFO, "Refreshing %s after 1 second...\n",
-                                         vpninfo->urlpath);
+
+       /* Step 4: Run the CSD trojan, if applicable */
+       if (vpninfo->csd_starturl) {
+               char *form_path = NULL;
+
+               if (vpninfo->urlpath) {
+                       form_path = strdup(vpninfo->urlpath);
+                       if (!form_path) {
+                               result = -ENOMEM;
+                               goto out;
+                       }
+               }
+
+               /* fetch the CSD program, if available */
+               if (vpninfo->csd_stuburl) {
+                       buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
+                       if (buflen <= 0) {
+                               result = -EINVAL;
+                               goto out;
+                       }
+               }
+
+               /* This is the CSD stub script, which we now need to run */
+               result = run_csd_script(vpninfo, form_buf, buflen);
+               if (result)
+                       goto out;
+
+               /* vpninfo->urlpath now points to the wait page */
+               while (1) {
+                       result = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
+                       if (result <= 0)
+                               break;
+
+                       result = check_response_type(vpninfo, form_buf);
+                       if (result <= 0)
+                               break;
+
+                       vpn_progress(vpninfo, PRG_INFO,
+                                    _("Refreshing %s after 1 second...\n"),
+                                    vpninfo->urlpath);
                        sleep(1);
-                       goto retry;
                }
-               vpninfo->progress(vpninfo, PRG_ERR, "Unknown response from server\n");
-               free(form_buf);
-               return -EINVAL;
+               if (result < 0)
+                       goto out;
+
+               /* refresh the form page, to see if we're authorized now */
+               free(vpninfo->urlpath);
+               vpninfo->urlpath = form_path;
+
+               result = do_https_request(vpninfo, xmlpost ? "POST" : "GET",
+                                         request_body_type, request_body, &form_buf, 1);
+               if (result < 0)
+                       goto out;
+
+               result = parse_xml_response(vpninfo, form_buf, &form);
+               if (result < 0)
+                       goto out;
        }
-       request_body[0] = 0;
-       result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
-                                   &method, &request_body_type);
 
-       if (!result)
-               goto redirect;
+       /* Step 5: Ask the user to fill in the auth form; repeat as necessary */
+       while (1) {
+               request_body[0] = 0;
+               result = handle_auth_form(vpninfo, form, request_body, sizeof(request_body),
+                                         &method, &request_body_type, xmlpost);
+               if (result < 0 || result == 1)
+                       goto out;
+               if (result == 2)
+                       break;
 
-       free(form_buf);
+               result = do_https_request(vpninfo, method, request_body_type, request_body,
+                                         &form_buf, 1);
+               if (result < 0)
+                       goto out;
 
-       if (result != 2)
-               return result;
+               result = parse_xml_response(vpninfo, form_buf, &form);
+               if (result < 0)
+                       goto out;
+       }
 
        /* A return value of 2 means the XML form indicated
           success. We _should_ have a cookie... */
@@ -762,7 +1095,7 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
                                        fu = tok + 3;
                                else if (!strncmp(tok, "fh:", 3)) {
                                        if (!strncasecmp(tok+3, vpninfo->xmlsha1,
-                                                        SHA_DIGEST_LENGTH * 2))
+                                                        SHA1_SIZE * 2))
                                                break;
                                        sha = tok + 3;
                                }
@@ -772,25 +1105,33 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
                                fetch_config(vpninfo, bu, fu, sha);
                }
        }
+       result = 0;
+
+out:
+       free(form_buf);
+       free_auth_form(form);
+
        if (vpninfo->csd_scriptname) {
                unlink(vpninfo->csd_scriptname);
                free(vpninfo->csd_scriptname);
                vpninfo->csd_scriptname = NULL;
        }
-       return 0;
+
+       return result;
 }
 
-char *openconnect_create_useragent(char *base)
+char *openconnect_create_useragent(const char *base)
 {
        char *uagent;
 
-       if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
+       if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
                return NULL;
 
        return uagent;
 }
 
-static int proxy_gets(int fd, char *buf, size_t len)
+static int proxy_gets(struct openconnect_info *vpninfo, int fd,
+                     char *buf, size_t len)
 {
        int i = 0;
        int ret;
@@ -798,7 +1139,7 @@ static int proxy_gets(int fd, char *buf, size_t len)
        if (len < 2)
                return -EINVAL;
 
-       while ( (ret = read(fd, buf + i, 1)) == 1) {
+       while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
                if (buf[i] == '\n') {
                        buf[i] = 0;
                        if (i && buf[i-1] == '\r') {
@@ -814,19 +1155,39 @@ static int proxy_gets(int fd, char *buf, size_t len)
                        return i;
                }
        }
-       if (ret < 0)
-               ret = -errno;
-
        buf[i] = 0;
        return i ?: ret;
 }
 
-static int proxy_write(int fd, unsigned char *buf, size_t len)
+static int proxy_write(struct openconnect_info *vpninfo, int fd,
+                      unsigned char *buf, size_t len)
 {
        size_t count;
-       
+
        for (count = 0; count < len; ) {
-               int i = write(fd, buf + count, len - count);
+               fd_set rd_set, wr_set;
+               int maxfd = fd;
+               int i;
+
+               FD_ZERO(&wr_set);
+               FD_ZERO(&rd_set);
+               FD_SET(fd, &wr_set);
+               if (vpninfo->cancel_fd != -1) {
+                       FD_SET(vpninfo->cancel_fd, &rd_set);
+                       if (vpninfo->cancel_fd > fd)
+                               maxfd = vpninfo->cancel_fd;
+               }
+
+               select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
+               if (vpninfo->cancel_fd != -1 &&
+                   FD_ISSET(vpninfo->cancel_fd, &rd_set))
+                       return -EINTR;
+
+               /* Not that this should ever be able to happen... */
+               if (!FD_ISSET(fd, &wr_set))
+                       continue;
+
+               i = write(fd, buf + count, len - count);
                if (i < 0)
                        return -errno;
 
@@ -835,12 +1196,34 @@ static int proxy_write(int fd, unsigned char *buf, size_t len)
        return 0;
 }
 
-static int proxy_read(int fd, unsigned char *buf, size_t len)
+static int proxy_read(struct openconnect_info *vpninfo, int fd,
+                     unsigned char *buf, size_t len)
 {
        size_t count;
 
        for (count = 0; count < len; ) {
-               int i = read(fd, buf + count, len - count);
+               fd_set rd_set;
+               int maxfd = fd;
+               int i;
+
+               FD_ZERO(&rd_set);
+               FD_SET(fd, &rd_set);
+               if (vpninfo->cancel_fd != -1) {
+                       FD_SET(vpninfo->cancel_fd, &rd_set);
+                       if (vpninfo->cancel_fd > fd)
+                               maxfd = vpninfo->cancel_fd;
+               }
+
+               select(maxfd + 1, &rd_set, NULL, NULL, NULL);
+               if (vpninfo->cancel_fd != -1 &&
+                   FD_ISSET(vpninfo->cancel_fd, &rd_set))
+                       return -EINTR;
+
+               /* Not that this should ever be able to happen... */
+               if (!FD_ISSET(fd, &rd_set))
+                       continue;
+
+               i = read(fd, buf + count, len - count);
                if (i < 0)
                        return -errno;
 
@@ -850,15 +1233,15 @@ static int proxy_read(int fd, unsigned char *buf, size_t len)
 }
 
 static const char *socks_errors[] = {
-       "request granted",
-       "general failure",
-       "connection not allowed by ruleset",
-       "network unreachable",
-       "host unreachable",
-       "connection refused by destination host",
-       "TTL expired",
-       "command not supported / protocol error",
-       "address type not supported"
+       N_("request granted"),
+       N_("general failure"),
+       N_("connection not allowed by ruleset"),
+       N_("network unreachable"),
+       N_("host unreachable"),
+       N_("connection refused by destination host"),
+       N_("TTL expired"),
+       N_("command not supported / protocol error"),
+       N_("address type not supported")
 };
 
 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
@@ -870,40 +1253,41 @@ static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
        buf[1] = 1; /* # auth methods */
        buf[2] = 0; /* No auth supported */
 
-       if ((i = proxy_write(ssl_sock, buf, 3))) {
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Error writing auth request to SOCKS proxy: %s\n",
-                                 strerror(-i));
+       if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error writing auth request to SOCKS proxy: %s\n"),
+                            strerror(-i));
                return i;
        }
        
-       if ((i = proxy_read(ssl_sock, buf, 2))) {
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Error reading auth response from SOCKS proxy: %s\n",
-                                 strerror(-i));
+       if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error reading auth response from SOCKS proxy: %s\n"),
+                            strerror(-i));
                return i;
        }
        if (buf[0] != 5) {
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Unexpected auth response from SOCKS proxy: %02x %02x\n",
-                                 buf[0], buf[1]);
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
+                            buf[0], buf[1]);
                return -EIO;
        }
        if (buf[1]) {
        socks_err:
                if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
-                       vpninfo->progress(vpninfo, PRG_ERR,
-                                         "SOCKS proxy error %02x: %s\n",
-                                         buf[1], socks_errors[buf[1]]);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("SOCKS proxy error %02x: %s\n"),
+                                    buf[1], _(socks_errors[buf[1]]));
                else
-                       vpninfo->progress(vpninfo, PRG_ERR,
-                                         "SOCKS proxy error %02x\n",
-                                         buf[1]);
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("SOCKS proxy error %02x\n"),
+                                    buf[1]);
                return -EIO;
        }
 
-       vpninfo->progress(vpninfo, PRG_INFO, "Requesting SOCKS proxy connection to %s:%d\n",
-                         vpninfo->hostname, vpninfo->port);
+       vpn_progress(vpninfo, PRG_INFO,
+                    _("Requesting SOCKS proxy connection to %s:%d\n"),
+                    vpninfo->hostname, vpninfo->port);
 
        buf[0] = 5; /* SOCKS version */
        buf[1] = 1; /* CONNECT */
@@ -915,24 +1299,24 @@ static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
        buf[i++] = vpninfo->port >> 8;
        buf[i++] = vpninfo->port & 0xff;
 
-       if ((i = proxy_write(ssl_sock, buf, i))) {
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Error writing connect request to SOCKS proxy: %s\n",
-                                 strerror(-i));
+       if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error writing connect request to SOCKS proxy: %s\n"),
+                            strerror(-i));
                return i;
        }
        /* Read 5 bytes -- up to and including the first byte of the returned
           address (which might be the length byte of a domain name) */
-       if ((i = proxy_read(ssl_sock, buf, 5))) {
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Error reading connect response from SOCKS proxy: %s\n",
-                                 strerror(-i));
+       if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error reading connect response from SOCKS proxy: %s\n"),
+                            strerror(-i));
                return i;
        }
        if (buf[0] != 5) {
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Unexpected connect response from SOCKS proxy: %02x %02x...\n",
-                                 buf[0], buf[1]);
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
+                            buf[0], buf[1]);
                return -EIO;
        }
        if (buf[1])
@@ -950,16 +1334,16 @@ static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
                i = 17;
                break;
        default:
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Unexpected address type %02x in SOCKS connect response\n",
-                                 buf[3]);
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unexpected address type %02x in SOCKS connect response\n"),
+                            buf[3]);
                return -EIO;
        }
 
-       if ((i = proxy_read(ssl_sock, buf, i))) {
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Error reading connect response from SOCKS proxy: %s\n",
-                                 strerror(-i));
+       if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error reading connect response from SOCKS proxy: %s\n"),
+                            strerror(-i));
                return i;
        }
        return 0;
@@ -968,52 +1352,63 @@ static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
 {
        char buf[MAX_BUF_LEN];
+       struct oc_text_buf *reqbuf;
        int buflen, result;
 
-       sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
-       sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
-       sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
-       sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
-       sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
-       sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
-       sprintf(buf + strlen(buf), "\r\n");
-
-       vpninfo->progress(vpninfo, PRG_INFO, "Requesting HTTP proxy connection to %s:%d\n",
-                         vpninfo->hostname, vpninfo->port);
-
-       if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
-               result = -errno;
-               vpninfo->progress(vpninfo, PRG_ERR, "Sending proxy request failed: %s\n",
-                                 strerror(errno));
+       reqbuf = buf_alloc();
+       buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
+       buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
+       buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
+       buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
+       buf_append(reqbuf, "Connection: keep-alive\r\n");
+       buf_append(reqbuf, "Accept-Encoding: identity\r\n");
+       buf_append(reqbuf, "\r\n");
+
+       if (buf_error(reqbuf))
+               return buf_free(reqbuf);
+
+       vpn_progress(vpninfo, PRG_INFO,
+                    _("Requesting HTTP proxy connection to %s:%d\n"),
+                    vpninfo->hostname, vpninfo->port);
+
+       result = proxy_write(vpninfo, ssl_sock, (unsigned char *)reqbuf->data, reqbuf->pos);
+       buf_free(reqbuf);
+
+       if (result) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Sending proxy request failed: %s\n"),
+                            strerror(-result));
                return result;
        }
 
-       if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
-               vpninfo->progress(vpninfo, PRG_ERR, "Error fetching proxy response\n");
+       if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Error fetching proxy response\n"));
                return -EIO;
        }
 
        if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
            buf[8] != ' ' || !(result = atoi(buf+9))) {
-               vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse proxy response '%s'\n",
-                                 buf);
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to parse proxy response '%s'\n"), buf);
                return -EINVAL;
        }
 
        if (result != 200) {
-               vpninfo->progress(vpninfo, PRG_ERR, "Proxy CONNECT request failed: %s\n",
-                                 buf);
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Proxy CONNECT request failed: %s\n"), buf);
                return -EIO;
        }
 
-       while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
+       while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
                if (buflen < 0) {
-                       vpninfo->progress(vpninfo, PRG_ERR, "Failed to read proxy response\n");
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to read proxy response\n"));
                        return -EIO;
                }
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Unexpected continuation line after CONNECT response: '%s'\n",
-                                 buf);
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Unexpected continuation line after CONNECT response: '%s'\n"),
+                            buf);
        }
 
        return 0;
@@ -1028,14 +1423,14 @@ int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
            !strcmp(vpninfo->proxy_type, "socks5"))
                return process_socks_proxy(vpninfo, ssl_sock);
 
-       vpninfo->progress(vpninfo, PRG_ERR, "Unknown proxy type '%s'\n",
-                                 vpninfo->proxy_type);
+       vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
+                    vpninfo->proxy_type);
        return -EIO;
 }
 
-int set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
+int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
 {
-       char *url = strdup(proxy);
+       char *url = proxy;
        int ret;
 
        if (!url)
@@ -1046,8 +1441,8 @@ int set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
        free(vpninfo->proxy);
        vpninfo->proxy = NULL;
 
-       ret = parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
-                       &vpninfo->proxy_port, NULL, 80);
+       ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
+                                &vpninfo->proxy_port, NULL, 80);
        if (ret)
                goto out;
 
@@ -1055,8 +1450,8 @@ int set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
            strcmp(vpninfo->proxy_type, "http") &&
            strcmp(vpninfo->proxy_type, "socks") &&
            strcmp(vpninfo->proxy_type, "socks5")) {
-               vpninfo->progress(vpninfo, PRG_ERR,
-                                 "Only http or socks(5) proxies supported\n");
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Only http or socks(5) proxies supported\n"));
                free(vpninfo->proxy_type);
                vpninfo->proxy_type = NULL;
                free(vpninfo->proxy);