X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=http.c;h=bcc4e988a6be4e2865450af60f2fa1cee56e9cb1;hb=67e0f8216ea375b53575d7bfda2c675bc1195a4e;hp=4802007afa33148797848e6103627119abbbc45e;hpb=423eee0b51a204562d6f2ec67893133ebcf200d6;p=platform%2Fupstream%2Fopenconnect.git diff --git a/http.c b/http.c index 4802007..bcc4e98 100644 --- 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 * * Author: David Woodhouse @@ -23,7 +23,6 @@ * Boston, MA 02110-1301 USA */ -#define _GNU_SOURCE #include #include #include @@ -33,16 +32,98 @@ #include #include #include - -#include -#include -#include +#include +#include +#include +#include #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 +#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 @@ -59,7 +140,8 @@ static int http_add_cookie(struct openconnect_info *vpninfo, if (*value) { new = malloc(sizeof(*new)); if (!new) { - vpn_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; @@ -112,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) { - vpn_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; } @@ -121,24 +204,27 @@ 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))) { - vpn_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; } vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO, - "Got HTTP response: %s\n", buf); + _("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) { - vpn_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) { - vpn_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; @@ -149,14 +235,16 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result, webvpn cookie in the verbose debug output */ if (!strcasecmp(buf, "Set-Cookie")) { char *semicolon = strchr(colon, ';'); - char *print_equals, *equals = strchr(colon, '='); + const char *print_equals; + char *equals = strchr(colon, '='); int ret; if (semicolon) *semicolon = 0; if (!equals) { - vpn_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; @@ -165,10 +253,17 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result, /* 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 = ""; + print_equals = _(""); vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n", - buf, colon, print_equals, semicolon?";":"", - semicolon?(semicolon+1):""); + 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) @@ -198,8 +293,9 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result, if (!strcasecmp(buf, "Content-Length")) { bodylen = atoi(colon); if (bodylen < 0) { - vpn_progress(vpninfo, PRG_ERR, "Response body has negative size (%d)\n", - bodylen); + vpn_progress(vpninfo, PRG_ERR, + _("Response body has negative size (%d)\n"), + bodylen); return -EINVAL; } } @@ -207,7 +303,9 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result, if (!strcasecmp(colon, "chunked")) bodylen = BODY_CHUNKED; else { - vpn_progress(vpninfo, PRG_ERR, "Unknown Transfer-Encoding: %s\n", colon); + vpn_progress(vpninfo, PRG_ERR, + _("Unknown Transfer-Encoding: %s\n"), + colon); return -EINVAL; } } @@ -220,10 +318,10 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result, goto cont; /* Now the body, if there is one */ - vpn_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) { @@ -231,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) { - vpn_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; } @@ -241,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) { - vpn_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) { @@ -257,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) { - vpn_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; } @@ -267,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) { - vpn_progress(vpninfo, PRG_ERR, "Error fetching HTTP response body\n"); + vpn_progress(vpninfo, PRG_ERR, + _("Error fetching HTTP response body\n")); } else { - vpn_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; @@ -283,7 +386,9 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result, } } else if (bodylen == BODY_HTTP10) { if (!closeconn) { - vpn_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; } @@ -292,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 || 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; @@ -316,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); +} + +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"); - SSL_write(vpninfo->https_ssl, buf, strlen(buf)); + 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) { @@ -355,15 +488,14 @@ 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)) { - vpn_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; } @@ -380,68 +512,68 @@ static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int bufle 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."); + _("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__ vpn_progress(vpninfo, PRG_INFO, - "Trying to run Linux CSD trojan script."); + _("Trying to run Linux CSD trojan script.")); #endif sprintf(fname, "/tmp/csdXXXXXX"); fd = mkstemp(fname); if (fd < 0) { int err = -errno; - vpn_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) { - vpn_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 && !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 (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); @@ -449,48 +581,48 @@ static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int bufle 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; + goto out; - get_cert_md5_fingerprint(vpninfo, scert, scertbuf); - if (ccert) - get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf); - else - ccertbuf[0] = 0; - - 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; + 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); - vpn_progress(vpninfo, PRG_ERR, "Failed to exec CSD script %s\n", csd_argv[0]); + +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); @@ -499,27 +631,6 @@ 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 internal_parse_url(char *url, char **res_proto, char **res_host, int *res_port, char **res_path, int default_port) { @@ -576,6 +687,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) @@ -583,29 +696,138 @@ int internal_parse_url(char *url, char **res_proto, char **res_host, 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 (form_buf) { - free(form_buf); - form_buf = NULL; + vpninfo->redirect_type = REDIR_TYPE_NONE; + + if (*form_buf) { + free(*form_buf); + *form_buf = NULL; } - if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) { - vpn_progress(vpninfo, PRG_ERR, "Failed to open HTTPS connection to %s\n", - vpninfo->hostname); + if (openconnect_open_https(vpninfo)) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to open HTTPS connection to %s\n"), + vpninfo->hostname); return -EINVAL; } @@ -618,170 +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) vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n", - method, vpninfo->hostname, - vpninfo->urlpath ?: ""); + method, vpninfo->hostname, + vpninfo->urlpath ?: ""); else vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n", - method, vpninfo->hostname, vpninfo->port, - vpninfo->urlpath ?: ""); + method, vpninfo->hostname, vpninfo->port, + vpninfo->urlpath ?: ""); + + if (buf_error(buf)) + return buf_free(buf); - SSL_write(vpninfo->https_ssl, buf, strlen(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 = 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); - 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, "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 = 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; - } - goto retry; - } - } - if (!form_buf || result != 200) { - vpn_progress(vpninfo, PRG_ERR, - "Unexpected %d result from server\n", - result); - free(form_buf); - return -EINVAL; + 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, "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; } - vpn_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... */ @@ -804,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; } @@ -814,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; @@ -840,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') { @@ -856,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; @@ -877,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; @@ -892,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) @@ -912,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))) { + 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)); + _("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)); + _("Error reading auth response from SOCKS proxy: %s\n"), + strerror(-i)); return i; } if (buf[0] != 5) { vpn_progress(vpninfo, PRG_ERR, - "Unexpected auth response from SOCKS proxy: %02x %02x\n", - buf[0], buf[1]); + _("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])) vpn_progress(vpninfo, PRG_ERR, - "SOCKS proxy error %02x: %s\n", - buf[1], socks_errors[buf[1]]); + _("SOCKS proxy error %02x: %s\n"), + buf[1], _(socks_errors[buf[1]])); else vpn_progress(vpninfo, PRG_ERR, - "SOCKS proxy error %02x\n", - buf[1]); + _("SOCKS proxy error %02x\n"), + buf[1]); return -EIO; } - vpn_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 */ @@ -957,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))) { + 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)); + _("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))) { + 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)); + _("Error reading connect response from SOCKS proxy: %s\n"), + strerror(-i)); return i; } if (buf[0] != 5) { vpn_progress(vpninfo, PRG_ERR, - "Unexpected connect response from SOCKS proxy: %02x %02x...\n", - buf[0], buf[1]); + _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"), + buf[0], buf[1]); return -EIO; } if (buf[1]) @@ -993,15 +1335,15 @@ static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock) break; default: vpn_progress(vpninfo, PRG_ERR, - "Unexpected address type %02x in SOCKS connect response\n", - buf[3]); + _("Unexpected address type %02x in SOCKS connect response\n"), + buf[3]); 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)); + _("Error reading connect response from SOCKS proxy: %s\n"), + strerror(-i)); return i; } return 0; @@ -1010,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"); - - vpn_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; - vpn_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) { - vpn_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))) { - vpn_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) { - vpn_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) { - vpn_progress(vpninfo, PRG_ERR, "Failed to read proxy response\n"); + vpn_progress(vpninfo, PRG_ERR, + _("Failed to read proxy response\n")); return -EIO; } vpn_progress(vpninfo, PRG_ERR, - "Unexpected continuation line after CONNECT response: '%s'\n", - buf); + _("Unexpected continuation line after CONNECT response: '%s'\n"), + buf); } return 0; @@ -1070,8 +1423,8 @@ int process_proxy(struct openconnect_info *vpninfo, int ssl_sock) !strcmp(vpninfo->proxy_type, "socks5")) return process_socks_proxy(vpninfo, ssl_sock); - vpn_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; } @@ -1098,7 +1451,7 @@ int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy) strcmp(vpninfo->proxy_type, "socks") && strcmp(vpninfo->proxy_type, "socks5")) { vpn_progress(vpninfo, PRG_ERR, - "Only http or socks(5) proxies supported\n"); + _("Only http or socks(5) proxies supported\n")); free(vpninfo->proxy_type); vpninfo->proxy_type = NULL; free(vpninfo->proxy);