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
34 #include <sys/types.h>
36 #include <openssl/ssl.h>
37 #include <openssl/err.h>
38 #include <openssl/engine.h>
40 #include "openconnect-internal.h"
42 static int proxy_write(struct openconnect_info *vpninfo, int fd,
43 unsigned char *buf, size_t len);
44 static int proxy_read(struct openconnect_info *vpninfo, int fd,
45 unsigned char *buf, size_t len);
47 #define MAX_BUF_LEN 131072
49 * We didn't really want to have to do this for ourselves -- one might have
50 * thought that it would be available in a library somewhere. But neither
51 * cURL nor Neon have reliable cross-platform ways of either using a cert
52 * from the TPM, or just reading from / writing to a transport which is
53 * provided by their caller.
56 static int http_add_cookie(struct openconnect_info *vpninfo,
57 const char *option, const char *value)
59 struct vpn_option *new, **this;
62 new = malloc(sizeof(*new));
64 vpn_progress(vpninfo, PRG_ERR,
65 _("No memory for allocating cookies\n"));
69 new->option = strdup(option);
70 new->value = strdup(value);
71 if (!new->option || !new->value) {
78 /* Kill cookie; don't replace it */
81 for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
82 if (!strcmp(option, (*this)->option)) {
83 /* Replace existing cookie */
85 new->next = (*this)->next;
89 free((*this)->option);
103 #define BODY_HTTP10 -1
104 #define BODY_CHUNKED -2
106 static int process_http_response(struct openconnect_info *vpninfo, int *result,
107 int (*header_cb)(struct openconnect_info *, char *, char *),
110 char buf[MAX_BUF_LEN];
112 int bodylen = BODY_HTTP10;
118 if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
119 vpn_progress(vpninfo, PRG_ERR,
120 _("Error fetching HTTPS response\n"));
124 if (!strncmp(buf, "HTTP/1.0 ", 9))
127 if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
128 vpn_progress(vpninfo, PRG_ERR,
129 _("Failed to parse HTTP response '%s'\n"), buf);
133 vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
134 _("Got HTTP response: %s\n"), buf);
137 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
141 vpn_progress(vpninfo, PRG_ERR,
142 _("Error processing HTTP response\n"));
145 colon = strchr(buf, ':');
147 vpn_progress(vpninfo, PRG_ERR,
148 _("Ignoring unknown HTTP response line '%s'\n"), buf);
155 /* Handle Set-Cookie first so that we can avoid printing the
156 webvpn cookie in the verbose debug output */
157 if (!strcasecmp(buf, "Set-Cookie")) {
158 char *semicolon = strchr(colon, ';');
159 const char *print_equals;
160 char *equals = strchr(colon, '=');
167 vpn_progress(vpninfo, PRG_ERR,
168 _("Invalid cookie offered: %s\n"), buf);
173 print_equals = equals;
174 /* Don't print the webvpn cookie unless it's empty; we don't
175 want people posting it in public with debugging output */
176 if (!strcmp(colon, "webvpn") && *equals)
177 print_equals = _("<elided>");
178 vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
179 buf, colon, print_equals, semicolon?";":"",
180 semicolon?(semicolon+1):"");
182 /* The server tends to ask for the username and password as
183 usual, even if we've already failed because it didn't like
184 our cert. Thankfully it does give us this hint... */
185 if (!strcmp(colon, "ClientCertAuthFailed"))
186 vpn_progress(vpninfo, PRG_ERR,
187 _("SSL certificate authentication failed\n"));
189 ret = http_add_cookie(vpninfo, colon, equals);
193 vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
196 if (!strcasecmp(buf, "Connection")) {
197 if (!strcasecmp(colon, "Close"))
200 /* This might seem reasonable, but in fact it breaks
201 certificate authentication with some servers. If
202 they give an HTTP/1.0 response, even if they
203 explicitly give a Connection: Keep-Alive header,
204 just close the connection. */
205 else if (!strcasecmp(colon, "Keep-Alive"))
209 if (!strcasecmp(buf, "Location")) {
210 vpninfo->redirect_url = strdup(colon);
211 if (!vpninfo->redirect_url)
214 if (!strcasecmp(buf, "Content-Length")) {
215 bodylen = atoi(colon);
217 vpn_progress(vpninfo, PRG_ERR,
218 _("Response body has negative size (%d)\n"),
223 if (!strcasecmp(buf, "Transfer-Encoding")) {
224 if (!strcasecmp(colon, "chunked"))
225 bodylen = BODY_CHUNKED;
227 vpn_progress(vpninfo, PRG_ERR,
228 _("Unknown Transfer-Encoding: %s\n"),
233 if (header_cb && !strncmp(buf, "X-", 2))
234 header_cb(vpninfo, buf, colon);
237 /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
241 /* Now the body, if there is one */
242 vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"),
243 bodylen==BODY_HTTP10?"http 1.0" :
244 bodylen==BODY_CHUNKED?"chunked" : "length: ",
247 /* If we were given Content-Length, it's nice and easy... */
249 body = malloc(bodylen + 1);
252 while (done < bodylen) {
253 i = SSL_read(vpninfo->https_ssl, body + done, bodylen - done);
255 vpn_progress(vpninfo, PRG_ERR,
256 _("Error reading HTTP response body\n"));
262 } else if (bodylen == BODY_CHUNKED) {
263 /* ... else, chunked */
264 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
265 int chunklen, lastchunk = 0;
268 vpn_progress(vpninfo, PRG_ERR,
269 _("Error fetching chunk header\n"));
272 chunklen = strtol(buf, NULL, 16);
277 body = realloc(body, done + chunklen + 1);
281 i = SSL_read(vpninfo->https_ssl, body + done, chunklen);
283 vpn_progress(vpninfo, PRG_ERR,
284 _("Error reading HTTP response body\n"));
292 if ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
294 vpn_progress(vpninfo, PRG_ERR,
295 _("Error fetching HTTP response body\n"));
297 vpn_progress(vpninfo, PRG_ERR,
298 _("Error in chunked decoding. Expected '', got: '%s'"),
308 } else if (bodylen == BODY_HTTP10) {
310 vpn_progress(vpninfo, PRG_ERR,
311 _("Cannot receive HTTP 1.0 body without closing connection\n"));
315 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
317 body = realloc(body, done + 16384);
320 i = SSL_read(vpninfo->https_ssl, body + done, 16384);
322 body = realloc(body, done + 1);
331 if (closeconn || vpninfo->no_http_keepalive) {
332 SSL_free(vpninfo->https_ssl);
333 vpninfo->https_ssl = NULL;
334 close(vpninfo->ssl_fd);
335 vpninfo->ssl_fd = -1;
344 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
347 struct vpn_option *opt;
348 char buf[MAX_BUF_LEN];
349 char *config_buf = NULL;
351 unsigned char local_sha1_bin[SHA_DIGEST_LENGTH];
352 char local_sha1_ascii[(SHA_DIGEST_LENGTH * 2)+1];
356 sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
357 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
358 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
359 sprintf(buf + strlen(buf), "Accept: */*\r\n");
360 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
362 if (vpninfo->cookies) {
363 sprintf(buf + strlen(buf), "Cookie: ");
364 for (opt = vpninfo->cookies; opt; opt = opt->next)
365 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
366 opt->value, opt->next ? "; " : "\r\n");
368 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
370 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
372 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
374 /* We'll already have complained about whatever offended us */
384 EVP_Digest(config_buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
385 EVP_MD_CTX_cleanup(&c);
387 for (i = 0; i < SHA_DIGEST_LENGTH; i++)
388 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
390 if (strcasecmp(server_sha1, local_sha1_ascii)) {
391 vpn_progress(vpninfo, PRG_ERR,
392 _("Downloaded config file did not match intended SHA1\n"));
397 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
402 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
407 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
408 vpn_progress(vpninfo, PRG_ERR,
409 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
410 "This facility is disabled by default for security reasons, so you may wish to enable it."));
415 vpn_progress(vpninfo, PRG_INFO,
416 _("Trying to run Linux CSD trojan script."));
419 sprintf(fname, "/tmp/csdXXXXXX");
423 vpn_progress(vpninfo, PRG_ERR,
424 _("Failed to open temporary CSD script file: %s\n"),
429 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
431 vpn_progress(vpninfo, PRG_ERR,
432 _("Failed to write temporary CSD script file: %s\n"),
440 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
441 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
442 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
443 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
447 if (vpninfo->uid_csd != getuid()) {
450 if (setuid(vpninfo->uid_csd)) {
451 fprintf(stderr, _("Failed to set uid %ld\n"),
452 (long)vpninfo->uid_csd);
455 if (!(pw = getpwuid(vpninfo->uid_csd))) {
456 fprintf(stderr, _("Invalid user uid=%ld\n"),
457 (long)vpninfo->uid_csd);
460 setenv("HOME", pw->pw_dir, 1);
461 if (chdir(pw->pw_dir)) {
462 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
463 pw->pw_dir, strerror(errno));
467 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
468 fprintf(stderr, _("Warning: you are running insecure "
469 "CSD code with root privileges\n"
470 "\t Use command line option \"--csd-user\"\n"));
472 if (vpninfo->uid_csd_given == 2) {
473 /* The NM tool really needs not to get spurious output
474 on stdout, which the CSD trojan spews. */
477 if (vpninfo->csd_wrapper)
478 csd_argv[i++] = vpninfo->csd_wrapper;
479 csd_argv[i++] = fname;
480 csd_argv[i++]= (char *)"-ticket";
481 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
483 csd_argv[i++]= (char *)"-stub";
484 csd_argv[i++]= (char *)"\"0\"";
485 csd_argv[i++]= (char *)"-group";
486 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
489 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
491 get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
495 csd_argv[i++]= (char *)"-certhash";
496 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
498 csd_argv[i++]= (char *)"-url";
499 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
501 /* WTF would it want to know this for? */
502 csd_argv[i++]= (char *)"-vpnclient";
503 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
504 csd_argv[i++]= (char *)"-connect";
505 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
507 csd_argv[i++]= (char *)"-connectparam";
508 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
510 csd_argv[i++]= (char *)"-langselen";
511 csd_argv[i++] = NULL;
513 execv(csd_argv[0], csd_argv);
514 vpn_progress(vpninfo, PRG_ERR,
515 _("Failed to exec CSD script %s\n"), csd_argv[0]);
519 free(vpninfo->csd_stuburl);
520 vpninfo->csd_stuburl = NULL;
521 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
522 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
523 vpninfo->csd_waiturl = NULL;
524 vpninfo->csd_scriptname = strdup(fname);
526 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
531 #ifndef HAVE_STRCASESTR
532 static char *openconnect__strcasestr(const char *haystack, const char *needle)
534 int hlen = strlen(haystack);
535 int nlen = strlen(needle);
538 for (i = 0; i < hlen - nlen + 1; i++) {
539 for (j = 0; j < nlen; j++) {
540 if (tolower(haystack[i + j]) !=
545 return (char *)haystack + i;
549 #define strcasestr openconnect__strcasestr
553 int internal_parse_url(char *url, char **res_proto, char **res_host,
554 int *res_port, char **res_path, int default_port)
557 char *host, *path, *port_str;
560 host = strstr(url, "://");
565 if (!strcasecmp(proto, "https"))
567 else if (!strcasecmp(proto, "http"))
569 else if (!strcasecmp(proto, "socks") ||
570 !strcasecmp(proto, "socks4") ||
571 !strcasecmp(proto, "socks5"))
574 return -EPROTONOSUPPORT;
584 path = strchr(host, '/');
588 port_str = strrchr(host, ':');
591 int new_port = strtol(port_str + 1, &end, 10);
600 *res_proto = proto ? strdup(proto) : NULL;
602 *res_host = strdup(host);
606 *res_path = (path && *path) ? strdup(path) : NULL;
608 /* Undo the damage we did to the original string */
618 * = 0, no cookie (user cancel)
619 * = 1, obtained cookie
621 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
623 struct vpn_option *opt, *next;
624 char buf[MAX_BUF_LEN];
625 char *form_buf = NULL;
627 char request_body[2048];
628 const char *request_body_type = NULL;
629 const char *method = "GET";
636 if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
637 vpn_progress(vpninfo, PRG_ERR,
638 _("Failed to open HTTPS connection to %s\n"),
644 * It would be nice to use cURL for this, but we really need to guarantee
645 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
646 * to have any way to let us provide our own socket read/write functions.
647 * We can only provide a socket _open_ function. Which would require having
648 * a socketpair() and servicing the "other" end of it.
650 * So we process the HTTP for ourselves...
652 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
653 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
654 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
655 sprintf(buf + strlen(buf), "Accept: */*\r\n");
656 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
658 if (vpninfo->cookies) {
659 sprintf(buf + strlen(buf), "Cookie: ");
660 for (opt = vpninfo->cookies; opt; opt = opt->next)
661 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
662 opt->value, opt->next ? "; " : "\r\n");
664 if (request_body_type) {
665 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
667 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
668 strlen(request_body));
670 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
671 if (request_body_type)
672 sprintf(buf + strlen(buf), "%s", request_body);
674 if (vpninfo->port == 443)
675 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
676 method, vpninfo->hostname,
677 vpninfo->urlpath ?: "");
679 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
680 method, vpninfo->hostname, vpninfo->port,
681 vpninfo->urlpath ?: "");
683 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
685 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
687 /* We'll already have complained about whatever offended us */
691 if (result != 200 && vpninfo->redirect_url) {
693 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
694 /* New host. Tear down the existing connection and make a new one */
699 free(vpninfo->urlpath);
700 vpninfo->urlpath = NULL;
702 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
704 vpn_progress(vpninfo, PRG_ERR,
705 _("Failed to parse redirected URL '%s': %s\n"),
706 vpninfo->redirect_url, strerror(-ret));
707 free(vpninfo->redirect_url);
712 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
713 free(vpninfo->hostname);
714 vpninfo->hostname = host;
715 vpninfo->port = port;
717 /* Kill the existing connection, and a new one will happen */
718 free(vpninfo->peer_addr);
719 vpninfo->peer_addr = NULL;
720 if (vpninfo->https_ssl) {
721 SSL_free(vpninfo->https_ssl);
722 vpninfo->https_ssl = NULL;
723 close(vpninfo->ssl_fd);
724 vpninfo->ssl_fd = -1;
727 for (opt = vpninfo->cookies; opt; opt = next) {
734 vpninfo->cookies = NULL;
738 free(vpninfo->redirect_url);
739 vpninfo->redirect_url = NULL;
742 } else if (vpninfo->redirect_url[0] == '/') {
743 /* Absolute redirect within same host */
744 free(vpninfo->urlpath);
745 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
746 free(vpninfo->redirect_url);
747 vpninfo->redirect_url = NULL;
750 char *lastslash = NULL;
751 if (vpninfo->urlpath)
752 lastslash = strrchr(vpninfo->urlpath, '/');
754 free(vpninfo->urlpath);
755 vpninfo->urlpath = vpninfo->redirect_url;
756 vpninfo->redirect_url = NULL;
758 char *oldurl = vpninfo->urlpath;
760 vpninfo->urlpath = NULL;
761 if (asprintf(&vpninfo->urlpath, "%s/%s",
762 oldurl, vpninfo->redirect_url) == -1) {
764 vpn_progress(vpninfo, PRG_ERR,
765 _("Allocating new path for relative redirect failed: %s\n"),
770 free(vpninfo->redirect_url);
771 vpninfo->redirect_url = NULL;
776 if (!form_buf || result != 200) {
777 vpn_progress(vpninfo, PRG_ERR,
778 _("Unexpected %d result from server\n"),
783 if (vpninfo->csd_stuburl) {
784 /* This is the CSD stub script, which we now need to run */
785 result = run_csd_script(vpninfo, form_buf, buflen);
791 /* Now we'll be redirected to the waiturl */
794 if (strncmp(form_buf, "<?xml", 5)) {
795 /* Not XML? Perhaps it's HTML with a refresh... */
796 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
797 vpn_progress(vpninfo, PRG_INFO,
798 _("Refreshing %s after 1 second...\n"),
803 vpn_progress(vpninfo, PRG_ERR,
804 _("Unknown response from server\n"));
809 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
810 &method, &request_body_type);
820 /* A return value of 2 means the XML form indicated
821 success. We _should_ have a cookie... */
823 for (opt = vpninfo->cookies; opt; opt = opt->next) {
825 if (!strcmp(opt->option, "webvpn"))
826 vpninfo->cookie = opt->value;
827 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
828 char *tok = opt->value;
829 char *bu = NULL, *fu = NULL, *sha = NULL;
832 if (tok != opt->value)
835 if (!strncmp(tok, "bu:", 3))
837 else if (!strncmp(tok, "fu:", 3))
839 else if (!strncmp(tok, "fh:", 3)) {
840 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
841 SHA_DIGEST_LENGTH * 2))
845 } while ((tok = strchr(tok, '&')));
848 fetch_config(vpninfo, bu, fu, sha);
851 if (vpninfo->csd_scriptname) {
852 unlink(vpninfo->csd_scriptname);
853 free(vpninfo->csd_scriptname);
854 vpninfo->csd_scriptname = NULL;
859 char *openconnect_create_useragent(const char *base)
863 if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
869 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
870 char *buf, size_t len)
878 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
879 if (buf[i] == '\n') {
881 if (i && buf[i-1] == '\r') {
898 static int proxy_write(struct openconnect_info *vpninfo, int fd,
899 unsigned char *buf, size_t len)
903 for (count = 0; count < len; ) {
904 fd_set rd_set, wr_set;
911 if (vpninfo->cancel_fd != -1) {
912 FD_SET(vpninfo->cancel_fd, &rd_set);
913 if (vpninfo->cancel_fd > fd)
914 maxfd = vpninfo->cancel_fd;
917 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
918 if (vpninfo->cancel_fd != -1 &&
919 FD_ISSET(vpninfo->cancel_fd, &rd_set))
922 /* Not that this should ever be able to happen... */
923 if (!FD_ISSET(fd, &wr_set))
926 i = write(fd, buf + count, len - count);
935 static int proxy_read(struct openconnect_info *vpninfo, int fd,
936 unsigned char *buf, size_t len)
940 for (count = 0; count < len; ) {
947 if (vpninfo->cancel_fd != -1) {
948 FD_SET(vpninfo->cancel_fd, &rd_set);
949 if (vpninfo->cancel_fd > fd)
950 maxfd = vpninfo->cancel_fd;
953 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
954 if (vpninfo->cancel_fd != -1 &&
955 FD_ISSET(vpninfo->cancel_fd, &rd_set))
958 /* Not that this should ever be able to happen... */
959 if (!FD_ISSET(fd, &rd_set))
962 i = read(fd, buf + count, len - count);
971 static const char *socks_errors[] = {
972 N_("request granted"),
973 N_("general failure"),
974 N_("connection not allowed by ruleset"),
975 N_("network unreachable"),
976 N_("host unreachable"),
977 N_("connection refused by destination host"),
979 N_("command not supported / protocol error"),
980 N_("address type not supported")
983 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
985 unsigned char buf[1024];
988 buf[0] = 5; /* SOCKS version */
989 buf[1] = 1; /* # auth methods */
990 buf[2] = 0; /* No auth supported */
992 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
993 vpn_progress(vpninfo, PRG_ERR,
994 _("Error writing auth request to SOCKS proxy: %s\n"),
999 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1000 vpn_progress(vpninfo, PRG_ERR,
1001 _("Error reading auth response from SOCKS proxy: %s\n"),
1006 vpn_progress(vpninfo, PRG_ERR,
1007 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1013 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1014 vpn_progress(vpninfo, PRG_ERR,
1015 _("SOCKS proxy error %02x: %s\n"),
1016 buf[1], _(socks_errors[buf[1]]));
1018 vpn_progress(vpninfo, PRG_ERR,
1019 _("SOCKS proxy error %02x\n"),
1024 vpn_progress(vpninfo, PRG_INFO,
1025 _("Requesting SOCKS proxy connection to %s:%d\n"),
1026 vpninfo->hostname, vpninfo->port);
1028 buf[0] = 5; /* SOCKS version */
1029 buf[1] = 1; /* CONNECT */
1030 buf[2] = 0; /* Reserved */
1031 buf[3] = 3; /* Address type is domain name */
1032 buf[4] = strlen(vpninfo->hostname);
1033 strcpy((char *)buf + 5, vpninfo->hostname);
1034 i = strlen(vpninfo->hostname) + 5;
1035 buf[i++] = vpninfo->port >> 8;
1036 buf[i++] = vpninfo->port & 0xff;
1038 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1039 vpn_progress(vpninfo, PRG_ERR,
1040 _("Error writing connect request to SOCKS proxy: %s\n"),
1044 /* Read 5 bytes -- up to and including the first byte of the returned
1045 address (which might be the length byte of a domain name) */
1046 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1047 vpn_progress(vpninfo, PRG_ERR,
1048 _("Error reading connect response from SOCKS proxy: %s\n"),
1053 vpn_progress(vpninfo, PRG_ERR,
1054 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1061 /* Connect responses contain an address */
1063 case 1: /* Legacy IP */
1066 case 3: /* Domain name */
1073 vpn_progress(vpninfo, PRG_ERR,
1074 _("Unexpected address type %02x in SOCKS connect response\n"),
1079 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1080 vpn_progress(vpninfo, PRG_ERR,
1081 _("Error reading connect response from SOCKS proxy: %s\n"),
1088 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1090 char buf[MAX_BUF_LEN];
1093 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1094 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1095 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1096 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1097 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1098 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1099 sprintf(buf + strlen(buf), "\r\n");
1101 vpn_progress(vpninfo, PRG_INFO,
1102 _("Requesting HTTP proxy connection to %s:%d\n"),
1103 vpninfo->hostname, vpninfo->port);
1105 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1107 vpn_progress(vpninfo, PRG_ERR,
1108 _("Sending proxy request failed: %s\n"),
1113 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1114 vpn_progress(vpninfo, PRG_ERR,
1115 _("Error fetching proxy response\n"));
1119 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1120 buf[8] != ' ' || !(result = atoi(buf+9))) {
1121 vpn_progress(vpninfo, PRG_ERR,
1122 _("Failed to parse proxy response '%s'\n"), buf);
1126 if (result != 200) {
1127 vpn_progress(vpninfo, PRG_ERR,
1128 _("Proxy CONNECT request failed: %s\n"), buf);
1132 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1134 vpn_progress(vpninfo, PRG_ERR,
1135 _("Failed to read proxy response\n"));
1138 vpn_progress(vpninfo, PRG_ERR,
1139 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1146 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1148 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1149 return process_http_proxy(vpninfo, ssl_sock);
1151 if (!strcmp(vpninfo->proxy_type, "socks") ||
1152 !strcmp(vpninfo->proxy_type, "socks5"))
1153 return process_socks_proxy(vpninfo, ssl_sock);
1155 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1156 vpninfo->proxy_type);
1160 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1168 free(vpninfo->proxy_type);
1169 vpninfo->proxy_type = NULL;
1170 free(vpninfo->proxy);
1171 vpninfo->proxy = NULL;
1173 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1174 &vpninfo->proxy_port, NULL, 80);
1178 if (vpninfo->proxy_type &&
1179 strcmp(vpninfo->proxy_type, "http") &&
1180 strcmp(vpninfo->proxy_type, "socks") &&
1181 strcmp(vpninfo->proxy_type, "socks5")) {
1182 vpn_progress(vpninfo, PRG_ERR,
1183 _("Only http or socks(5) proxies supported\n"));
1184 free(vpninfo->proxy_type);
1185 vpninfo->proxy_type = NULL;
1186 free(vpninfo->proxy);
1187 vpninfo->proxy = NULL;