/*
* 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 <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
/*
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;
_("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) {
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;
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"));
}
} 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) {
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"));
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"));
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;
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);
}
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) {
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)) {
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)) {
- 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);
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);
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;
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)
{
*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)
/* 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)
{
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);
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) {
_("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;
}
/* 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;
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);
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;
}
{
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;
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));
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));
}
/* 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));
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));
_("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;
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"));