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 /* We don't cope with nonblocking mode... yet */
248 fcntl(vpninfo->ssl_fd, F_SETFL, fcntl(vpninfo->ssl_fd, F_GETFL) & ~O_NONBLOCK);
250 /* If we were given Content-Length, it's nice and easy... */
252 body = malloc(bodylen + 1);
255 while (done < bodylen) {
256 i = SSL_read(vpninfo->https_ssl, body + done, bodylen - done);
258 vpn_progress(vpninfo, PRG_ERR,
259 _("Error reading HTTP response body\n"));
265 } else if (bodylen == BODY_CHUNKED) {
266 /* ... else, chunked */
267 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
268 int chunklen, lastchunk = 0;
271 vpn_progress(vpninfo, PRG_ERR,
272 _("Error fetching chunk header\n"));
275 chunklen = strtol(buf, NULL, 16);
280 body = realloc(body, done + chunklen + 1);
284 i = SSL_read(vpninfo->https_ssl, body + done, chunklen);
286 vpn_progress(vpninfo, PRG_ERR,
287 _("Error reading HTTP response body\n"));
295 if ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
297 vpn_progress(vpninfo, PRG_ERR,
298 _("Error fetching HTTP response body\n"));
300 vpn_progress(vpninfo, PRG_ERR,
301 _("Error in chunked decoding. Expected '', got: '%s'"),
311 } else if (bodylen == BODY_HTTP10) {
313 vpn_progress(vpninfo, PRG_ERR,
314 _("Cannot receive HTTP 1.0 body without closing connection\n"));
318 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
320 body = realloc(body, done + 16384);
323 i = SSL_read(vpninfo->https_ssl, body + done, 16384);
325 body = realloc(body, done + 1);
334 if (closeconn || vpninfo->no_http_keepalive) {
335 SSL_free(vpninfo->https_ssl);
336 vpninfo->https_ssl = NULL;
337 close(vpninfo->ssl_fd);
338 vpninfo->ssl_fd = -1;
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[SHA_DIGEST_LENGTH];
355 char local_sha1_ascii[(SHA_DIGEST_LENGTH * 2)+1];
359 sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
360 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
361 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
362 sprintf(buf + strlen(buf), "Accept: */*\r\n");
363 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
365 if (vpninfo->cookies) {
366 sprintf(buf + strlen(buf), "Cookie: ");
367 for (opt = vpninfo->cookies; opt; opt = opt->next)
368 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
369 opt->value, opt->next ? "; " : "\r\n");
371 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
373 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
375 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
377 /* We'll already have complained about whatever offended us */
387 EVP_Digest(config_buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
388 EVP_MD_CTX_cleanup(&c);
390 for (i = 0; i < SHA_DIGEST_LENGTH; i++)
391 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
393 if (strcasecmp(server_sha1, local_sha1_ascii)) {
394 vpn_progress(vpninfo, PRG_ERR,
395 _("Downloaded config file did not match intended SHA1\n"));
400 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
405 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
410 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
411 vpn_progress(vpninfo, PRG_ERR,
412 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
413 "This facility is disabled by default for security reasons, so you may wish to enable it."));
418 vpn_progress(vpninfo, PRG_INFO,
419 _("Trying to run Linux CSD trojan script."));
422 sprintf(fname, "/tmp/csdXXXXXX");
426 vpn_progress(vpninfo, PRG_ERR,
427 _("Failed to open temporary CSD script file: %s\n"),
432 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
434 vpn_progress(vpninfo, PRG_ERR,
435 _("Failed to write temporary CSD script file: %s\n"),
443 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
444 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
445 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
446 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
450 if (vpninfo->uid_csd != getuid()) {
453 if (setuid(vpninfo->uid_csd)) {
454 fprintf(stderr, _("Failed to set uid %ld\n"),
455 (long)vpninfo->uid_csd);
458 if (!(pw = getpwuid(vpninfo->uid_csd))) {
459 fprintf(stderr, _("Invalid user uid=%ld\n"),
460 (long)vpninfo->uid_csd);
463 setenv("HOME", pw->pw_dir, 1);
464 if (chdir(pw->pw_dir)) {
465 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
466 pw->pw_dir, strerror(errno));
470 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
471 fprintf(stderr, _("Warning: you are running insecure "
472 "CSD code with root privileges\n"
473 "\t Use command line option \"--csd-user\"\n"));
475 if (vpninfo->uid_csd_given == 2) {
476 /* The NM tool really needs not to get spurious output
477 on stdout, which the CSD trojan spews. */
480 if (vpninfo->csd_wrapper)
481 csd_argv[i++] = vpninfo->csd_wrapper;
482 csd_argv[i++] = fname;
483 csd_argv[i++]= (char *)"-ticket";
484 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
486 csd_argv[i++]= (char *)"-stub";
487 csd_argv[i++]= (char *)"\"0\"";
488 csd_argv[i++]= (char *)"-group";
489 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
492 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
494 get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
498 csd_argv[i++]= (char *)"-certhash";
499 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
501 csd_argv[i++]= (char *)"-url";
502 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
504 /* WTF would it want to know this for? */
505 csd_argv[i++]= (char *)"-vpnclient";
506 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
507 csd_argv[i++]= (char *)"-connect";
508 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
510 csd_argv[i++]= (char *)"-connectparam";
511 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
513 csd_argv[i++]= (char *)"-langselen";
514 csd_argv[i++] = NULL;
516 execv(csd_argv[0], csd_argv);
517 vpn_progress(vpninfo, PRG_ERR,
518 _("Failed to exec CSD script %s\n"), csd_argv[0]);
522 free(vpninfo->csd_stuburl);
523 vpninfo->csd_stuburl = NULL;
524 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
525 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
526 vpninfo->csd_waiturl = NULL;
527 vpninfo->csd_scriptname = strdup(fname);
529 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
534 #ifndef HAVE_STRCASESTR
535 static char *openconnect__strcasestr(const char *haystack, const char *needle)
537 int hlen = strlen(haystack);
538 int nlen = strlen(needle);
541 for (i = 0; i < hlen - nlen + 1; i++) {
542 for (j = 0; j < nlen; j++) {
543 if (tolower(haystack[i + j]) !=
548 return (char *)haystack + i;
552 #define strcasestr openconnect__strcasestr
556 int internal_parse_url(char *url, char **res_proto, char **res_host,
557 int *res_port, char **res_path, int default_port)
560 char *host, *path, *port_str;
563 host = strstr(url, "://");
568 if (!strcasecmp(proto, "https"))
570 else if (!strcasecmp(proto, "http"))
572 else if (!strcasecmp(proto, "socks") ||
573 !strcasecmp(proto, "socks4") ||
574 !strcasecmp(proto, "socks5"))
577 return -EPROTONOSUPPORT;
587 path = strchr(host, '/');
591 port_str = strrchr(host, ':');
594 int new_port = strtol(port_str + 1, &end, 10);
603 *res_proto = proto ? strdup(proto) : NULL;
605 *res_host = strdup(host);
609 *res_path = (path && *path) ? strdup(path) : NULL;
611 /* Undo the damage we did to the original string */
621 * = 0, no cookie (user cancel)
622 * = 1, obtained cookie
624 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
626 struct vpn_option *opt, *next;
627 char buf[MAX_BUF_LEN];
628 char *form_buf = NULL;
630 char request_body[2048];
631 const char *request_body_type = NULL;
632 const char *method = "GET";
639 if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
640 vpn_progress(vpninfo, PRG_ERR,
641 _("Failed to open HTTPS connection to %s\n"),
647 * It would be nice to use cURL for this, but we really need to guarantee
648 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
649 * to have any way to let us provide our own socket read/write functions.
650 * We can only provide a socket _open_ function. Which would require having
651 * a socketpair() and servicing the "other" end of it.
653 * So we process the HTTP for ourselves...
655 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
656 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
657 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
658 sprintf(buf + strlen(buf), "Accept: */*\r\n");
659 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
661 if (vpninfo->cookies) {
662 sprintf(buf + strlen(buf), "Cookie: ");
663 for (opt = vpninfo->cookies; opt; opt = opt->next)
664 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
665 opt->value, opt->next ? "; " : "\r\n");
667 if (request_body_type) {
668 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
670 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
671 strlen(request_body));
673 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
674 if (request_body_type)
675 sprintf(buf + strlen(buf), "%s", request_body);
677 if (vpninfo->port == 443)
678 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
679 method, vpninfo->hostname,
680 vpninfo->urlpath ?: "");
682 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
683 method, vpninfo->hostname, vpninfo->port,
684 vpninfo->urlpath ?: "");
686 result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
690 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
692 /* We'll already have complained about whatever offended us */
696 if (result != 200 && vpninfo->redirect_url) {
698 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
699 /* New host. Tear down the existing connection and make a new one */
704 free(vpninfo->urlpath);
705 vpninfo->urlpath = NULL;
707 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
709 vpn_progress(vpninfo, PRG_ERR,
710 _("Failed to parse redirected URL '%s': %s\n"),
711 vpninfo->redirect_url, strerror(-ret));
712 free(vpninfo->redirect_url);
717 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
718 free(vpninfo->hostname);
719 vpninfo->hostname = host;
720 vpninfo->port = port;
722 /* Kill the existing connection, and a new one will happen */
723 free(vpninfo->peer_addr);
724 vpninfo->peer_addr = NULL;
725 if (vpninfo->https_ssl) {
726 SSL_free(vpninfo->https_ssl);
727 vpninfo->https_ssl = NULL;
728 close(vpninfo->ssl_fd);
729 vpninfo->ssl_fd = -1;
732 for (opt = vpninfo->cookies; opt; opt = next) {
739 vpninfo->cookies = NULL;
743 free(vpninfo->redirect_url);
744 vpninfo->redirect_url = NULL;
747 } else if (vpninfo->redirect_url[0] == '/') {
748 /* Absolute redirect within same host */
749 free(vpninfo->urlpath);
750 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
751 free(vpninfo->redirect_url);
752 vpninfo->redirect_url = NULL;
755 char *lastslash = NULL;
756 if (vpninfo->urlpath)
757 lastslash = strrchr(vpninfo->urlpath, '/');
759 free(vpninfo->urlpath);
760 vpninfo->urlpath = vpninfo->redirect_url;
761 vpninfo->redirect_url = NULL;
763 char *oldurl = vpninfo->urlpath;
765 vpninfo->urlpath = NULL;
766 if (asprintf(&vpninfo->urlpath, "%s/%s",
767 oldurl, vpninfo->redirect_url) == -1) {
769 vpn_progress(vpninfo, PRG_ERR,
770 _("Allocating new path for relative redirect failed: %s\n"),
775 free(vpninfo->redirect_url);
776 vpninfo->redirect_url = NULL;
781 if (!form_buf || result != 200) {
782 vpn_progress(vpninfo, PRG_ERR,
783 _("Unexpected %d result from server\n"),
788 if (vpninfo->csd_stuburl) {
789 /* This is the CSD stub script, which we now need to run */
790 result = run_csd_script(vpninfo, form_buf, buflen);
796 /* Now we'll be redirected to the waiturl */
799 if (strncmp(form_buf, "<?xml", 5)) {
800 /* Not XML? Perhaps it's HTML with a refresh... */
801 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
802 vpn_progress(vpninfo, PRG_INFO,
803 _("Refreshing %s after 1 second...\n"),
808 vpn_progress(vpninfo, PRG_ERR,
809 _("Unknown response from server\n"));
814 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
815 &method, &request_body_type);
825 /* A return value of 2 means the XML form indicated
826 success. We _should_ have a cookie... */
828 for (opt = vpninfo->cookies; opt; opt = opt->next) {
830 if (!strcmp(opt->option, "webvpn"))
831 vpninfo->cookie = opt->value;
832 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
833 char *tok = opt->value;
834 char *bu = NULL, *fu = NULL, *sha = NULL;
837 if (tok != opt->value)
840 if (!strncmp(tok, "bu:", 3))
842 else if (!strncmp(tok, "fu:", 3))
844 else if (!strncmp(tok, "fh:", 3)) {
845 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
846 SHA_DIGEST_LENGTH * 2))
850 } while ((tok = strchr(tok, '&')));
853 fetch_config(vpninfo, bu, fu, sha);
856 if (vpninfo->csd_scriptname) {
857 unlink(vpninfo->csd_scriptname);
858 free(vpninfo->csd_scriptname);
859 vpninfo->csd_scriptname = NULL;
864 char *openconnect_create_useragent(const char *base)
868 if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
874 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
875 char *buf, size_t len)
883 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
884 if (buf[i] == '\n') {
886 if (i && buf[i-1] == '\r') {
903 static int proxy_write(struct openconnect_info *vpninfo, int fd,
904 unsigned char *buf, size_t len)
908 for (count = 0; count < len; ) {
909 fd_set rd_set, wr_set;
916 if (vpninfo->cancel_fd != -1) {
917 FD_SET(vpninfo->cancel_fd, &rd_set);
918 if (vpninfo->cancel_fd > fd)
919 maxfd = vpninfo->cancel_fd;
922 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
923 if (vpninfo->cancel_fd != -1 &&
924 FD_ISSET(vpninfo->cancel_fd, &rd_set))
927 /* Not that this should ever be able to happen... */
928 if (!FD_ISSET(fd, &wr_set))
931 i = write(fd, buf + count, len - count);
940 static int proxy_read(struct openconnect_info *vpninfo, int fd,
941 unsigned char *buf, size_t len)
945 for (count = 0; count < len; ) {
952 if (vpninfo->cancel_fd != -1) {
953 FD_SET(vpninfo->cancel_fd, &rd_set);
954 if (vpninfo->cancel_fd > fd)
955 maxfd = vpninfo->cancel_fd;
958 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
959 if (vpninfo->cancel_fd != -1 &&
960 FD_ISSET(vpninfo->cancel_fd, &rd_set))
963 /* Not that this should ever be able to happen... */
964 if (!FD_ISSET(fd, &rd_set))
967 i = read(fd, buf + count, len - count);
976 static const char *socks_errors[] = {
977 N_("request granted"),
978 N_("general failure"),
979 N_("connection not allowed by ruleset"),
980 N_("network unreachable"),
981 N_("host unreachable"),
982 N_("connection refused by destination host"),
984 N_("command not supported / protocol error"),
985 N_("address type not supported")
988 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
990 unsigned char buf[1024];
993 buf[0] = 5; /* SOCKS version */
994 buf[1] = 1; /* # auth methods */
995 buf[2] = 0; /* No auth supported */
997 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
998 vpn_progress(vpninfo, PRG_ERR,
999 _("Error writing auth request to SOCKS proxy: %s\n"),
1004 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1005 vpn_progress(vpninfo, PRG_ERR,
1006 _("Error reading auth response from SOCKS proxy: %s\n"),
1011 vpn_progress(vpninfo, PRG_ERR,
1012 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1018 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1019 vpn_progress(vpninfo, PRG_ERR,
1020 _("SOCKS proxy error %02x: %s\n"),
1021 buf[1], _(socks_errors[buf[1]]));
1023 vpn_progress(vpninfo, PRG_ERR,
1024 _("SOCKS proxy error %02x\n"),
1029 vpn_progress(vpninfo, PRG_INFO,
1030 _("Requesting SOCKS proxy connection to %s:%d\n"),
1031 vpninfo->hostname, vpninfo->port);
1033 buf[0] = 5; /* SOCKS version */
1034 buf[1] = 1; /* CONNECT */
1035 buf[2] = 0; /* Reserved */
1036 buf[3] = 3; /* Address type is domain name */
1037 buf[4] = strlen(vpninfo->hostname);
1038 strcpy((char *)buf + 5, vpninfo->hostname);
1039 i = strlen(vpninfo->hostname) + 5;
1040 buf[i++] = vpninfo->port >> 8;
1041 buf[i++] = vpninfo->port & 0xff;
1043 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1044 vpn_progress(vpninfo, PRG_ERR,
1045 _("Error writing connect request to SOCKS proxy: %s\n"),
1049 /* Read 5 bytes -- up to and including the first byte of the returned
1050 address (which might be the length byte of a domain name) */
1051 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1052 vpn_progress(vpninfo, PRG_ERR,
1053 _("Error reading connect response from SOCKS proxy: %s\n"),
1058 vpn_progress(vpninfo, PRG_ERR,
1059 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1066 /* Connect responses contain an address */
1068 case 1: /* Legacy IP */
1071 case 3: /* Domain name */
1078 vpn_progress(vpninfo, PRG_ERR,
1079 _("Unexpected address type %02x in SOCKS connect response\n"),
1084 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1085 vpn_progress(vpninfo, PRG_ERR,
1086 _("Error reading connect response from SOCKS proxy: %s\n"),
1093 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1095 char buf[MAX_BUF_LEN];
1098 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1099 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1100 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1101 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1102 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1103 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1104 sprintf(buf + strlen(buf), "\r\n");
1106 vpn_progress(vpninfo, PRG_INFO,
1107 _("Requesting HTTP proxy connection to %s:%d\n"),
1108 vpninfo->hostname, vpninfo->port);
1110 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1112 vpn_progress(vpninfo, PRG_ERR,
1113 _("Sending proxy request failed: %s\n"),
1118 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1119 vpn_progress(vpninfo, PRG_ERR,
1120 _("Error fetching proxy response\n"));
1124 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1125 buf[8] != ' ' || !(result = atoi(buf+9))) {
1126 vpn_progress(vpninfo, PRG_ERR,
1127 _("Failed to parse proxy response '%s'\n"), buf);
1131 if (result != 200) {
1132 vpn_progress(vpninfo, PRG_ERR,
1133 _("Proxy CONNECT request failed: %s\n"), buf);
1137 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1139 vpn_progress(vpninfo, PRG_ERR,
1140 _("Failed to read proxy response\n"));
1143 vpn_progress(vpninfo, PRG_ERR,
1144 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1151 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1153 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1154 return process_http_proxy(vpninfo, ssl_sock);
1156 if (!strcmp(vpninfo->proxy_type, "socks") ||
1157 !strcmp(vpninfo->proxy_type, "socks5"))
1158 return process_socks_proxy(vpninfo, ssl_sock);
1160 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1161 vpninfo->proxy_type);
1165 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1173 free(vpninfo->proxy_type);
1174 vpninfo->proxy_type = NULL;
1175 free(vpninfo->proxy);
1176 vpninfo->proxy = NULL;
1178 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1179 &vpninfo->proxy_port, NULL, 80);
1183 if (vpninfo->proxy_type &&
1184 strcmp(vpninfo->proxy_type, "http") &&
1185 strcmp(vpninfo->proxy_type, "socks") &&
1186 strcmp(vpninfo->proxy_type, "socks5")) {
1187 vpn_progress(vpninfo, PRG_ERR,
1188 _("Only http or socks(5) proxies supported\n"));
1189 free(vpninfo->proxy_type);
1190 vpninfo->proxy_type = NULL;
1191 free(vpninfo->proxy);
1192 vpninfo->proxy = NULL;