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 sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
358 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
359 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
360 sprintf(buf + strlen(buf), "Accept: */*\r\n");
361 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
363 if (vpninfo->cookies) {
364 sprintf(buf + strlen(buf), "Cookie: ");
365 for (opt = vpninfo->cookies; opt; opt = opt->next)
366 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
367 opt->value, opt->next ? "; " : "\r\n");
369 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
371 if (openconnect_SSL_write(vpninfo, buf, strlen(buf))) {
372 vpn_progress(vpninfo, PRG_ERR,
373 _("Failed to send GET request for new config\n"));
377 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
379 /* We'll already have complained about whatever offended us */
388 openconnect_sha1(local_sha1_bin, config_buf, buflen);
390 for (i = 0; i < SHA1_SIZE; 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 char scertbuf[MD5_SIZE * 2 + 1];
444 char ccertbuf[MD5_SIZE * 2 + 1];
448 if (vpninfo->uid_csd != getuid()) {
451 if (setuid(vpninfo->uid_csd)) {
452 fprintf(stderr, _("Failed to set uid %ld\n"),
453 (long)vpninfo->uid_csd);
456 if (!(pw = getpwuid(vpninfo->uid_csd))) {
457 fprintf(stderr, _("Invalid user uid=%ld\n"),
458 (long)vpninfo->uid_csd);
461 setenv("HOME", pw->pw_dir, 1);
462 if (chdir(pw->pw_dir)) {
463 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
464 pw->pw_dir, strerror(errno));
468 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
469 fprintf(stderr, _("Warning: you are running insecure "
470 "CSD code with root privileges\n"
471 "\t Use command line option \"--csd-user\"\n"));
473 if (vpninfo->uid_csd_given == 2) {
474 /* The NM tool really needs not to get spurious output
475 on stdout, which the CSD trojan spews. */
478 if (vpninfo->csd_wrapper)
479 csd_argv[i++] = vpninfo->csd_wrapper;
480 csd_argv[i++] = fname;
481 csd_argv[i++]= (char *)"-ticket";
482 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
484 csd_argv[i++]= (char *)"-stub";
485 csd_argv[i++]= (char *)"\"0\"";
486 csd_argv[i++]= (char *)"-group";
487 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
490 openconnect_local_cert_md5(vpninfo, ccertbuf);
492 get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
493 csd_argv[i++]= (char *)"-certhash";
494 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
497 csd_argv[i++]= (char *)"-url";
498 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
500 /* WTF would it want to know this for? */
501 csd_argv[i++]= (char *)"-vpnclient";
502 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
503 csd_argv[i++]= (char *)"-connect";
504 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
506 csd_argv[i++]= (char *)"-connectparam";
507 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
509 csd_argv[i++]= (char *)"-langselen";
510 csd_argv[i++] = NULL;
512 execv(csd_argv[0], csd_argv);
513 vpn_progress(vpninfo, PRG_ERR,
514 _("Failed to exec CSD script %s\n"), csd_argv[0]);
518 free(vpninfo->csd_stuburl);
519 vpninfo->csd_stuburl = NULL;
520 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
521 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
522 vpninfo->csd_waiturl = NULL;
523 vpninfo->csd_scriptname = strdup(fname);
525 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
530 #ifndef HAVE_STRCASESTR
531 static char *openconnect__strcasestr(const char *haystack, const char *needle)
533 int hlen = strlen(haystack);
534 int nlen = strlen(needle);
537 for (i = 0; i < hlen - nlen + 1; i++) {
538 for (j = 0; j < nlen; j++) {
539 if (tolower(haystack[i + j]) !=
544 return (char *)haystack + i;
548 #define strcasestr openconnect__strcasestr
552 int internal_parse_url(char *url, char **res_proto, char **res_host,
553 int *res_port, char **res_path, int default_port)
556 char *host, *path, *port_str;
559 host = strstr(url, "://");
564 if (!strcasecmp(proto, "https"))
566 else if (!strcasecmp(proto, "http"))
568 else if (!strcasecmp(proto, "socks") ||
569 !strcasecmp(proto, "socks4") ||
570 !strcasecmp(proto, "socks5"))
573 return -EPROTONOSUPPORT;
583 path = strchr(host, '/');
587 port_str = strrchr(host, ':');
590 int new_port = strtol(port_str + 1, &end, 10);
599 *res_proto = proto ? strdup(proto) : NULL;
601 *res_host = strdup(host);
605 *res_path = (path && *path) ? strdup(path) : NULL;
607 /* Undo the damage we did to the original string */
619 * = 0, no cookie (user cancel)
620 * = 1, obtained cookie
622 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
624 struct vpn_option *opt, *next;
625 char buf[MAX_BUF_LEN];
626 char *form_buf = NULL;
628 char request_body[2048];
629 const char *request_body_type = NULL;
630 const char *method = "GET";
637 if (openconnect_open_https(vpninfo)) {
638 vpn_progress(vpninfo, PRG_ERR,
639 _("Failed to open HTTPS connection to %s\n"),
645 * It would be nice to use cURL for this, but we really need to guarantee
646 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
647 * to have any way to let us provide our own socket read/write functions.
648 * We can only provide a socket _open_ function. Which would require having
649 * a socketpair() and servicing the "other" end of it.
651 * So we process the HTTP for ourselves...
653 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
654 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
655 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
656 sprintf(buf + strlen(buf), "Accept: */*\r\n");
657 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
659 if (vpninfo->cookies) {
660 sprintf(buf + strlen(buf), "Cookie: ");
661 for (opt = vpninfo->cookies; opt; opt = opt->next)
662 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
663 opt->value, opt->next ? "; " : "\r\n");
665 if (request_body_type) {
666 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
668 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
669 strlen(request_body));
671 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
672 if (request_body_type)
673 sprintf(buf + strlen(buf), "%s", request_body);
675 if (vpninfo->port == 443)
676 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
677 method, vpninfo->hostname,
678 vpninfo->urlpath ?: "");
680 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
681 method, vpninfo->hostname, vpninfo->port,
682 vpninfo->urlpath ?: "");
684 result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
688 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
690 /* We'll already have complained about whatever offended us */
694 if (result != 200 && vpninfo->redirect_url) {
696 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
697 /* New host. Tear down the existing connection and make a new one */
702 free(vpninfo->urlpath);
703 vpninfo->urlpath = NULL;
705 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
707 vpn_progress(vpninfo, PRG_ERR,
708 _("Failed to parse redirected URL '%s': %s\n"),
709 vpninfo->redirect_url, strerror(-ret));
710 free(vpninfo->redirect_url);
711 vpninfo->redirect_url = NULL;
716 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
717 free(vpninfo->hostname);
718 vpninfo->hostname = host;
719 vpninfo->port = port;
721 /* Kill the existing connection, and a new one will happen */
722 free(vpninfo->peer_addr);
723 vpninfo->peer_addr = NULL;
724 openconnect_close_https(vpninfo);
726 for (opt = vpninfo->cookies; opt; opt = next) {
733 vpninfo->cookies = NULL;
737 free(vpninfo->redirect_url);
738 vpninfo->redirect_url = NULL;
741 } else if (strstr(vpninfo->redirect_url, "://")) {
742 vpn_progress(vpninfo, PRG_ERR,
743 _("Cannot follow redirection to non-https URL '%s'\n"),
744 vpninfo->redirect_url);
745 free(vpninfo->redirect_url);
746 vpninfo->redirect_url = NULL;
749 } else if (vpninfo->redirect_url[0] == '/') {
750 /* Absolute redirect within same host */
751 free(vpninfo->urlpath);
752 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
753 free(vpninfo->redirect_url);
754 vpninfo->redirect_url = NULL;
757 char *lastslash = NULL;
758 if (vpninfo->urlpath)
759 lastslash = strrchr(vpninfo->urlpath, '/');
761 free(vpninfo->urlpath);
762 vpninfo->urlpath = vpninfo->redirect_url;
763 vpninfo->redirect_url = NULL;
765 char *oldurl = vpninfo->urlpath;
767 vpninfo->urlpath = NULL;
768 if (asprintf(&vpninfo->urlpath, "%s/%s",
769 oldurl, vpninfo->redirect_url) == -1) {
771 vpn_progress(vpninfo, PRG_ERR,
772 _("Allocating new path for relative redirect failed: %s\n"),
777 free(vpninfo->redirect_url);
778 vpninfo->redirect_url = NULL;
783 if (!form_buf || result != 200) {
784 vpn_progress(vpninfo, PRG_ERR,
785 _("Unexpected %d result from server\n"),
790 if (vpninfo->csd_stuburl) {
791 /* This is the CSD stub script, which we now need to run */
792 result = run_csd_script(vpninfo, form_buf, buflen);
798 /* Now we'll be redirected to the waiturl */
801 if (strncmp(form_buf, "<?xml", 5)) {
802 /* Not XML? Perhaps it's HTML with a refresh... */
803 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
804 vpn_progress(vpninfo, PRG_INFO,
805 _("Refreshing %s after 1 second...\n"),
810 vpn_progress(vpninfo, PRG_ERR,
811 _("Unknown response from server\n"));
816 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
817 &method, &request_body_type);
827 /* A return value of 2 means the XML form indicated
828 success. We _should_ have a cookie... */
830 for (opt = vpninfo->cookies; opt; opt = opt->next) {
832 if (!strcmp(opt->option, "webvpn"))
833 vpninfo->cookie = opt->value;
834 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
835 char *tok = opt->value;
836 char *bu = NULL, *fu = NULL, *sha = NULL;
839 if (tok != opt->value)
842 if (!strncmp(tok, "bu:", 3))
844 else if (!strncmp(tok, "fu:", 3))
846 else if (!strncmp(tok, "fh:", 3)) {
847 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
852 } while ((tok = strchr(tok, '&')));
855 fetch_config(vpninfo, bu, fu, sha);
858 if (vpninfo->csd_scriptname) {
859 unlink(vpninfo->csd_scriptname);
860 free(vpninfo->csd_scriptname);
861 vpninfo->csd_scriptname = NULL;
866 char *openconnect_create_useragent(const char *base)
870 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
876 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
877 char *buf, size_t len)
885 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
886 if (buf[i] == '\n') {
888 if (i && buf[i-1] == '\r') {
905 static int proxy_write(struct openconnect_info *vpninfo, int fd,
906 unsigned char *buf, size_t len)
910 for (count = 0; count < len; ) {
911 fd_set rd_set, wr_set;
918 if (vpninfo->cancel_fd != -1) {
919 FD_SET(vpninfo->cancel_fd, &rd_set);
920 if (vpninfo->cancel_fd > fd)
921 maxfd = vpninfo->cancel_fd;
924 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
925 if (vpninfo->cancel_fd != -1 &&
926 FD_ISSET(vpninfo->cancel_fd, &rd_set))
929 /* Not that this should ever be able to happen... */
930 if (!FD_ISSET(fd, &wr_set))
933 i = write(fd, buf + count, len - count);
942 static int proxy_read(struct openconnect_info *vpninfo, int fd,
943 unsigned char *buf, size_t len)
947 for (count = 0; count < len; ) {
954 if (vpninfo->cancel_fd != -1) {
955 FD_SET(vpninfo->cancel_fd, &rd_set);
956 if (vpninfo->cancel_fd > fd)
957 maxfd = vpninfo->cancel_fd;
960 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
961 if (vpninfo->cancel_fd != -1 &&
962 FD_ISSET(vpninfo->cancel_fd, &rd_set))
965 /* Not that this should ever be able to happen... */
966 if (!FD_ISSET(fd, &rd_set))
969 i = read(fd, buf + count, len - count);
978 static const char *socks_errors[] = {
979 N_("request granted"),
980 N_("general failure"),
981 N_("connection not allowed by ruleset"),
982 N_("network unreachable"),
983 N_("host unreachable"),
984 N_("connection refused by destination host"),
986 N_("command not supported / protocol error"),
987 N_("address type not supported")
990 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
992 unsigned char buf[1024];
995 buf[0] = 5; /* SOCKS version */
996 buf[1] = 1; /* # auth methods */
997 buf[2] = 0; /* No auth supported */
999 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1000 vpn_progress(vpninfo, PRG_ERR,
1001 _("Error writing auth request to SOCKS proxy: %s\n"),
1006 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1007 vpn_progress(vpninfo, PRG_ERR,
1008 _("Error reading auth response from SOCKS proxy: %s\n"),
1013 vpn_progress(vpninfo, PRG_ERR,
1014 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1020 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1021 vpn_progress(vpninfo, PRG_ERR,
1022 _("SOCKS proxy error %02x: %s\n"),
1023 buf[1], _(socks_errors[buf[1]]));
1025 vpn_progress(vpninfo, PRG_ERR,
1026 _("SOCKS proxy error %02x\n"),
1031 vpn_progress(vpninfo, PRG_INFO,
1032 _("Requesting SOCKS proxy connection to %s:%d\n"),
1033 vpninfo->hostname, vpninfo->port);
1035 buf[0] = 5; /* SOCKS version */
1036 buf[1] = 1; /* CONNECT */
1037 buf[2] = 0; /* Reserved */
1038 buf[3] = 3; /* Address type is domain name */
1039 buf[4] = strlen(vpninfo->hostname);
1040 strcpy((char *)buf + 5, vpninfo->hostname);
1041 i = strlen(vpninfo->hostname) + 5;
1042 buf[i++] = vpninfo->port >> 8;
1043 buf[i++] = vpninfo->port & 0xff;
1045 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1046 vpn_progress(vpninfo, PRG_ERR,
1047 _("Error writing connect request to SOCKS proxy: %s\n"),
1051 /* Read 5 bytes -- up to and including the first byte of the returned
1052 address (which might be the length byte of a domain name) */
1053 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1054 vpn_progress(vpninfo, PRG_ERR,
1055 _("Error reading connect response from SOCKS proxy: %s\n"),
1060 vpn_progress(vpninfo, PRG_ERR,
1061 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1068 /* Connect responses contain an address */
1070 case 1: /* Legacy IP */
1073 case 3: /* Domain name */
1080 vpn_progress(vpninfo, PRG_ERR,
1081 _("Unexpected address type %02x in SOCKS connect response\n"),
1086 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1087 vpn_progress(vpninfo, PRG_ERR,
1088 _("Error reading connect response from SOCKS proxy: %s\n"),
1095 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1097 char buf[MAX_BUF_LEN];
1100 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1101 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1102 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1103 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1104 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1105 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1106 sprintf(buf + strlen(buf), "\r\n");
1108 vpn_progress(vpninfo, PRG_INFO,
1109 _("Requesting HTTP proxy connection to %s:%d\n"),
1110 vpninfo->hostname, vpninfo->port);
1112 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1114 vpn_progress(vpninfo, PRG_ERR,
1115 _("Sending proxy request failed: %s\n"),
1120 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1121 vpn_progress(vpninfo, PRG_ERR,
1122 _("Error fetching proxy response\n"));
1126 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1127 buf[8] != ' ' || !(result = atoi(buf+9))) {
1128 vpn_progress(vpninfo, PRG_ERR,
1129 _("Failed to parse proxy response '%s'\n"), buf);
1133 if (result != 200) {
1134 vpn_progress(vpninfo, PRG_ERR,
1135 _("Proxy CONNECT request failed: %s\n"), buf);
1139 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1141 vpn_progress(vpninfo, PRG_ERR,
1142 _("Failed to read proxy response\n"));
1145 vpn_progress(vpninfo, PRG_ERR,
1146 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1153 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1155 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1156 return process_http_proxy(vpninfo, ssl_sock);
1158 if (!strcmp(vpninfo->proxy_type, "socks") ||
1159 !strcmp(vpninfo->proxy_type, "socks5"))
1160 return process_socks_proxy(vpninfo, ssl_sock);
1162 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1163 vpninfo->proxy_type);
1167 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1175 free(vpninfo->proxy_type);
1176 vpninfo->proxy_type = NULL;
1177 free(vpninfo->proxy);
1178 vpninfo->proxy = NULL;
1180 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1181 &vpninfo->proxy_port, NULL, 80);
1185 if (vpninfo->proxy_type &&
1186 strcmp(vpninfo->proxy_type, "http") &&
1187 strcmp(vpninfo->proxy_type, "socks") &&
1188 strcmp(vpninfo->proxy_type, "socks5")) {
1189 vpn_progress(vpninfo, PRG_ERR,
1190 _("Only http or socks(5) proxies supported\n"));
1191 free(vpninfo->proxy_type);
1192 vpninfo->proxy_type = NULL;
1193 free(vpninfo->proxy);
1194 vpninfo->proxy = NULL;