2 * OpenConnect (SSL + DTLS) VPN client
4 * Copyright © 2008-2010 Intel Corporation.
5 * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
7 * Author: David Woodhouse <dwmw2@infradead.org>
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * version 2.1, as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to:
21 * Free Software Foundation, Inc.
22 * 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301 USA
35 #include <sys/types.h>
37 #include <openssl/ssl.h>
38 #include <openssl/err.h>
39 #include <openssl/engine.h>
41 #include "openconnect.h"
43 static int proxy_write(int fd, unsigned char *buf, size_t len);
45 #define MAX_BUF_LEN 131072
47 * We didn't really want to have to do this for ourselves -- one might have
48 * thought that it would be available in a library somewhere. But neither
49 * cURL nor Neon have reliable cross-platform ways of either using a cert
50 * from the TPM, or just reading from / writing to a transport which is
51 * provided by their caller.
54 static int http_add_cookie(struct openconnect_info *vpninfo,
55 const char *option, const char *value)
57 struct vpn_option *new, **this;
60 new = malloc(sizeof(*new));
62 vpninfo->progress(vpninfo, PRG_ERR, "No memory for allocating cookies\n");
66 new->option = strdup(option);
67 new->value = strdup(value);
68 if (!new->option || !new->value) {
75 /* Kill cookie; don't replace it */
78 for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
79 if (!strcmp(option, (*this)->option)) {
80 /* Replace existing cookie */
82 new->next = (*this)->next;
86 free((*this)->option);
100 #define BODY_HTTP10 -1
101 #define BODY_CHUNKED -2
103 static int process_http_response(struct openconnect_info *vpninfo, int *result,
104 int (*header_cb)(struct openconnect_info *, char *, char *),
107 char buf[MAX_BUF_LEN];
109 int bodylen = BODY_HTTP10;
115 if (openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)) < 0) {
116 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching HTTPS response\n");
120 if (!strncmp(buf, "HTTP/1.0 ", 9))
123 if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
124 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse HTTP response '%s'\n", buf);
128 vpninfo->progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
129 "Got HTTP response: %s\n", buf);
132 while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
135 vpninfo->progress(vpninfo, PRG_TRACE, "%s\n", buf);
138 vpninfo->progress(vpninfo, PRG_ERR, "Error processing HTTP response\n");
141 colon = strchr(buf, ':');
143 vpninfo->progress(vpninfo, PRG_ERR, "Ignoring unknown HTTP response line '%s'\n", buf);
150 if (!strcasecmp(buf, "Connection")) {
151 if (!strcasecmp(colon, "Close"))
154 /* This might seem reasonable, but in fact it breaks
155 certificate authentication with some servers. If
156 they give an HTTP/1.0 response, even if they
157 explicitly give a Connection: Keep-Alive header,
158 just close the connection. */
159 else if (!strcasecmp(colon, "Keep-Alive"))
163 if (!strcasecmp(buf, "Location")) {
164 vpninfo->redirect_url = strdup(colon);
165 if (!vpninfo->redirect_url)
168 if (!strcasecmp(buf, "Content-Length")) {
169 bodylen = atoi(colon);
171 vpninfo->progress(vpninfo, PRG_ERR, "Response body has negative size (%d)\n",
176 if (!strcasecmp(buf, "Set-Cookie")) {
177 char *semicolon = strchr(colon, ';');
178 char *equals = strchr(colon, '=');
185 vpninfo->progress(vpninfo, PRG_ERR, "Invalid cookie offered: %s\n", buf);
190 ret = http_add_cookie(vpninfo, colon, equals);
194 if (!strcasecmp(buf, "Transfer-Encoding")) {
195 if (!strcasecmp(colon, "chunked"))
196 bodylen = BODY_CHUNKED;
198 vpninfo->progress(vpninfo, PRG_ERR, "Unknown Transfer-Encoding: %s\n", colon);
202 if (header_cb && !strncmp(buf, "X-", 2))
203 header_cb(vpninfo, buf, colon);
206 /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
210 /* Now the body, if there is one */
211 vpninfo->progress(vpninfo, PRG_TRACE, "HTTP body %s (%d)\n",
212 bodylen==BODY_HTTP10?"http 1.0" :
213 bodylen==BODY_CHUNKED?"chunked" : "length: ",
216 /* If we were given Content-Length, it's nice and easy... */
218 body = malloc(bodylen + 1);
221 while (done < bodylen) {
222 i = SSL_read(vpninfo->https_ssl, body + done, bodylen - done);
224 vpninfo->progress(vpninfo, PRG_ERR, "Error reading HTTP response body\n");
230 } else if (bodylen == BODY_CHUNKED) {
231 /* ... else, chunked */
232 while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
233 int chunklen, lastchunk = 0;
236 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching chunk header\n");
239 chunklen = strtol(buf, NULL, 16);
244 body = realloc(body, done + chunklen + 1);
248 i = SSL_read(vpninfo->https_ssl, body + done, chunklen);
250 vpninfo->progress(vpninfo, PRG_ERR, "Error reading HTTP response body\n");
258 if ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
260 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching HTTP response body\n");
262 vpninfo->progress(vpninfo, PRG_ERR, "Error in chunked decoding. Expected '', got: '%s'",
272 } else if (bodylen == BODY_HTTP10) {
274 vpninfo->progress(vpninfo, PRG_ERR, "Cannot receive HTTP 1.0 body without closing connection\n");
278 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
280 body = realloc(body, done + 16384);
283 i = SSL_read(vpninfo->https_ssl, body + done, 16384);
285 body = realloc(body, done + 1);
294 if (closeconn || vpninfo->no_http_keepalive) {
295 SSL_free(vpninfo->https_ssl);
296 vpninfo->https_ssl = NULL;
297 close(vpninfo->ssl_fd);
298 vpninfo->ssl_fd = -1;
307 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
310 struct vpn_option *opt;
311 char buf[MAX_BUF_LEN];
312 char *config_buf = NULL;
314 unsigned char local_sha1_bin[SHA_DIGEST_LENGTH];
315 char local_sha1_ascii[(SHA_DIGEST_LENGTH * 2)+1];
319 sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
320 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
321 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
322 sprintf(buf + strlen(buf), "Accept: */*\r\n");
323 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
325 if (vpninfo->cookies) {
326 sprintf(buf + strlen(buf), "Cookie: ");
327 for (opt = vpninfo->cookies; opt; opt = opt->next)
328 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
329 opt->value, opt->next ? "; " : "\r\n");
331 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
333 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
335 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
337 /* We'll already have complained about whatever offended us */
347 EVP_Digest(config_buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
348 EVP_MD_CTX_cleanup(&c);
350 for (i = 0; i < SHA_DIGEST_LENGTH; i++)
351 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
353 if (strcasecmp(server_sha1, local_sha1_ascii)) {
354 vpninfo->progress(vpninfo, PRG_ERR, "Downloaded config file did not match intended SHA1\n");
359 result = vpninfo->write_new_config(vpninfo, config_buf, buflen);
364 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
369 if (!vpninfo->uid_csd_given) {
370 vpninfo->progress(vpninfo, PRG_ERR,
371 "Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
372 "This facility is disabled by default for security reasons, so you may wish to enable it.");
377 vpninfo->progress(vpninfo, PRG_INFO,
378 "Trying to run Linux CSD trojan script.");
381 sprintf(fname, "/tmp/csdXXXXXX");
385 vpninfo->progress(vpninfo, PRG_ERR, "Failed to open temporary CSD script file: %s\n",
390 ret = proxy_write(fd, (void *)buf, buflen);
392 vpninfo->progress(vpninfo, PRG_ERR, "Failed to write temporary CSD script file: %s\n",
400 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
401 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
402 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
403 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
407 if (vpninfo->uid_csd != getuid()) {
410 if (setuid(vpninfo->uid_csd)) {
411 fprintf(stderr, "Failed to set uid %d\n",
415 if (!(pw = getpwuid(vpninfo->uid_csd))) {
416 fprintf(stderr, "Invalid user uid=%d\n",
420 setenv("HOME", pw->pw_dir, 1);
421 if (chdir(pw->pw_dir)) {
422 fprintf(stderr, "Failed to change to CSD home directory '%s': %s\n",
423 pw->pw_dir, strerror(errno));
427 if (vpninfo->uid_csd == 0) {
428 fprintf(stderr, "Warning: you are running insecure "
429 "CSD code with root privileges\n"
430 "\t Use command line option \"--csd-user\"\n");
432 if (vpninfo->uid_csd_given == 2) {
433 /* The NM tool really needs not to get spurious output
434 on stdout, which the CSD trojan spews. */
437 csd_argv[i++] = fname;
438 csd_argv[i++] = "-ticket";
439 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
441 csd_argv[i++] = "-stub";
442 csd_argv[i++] = "\"0\"";
443 csd_argv[i++] = "-group";
444 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
447 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
449 get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
453 csd_argv[i++] = "-certhash";
454 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
456 csd_argv[i++] = "-url";
457 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
459 /* WTF would it want to know this for? */
460 csd_argv[i++] = "-vpnclient";
461 csd_argv[i++] = "\"/opt/cisco/vpn/bin/vpnui";
462 csd_argv[i++] = "-connect";
463 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
465 csd_argv[i++] = "-connectparam";
466 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
468 csd_argv[i++] = "-langselen";
469 csd_argv[i++] = NULL;
471 execv(fname, csd_argv);
472 vpninfo->progress(vpninfo, PRG_ERR, "Failed to exec CSD script %s\n", fname);
476 free(vpninfo->csd_stuburl);
477 vpninfo->csd_stuburl = NULL;
478 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
479 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
480 vpninfo->csd_waiturl = NULL;
481 vpninfo->csd_scriptname = strdup(fname);
483 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
489 char *local_strcasestr(const char *haystack, const char *needle)
491 int hlen = strlen(haystack);
492 int nlen = strlen(needle);
495 for (i = 0; i < hlen - nlen + 1; i++) {
496 for (j = 0; j < nlen; j++) {
497 if (tolower(haystack[i + j]) !=
502 return (char *)haystack + i;
506 #define strcasestr local_strcasestr
509 int parse_url(char *url, char **res_proto, char **res_host, int *res_port,
510 char **res_path, int default_port)
513 char *host, *path, *port_str;
516 host = strstr(url, "://");
521 if (!strcasecmp(proto, "https"))
523 else if (!strcasecmp(proto, "http"))
525 else if (!strcasecmp(proto, "socks") ||
526 !strcasecmp(proto, "socks4") ||
527 !strcasecmp(proto, "socks5"))
530 return -EPROTONOSUPPORT;
540 path = strchr(host, '/');
547 port_str = strrchr(host, ':');
550 int new_port = strtol(port_str + 1, &end, 10);
559 *res_proto = proto ? strdup(proto) : NULL;
561 *res_host = strdup(host);
565 *res_path = path ? strdup(path) : NULL;
571 * = 0, no cookie (user cancel)
572 * = 1, obtained cookie
574 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
576 struct vpn_option *opt, *next;
577 char buf[MAX_BUF_LEN];
578 char *form_buf = NULL;
580 char request_body[2048];
581 char *request_body_type = NULL;
582 char *method = "GET";
585 if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
586 vpninfo->progress(vpninfo, PRG_ERR, "Failed to open HTTPS connection to %s\n",
592 * It would be nice to use cURL for this, but we really need to guarantee
593 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
594 * to have any way to let us provide our own socket read/write functions.
595 * We can only provide a socket _open_ function. Which would require having
596 * a socketpair() and servicing the "other" end of it.
598 * So we process the HTTP for ourselves...
600 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
601 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
602 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
603 sprintf(buf + strlen(buf), "Accept: */*\r\n");
604 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
606 if (vpninfo->cookies) {
607 sprintf(buf + strlen(buf), "Cookie: ");
608 for (opt = vpninfo->cookies; opt; opt = opt->next)
609 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
610 opt->value, opt->next ? "; " : "\r\n");
612 if (request_body_type) {
613 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
615 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
616 strlen(request_body));
618 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
619 if (request_body_type)
620 sprintf(buf + strlen(buf), "%s", request_body);
622 if (vpninfo->port == 443)
623 vpninfo->progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
624 method, vpninfo->hostname,
625 vpninfo->urlpath ?: "");
627 vpninfo->progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
628 method, vpninfo->hostname, vpninfo->port,
629 vpninfo->urlpath ?: "");
631 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
633 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
635 /* We'll already have complained about whatever offended us */
639 if (result != 200 && vpninfo->redirect_url) {
641 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
642 /* New host. Tear down the existing connection and make a new one */
647 free(vpninfo->urlpath);
648 vpninfo->urlpath = NULL;
650 ret = parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
652 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse redirected URL '%s': %s\n",
653 vpninfo->redirect_url, strerror(-ret));
654 free(vpninfo->redirect_url);
659 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
660 free(vpninfo->hostname);
661 vpninfo->hostname = host;
662 vpninfo->port = port;
664 /* Kill the existing connection, and a new one will happen */
665 free(vpninfo->peer_addr);
666 vpninfo->peer_addr = NULL;
667 if (vpninfo->https_ssl) {
668 SSL_free(vpninfo->https_ssl);
669 vpninfo->https_ssl = NULL;
670 close(vpninfo->ssl_fd);
671 vpninfo->ssl_fd = -1;
674 for (opt = vpninfo->cookies; opt; opt = next) {
681 vpninfo->cookies = NULL;
685 free(vpninfo->redirect_url);
686 vpninfo->redirect_url = NULL;
689 } else if (vpninfo->redirect_url[0] == '/') {
690 /* Absolute redirect within same host */
691 free(vpninfo->urlpath);
692 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
693 free(vpninfo->redirect_url);
694 vpninfo->redirect_url = NULL;
697 char *lastslash = strrchr(vpninfo->urlpath, '/');
699 free(vpninfo->urlpath);
700 vpninfo->urlpath = vpninfo->redirect_url;
701 vpninfo->redirect_url = NULL;
703 char *oldurl = vpninfo->urlpath;
705 vpninfo->urlpath = NULL;
706 if (asprintf(&vpninfo->urlpath, "%s/%s",
707 oldurl, vpninfo->redirect_url) == -1) {
709 vpninfo->progress(vpninfo, PRG_ERR,
710 "Allocating new path for relative redirect failed: %s\n",
715 free(vpninfo->redirect_url);
716 vpninfo->redirect_url = NULL;
721 if (!form_buf || result != 200) {
722 vpninfo->progress(vpninfo, PRG_ERR,
723 "Unexpected %d result from server\n",
728 if (vpninfo->csd_stuburl) {
729 /* This is the CSD stub script, which we now need to run */
730 result = run_csd_script(vpninfo, form_buf, buflen);
736 /* Now we'll be redirected to the waiturl */
739 if (strncmp(form_buf, "<?xml", 5)) {
740 /* Not XML? Perhaps it's HTML with a refresh... */
741 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
742 vpninfo->progress(vpninfo, PRG_INFO, "Refreshing %s after 1 second...\n",
747 vpninfo->progress(vpninfo, PRG_ERR, "Unknown response from server\n");
752 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
753 &method, &request_body_type);
763 /* A return value of 2 means the XML form indicated
764 success. We _should_ have a cookie... */
766 for (opt = vpninfo->cookies; opt; opt = opt->next) {
768 if (!strcmp(opt->option, "webvpn"))
769 vpninfo->cookie = opt->value;
770 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
771 char *tok = opt->value;
772 char *bu = NULL, *fu = NULL, *sha = NULL;
775 if (tok != opt->value)
778 if (!strncmp(tok, "bu:", 3))
780 else if (!strncmp(tok, "fu:", 3))
782 else if (!strncmp(tok, "fh:", 3)) {
783 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
784 SHA_DIGEST_LENGTH * 2))
788 } while ((tok = strchr(tok, '&')));
791 fetch_config(vpninfo, bu, fu, sha);
794 if (vpninfo->csd_scriptname) {
795 unlink(vpninfo->csd_scriptname);
796 free(vpninfo->csd_scriptname);
797 vpninfo->csd_scriptname = NULL;
802 char *openconnect_create_useragent(char *base)
806 if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
812 static int proxy_gets(int fd, char *buf, size_t len)
820 while ( (ret = read(fd, buf + i, 1)) == 1) {
821 if (buf[i] == '\n') {
823 if (i && buf[i-1] == '\r') {
843 static int proxy_write(int fd, unsigned char *buf, size_t len)
847 for (count = 0; count < len; ) {
848 int i = write(fd, buf + count, len - count);
857 static int proxy_read(int fd, unsigned char *buf, size_t len)
861 for (count = 0; count < len; ) {
862 int i = read(fd, buf + count, len - count);
871 static const char *socks_errors[] = {
874 "connection not allowed by ruleset",
875 "network unreachable",
877 "connection refused by destination host",
879 "command not supported / protocol error",
880 "address type not supported"
883 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
885 unsigned char buf[1024];
888 buf[0] = 5; /* SOCKS version */
889 buf[1] = 1; /* # auth methods */
890 buf[2] = 0; /* No auth supported */
892 if ((i = proxy_write(ssl_sock, buf, 3))) {
893 vpninfo->progress(vpninfo, PRG_ERR,
894 "Error writing auth request to SOCKS proxy: %s\n",
899 if ((i = proxy_read(ssl_sock, buf, 2))) {
900 vpninfo->progress(vpninfo, PRG_ERR,
901 "Error reading auth response from SOCKS proxy: %s\n",
906 vpninfo->progress(vpninfo, PRG_ERR,
907 "Unexpected auth response from SOCKS proxy: %02x %02x\n",
913 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
914 vpninfo->progress(vpninfo, PRG_ERR,
915 "SOCKS proxy error %02x: %s\n",
916 buf[1], socks_errors[buf[1]]);
918 vpninfo->progress(vpninfo, PRG_ERR,
919 "SOCKS proxy error %02x\n",
924 vpninfo->progress(vpninfo, PRG_INFO, "Requesting SOCKS proxy connection to %s:%d\n",
925 vpninfo->hostname, vpninfo->port);
927 buf[0] = 5; /* SOCKS version */
928 buf[1] = 1; /* CONNECT */
929 buf[2] = 0; /* Reserved */
930 buf[3] = 3; /* Address type is domain name */
931 buf[4] = strlen(vpninfo->hostname);
932 strcpy((char *)buf + 5, vpninfo->hostname);
933 i = strlen(vpninfo->hostname) + 5;
934 buf[i++] = vpninfo->port >> 8;
935 buf[i++] = vpninfo->port & 0xff;
937 if ((i = proxy_write(ssl_sock, buf, i))) {
938 vpninfo->progress(vpninfo, PRG_ERR,
939 "Error writing connect request to SOCKS proxy: %s\n",
943 /* Read 5 bytes -- up to and including the first byte of the returned
944 address (which might be the length byte of a domain name) */
945 if ((i = proxy_read(ssl_sock, buf, 5))) {
946 vpninfo->progress(vpninfo, PRG_ERR,
947 "Error reading connect response from SOCKS proxy: %s\n",
952 vpninfo->progress(vpninfo, PRG_ERR,
953 "Unexpected connect response from SOCKS proxy: %02x %02x...\n",
960 /* Connect responses contain an address */
962 case 1: /* Legacy IP */
965 case 3: /* Domain name */
972 vpninfo->progress(vpninfo, PRG_ERR,
973 "Unexpected address type %02x in SOCKS connect response\n",
978 if ((i = proxy_read(ssl_sock, buf, i))) {
979 vpninfo->progress(vpninfo, PRG_ERR,
980 "Error reading connect response from SOCKS proxy: %s\n",
987 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
989 char buf[MAX_BUF_LEN];
992 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
993 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
994 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
995 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
996 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
997 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
998 sprintf(buf + strlen(buf), "\r\n");
1000 vpninfo->progress(vpninfo, PRG_INFO, "Requesting HTTP proxy connection to %s:%d\n",
1001 vpninfo->hostname, vpninfo->port);
1003 if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
1005 vpninfo->progress(vpninfo, PRG_ERR, "Sending proxy request failed: %s\n",
1010 if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
1011 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching proxy response\n");
1015 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1016 buf[8] != ' ' || !(result = atoi(buf+9))) {
1017 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse proxy response '%s'\n",
1022 if (result != 200) {
1023 vpninfo->progress(vpninfo, PRG_ERR, "Proxy CONNECT request failed: %s\n",
1028 while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
1030 vpninfo->progress(vpninfo, PRG_ERR, "Failed to read proxy response\n");
1033 vpninfo->progress(vpninfo, PRG_ERR,
1034 "Unexpected continuation line after CONNECT response: '%s'\n",
1041 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1043 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1044 return process_http_proxy(vpninfo, ssl_sock);
1046 if (!strcmp(vpninfo->proxy_type, "socks") ||
1047 !strcmp(vpninfo->proxy_type, "socks5"))
1048 return process_socks_proxy(vpninfo, ssl_sock);
1050 vpninfo->progress(vpninfo, PRG_ERR, "Unknown proxy type '%s'\n",
1051 vpninfo->proxy_type);
1055 int set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1057 char *url = strdup(proxy);
1063 free(vpninfo->proxy_type);
1064 vpninfo->proxy_type = NULL;
1065 free(vpninfo->proxy);
1066 vpninfo->proxy = NULL;
1068 ret = parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1069 &vpninfo->proxy_port, NULL, 80);
1073 if (vpninfo->proxy_type &&
1074 strcmp(vpninfo->proxy_type, "http") &&
1075 strcmp(vpninfo->proxy_type, "socks") &&
1076 strcmp(vpninfo->proxy_type, "socks5")) {
1077 vpninfo->progress(vpninfo, PRG_ERR,
1078 "Only http or socks(5) proxies supported\n");
1079 free(vpninfo->proxy_type);
1080 vpninfo->proxy_type = NULL;
1081 free(vpninfo->proxy);
1082 vpninfo->proxy = NULL;