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 SSL_free(vpninfo->https_ssl);
340 vpninfo->https_ssl = NULL;
341 close(vpninfo->ssl_fd);
342 vpninfo->ssl_fd = -1;
351 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
354 struct vpn_option *opt;
355 char buf[MAX_BUF_LEN];
356 char *config_buf = NULL;
358 unsigned char local_sha1_bin[SHA_DIGEST_LENGTH];
359 char local_sha1_ascii[(SHA_DIGEST_LENGTH * 2)+1];
363 sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
364 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
365 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
366 sprintf(buf + strlen(buf), "Accept: */*\r\n");
367 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
369 if (vpninfo->cookies) {
370 sprintf(buf + strlen(buf), "Cookie: ");
371 for (opt = vpninfo->cookies; opt; opt = opt->next)
372 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
373 opt->value, opt->next ? "; " : "\r\n");
375 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
377 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
379 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
381 /* We'll already have complained about whatever offended us */
391 EVP_Digest(config_buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
392 EVP_MD_CTX_cleanup(&c);
394 for (i = 0; i < SHA_DIGEST_LENGTH; i++)
395 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
397 if (strcasecmp(server_sha1, local_sha1_ascii)) {
398 vpn_progress(vpninfo, PRG_ERR,
399 _("Downloaded config file did not match intended SHA1\n"));
404 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
409 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
414 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
415 vpn_progress(vpninfo, PRG_ERR,
416 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
417 "This facility is disabled by default for security reasons, so you may wish to enable it."));
422 vpn_progress(vpninfo, PRG_INFO,
423 _("Trying to run Linux CSD trojan script."));
426 sprintf(fname, "/tmp/csdXXXXXX");
430 vpn_progress(vpninfo, PRG_ERR,
431 _("Failed to open temporary CSD script file: %s\n"),
436 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
438 vpn_progress(vpninfo, PRG_ERR,
439 _("Failed to write temporary CSD script file: %s\n"),
447 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
448 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
449 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
450 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
454 if (vpninfo->uid_csd != getuid()) {
457 if (setuid(vpninfo->uid_csd)) {
458 fprintf(stderr, _("Failed to set uid %ld\n"),
459 (long)vpninfo->uid_csd);
462 if (!(pw = getpwuid(vpninfo->uid_csd))) {
463 fprintf(stderr, _("Invalid user uid=%ld\n"),
464 (long)vpninfo->uid_csd);
467 setenv("HOME", pw->pw_dir, 1);
468 if (chdir(pw->pw_dir)) {
469 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
470 pw->pw_dir, strerror(errno));
474 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
475 fprintf(stderr, _("Warning: you are running insecure "
476 "CSD code with root privileges\n"
477 "\t Use command line option \"--csd-user\"\n"));
479 if (vpninfo->uid_csd_given == 2) {
480 /* The NM tool really needs not to get spurious output
481 on stdout, which the CSD trojan spews. */
484 if (vpninfo->csd_wrapper)
485 csd_argv[i++] = vpninfo->csd_wrapper;
486 csd_argv[i++] = fname;
487 csd_argv[i++]= (char *)"-ticket";
488 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
490 csd_argv[i++]= (char *)"-stub";
491 csd_argv[i++]= (char *)"\"0\"";
492 csd_argv[i++]= (char *)"-group";
493 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
496 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
498 get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
502 csd_argv[i++]= (char *)"-certhash";
503 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
505 csd_argv[i++]= (char *)"-url";
506 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
508 /* WTF would it want to know this for? */
509 csd_argv[i++]= (char *)"-vpnclient";
510 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
511 csd_argv[i++]= (char *)"-connect";
512 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
514 csd_argv[i++]= (char *)"-connectparam";
515 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
517 csd_argv[i++]= (char *)"-langselen";
518 csd_argv[i++] = NULL;
520 execv(csd_argv[0], csd_argv);
521 vpn_progress(vpninfo, PRG_ERR,
522 _("Failed to exec CSD script %s\n"), csd_argv[0]);
526 free(vpninfo->csd_stuburl);
527 vpninfo->csd_stuburl = NULL;
528 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
529 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
530 vpninfo->csd_waiturl = NULL;
531 vpninfo->csd_scriptname = strdup(fname);
533 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
538 #ifndef HAVE_STRCASESTR
539 static char *openconnect__strcasestr(const char *haystack, const char *needle)
541 int hlen = strlen(haystack);
542 int nlen = strlen(needle);
545 for (i = 0; i < hlen - nlen + 1; i++) {
546 for (j = 0; j < nlen; j++) {
547 if (tolower(haystack[i + j]) !=
552 return (char *)haystack + i;
556 #define strcasestr openconnect__strcasestr
560 int internal_parse_url(char *url, char **res_proto, char **res_host,
561 int *res_port, char **res_path, int default_port)
564 char *host, *path, *port_str;
567 host = strstr(url, "://");
572 if (!strcasecmp(proto, "https"))
574 else if (!strcasecmp(proto, "http"))
576 else if (!strcasecmp(proto, "socks") ||
577 !strcasecmp(proto, "socks4") ||
578 !strcasecmp(proto, "socks5"))
581 return -EPROTONOSUPPORT;
591 path = strchr(host, '/');
595 port_str = strrchr(host, ':');
598 int new_port = strtol(port_str + 1, &end, 10);
607 *res_proto = proto ? strdup(proto) : NULL;
609 *res_host = strdup(host);
613 *res_path = (path && *path) ? strdup(path) : NULL;
615 /* Undo the damage we did to the original string */
627 * = 0, no cookie (user cancel)
628 * = 1, obtained cookie
630 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
632 struct vpn_option *opt, *next;
633 char buf[MAX_BUF_LEN];
634 char *form_buf = NULL;
636 char request_body[2048];
637 const char *request_body_type = NULL;
638 const char *method = "GET";
645 if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
646 vpn_progress(vpninfo, PRG_ERR,
647 _("Failed to open HTTPS connection to %s\n"),
653 * It would be nice to use cURL for this, but we really need to guarantee
654 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
655 * to have any way to let us provide our own socket read/write functions.
656 * We can only provide a socket _open_ function. Which would require having
657 * a socketpair() and servicing the "other" end of it.
659 * So we process the HTTP for ourselves...
661 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
662 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
663 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
664 sprintf(buf + strlen(buf), "Accept: */*\r\n");
665 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
667 if (vpninfo->cookies) {
668 sprintf(buf + strlen(buf), "Cookie: ");
669 for (opt = vpninfo->cookies; opt; opt = opt->next)
670 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
671 opt->value, opt->next ? "; " : "\r\n");
673 if (request_body_type) {
674 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
676 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
677 strlen(request_body));
679 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
680 if (request_body_type)
681 sprintf(buf + strlen(buf), "%s", request_body);
683 if (vpninfo->port == 443)
684 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
685 method, vpninfo->hostname,
686 vpninfo->urlpath ?: "");
688 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
689 method, vpninfo->hostname, vpninfo->port,
690 vpninfo->urlpath ?: "");
692 result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
696 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
698 /* We'll already have complained about whatever offended us */
702 if (result != 200 && vpninfo->redirect_url) {
704 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
705 /* New host. Tear down the existing connection and make a new one */
710 free(vpninfo->urlpath);
711 vpninfo->urlpath = NULL;
713 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
715 vpn_progress(vpninfo, PRG_ERR,
716 _("Failed to parse redirected URL '%s': %s\n"),
717 vpninfo->redirect_url, strerror(-ret));
718 free(vpninfo->redirect_url);
719 vpninfo->redirect_url = NULL;
724 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
725 free(vpninfo->hostname);
726 vpninfo->hostname = host;
727 vpninfo->port = port;
729 /* Kill the existing connection, and a new one will happen */
730 free(vpninfo->peer_addr);
731 vpninfo->peer_addr = NULL;
732 if (vpninfo->https_ssl) {
733 SSL_free(vpninfo->https_ssl);
734 vpninfo->https_ssl = NULL;
735 close(vpninfo->ssl_fd);
736 vpninfo->ssl_fd = -1;
739 for (opt = vpninfo->cookies; opt; opt = next) {
746 vpninfo->cookies = NULL;
750 free(vpninfo->redirect_url);
751 vpninfo->redirect_url = NULL;
754 } else if (strstr(vpninfo->redirect_url, "://")) {
755 vpn_progress(vpninfo, PRG_ERR,
756 _("Cannot follow redirection to non-https URL '%s'\n"),
757 vpninfo->redirect_url);
758 free(vpninfo->redirect_url);
759 vpninfo->redirect_url = NULL;
762 } else if (vpninfo->redirect_url[0] == '/') {
763 /* Absolute redirect within same host */
764 free(vpninfo->urlpath);
765 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
766 free(vpninfo->redirect_url);
767 vpninfo->redirect_url = NULL;
770 char *lastslash = NULL;
771 if (vpninfo->urlpath)
772 lastslash = strrchr(vpninfo->urlpath, '/');
774 free(vpninfo->urlpath);
775 vpninfo->urlpath = vpninfo->redirect_url;
776 vpninfo->redirect_url = NULL;
778 char *oldurl = vpninfo->urlpath;
780 vpninfo->urlpath = NULL;
781 if (asprintf(&vpninfo->urlpath, "%s/%s",
782 oldurl, vpninfo->redirect_url) == -1) {
784 vpn_progress(vpninfo, PRG_ERR,
785 _("Allocating new path for relative redirect failed: %s\n"),
790 free(vpninfo->redirect_url);
791 vpninfo->redirect_url = NULL;
796 if (!form_buf || result != 200) {
797 vpn_progress(vpninfo, PRG_ERR,
798 _("Unexpected %d result from server\n"),
803 if (vpninfo->csd_stuburl) {
804 /* This is the CSD stub script, which we now need to run */
805 result = run_csd_script(vpninfo, form_buf, buflen);
811 /* Now we'll be redirected to the waiturl */
814 if (strncmp(form_buf, "<?xml", 5)) {
815 /* Not XML? Perhaps it's HTML with a refresh... */
816 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
817 vpn_progress(vpninfo, PRG_INFO,
818 _("Refreshing %s after 1 second...\n"),
823 vpn_progress(vpninfo, PRG_ERR,
824 _("Unknown response from server\n"));
829 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
830 &method, &request_body_type);
840 /* A return value of 2 means the XML form indicated
841 success. We _should_ have a cookie... */
843 for (opt = vpninfo->cookies; opt; opt = opt->next) {
845 if (!strcmp(opt->option, "webvpn"))
846 vpninfo->cookie = opt->value;
847 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
848 char *tok = opt->value;
849 char *bu = NULL, *fu = NULL, *sha = NULL;
852 if (tok != opt->value)
855 if (!strncmp(tok, "bu:", 3))
857 else if (!strncmp(tok, "fu:", 3))
859 else if (!strncmp(tok, "fh:", 3)) {
860 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
861 SHA_DIGEST_LENGTH * 2))
865 } while ((tok = strchr(tok, '&')));
868 fetch_config(vpninfo, bu, fu, sha);
871 if (vpninfo->csd_scriptname) {
872 unlink(vpninfo->csd_scriptname);
873 free(vpninfo->csd_scriptname);
874 vpninfo->csd_scriptname = NULL;
879 char *openconnect_create_useragent(const char *base)
883 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
889 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
890 char *buf, size_t len)
898 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
899 if (buf[i] == '\n') {
901 if (i && buf[i-1] == '\r') {
918 static int proxy_write(struct openconnect_info *vpninfo, int fd,
919 unsigned char *buf, size_t len)
923 for (count = 0; count < len; ) {
924 fd_set rd_set, wr_set;
931 if (vpninfo->cancel_fd != -1) {
932 FD_SET(vpninfo->cancel_fd, &rd_set);
933 if (vpninfo->cancel_fd > fd)
934 maxfd = vpninfo->cancel_fd;
937 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
938 if (vpninfo->cancel_fd != -1 &&
939 FD_ISSET(vpninfo->cancel_fd, &rd_set))
942 /* Not that this should ever be able to happen... */
943 if (!FD_ISSET(fd, &wr_set))
946 i = write(fd, buf + count, len - count);
955 static int proxy_read(struct openconnect_info *vpninfo, int fd,
956 unsigned char *buf, size_t len)
960 for (count = 0; count < len; ) {
967 if (vpninfo->cancel_fd != -1) {
968 FD_SET(vpninfo->cancel_fd, &rd_set);
969 if (vpninfo->cancel_fd > fd)
970 maxfd = vpninfo->cancel_fd;
973 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
974 if (vpninfo->cancel_fd != -1 &&
975 FD_ISSET(vpninfo->cancel_fd, &rd_set))
978 /* Not that this should ever be able to happen... */
979 if (!FD_ISSET(fd, &rd_set))
982 i = read(fd, buf + count, len - count);
991 static const char *socks_errors[] = {
992 N_("request granted"),
993 N_("general failure"),
994 N_("connection not allowed by ruleset"),
995 N_("network unreachable"),
996 N_("host unreachable"),
997 N_("connection refused by destination host"),
999 N_("command not supported / protocol error"),
1000 N_("address type not supported")
1003 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1005 unsigned char buf[1024];
1008 buf[0] = 5; /* SOCKS version */
1009 buf[1] = 1; /* # auth methods */
1010 buf[2] = 0; /* No auth supported */
1012 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1013 vpn_progress(vpninfo, PRG_ERR,
1014 _("Error writing auth request to SOCKS proxy: %s\n"),
1019 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1020 vpn_progress(vpninfo, PRG_ERR,
1021 _("Error reading auth response from SOCKS proxy: %s\n"),
1026 vpn_progress(vpninfo, PRG_ERR,
1027 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1033 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1034 vpn_progress(vpninfo, PRG_ERR,
1035 _("SOCKS proxy error %02x: %s\n"),
1036 buf[1], _(socks_errors[buf[1]]));
1038 vpn_progress(vpninfo, PRG_ERR,
1039 _("SOCKS proxy error %02x\n"),
1044 vpn_progress(vpninfo, PRG_INFO,
1045 _("Requesting SOCKS proxy connection to %s:%d\n"),
1046 vpninfo->hostname, vpninfo->port);
1048 buf[0] = 5; /* SOCKS version */
1049 buf[1] = 1; /* CONNECT */
1050 buf[2] = 0; /* Reserved */
1051 buf[3] = 3; /* Address type is domain name */
1052 buf[4] = strlen(vpninfo->hostname);
1053 strcpy((char *)buf + 5, vpninfo->hostname);
1054 i = strlen(vpninfo->hostname) + 5;
1055 buf[i++] = vpninfo->port >> 8;
1056 buf[i++] = vpninfo->port & 0xff;
1058 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1059 vpn_progress(vpninfo, PRG_ERR,
1060 _("Error writing connect request to SOCKS proxy: %s\n"),
1064 /* Read 5 bytes -- up to and including the first byte of the returned
1065 address (which might be the length byte of a domain name) */
1066 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1067 vpn_progress(vpninfo, PRG_ERR,
1068 _("Error reading connect response from SOCKS proxy: %s\n"),
1073 vpn_progress(vpninfo, PRG_ERR,
1074 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1081 /* Connect responses contain an address */
1083 case 1: /* Legacy IP */
1086 case 3: /* Domain name */
1093 vpn_progress(vpninfo, PRG_ERR,
1094 _("Unexpected address type %02x in SOCKS connect response\n"),
1099 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1100 vpn_progress(vpninfo, PRG_ERR,
1101 _("Error reading connect response from SOCKS proxy: %s\n"),
1108 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1110 char buf[MAX_BUF_LEN];
1113 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1114 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1115 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1116 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1117 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1118 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1119 sprintf(buf + strlen(buf), "\r\n");
1121 vpn_progress(vpninfo, PRG_INFO,
1122 _("Requesting HTTP proxy connection to %s:%d\n"),
1123 vpninfo->hostname, vpninfo->port);
1125 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1127 vpn_progress(vpninfo, PRG_ERR,
1128 _("Sending proxy request failed: %s\n"),
1133 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1134 vpn_progress(vpninfo, PRG_ERR,
1135 _("Error fetching proxy response\n"));
1139 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1140 buf[8] != ' ' || !(result = atoi(buf+9))) {
1141 vpn_progress(vpninfo, PRG_ERR,
1142 _("Failed to parse proxy response '%s'\n"), buf);
1146 if (result != 200) {
1147 vpn_progress(vpninfo, PRG_ERR,
1148 _("Proxy CONNECT request failed: %s\n"), buf);
1152 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1154 vpn_progress(vpninfo, PRG_ERR,
1155 _("Failed to read proxy response\n"));
1158 vpn_progress(vpninfo, PRG_ERR,
1159 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1166 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1168 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1169 return process_http_proxy(vpninfo, ssl_sock);
1171 if (!strcmp(vpninfo->proxy_type, "socks") ||
1172 !strcmp(vpninfo->proxy_type, "socks5"))
1173 return process_socks_proxy(vpninfo, ssl_sock);
1175 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1176 vpninfo->proxy_type);
1180 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1188 free(vpninfo->proxy_type);
1189 vpninfo->proxy_type = NULL;
1190 free(vpninfo->proxy);
1191 vpninfo->proxy = NULL;
1193 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1194 &vpninfo->proxy_port, NULL, 80);
1198 if (vpninfo->proxy_type &&
1199 strcmp(vpninfo->proxy_type, "http") &&
1200 strcmp(vpninfo->proxy_type, "socks") &&
1201 strcmp(vpninfo->proxy_type, "socks5")) {
1202 vpn_progress(vpninfo, PRG_ERR,
1203 _("Only http or socks(5) proxies supported\n"));
1204 free(vpninfo->proxy_type);
1205 vpninfo->proxy_type = NULL;
1206 free(vpninfo->proxy);
1207 vpninfo->proxy = NULL;