/*
* 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>
* 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
* 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;
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;
}
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++;
+ /* 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;
+
+ if (semicolon)
+ *semicolon = 0;
+
+ if (!equals) {
+ 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 (!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;
- }
- }
- if (!strcasecmp(buf, "Set-Cookie")) {
- char *semicolon = strchr(colon, ';');
- char *equals = strchr(colon, '=');
- int ret;
-
- if (semicolon)
- *semicolon = 0;
-
- if (!equals) {
- vpninfo->progress(vpninfo, PRG_ERR, "Invalid cookie offered: %s\n", buf);
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Response body has negative size (%d)\n"),
+ bodylen);
return -EINVAL;
}
- *(equals++) = 0;
-
- ret = http_add_cookie(vpninfo, colon, equals);
- if (ret)
- return ret;
}
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;
}
}
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) {
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;
}
}
} 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) {
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;
}
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;
}
} 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;
}
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;
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) {
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;
}
char fname[16];
int fd, ret;
- if (!vpninfo->uid_csd_given) {
- vpninfo->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.");
+ 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) {
+ 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);
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;
}
path = strchr(host, '/');
- if (path) {
+ if (path)
*(path++) = 0;
- if (!*path)
- path = NULL;
- }
port_str = strrchr(host, ':');
if (port_str) {
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;
}
*
* 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);
- buflen = process_http_response(vpninfo, &result, NULL, &form_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);
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... */
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;
}
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;
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') {
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;
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;
}
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)
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 */
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])
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;
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;
!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)
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;
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);