2 * OpenConnect (SSL + DTLS) VPN client
4 * Copyright © 2008-2012 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 = openconnect_SSL_read(vpninfo, 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 = openconnect_SSL_read(vpninfo, 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 = openconnect_SSL_read(vpninfo, body + done, 16384);
329 /* Connection closed. Reduce allocation to just what we need */
330 body = realloc(body, done + 1);
338 if (closeconn || vpninfo->no_http_keepalive)
339 openconnect_close_https(vpninfo);
347 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
350 struct vpn_option *opt;
351 char buf[MAX_BUF_LEN];
352 char *config_buf = NULL;
354 unsigned char local_sha1_bin[SHA1_SIZE];
355 char local_sha1_ascii[(SHA1_SIZE * 2)+1];
358 sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
359 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
360 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
361 sprintf(buf + strlen(buf), "Accept: */*\r\n");
362 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
364 if (vpninfo->cookies) {
365 sprintf(buf + strlen(buf), "Cookie: ");
366 for (opt = vpninfo->cookies; opt; opt = opt->next)
367 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
368 opt->value, opt->next ? "; " : "\r\n");
370 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
372 if (openconnect_SSL_write(vpninfo, buf, strlen(buf))) {
373 vpn_progress(vpninfo, PRG_ERR,
374 _("Failed to send GET request for new config\n"));
378 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
380 /* We'll already have complained about whatever offended us */
389 openconnect_sha1(local_sha1_bin, config_buf, buflen);
391 for (i = 0; i < SHA1_SIZE; i++)
392 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
394 if (strcasecmp(server_sha1, local_sha1_ascii)) {
395 vpn_progress(vpninfo, PRG_ERR,
396 _("Downloaded config file did not match intended SHA1\n"));
401 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
406 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
411 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
412 vpn_progress(vpninfo, PRG_ERR,
413 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
414 "This facility is disabled by default for security reasons, so you may wish to enable it."));
419 vpn_progress(vpninfo, PRG_INFO,
420 _("Trying to run Linux CSD trojan script."));
423 sprintf(fname, "/tmp/csdXXXXXX");
427 vpn_progress(vpninfo, PRG_ERR,
428 _("Failed to open temporary CSD script file: %s\n"),
433 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
435 vpn_progress(vpninfo, PRG_ERR,
436 _("Failed to write temporary CSD script file: %s\n"),
444 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
445 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
446 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
447 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
451 if (vpninfo->uid_csd != getuid()) {
454 if (setuid(vpninfo->uid_csd)) {
455 fprintf(stderr, _("Failed to set uid %ld\n"),
456 (long)vpninfo->uid_csd);
459 if (!(pw = getpwuid(vpninfo->uid_csd))) {
460 fprintf(stderr, _("Invalid user uid=%ld\n"),
461 (long)vpninfo->uid_csd);
464 setenv("HOME", pw->pw_dir, 1);
465 if (chdir(pw->pw_dir)) {
466 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
467 pw->pw_dir, strerror(errno));
471 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
472 fprintf(stderr, _("Warning: you are running insecure "
473 "CSD code with root privileges\n"
474 "\t Use command line option \"--csd-user\"\n"));
476 if (vpninfo->uid_csd_given == 2) {
477 /* The NM tool really needs not to get spurious output
478 on stdout, which the CSD trojan spews. */
481 if (vpninfo->csd_wrapper)
482 csd_argv[i++] = vpninfo->csd_wrapper;
483 csd_argv[i++] = fname;
484 csd_argv[i++]= (char *)"-ticket";
485 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
487 csd_argv[i++]= (char *)"-stub";
488 csd_argv[i++]= (char *)"\"0\"";
489 csd_argv[i++]= (char *)"-group";
490 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
493 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
495 get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
499 csd_argv[i++]= (char *)"-certhash";
500 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
502 csd_argv[i++]= (char *)"-url";
503 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
505 /* WTF would it want to know this for? */
506 csd_argv[i++]= (char *)"-vpnclient";
507 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
508 csd_argv[i++]= (char *)"-connect";
509 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
511 csd_argv[i++]= (char *)"-connectparam";
512 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
514 csd_argv[i++]= (char *)"-langselen";
515 csd_argv[i++] = NULL;
517 execv(csd_argv[0], csd_argv);
518 vpn_progress(vpninfo, PRG_ERR,
519 _("Failed to exec CSD script %s\n"), csd_argv[0]);
523 free(vpninfo->csd_stuburl);
524 vpninfo->csd_stuburl = NULL;
525 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
526 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
527 vpninfo->csd_waiturl = NULL;
528 vpninfo->csd_scriptname = strdup(fname);
530 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
535 #ifndef HAVE_STRCASESTR
536 static char *openconnect__strcasestr(const char *haystack, const char *needle)
538 int hlen = strlen(haystack);
539 int nlen = strlen(needle);
542 for (i = 0; i < hlen - nlen + 1; i++) {
543 for (j = 0; j < nlen; j++) {
544 if (tolower(haystack[i + j]) !=
549 return (char *)haystack + i;
553 #define strcasestr openconnect__strcasestr
557 int internal_parse_url(char *url, char **res_proto, char **res_host,
558 int *res_port, char **res_path, int default_port)
561 char *host, *path, *port_str;
564 host = strstr(url, "://");
569 if (!strcasecmp(proto, "https"))
571 else if (!strcasecmp(proto, "http"))
573 else if (!strcasecmp(proto, "socks") ||
574 !strcasecmp(proto, "socks4") ||
575 !strcasecmp(proto, "socks5"))
578 return -EPROTONOSUPPORT;
588 path = strchr(host, '/');
592 port_str = strrchr(host, ':');
595 int new_port = strtol(port_str + 1, &end, 10);
604 *res_proto = proto ? strdup(proto) : NULL;
606 *res_host = strdup(host);
610 *res_path = (path && *path) ? strdup(path) : NULL;
612 /* Undo the damage we did to the original string */
624 * = 0, no cookie (user cancel)
625 * = 1, obtained cookie
627 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
629 struct vpn_option *opt, *next;
630 char buf[MAX_BUF_LEN];
631 char *form_buf = NULL;
633 char request_body[2048];
634 const char *request_body_type = NULL;
635 const char *method = "GET";
642 if (openconnect_open_https(vpninfo)) {
643 vpn_progress(vpninfo, PRG_ERR,
644 _("Failed to open HTTPS connection to %s\n"),
650 * It would be nice to use cURL for this, but we really need to guarantee
651 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
652 * to have any way to let us provide our own socket read/write functions.
653 * We can only provide a socket _open_ function. Which would require having
654 * a socketpair() and servicing the "other" end of it.
656 * So we process the HTTP for ourselves...
658 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
659 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
660 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
661 sprintf(buf + strlen(buf), "Accept: */*\r\n");
662 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
664 if (vpninfo->cookies) {
665 sprintf(buf + strlen(buf), "Cookie: ");
666 for (opt = vpninfo->cookies; opt; opt = opt->next)
667 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
668 opt->value, opt->next ? "; " : "\r\n");
670 if (request_body_type) {
671 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
673 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
674 strlen(request_body));
676 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
677 if (request_body_type)
678 sprintf(buf + strlen(buf), "%s", request_body);
680 if (vpninfo->port == 443)
681 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
682 method, vpninfo->hostname,
683 vpninfo->urlpath ?: "");
685 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
686 method, vpninfo->hostname, vpninfo->port,
687 vpninfo->urlpath ?: "");
689 result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
693 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
695 /* We'll already have complained about whatever offended us */
699 if (result != 200 && vpninfo->redirect_url) {
701 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
702 /* New host. Tear down the existing connection and make a new one */
707 free(vpninfo->urlpath);
708 vpninfo->urlpath = NULL;
710 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
712 vpn_progress(vpninfo, PRG_ERR,
713 _("Failed to parse redirected URL '%s': %s\n"),
714 vpninfo->redirect_url, strerror(-ret));
715 free(vpninfo->redirect_url);
716 vpninfo->redirect_url = NULL;
721 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
722 free(vpninfo->hostname);
723 vpninfo->hostname = host;
724 vpninfo->port = port;
726 /* Kill the existing connection, and a new one will happen */
727 free(vpninfo->peer_addr);
728 vpninfo->peer_addr = NULL;
729 openconnect_close_https(vpninfo);
731 for (opt = vpninfo->cookies; opt; opt = next) {
738 vpninfo->cookies = NULL;
742 free(vpninfo->redirect_url);
743 vpninfo->redirect_url = NULL;
746 } else if (strstr(vpninfo->redirect_url, "://")) {
747 vpn_progress(vpninfo, PRG_ERR,
748 _("Cannot follow redirection to non-https URL '%s'\n"),
749 vpninfo->redirect_url);
750 free(vpninfo->redirect_url);
751 vpninfo->redirect_url = NULL;
754 } else if (vpninfo->redirect_url[0] == '/') {
755 /* Absolute redirect within same host */
756 free(vpninfo->urlpath);
757 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
758 free(vpninfo->redirect_url);
759 vpninfo->redirect_url = NULL;
762 char *lastslash = NULL;
763 if (vpninfo->urlpath)
764 lastslash = strrchr(vpninfo->urlpath, '/');
766 free(vpninfo->urlpath);
767 vpninfo->urlpath = vpninfo->redirect_url;
768 vpninfo->redirect_url = NULL;
770 char *oldurl = vpninfo->urlpath;
772 vpninfo->urlpath = NULL;
773 if (asprintf(&vpninfo->urlpath, "%s/%s",
774 oldurl, vpninfo->redirect_url) == -1) {
776 vpn_progress(vpninfo, PRG_ERR,
777 _("Allocating new path for relative redirect failed: %s\n"),
782 free(vpninfo->redirect_url);
783 vpninfo->redirect_url = NULL;
788 if (!form_buf || result != 200) {
789 vpn_progress(vpninfo, PRG_ERR,
790 _("Unexpected %d result from server\n"),
795 if (vpninfo->csd_stuburl) {
796 /* This is the CSD stub script, which we now need to run */
797 result = run_csd_script(vpninfo, form_buf, buflen);
803 /* Now we'll be redirected to the waiturl */
806 if (strncmp(form_buf, "<?xml", 5)) {
807 /* Not XML? Perhaps it's HTML with a refresh... */
808 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
809 vpn_progress(vpninfo, PRG_INFO,
810 _("Refreshing %s after 1 second...\n"),
815 vpn_progress(vpninfo, PRG_ERR,
816 _("Unknown response from server\n"));
821 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
822 &method, &request_body_type);
832 /* A return value of 2 means the XML form indicated
833 success. We _should_ have a cookie... */
835 for (opt = vpninfo->cookies; opt; opt = opt->next) {
837 if (!strcmp(opt->option, "webvpn"))
838 vpninfo->cookie = opt->value;
839 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
840 char *tok = opt->value;
841 char *bu = NULL, *fu = NULL, *sha = NULL;
844 if (tok != opt->value)
847 if (!strncmp(tok, "bu:", 3))
849 else if (!strncmp(tok, "fu:", 3))
851 else if (!strncmp(tok, "fh:", 3)) {
852 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
857 } while ((tok = strchr(tok, '&')));
860 fetch_config(vpninfo, bu, fu, sha);
863 if (vpninfo->csd_scriptname) {
864 unlink(vpninfo->csd_scriptname);
865 free(vpninfo->csd_scriptname);
866 vpninfo->csd_scriptname = NULL;
871 char *openconnect_create_useragent(const char *base)
875 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
881 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
882 char *buf, size_t len)
890 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
891 if (buf[i] == '\n') {
893 if (i && buf[i-1] == '\r') {
910 static int proxy_write(struct openconnect_info *vpninfo, int fd,
911 unsigned char *buf, size_t len)
915 for (count = 0; count < len; ) {
916 fd_set rd_set, wr_set;
923 if (vpninfo->cancel_fd != -1) {
924 FD_SET(vpninfo->cancel_fd, &rd_set);
925 if (vpninfo->cancel_fd > fd)
926 maxfd = vpninfo->cancel_fd;
929 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
930 if (vpninfo->cancel_fd != -1 &&
931 FD_ISSET(vpninfo->cancel_fd, &rd_set))
934 /* Not that this should ever be able to happen... */
935 if (!FD_ISSET(fd, &wr_set))
938 i = write(fd, buf + count, len - count);
947 static int proxy_read(struct openconnect_info *vpninfo, int fd,
948 unsigned char *buf, size_t len)
952 for (count = 0; count < len; ) {
959 if (vpninfo->cancel_fd != -1) {
960 FD_SET(vpninfo->cancel_fd, &rd_set);
961 if (vpninfo->cancel_fd > fd)
962 maxfd = vpninfo->cancel_fd;
965 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
966 if (vpninfo->cancel_fd != -1 &&
967 FD_ISSET(vpninfo->cancel_fd, &rd_set))
970 /* Not that this should ever be able to happen... */
971 if (!FD_ISSET(fd, &rd_set))
974 i = read(fd, buf + count, len - count);
983 static const char *socks_errors[] = {
984 N_("request granted"),
985 N_("general failure"),
986 N_("connection not allowed by ruleset"),
987 N_("network unreachable"),
988 N_("host unreachable"),
989 N_("connection refused by destination host"),
991 N_("command not supported / protocol error"),
992 N_("address type not supported")
995 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
997 unsigned char buf[1024];
1000 buf[0] = 5; /* SOCKS version */
1001 buf[1] = 1; /* # auth methods */
1002 buf[2] = 0; /* No auth supported */
1004 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1005 vpn_progress(vpninfo, PRG_ERR,
1006 _("Error writing auth request to SOCKS proxy: %s\n"),
1011 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1012 vpn_progress(vpninfo, PRG_ERR,
1013 _("Error reading auth response from SOCKS proxy: %s\n"),
1018 vpn_progress(vpninfo, PRG_ERR,
1019 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1025 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1026 vpn_progress(vpninfo, PRG_ERR,
1027 _("SOCKS proxy error %02x: %s\n"),
1028 buf[1], _(socks_errors[buf[1]]));
1030 vpn_progress(vpninfo, PRG_ERR,
1031 _("SOCKS proxy error %02x\n"),
1036 vpn_progress(vpninfo, PRG_INFO,
1037 _("Requesting SOCKS proxy connection to %s:%d\n"),
1038 vpninfo->hostname, vpninfo->port);
1040 buf[0] = 5; /* SOCKS version */
1041 buf[1] = 1; /* CONNECT */
1042 buf[2] = 0; /* Reserved */
1043 buf[3] = 3; /* Address type is domain name */
1044 buf[4] = strlen(vpninfo->hostname);
1045 strcpy((char *)buf + 5, vpninfo->hostname);
1046 i = strlen(vpninfo->hostname) + 5;
1047 buf[i++] = vpninfo->port >> 8;
1048 buf[i++] = vpninfo->port & 0xff;
1050 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1051 vpn_progress(vpninfo, PRG_ERR,
1052 _("Error writing connect request to SOCKS proxy: %s\n"),
1056 /* Read 5 bytes -- up to and including the first byte of the returned
1057 address (which might be the length byte of a domain name) */
1058 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1059 vpn_progress(vpninfo, PRG_ERR,
1060 _("Error reading connect response from SOCKS proxy: %s\n"),
1065 vpn_progress(vpninfo, PRG_ERR,
1066 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1073 /* Connect responses contain an address */
1075 case 1: /* Legacy IP */
1078 case 3: /* Domain name */
1085 vpn_progress(vpninfo, PRG_ERR,
1086 _("Unexpected address type %02x in SOCKS connect response\n"),
1091 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1092 vpn_progress(vpninfo, PRG_ERR,
1093 _("Error reading connect response from SOCKS proxy: %s\n"),
1100 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1102 char buf[MAX_BUF_LEN];
1105 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1106 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1107 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1108 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1109 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1110 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1111 sprintf(buf + strlen(buf), "\r\n");
1113 vpn_progress(vpninfo, PRG_INFO,
1114 _("Requesting HTTP proxy connection to %s:%d\n"),
1115 vpninfo->hostname, vpninfo->port);
1117 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1119 vpn_progress(vpninfo, PRG_ERR,
1120 _("Sending proxy request failed: %s\n"),
1125 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1126 vpn_progress(vpninfo, PRG_ERR,
1127 _("Error fetching proxy response\n"));
1131 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1132 buf[8] != ' ' || !(result = atoi(buf+9))) {
1133 vpn_progress(vpninfo, PRG_ERR,
1134 _("Failed to parse proxy response '%s'\n"), buf);
1138 if (result != 200) {
1139 vpn_progress(vpninfo, PRG_ERR,
1140 _("Proxy CONNECT request failed: %s\n"), buf);
1144 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1146 vpn_progress(vpninfo, PRG_ERR,
1147 _("Failed to read proxy response\n"));
1150 vpn_progress(vpninfo, PRG_ERR,
1151 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1158 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1160 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1161 return process_http_proxy(vpninfo, ssl_sock);
1163 if (!strcmp(vpninfo->proxy_type, "socks") ||
1164 !strcmp(vpninfo->proxy_type, "socks5"))
1165 return process_socks_proxy(vpninfo, ssl_sock);
1167 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1168 vpninfo->proxy_type);
1172 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1180 free(vpninfo->proxy_type);
1181 vpninfo->proxy_type = NULL;
1182 free(vpninfo->proxy);
1183 vpninfo->proxy = NULL;
1185 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1186 &vpninfo->proxy_port, NULL, 80);
1190 if (vpninfo->proxy_type &&
1191 strcmp(vpninfo->proxy_type, "http") &&
1192 strcmp(vpninfo->proxy_type, "socks") &&
1193 strcmp(vpninfo->proxy_type, "socks5")) {
1194 vpn_progress(vpninfo, PRG_ERR,
1195 _("Only http or socks(5) proxies supported\n"));
1196 free(vpninfo->proxy_type);
1197 vpninfo->proxy_type = NULL;
1198 free(vpninfo->proxy);
1199 vpninfo->proxy = NULL;