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>
39 #include "openconnect-internal.h"
41 static int proxy_write(struct openconnect_info *vpninfo, int fd,
42 unsigned char *buf, size_t len);
43 static int proxy_read(struct openconnect_info *vpninfo, int fd,
44 unsigned char *buf, size_t len);
46 #define MAX_BUF_LEN 131072
48 * We didn't really want to have to do this for ourselves -- one might have
49 * thought that it would be available in a library somewhere. But neither
50 * cURL nor Neon have reliable cross-platform ways of either using a cert
51 * from the TPM, or just reading from / writing to a transport which is
52 * provided by their caller.
55 static int http_add_cookie(struct openconnect_info *vpninfo,
56 const char *option, const char *value)
58 struct vpn_option *new, **this;
61 new = malloc(sizeof(*new));
63 vpn_progress(vpninfo, PRG_ERR,
64 _("No memory for allocating cookies\n"));
68 new->option = strdup(option);
69 new->value = strdup(value);
70 if (!new->option || !new->value) {
77 /* Kill cookie; don't replace it */
80 for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
81 if (!strcmp(option, (*this)->option)) {
82 /* Replace existing cookie */
84 new->next = (*this)->next;
88 free((*this)->option);
102 #define BODY_HTTP10 -1
103 #define BODY_CHUNKED -2
105 static int process_http_response(struct openconnect_info *vpninfo, int *result,
106 int (*header_cb)(struct openconnect_info *, char *, char *),
109 char buf[MAX_BUF_LEN];
111 int bodylen = BODY_HTTP10;
117 if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
118 vpn_progress(vpninfo, PRG_ERR,
119 _("Error fetching HTTPS response\n"));
123 if (!strncmp(buf, "HTTP/1.0 ", 9))
126 if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
127 vpn_progress(vpninfo, PRG_ERR,
128 _("Failed to parse HTTP response '%s'\n"), buf);
132 vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
133 _("Got HTTP response: %s\n"), buf);
136 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
140 vpn_progress(vpninfo, PRG_ERR,
141 _("Error processing HTTP response\n"));
144 colon = strchr(buf, ':');
146 vpn_progress(vpninfo, PRG_ERR,
147 _("Ignoring unknown HTTP response line '%s'\n"), buf);
154 /* Handle Set-Cookie first so that we can avoid printing the
155 webvpn cookie in the verbose debug output */
156 if (!strcasecmp(buf, "Set-Cookie")) {
157 char *semicolon = strchr(colon, ';');
158 const char *print_equals;
159 char *equals = strchr(colon, '=');
166 vpn_progress(vpninfo, PRG_ERR,
167 _("Invalid cookie offered: %s\n"), buf);
172 print_equals = equals;
173 /* Don't print the webvpn cookie unless it's empty; we don't
174 want people posting it in public with debugging output */
175 if (!strcmp(colon, "webvpn") && *equals)
176 print_equals = _("<elided>");
177 vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
178 buf, colon, print_equals, semicolon?";":"",
179 semicolon?(semicolon+1):"");
181 /* The server tends to ask for the username and password as
182 usual, even if we've already failed because it didn't like
183 our cert. Thankfully it does give us this hint... */
184 if (!strcmp(colon, "ClientCertAuthFailed"))
185 vpn_progress(vpninfo, PRG_ERR,
186 _("SSL certificate authentication failed\n"));
188 ret = http_add_cookie(vpninfo, colon, equals);
192 vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
195 if (!strcasecmp(buf, "Connection")) {
196 if (!strcasecmp(colon, "Close"))
199 /* This might seem reasonable, but in fact it breaks
200 certificate authentication with some servers. If
201 they give an HTTP/1.0 response, even if they
202 explicitly give a Connection: Keep-Alive header,
203 just close the connection. */
204 else if (!strcasecmp(colon, "Keep-Alive"))
208 if (!strcasecmp(buf, "Location")) {
209 vpninfo->redirect_url = strdup(colon);
210 if (!vpninfo->redirect_url)
213 if (!strcasecmp(buf, "Content-Length")) {
214 bodylen = atoi(colon);
216 vpn_progress(vpninfo, PRG_ERR,
217 _("Response body has negative size (%d)\n"),
222 if (!strcasecmp(buf, "Transfer-Encoding")) {
223 if (!strcasecmp(colon, "chunked"))
224 bodylen = BODY_CHUNKED;
226 vpn_progress(vpninfo, PRG_ERR,
227 _("Unknown Transfer-Encoding: %s\n"),
232 if (header_cb && !strncmp(buf, "X-", 2))
233 header_cb(vpninfo, buf, colon);
236 /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
240 /* Now the body, if there is one */
241 vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"),
242 bodylen==BODY_HTTP10?"http 1.0" :
243 bodylen==BODY_CHUNKED?"chunked" : "length: ",
246 /* If we were given Content-Length, it's nice and easy... */
248 body = malloc(bodylen + 1);
251 while (done < bodylen) {
252 i = openconnect_SSL_read(vpninfo, body + done, bodylen - done);
254 vpn_progress(vpninfo, PRG_ERR,
255 _("Error reading HTTP response body\n"));
261 } else if (bodylen == BODY_CHUNKED) {
262 /* ... else, chunked */
263 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
264 int chunklen, lastchunk = 0;
267 vpn_progress(vpninfo, PRG_ERR,
268 _("Error fetching chunk header\n"));
271 chunklen = strtol(buf, NULL, 16);
276 body = realloc(body, done + chunklen + 1);
280 i = openconnect_SSL_read(vpninfo, body + done, chunklen);
282 vpn_progress(vpninfo, PRG_ERR,
283 _("Error reading HTTP response body\n"));
291 if ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
293 vpn_progress(vpninfo, PRG_ERR,
294 _("Error fetching HTTP response body\n"));
296 vpn_progress(vpninfo, PRG_ERR,
297 _("Error in chunked decoding. Expected '', got: '%s'"),
307 } else if (bodylen == BODY_HTTP10) {
309 vpn_progress(vpninfo, PRG_ERR,
310 _("Cannot receive HTTP 1.0 body without closing connection\n"));
314 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
316 body = realloc(body, done + 16384);
319 i = openconnect_SSL_read(vpninfo, body + done, 16384);
328 /* Connection closed. Reduce allocation to just what we need */
329 body = realloc(body, done + 1);
337 if (closeconn || vpninfo->no_http_keepalive)
338 openconnect_close_https(vpninfo);
346 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
349 struct vpn_option *opt;
350 char buf[MAX_BUF_LEN];
351 char *config_buf = NULL;
353 unsigned char local_sha1_bin[SHA1_SIZE];
354 char local_sha1_ascii[(SHA1_SIZE * 2)+1];
357 if (openconnect_open_https(vpninfo)) {
358 vpn_progress(vpninfo, PRG_ERR,
359 _("Failed to open HTTPS connection to %s\n"),
364 sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
365 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
366 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
367 sprintf(buf + strlen(buf), "Accept: */*\r\n");
368 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
370 if (vpninfo->cookies) {
371 sprintf(buf + strlen(buf), "Cookie: ");
372 for (opt = vpninfo->cookies; opt; opt = opt->next)
373 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
374 opt->value, opt->next ? "; " : "\r\n");
376 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
378 if (openconnect_SSL_write(vpninfo, buf, strlen(buf)) != strlen(buf)) {
379 vpn_progress(vpninfo, PRG_ERR,
380 _("Failed to send GET request for new config\n"));
384 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
386 /* We'll already have complained about whatever offended us */
395 openconnect_sha1(local_sha1_bin, config_buf, buflen);
397 for (i = 0; i < SHA1_SIZE; i++)
398 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
400 if (strcasecmp(server_sha1, local_sha1_ascii)) {
401 vpn_progress(vpninfo, PRG_ERR,
402 _("Downloaded config file did not match intended SHA1\n"));
407 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
412 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
417 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
418 vpn_progress(vpninfo, PRG_ERR,
419 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
420 "This facility is disabled by default for security reasons, so you may wish to enable it."));
425 vpn_progress(vpninfo, PRG_INFO,
426 _("Trying to run Linux CSD trojan script."));
429 sprintf(fname, "/tmp/csdXXXXXX");
433 vpn_progress(vpninfo, PRG_ERR,
434 _("Failed to open temporary CSD script file: %s\n"),
439 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
441 vpn_progress(vpninfo, PRG_ERR,
442 _("Failed to write temporary CSD script file: %s\n"),
450 char scertbuf[MD5_SIZE * 2 + 1];
451 char ccertbuf[MD5_SIZE * 2 + 1];
455 if (vpninfo->uid_csd != getuid()) {
458 if (setuid(vpninfo->uid_csd)) {
459 fprintf(stderr, _("Failed to set uid %ld\n"),
460 (long)vpninfo->uid_csd);
463 if (!(pw = getpwuid(vpninfo->uid_csd))) {
464 fprintf(stderr, _("Invalid user uid=%ld\n"),
465 (long)vpninfo->uid_csd);
468 setenv("HOME", pw->pw_dir, 1);
469 if (chdir(pw->pw_dir)) {
470 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
471 pw->pw_dir, strerror(errno));
475 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
476 fprintf(stderr, _("Warning: you are running insecure "
477 "CSD code with root privileges\n"
478 "\t Use command line option \"--csd-user\"\n"));
480 if (vpninfo->uid_csd_given == 2) {
481 /* The NM tool really needs not to get spurious output
482 on stdout, which the CSD trojan spews. */
485 if (vpninfo->csd_wrapper)
486 csd_argv[i++] = vpninfo->csd_wrapper;
487 csd_argv[i++] = fname;
488 csd_argv[i++]= (char *)"-ticket";
489 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
491 csd_argv[i++]= (char *)"-stub";
492 csd_argv[i++]= (char *)"\"0\"";
493 csd_argv[i++]= (char *)"-group";
494 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
497 openconnect_local_cert_md5(vpninfo, ccertbuf);
499 get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
500 csd_argv[i++]= (char *)"-certhash";
501 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
504 csd_argv[i++]= (char *)"-url";
505 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
507 /* WTF would it want to know this for? */
508 csd_argv[i++]= (char *)"-vpnclient";
509 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
510 csd_argv[i++]= (char *)"-connect";
511 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
513 csd_argv[i++]= (char *)"-connectparam";
514 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
516 csd_argv[i++]= (char *)"-langselen";
517 csd_argv[i++] = NULL;
519 execv(csd_argv[0], csd_argv);
520 vpn_progress(vpninfo, PRG_ERR,
521 _("Failed to exec CSD script %s\n"), csd_argv[0]);
525 free(vpninfo->csd_stuburl);
526 vpninfo->csd_stuburl = NULL;
527 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
528 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
529 vpninfo->csd_waiturl = NULL;
530 vpninfo->csd_scriptname = strdup(fname);
532 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
537 #ifndef HAVE_STRCASESTR
538 static char *openconnect__strcasestr(const char *haystack, const char *needle)
540 int hlen = strlen(haystack);
541 int nlen = strlen(needle);
544 for (i = 0; i < hlen - nlen + 1; i++) {
545 for (j = 0; j < nlen; j++) {
546 if (tolower(haystack[i + j]) !=
551 return (char *)haystack + i;
555 #define strcasestr openconnect__strcasestr
559 int internal_parse_url(char *url, char **res_proto, char **res_host,
560 int *res_port, char **res_path, int default_port)
563 char *host, *path, *port_str;
566 host = strstr(url, "://");
571 if (!strcasecmp(proto, "https"))
573 else if (!strcasecmp(proto, "http"))
575 else if (!strcasecmp(proto, "socks") ||
576 !strcasecmp(proto, "socks4") ||
577 !strcasecmp(proto, "socks5"))
580 return -EPROTONOSUPPORT;
590 path = strchr(host, '/');
594 port_str = strrchr(host, ':');
597 int new_port = strtol(port_str + 1, &end, 10);
606 *res_proto = proto ? strdup(proto) : NULL;
608 *res_host = strdup(host);
612 *res_path = (path && *path) ? strdup(path) : NULL;
614 /* Undo the damage we did to the original string */
626 * = 0, no cookie (user cancel)
627 * = 1, obtained cookie
629 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
631 struct vpn_option *opt, *next;
632 char buf[MAX_BUF_LEN];
633 char *form_buf = NULL;
635 char request_body[2048];
636 const char *request_body_type = NULL;
637 const char *method = "GET";
644 if (openconnect_open_https(vpninfo)) {
645 vpn_progress(vpninfo, PRG_ERR,
646 _("Failed to open HTTPS connection to %s\n"),
652 * It would be nice to use cURL for this, but we really need to guarantee
653 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
654 * to have any way to let us provide our own socket read/write functions.
655 * We can only provide a socket _open_ function. Which would require having
656 * a socketpair() and servicing the "other" end of it.
658 * So we process the HTTP for ourselves...
660 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
661 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
662 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
663 sprintf(buf + strlen(buf), "Accept: */*\r\n");
664 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
666 if (vpninfo->cookies) {
667 sprintf(buf + strlen(buf), "Cookie: ");
668 for (opt = vpninfo->cookies; opt; opt = opt->next)
669 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
670 opt->value, opt->next ? "; " : "\r\n");
672 if (request_body_type) {
673 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
675 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
676 strlen(request_body));
678 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
679 if (request_body_type)
680 sprintf(buf + strlen(buf), "%s", request_body);
682 if (vpninfo->port == 443)
683 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
684 method, vpninfo->hostname,
685 vpninfo->urlpath ?: "");
687 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
688 method, vpninfo->hostname, vpninfo->port,
689 vpninfo->urlpath ?: "");
691 result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
695 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
697 /* We'll already have complained about whatever offended us */
701 if (result != 200 && vpninfo->redirect_url) {
703 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
704 /* New host. Tear down the existing connection and make a new one */
709 free(vpninfo->urlpath);
710 vpninfo->urlpath = NULL;
712 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
714 vpn_progress(vpninfo, PRG_ERR,
715 _("Failed to parse redirected URL '%s': %s\n"),
716 vpninfo->redirect_url, strerror(-ret));
717 free(vpninfo->redirect_url);
718 vpninfo->redirect_url = NULL;
723 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
724 free(vpninfo->hostname);
725 vpninfo->hostname = host;
726 vpninfo->port = port;
728 /* Kill the existing connection, and a new one will happen */
729 free(vpninfo->peer_addr);
730 vpninfo->peer_addr = NULL;
731 openconnect_close_https(vpninfo);
733 for (opt = vpninfo->cookies; opt; opt = next) {
740 vpninfo->cookies = NULL;
744 free(vpninfo->redirect_url);
745 vpninfo->redirect_url = NULL;
748 } else if (strstr(vpninfo->redirect_url, "://")) {
749 vpn_progress(vpninfo, PRG_ERR,
750 _("Cannot follow redirection to non-https URL '%s'\n"),
751 vpninfo->redirect_url);
752 free(vpninfo->redirect_url);
753 vpninfo->redirect_url = NULL;
756 } else if (vpninfo->redirect_url[0] == '/') {
757 /* Absolute redirect within same host */
758 free(vpninfo->urlpath);
759 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
760 free(vpninfo->redirect_url);
761 vpninfo->redirect_url = NULL;
764 char *lastslash = NULL;
765 if (vpninfo->urlpath)
766 lastslash = strrchr(vpninfo->urlpath, '/');
768 free(vpninfo->urlpath);
769 vpninfo->urlpath = vpninfo->redirect_url;
770 vpninfo->redirect_url = NULL;
772 char *oldurl = vpninfo->urlpath;
774 vpninfo->urlpath = NULL;
775 if (asprintf(&vpninfo->urlpath, "%s/%s",
776 oldurl, vpninfo->redirect_url) == -1) {
778 vpn_progress(vpninfo, PRG_ERR,
779 _("Allocating new path for relative redirect failed: %s\n"),
784 free(vpninfo->redirect_url);
785 vpninfo->redirect_url = NULL;
790 if (!form_buf || result != 200) {
791 vpn_progress(vpninfo, PRG_ERR,
792 _("Unexpected %d result from server\n"),
797 if (vpninfo->csd_stuburl) {
798 /* This is the CSD stub script, which we now need to run */
799 result = run_csd_script(vpninfo, form_buf, buflen);
805 /* Now we'll be redirected to the waiturl */
808 if (strncmp(form_buf, "<?xml", 5)) {
809 /* Not XML? Perhaps it's HTML with a refresh... */
810 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
811 vpn_progress(vpninfo, PRG_INFO,
812 _("Refreshing %s after 1 second...\n"),
817 vpn_progress(vpninfo, PRG_ERR,
818 _("Unknown response from server\n"));
823 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
824 &method, &request_body_type);
834 /* A return value of 2 means the XML form indicated
835 success. We _should_ have a cookie... */
837 for (opt = vpninfo->cookies; opt; opt = opt->next) {
839 if (!strcmp(opt->option, "webvpn"))
840 vpninfo->cookie = opt->value;
841 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
842 char *tok = opt->value;
843 char *bu = NULL, *fu = NULL, *sha = NULL;
846 if (tok != opt->value)
849 if (!strncmp(tok, "bu:", 3))
851 else if (!strncmp(tok, "fu:", 3))
853 else if (!strncmp(tok, "fh:", 3)) {
854 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
859 } while ((tok = strchr(tok, '&')));
862 fetch_config(vpninfo, bu, fu, sha);
865 if (vpninfo->csd_scriptname) {
866 unlink(vpninfo->csd_scriptname);
867 free(vpninfo->csd_scriptname);
868 vpninfo->csd_scriptname = NULL;
873 char *openconnect_create_useragent(const char *base)
877 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
883 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
884 char *buf, size_t len)
892 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
893 if (buf[i] == '\n') {
895 if (i && buf[i-1] == '\r') {
912 static int proxy_write(struct openconnect_info *vpninfo, int fd,
913 unsigned char *buf, size_t len)
917 for (count = 0; count < len; ) {
918 fd_set rd_set, wr_set;
925 if (vpninfo->cancel_fd != -1) {
926 FD_SET(vpninfo->cancel_fd, &rd_set);
927 if (vpninfo->cancel_fd > fd)
928 maxfd = vpninfo->cancel_fd;
931 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
932 if (vpninfo->cancel_fd != -1 &&
933 FD_ISSET(vpninfo->cancel_fd, &rd_set))
936 /* Not that this should ever be able to happen... */
937 if (!FD_ISSET(fd, &wr_set))
940 i = write(fd, buf + count, len - count);
949 static int proxy_read(struct openconnect_info *vpninfo, int fd,
950 unsigned char *buf, size_t len)
954 for (count = 0; count < len; ) {
961 if (vpninfo->cancel_fd != -1) {
962 FD_SET(vpninfo->cancel_fd, &rd_set);
963 if (vpninfo->cancel_fd > fd)
964 maxfd = vpninfo->cancel_fd;
967 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
968 if (vpninfo->cancel_fd != -1 &&
969 FD_ISSET(vpninfo->cancel_fd, &rd_set))
972 /* Not that this should ever be able to happen... */
973 if (!FD_ISSET(fd, &rd_set))
976 i = read(fd, buf + count, len - count);
985 static const char *socks_errors[] = {
986 N_("request granted"),
987 N_("general failure"),
988 N_("connection not allowed by ruleset"),
989 N_("network unreachable"),
990 N_("host unreachable"),
991 N_("connection refused by destination host"),
993 N_("command not supported / protocol error"),
994 N_("address type not supported")
997 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
999 unsigned char buf[1024];
1002 buf[0] = 5; /* SOCKS version */
1003 buf[1] = 1; /* # auth methods */
1004 buf[2] = 0; /* No auth supported */
1006 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1007 vpn_progress(vpninfo, PRG_ERR,
1008 _("Error writing auth request to SOCKS proxy: %s\n"),
1013 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1014 vpn_progress(vpninfo, PRG_ERR,
1015 _("Error reading auth response from SOCKS proxy: %s\n"),
1020 vpn_progress(vpninfo, PRG_ERR,
1021 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1027 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1028 vpn_progress(vpninfo, PRG_ERR,
1029 _("SOCKS proxy error %02x: %s\n"),
1030 buf[1], _(socks_errors[buf[1]]));
1032 vpn_progress(vpninfo, PRG_ERR,
1033 _("SOCKS proxy error %02x\n"),
1038 vpn_progress(vpninfo, PRG_INFO,
1039 _("Requesting SOCKS proxy connection to %s:%d\n"),
1040 vpninfo->hostname, vpninfo->port);
1042 buf[0] = 5; /* SOCKS version */
1043 buf[1] = 1; /* CONNECT */
1044 buf[2] = 0; /* Reserved */
1045 buf[3] = 3; /* Address type is domain name */
1046 buf[4] = strlen(vpninfo->hostname);
1047 strcpy((char *)buf + 5, vpninfo->hostname);
1048 i = strlen(vpninfo->hostname) + 5;
1049 buf[i++] = vpninfo->port >> 8;
1050 buf[i++] = vpninfo->port & 0xff;
1052 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1053 vpn_progress(vpninfo, PRG_ERR,
1054 _("Error writing connect request to SOCKS proxy: %s\n"),
1058 /* Read 5 bytes -- up to and including the first byte of the returned
1059 address (which might be the length byte of a domain name) */
1060 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1061 vpn_progress(vpninfo, PRG_ERR,
1062 _("Error reading connect response from SOCKS proxy: %s\n"),
1067 vpn_progress(vpninfo, PRG_ERR,
1068 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1075 /* Connect responses contain an address */
1077 case 1: /* Legacy IP */
1080 case 3: /* Domain name */
1087 vpn_progress(vpninfo, PRG_ERR,
1088 _("Unexpected address type %02x in SOCKS connect response\n"),
1093 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1094 vpn_progress(vpninfo, PRG_ERR,
1095 _("Error reading connect response from SOCKS proxy: %s\n"),
1102 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1104 char buf[MAX_BUF_LEN];
1107 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1108 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1109 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1110 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1111 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1112 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1113 sprintf(buf + strlen(buf), "\r\n");
1115 vpn_progress(vpninfo, PRG_INFO,
1116 _("Requesting HTTP proxy connection to %s:%d\n"),
1117 vpninfo->hostname, vpninfo->port);
1119 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1121 vpn_progress(vpninfo, PRG_ERR,
1122 _("Sending proxy request failed: %s\n"),
1127 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1128 vpn_progress(vpninfo, PRG_ERR,
1129 _("Error fetching proxy response\n"));
1133 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1134 buf[8] != ' ' || !(result = atoi(buf+9))) {
1135 vpn_progress(vpninfo, PRG_ERR,
1136 _("Failed to parse proxy response '%s'\n"), buf);
1140 if (result != 200) {
1141 vpn_progress(vpninfo, PRG_ERR,
1142 _("Proxy CONNECT request failed: %s\n"), buf);
1146 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1148 vpn_progress(vpninfo, PRG_ERR,
1149 _("Failed to read proxy response\n"));
1152 vpn_progress(vpninfo, PRG_ERR,
1153 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1160 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1162 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1163 return process_http_proxy(vpninfo, ssl_sock);
1165 if (!strcmp(vpninfo->proxy_type, "socks") ||
1166 !strcmp(vpninfo->proxy_type, "socks5"))
1167 return process_socks_proxy(vpninfo, ssl_sock);
1169 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1170 vpninfo->proxy_type);
1174 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1182 free(vpninfo->proxy_type);
1183 vpninfo->proxy_type = NULL;
1184 free(vpninfo->proxy);
1185 vpninfo->proxy = NULL;
1187 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1188 &vpninfo->proxy_port, NULL, 80);
1192 if (vpninfo->proxy_type &&
1193 strcmp(vpninfo->proxy_type, "http") &&
1194 strcmp(vpninfo->proxy_type, "socks") &&
1195 strcmp(vpninfo->proxy_type, "socks5")) {
1196 vpn_progress(vpninfo, PRG_ERR,
1197 _("Only http or socks(5) proxies supported\n"));
1198 free(vpninfo->proxy_type);
1199 vpninfo->proxy_type = NULL;
1200 free(vpninfo->proxy);
1201 vpninfo->proxy = NULL;