stoken: Implement new auth form to gather soft token information
[platform/upstream/openconnect.git] / http.c
diff --git a/http.c b/http.c
index 179429d..38fc9a5 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>
 #include <pwd.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-
-#include <openssl/ssl.h>
-#include <openssl/err.h>
-#include <openssl/engine.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
 
 #include "openconnect-internal.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
 /*
@@ -112,7 +114,7 @@ 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) {
+       if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Error fetching HTTPS response\n"));
                return -EINVAL;
@@ -131,7 +133,7 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
                     _("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;
 
                if (i < 0) {
@@ -247,7 +249,7 @@ 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) {
                                vpn_progress(vpninfo, PRG_ERR,
                                             _("Error reading HTTP response body\n"));
@@ -258,13 +260,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) {
                                vpn_progress(vpninfo, PRG_ERR,
                                             _("Error fetching chunk header\n"));
-                               exit(1);
+                               return i;
                        }
                        chunklen = strtol(buf, NULL, 16);
                        if (!chunklen) {
@@ -275,7 +277,7 @@ 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) {
                                        vpn_progress(vpninfo, PRG_ERR,
                                                     _("Error reading HTTP response body\n"));
@@ -286,7 +288,7 @@ 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) {
                                        vpn_progress(vpninfo, PRG_ERR,
                                                     _("Error fetching HTTP response body\n"));
@@ -314,23 +316,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 || vpninfo->no_http_keepalive) {
-               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;
@@ -345,11 +350,17 @@ static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
        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;
+       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;
+       }
+
        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);
@@ -364,7 +375,11 @@ static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
        }
        sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
 
-       SSL_write(vpninfo->https_ssl, buf, strlen(buf));
+       if (openconnect_SSL_write(vpninfo, buf, strlen(buf)) != strlen(buf)) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to send GET request for new config\n"));
+               return -EIO;
+       }
 
        buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
        if (buflen < 0) {
@@ -377,11 +392,9 @@ 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)) {
@@ -423,25 +436,23 @@ static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int bufle
                return err;
        }
 
-       ret = proxy_write(fd, (void *)buf, buflen);
+       ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
        if (ret) {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Failed to write temporary CSD script file: %s\n"),
-                            strerror(ret));
+                            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)) {
@@ -461,12 +472,12 @@ static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int bufle
                                exit(1);
                        }
                }
-               if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
+               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) {             
+               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);
@@ -483,27 +494,17 @@ static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int bufle
                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;
-
+               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++]= (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++]= (char *)"-vpnclient";
-               csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
-               csd_argv[i++]= (char *)"-connect";
-               if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
-                       return -ENOMEM;
-               csd_argv[i++]= (char *)"-connectparam";
-               if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
-                       return -ENOMEM;
+
                csd_argv[i++]= (char *)"-langselen";
                csd_argv[i++] = NULL;
 
@@ -525,28 +526,6 @@ static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int bufle
        return 0;
 }
 
-#ifndef HAVE_STRCASESTR
-static char *openconnect__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 openconnect__strcasestr
-#endif
-
-
 int internal_parse_url(char *url, char **res_proto, char **res_host,
                       int *res_port, char **res_path, int default_port)
 {
@@ -603,6 +582,8 @@ int internal_parse_url(char *url, char **res_proto, char **res_host,
                *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)
@@ -612,8 +593,8 @@ int internal_parse_url(char *url, char **res_proto, char **res_host,
 
 /* Return value:
  *  < 0, on error
- *  = 0, no cookie (user cancel)
- *  = 1, obtained cookie
+ *  > 0, no cookie (user cancel)
+ *  = 0, obtained cookie
  */
 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
 {
@@ -625,12 +606,18 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
        const char *request_body_type = NULL;
        const char *method = "GET";
 
+       if (vpninfo->use_stoken) {
+               result = prepare_stoken(vpninfo);
+               if (result)
+                       return result;
+       }
+
  retry:
        if (form_buf) {
                free(form_buf);
                form_buf = NULL;
        }
-       if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
+       if (openconnect_open_https(vpninfo)) {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Failed to open HTTPS connection to %s\n"),
                             vpninfo->hostname);
@@ -677,12 +664,14 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
                             method, vpninfo->hostname, vpninfo->port,
                             vpninfo->urlpath ?: "");
 
-       SSL_write(vpninfo->https_ssl, buf, strlen(buf));
+       result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
+       if (result < 0)
+               return result;
 
        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) {
@@ -702,6 +691,7 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
                                             _("Failed to parse redirected URL '%s': %s\n"),
                                             vpninfo->redirect_url, strerror(-ret));
                                free(vpninfo->redirect_url);
+                               vpninfo->redirect_url = NULL;
                                free(form_buf);
                                return ret;
                        }
@@ -714,12 +704,7 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
                                /* 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;
-                               }
+                               openconnect_close_https(vpninfo, 0);
 
                                for (opt = vpninfo->cookies; opt; opt = next) {
                                        next = opt->next;
@@ -736,6 +721,14 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
                        vpninfo->redirect_url = NULL;
 
                        goto retry;
+               } 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;
+                       free(form_buf);
+                       return -EINVAL;
                } else if (vpninfo->redirect_url[0] == '/') {
                        /* Absolute redirect within same host */
                        free(vpninfo->urlpath);
@@ -835,7 +828,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;
                                }
@@ -857,13 +850,14 @@ 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;
@@ -871,7 +865,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') {
@@ -887,19 +881,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;
 
@@ -908,12 +922,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;
 
@@ -943,14 +979,14 @@ 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))) {
+       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))) {
+       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));
@@ -989,7 +1025,7 @@ 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))) {
+       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));
@@ -997,7 +1033,7 @@ static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
        }
        /* 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))) {
+       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));
@@ -1030,7 +1066,7 @@ static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
                return -EIO;
        }
 
-       if ((i = proxy_read(ssl_sock, buf, 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));
@@ -1056,15 +1092,15 @@ static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
                     _("Requesting HTTP proxy connection to %s:%d\n"),
                     vpninfo->hostname, vpninfo->port);
 
-       if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
-               result = -errno;
+       result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
+       if (result) {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Sending proxy request failed: %s\n"),
-                            strerror(errno));
+                            strerror(-result));
                return result;
        }
 
-       if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
+       if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Error fetching proxy response\n"));
                return -EIO;
@@ -1083,7 +1119,7 @@ static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
                return -EIO;
        }
 
-       while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
+       while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
                if (buflen < 0) {
                        vpn_progress(vpninfo, PRG_ERR,
                                     _("Failed to read proxy response\n"));