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 if (openconnect_SSL_write(vpninfo, buf, strlen(buf))) {
378 vpn_progress(vpninfo, PRG_ERR,
379 _("Failed to send GET request for new config\n"));
383 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
385 /* We'll already have complained about whatever offended us */
395 EVP_Digest(config_buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
396 EVP_MD_CTX_cleanup(&c);
398 for (i = 0; i < SHA_DIGEST_LENGTH; i++)
399 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
401 if (strcasecmp(server_sha1, local_sha1_ascii)) {
402 vpn_progress(vpninfo, PRG_ERR,
403 _("Downloaded config file did not match intended SHA1\n"));
408 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
413 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
418 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
419 vpn_progress(vpninfo, PRG_ERR,
420 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
421 "This facility is disabled by default for security reasons, so you may wish to enable it."));
426 vpn_progress(vpninfo, PRG_INFO,
427 _("Trying to run Linux CSD trojan script."));
430 sprintf(fname, "/tmp/csdXXXXXX");
434 vpn_progress(vpninfo, PRG_ERR,
435 _("Failed to open temporary CSD script file: %s\n"),
440 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
442 vpn_progress(vpninfo, PRG_ERR,
443 _("Failed to write temporary CSD script file: %s\n"),
451 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
452 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
453 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
454 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
458 if (vpninfo->uid_csd != getuid()) {
461 if (setuid(vpninfo->uid_csd)) {
462 fprintf(stderr, _("Failed to set uid %ld\n"),
463 (long)vpninfo->uid_csd);
466 if (!(pw = getpwuid(vpninfo->uid_csd))) {
467 fprintf(stderr, _("Invalid user uid=%ld\n"),
468 (long)vpninfo->uid_csd);
471 setenv("HOME", pw->pw_dir, 1);
472 if (chdir(pw->pw_dir)) {
473 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
474 pw->pw_dir, strerror(errno));
478 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
479 fprintf(stderr, _("Warning: you are running insecure "
480 "CSD code with root privileges\n"
481 "\t Use command line option \"--csd-user\"\n"));
483 if (vpninfo->uid_csd_given == 2) {
484 /* The NM tool really needs not to get spurious output
485 on stdout, which the CSD trojan spews. */
488 if (vpninfo->csd_wrapper)
489 csd_argv[i++] = vpninfo->csd_wrapper;
490 csd_argv[i++] = fname;
491 csd_argv[i++]= (char *)"-ticket";
492 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
494 csd_argv[i++]= (char *)"-stub";
495 csd_argv[i++]= (char *)"\"0\"";
496 csd_argv[i++]= (char *)"-group";
497 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
500 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
502 get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
506 csd_argv[i++]= (char *)"-certhash";
507 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
509 csd_argv[i++]= (char *)"-url";
510 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
512 /* WTF would it want to know this for? */
513 csd_argv[i++]= (char *)"-vpnclient";
514 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
515 csd_argv[i++]= (char *)"-connect";
516 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
518 csd_argv[i++]= (char *)"-connectparam";
519 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
521 csd_argv[i++]= (char *)"-langselen";
522 csd_argv[i++] = NULL;
524 execv(csd_argv[0], csd_argv);
525 vpn_progress(vpninfo, PRG_ERR,
526 _("Failed to exec CSD script %s\n"), csd_argv[0]);
530 free(vpninfo->csd_stuburl);
531 vpninfo->csd_stuburl = NULL;
532 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
533 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
534 vpninfo->csd_waiturl = NULL;
535 vpninfo->csd_scriptname = strdup(fname);
537 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
542 #ifndef HAVE_STRCASESTR
543 static char *openconnect__strcasestr(const char *haystack, const char *needle)
545 int hlen = strlen(haystack);
546 int nlen = strlen(needle);
549 for (i = 0; i < hlen - nlen + 1; i++) {
550 for (j = 0; j < nlen; j++) {
551 if (tolower(haystack[i + j]) !=
556 return (char *)haystack + i;
560 #define strcasestr openconnect__strcasestr
564 int internal_parse_url(char *url, char **res_proto, char **res_host,
565 int *res_port, char **res_path, int default_port)
568 char *host, *path, *port_str;
571 host = strstr(url, "://");
576 if (!strcasecmp(proto, "https"))
578 else if (!strcasecmp(proto, "http"))
580 else if (!strcasecmp(proto, "socks") ||
581 !strcasecmp(proto, "socks4") ||
582 !strcasecmp(proto, "socks5"))
585 return -EPROTONOSUPPORT;
595 path = strchr(host, '/');
599 port_str = strrchr(host, ':');
602 int new_port = strtol(port_str + 1, &end, 10);
611 *res_proto = proto ? strdup(proto) : NULL;
613 *res_host = strdup(host);
617 *res_path = (path && *path) ? strdup(path) : NULL;
619 /* Undo the damage we did to the original string */
631 * = 0, no cookie (user cancel)
632 * = 1, obtained cookie
634 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
636 struct vpn_option *opt, *next;
637 char buf[MAX_BUF_LEN];
638 char *form_buf = NULL;
640 char request_body[2048];
641 const char *request_body_type = NULL;
642 const char *method = "GET";
649 if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
650 vpn_progress(vpninfo, PRG_ERR,
651 _("Failed to open HTTPS connection to %s\n"),
657 * It would be nice to use cURL for this, but we really need to guarantee
658 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
659 * to have any way to let us provide our own socket read/write functions.
660 * We can only provide a socket _open_ function. Which would require having
661 * a socketpair() and servicing the "other" end of it.
663 * So we process the HTTP for ourselves...
665 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
666 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
667 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
668 sprintf(buf + strlen(buf), "Accept: */*\r\n");
669 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
671 if (vpninfo->cookies) {
672 sprintf(buf + strlen(buf), "Cookie: ");
673 for (opt = vpninfo->cookies; opt; opt = opt->next)
674 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
675 opt->value, opt->next ? "; " : "\r\n");
677 if (request_body_type) {
678 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
680 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
681 strlen(request_body));
683 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
684 if (request_body_type)
685 sprintf(buf + strlen(buf), "%s", request_body);
687 if (vpninfo->port == 443)
688 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
689 method, vpninfo->hostname,
690 vpninfo->urlpath ?: "");
692 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
693 method, vpninfo->hostname, vpninfo->port,
694 vpninfo->urlpath ?: "");
696 result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
700 /* Remember the peer's SSL certificate; it may disconnect during
701 the response and then we wouldn't be able to find it */
702 if (vpninfo->peer_cert)
703 X509_free(vpninfo->peer_cert);
704 vpninfo->peer_cert = SSL_get_peer_certificate(vpninfo->https_ssl);
706 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
708 /* We'll already have complained about whatever offended us */
712 if (result != 200 && vpninfo->redirect_url) {
714 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
715 /* New host. Tear down the existing connection and make a new one */
720 free(vpninfo->urlpath);
721 vpninfo->urlpath = NULL;
723 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
725 vpn_progress(vpninfo, PRG_ERR,
726 _("Failed to parse redirected URL '%s': %s\n"),
727 vpninfo->redirect_url, strerror(-ret));
728 free(vpninfo->redirect_url);
729 vpninfo->redirect_url = NULL;
734 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
735 free(vpninfo->hostname);
736 vpninfo->hostname = host;
737 vpninfo->port = port;
739 /* Kill the existing connection, and a new one will happen */
740 free(vpninfo->peer_addr);
741 vpninfo->peer_addr = NULL;
742 if (vpninfo->https_ssl) {
743 SSL_free(vpninfo->https_ssl);
744 vpninfo->https_ssl = NULL;
745 close(vpninfo->ssl_fd);
746 vpninfo->ssl_fd = -1;
749 for (opt = vpninfo->cookies; opt; opt = next) {
756 vpninfo->cookies = NULL;
760 free(vpninfo->redirect_url);
761 vpninfo->redirect_url = NULL;
764 } else if (strstr(vpninfo->redirect_url, "://")) {
765 vpn_progress(vpninfo, PRG_ERR,
766 _("Cannot follow redirection to non-https URL '%s'\n"),
767 vpninfo->redirect_url);
768 free(vpninfo->redirect_url);
769 vpninfo->redirect_url = NULL;
772 } else if (vpninfo->redirect_url[0] == '/') {
773 /* Absolute redirect within same host */
774 free(vpninfo->urlpath);
775 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
776 free(vpninfo->redirect_url);
777 vpninfo->redirect_url = NULL;
780 char *lastslash = NULL;
781 if (vpninfo->urlpath)
782 lastslash = strrchr(vpninfo->urlpath, '/');
784 free(vpninfo->urlpath);
785 vpninfo->urlpath = vpninfo->redirect_url;
786 vpninfo->redirect_url = NULL;
788 char *oldurl = vpninfo->urlpath;
790 vpninfo->urlpath = NULL;
791 if (asprintf(&vpninfo->urlpath, "%s/%s",
792 oldurl, vpninfo->redirect_url) == -1) {
794 vpn_progress(vpninfo, PRG_ERR,
795 _("Allocating new path for relative redirect failed: %s\n"),
800 free(vpninfo->redirect_url);
801 vpninfo->redirect_url = NULL;
806 if (!form_buf || result != 200) {
807 vpn_progress(vpninfo, PRG_ERR,
808 _("Unexpected %d result from server\n"),
813 if (vpninfo->csd_stuburl) {
814 /* This is the CSD stub script, which we now need to run */
815 result = run_csd_script(vpninfo, form_buf, buflen);
821 /* Now we'll be redirected to the waiturl */
824 if (strncmp(form_buf, "<?xml", 5)) {
825 /* Not XML? Perhaps it's HTML with a refresh... */
826 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
827 vpn_progress(vpninfo, PRG_INFO,
828 _("Refreshing %s after 1 second...\n"),
833 vpn_progress(vpninfo, PRG_ERR,
834 _("Unknown response from server\n"));
839 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
840 &method, &request_body_type);
850 /* A return value of 2 means the XML form indicated
851 success. We _should_ have a cookie... */
853 for (opt = vpninfo->cookies; opt; opt = opt->next) {
855 if (!strcmp(opt->option, "webvpn"))
856 vpninfo->cookie = opt->value;
857 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
858 char *tok = opt->value;
859 char *bu = NULL, *fu = NULL, *sha = NULL;
862 if (tok != opt->value)
865 if (!strncmp(tok, "bu:", 3))
867 else if (!strncmp(tok, "fu:", 3))
869 else if (!strncmp(tok, "fh:", 3)) {
870 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
871 SHA_DIGEST_LENGTH * 2))
875 } while ((tok = strchr(tok, '&')));
878 fetch_config(vpninfo, bu, fu, sha);
881 if (vpninfo->csd_scriptname) {
882 unlink(vpninfo->csd_scriptname);
883 free(vpninfo->csd_scriptname);
884 vpninfo->csd_scriptname = NULL;
889 char *openconnect_create_useragent(const char *base)
893 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
899 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
900 char *buf, size_t len)
908 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
909 if (buf[i] == '\n') {
911 if (i && buf[i-1] == '\r') {
928 static int proxy_write(struct openconnect_info *vpninfo, int fd,
929 unsigned char *buf, size_t len)
933 for (count = 0; count < len; ) {
934 fd_set rd_set, wr_set;
941 if (vpninfo->cancel_fd != -1) {
942 FD_SET(vpninfo->cancel_fd, &rd_set);
943 if (vpninfo->cancel_fd > fd)
944 maxfd = vpninfo->cancel_fd;
947 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
948 if (vpninfo->cancel_fd != -1 &&
949 FD_ISSET(vpninfo->cancel_fd, &rd_set))
952 /* Not that this should ever be able to happen... */
953 if (!FD_ISSET(fd, &wr_set))
956 i = write(fd, buf + count, len - count);
965 static int proxy_read(struct openconnect_info *vpninfo, int fd,
966 unsigned char *buf, size_t len)
970 for (count = 0; count < len; ) {
977 if (vpninfo->cancel_fd != -1) {
978 FD_SET(vpninfo->cancel_fd, &rd_set);
979 if (vpninfo->cancel_fd > fd)
980 maxfd = vpninfo->cancel_fd;
983 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
984 if (vpninfo->cancel_fd != -1 &&
985 FD_ISSET(vpninfo->cancel_fd, &rd_set))
988 /* Not that this should ever be able to happen... */
989 if (!FD_ISSET(fd, &rd_set))
992 i = read(fd, buf + count, len - count);
1001 static const char *socks_errors[] = {
1002 N_("request granted"),
1003 N_("general failure"),
1004 N_("connection not allowed by ruleset"),
1005 N_("network unreachable"),
1006 N_("host unreachable"),
1007 N_("connection refused by destination host"),
1009 N_("command not supported / protocol error"),
1010 N_("address type not supported")
1013 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1015 unsigned char buf[1024];
1018 buf[0] = 5; /* SOCKS version */
1019 buf[1] = 1; /* # auth methods */
1020 buf[2] = 0; /* No auth supported */
1022 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1023 vpn_progress(vpninfo, PRG_ERR,
1024 _("Error writing auth request to SOCKS proxy: %s\n"),
1029 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1030 vpn_progress(vpninfo, PRG_ERR,
1031 _("Error reading auth response from SOCKS proxy: %s\n"),
1036 vpn_progress(vpninfo, PRG_ERR,
1037 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1043 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1044 vpn_progress(vpninfo, PRG_ERR,
1045 _("SOCKS proxy error %02x: %s\n"),
1046 buf[1], _(socks_errors[buf[1]]));
1048 vpn_progress(vpninfo, PRG_ERR,
1049 _("SOCKS proxy error %02x\n"),
1054 vpn_progress(vpninfo, PRG_INFO,
1055 _("Requesting SOCKS proxy connection to %s:%d\n"),
1056 vpninfo->hostname, vpninfo->port);
1058 buf[0] = 5; /* SOCKS version */
1059 buf[1] = 1; /* CONNECT */
1060 buf[2] = 0; /* Reserved */
1061 buf[3] = 3; /* Address type is domain name */
1062 buf[4] = strlen(vpninfo->hostname);
1063 strcpy((char *)buf + 5, vpninfo->hostname);
1064 i = strlen(vpninfo->hostname) + 5;
1065 buf[i++] = vpninfo->port >> 8;
1066 buf[i++] = vpninfo->port & 0xff;
1068 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1069 vpn_progress(vpninfo, PRG_ERR,
1070 _("Error writing connect request to SOCKS proxy: %s\n"),
1074 /* Read 5 bytes -- up to and including the first byte of the returned
1075 address (which might be the length byte of a domain name) */
1076 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1077 vpn_progress(vpninfo, PRG_ERR,
1078 _("Error reading connect response from SOCKS proxy: %s\n"),
1083 vpn_progress(vpninfo, PRG_ERR,
1084 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1091 /* Connect responses contain an address */
1093 case 1: /* Legacy IP */
1096 case 3: /* Domain name */
1103 vpn_progress(vpninfo, PRG_ERR,
1104 _("Unexpected address type %02x in SOCKS connect response\n"),
1109 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1110 vpn_progress(vpninfo, PRG_ERR,
1111 _("Error reading connect response from SOCKS proxy: %s\n"),
1118 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1120 char buf[MAX_BUF_LEN];
1123 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1124 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1125 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1126 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1127 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1128 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1129 sprintf(buf + strlen(buf), "\r\n");
1131 vpn_progress(vpninfo, PRG_INFO,
1132 _("Requesting HTTP proxy connection to %s:%d\n"),
1133 vpninfo->hostname, vpninfo->port);
1135 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1137 vpn_progress(vpninfo, PRG_ERR,
1138 _("Sending proxy request failed: %s\n"),
1143 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1144 vpn_progress(vpninfo, PRG_ERR,
1145 _("Error fetching proxy response\n"));
1149 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1150 buf[8] != ' ' || !(result = atoi(buf+9))) {
1151 vpn_progress(vpninfo, PRG_ERR,
1152 _("Failed to parse proxy response '%s'\n"), buf);
1156 if (result != 200) {
1157 vpn_progress(vpninfo, PRG_ERR,
1158 _("Proxy CONNECT request failed: %s\n"), buf);
1162 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1164 vpn_progress(vpninfo, PRG_ERR,
1165 _("Failed to read proxy response\n"));
1168 vpn_progress(vpninfo, PRG_ERR,
1169 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1176 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1178 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1179 return process_http_proxy(vpninfo, ssl_sock);
1181 if (!strcmp(vpninfo->proxy_type, "socks") ||
1182 !strcmp(vpninfo->proxy_type, "socks5"))
1183 return process_socks_proxy(vpninfo, ssl_sock);
1185 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1186 vpninfo->proxy_type);
1190 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1198 free(vpninfo->proxy_type);
1199 vpninfo->proxy_type = NULL;
1200 free(vpninfo->proxy);
1201 vpninfo->proxy = NULL;
1203 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1204 &vpninfo->proxy_port, NULL, 80);
1208 if (vpninfo->proxy_type &&
1209 strcmp(vpninfo->proxy_type, "http") &&
1210 strcmp(vpninfo->proxy_type, "socks") &&
1211 strcmp(vpninfo->proxy_type, "socks5")) {
1212 vpn_progress(vpninfo, PRG_ERR,
1213 _("Only http or socks(5) proxies supported\n"));
1214 free(vpninfo->proxy_type);
1215 vpninfo->proxy_type = NULL;
1216 free(vpninfo->proxy);
1217 vpninfo->proxy = NULL;