2 * OpenConnect (SSL + DTLS) VPN client
4 * Copyright © 2008-2010 Intel Corporation.
5 * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
7 * Author: David Woodhouse <dwmw2@infradead.org>
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * version 2.1, as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to:
21 * Free Software Foundation, Inc.
22 * 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301 USA
34 #include <sys/types.h>
36 #include <openssl/ssl.h>
37 #include <openssl/err.h>
38 #include <openssl/engine.h>
40 #include "openconnect-internal.h"
42 static int proxy_write(struct openconnect_info *vpninfo, int fd,
43 unsigned char *buf, size_t len);
44 static int proxy_read(struct openconnect_info *vpninfo, int fd,
45 unsigned char *buf, size_t len);
47 #define MAX_BUF_LEN 131072
49 * We didn't really want to have to do this for ourselves -- one might have
50 * thought that it would be available in a library somewhere. But neither
51 * cURL nor Neon have reliable cross-platform ways of either using a cert
52 * from the TPM, or just reading from / writing to a transport which is
53 * provided by their caller.
56 static int http_add_cookie(struct openconnect_info *vpninfo,
57 const char *option, const char *value)
59 struct vpn_option *new, **this;
62 new = malloc(sizeof(*new));
64 vpn_progress(vpninfo, PRG_ERR,
65 _("No memory for allocating cookies\n"));
69 new->option = strdup(option);
70 new->value = strdup(value);
71 if (!new->option || !new->value) {
78 /* Kill cookie; don't replace it */
81 for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
82 if (!strcmp(option, (*this)->option)) {
83 /* Replace existing cookie */
85 new->next = (*this)->next;
89 free((*this)->option);
103 #define BODY_HTTP10 -1
104 #define BODY_CHUNKED -2
106 static int process_http_response(struct openconnect_info *vpninfo, int *result,
107 int (*header_cb)(struct openconnect_info *, char *, char *),
110 char buf[MAX_BUF_LEN];
112 int bodylen = BODY_HTTP10;
118 if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
119 vpn_progress(vpninfo, PRG_ERR,
120 _("Error fetching HTTPS response\n"));
124 if (!strncmp(buf, "HTTP/1.0 ", 9))
127 if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
128 vpn_progress(vpninfo, PRG_ERR,
129 _("Failed to parse HTTP response '%s'\n"), buf);
133 vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
134 _("Got HTTP response: %s\n"), buf);
137 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
141 vpn_progress(vpninfo, PRG_ERR,
142 _("Error processing HTTP response\n"));
145 colon = strchr(buf, ':');
147 vpn_progress(vpninfo, PRG_ERR,
148 _("Ignoring unknown HTTP response line '%s'\n"), buf);
155 /* Handle Set-Cookie first so that we can avoid printing the
156 webvpn cookie in the verbose debug output */
157 if (!strcasecmp(buf, "Set-Cookie")) {
158 char *semicolon = strchr(colon, ';');
159 const char *print_equals;
160 char *equals = strchr(colon, '=');
167 vpn_progress(vpninfo, PRG_ERR,
168 _("Invalid cookie offered: %s\n"), buf);
173 print_equals = equals;
174 /* Don't print the webvpn cookie unless it's empty; we don't
175 want people posting it in public with debugging output */
176 if (!strcmp(colon, "webvpn") && *equals)
177 print_equals = _("<elided>");
178 vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
179 buf, colon, print_equals, semicolon?";":"",
180 semicolon?(semicolon+1):"");
182 /* The server tends to ask for the username and password as
183 usual, even if we've already failed because it didn't like
184 our cert. Thankfully it does give us this hint... */
185 if (!strcmp(colon, "ClientCertAuthFailed"))
186 vpn_progress(vpninfo, PRG_ERR,
187 _("SSL certificate authentication failed\n"));
189 ret = http_add_cookie(vpninfo, colon, equals);
193 vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
196 if (!strcasecmp(buf, "Connection")) {
197 if (!strcasecmp(colon, "Close"))
200 /* This might seem reasonable, but in fact it breaks
201 certificate authentication with some servers. If
202 they give an HTTP/1.0 response, even if they
203 explicitly give a Connection: Keep-Alive header,
204 just close the connection. */
205 else if (!strcasecmp(colon, "Keep-Alive"))
209 if (!strcasecmp(buf, "Location")) {
210 vpninfo->redirect_url = strdup(colon);
211 if (!vpninfo->redirect_url)
214 if (!strcasecmp(buf, "Content-Length")) {
215 bodylen = atoi(colon);
217 vpn_progress(vpninfo, PRG_ERR,
218 _("Response body has negative size (%d)\n"),
223 if (!strcasecmp(buf, "Transfer-Encoding")) {
224 if (!strcasecmp(colon, "chunked"))
225 bodylen = BODY_CHUNKED;
227 vpn_progress(vpninfo, PRG_ERR,
228 _("Unknown Transfer-Encoding: %s\n"),
233 if (header_cb && !strncmp(buf, "X-", 2))
234 header_cb(vpninfo, buf, colon);
237 /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
241 /* Now the body, if there is one */
242 vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"),
243 bodylen==BODY_HTTP10?"http 1.0" :
244 bodylen==BODY_CHUNKED?"chunked" : "length: ",
247 /* If we were given Content-Length, it's nice and easy... */
249 body = malloc(bodylen + 1);
252 while (done < bodylen) {
253 i = SSL_read(vpninfo->https_ssl, 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 = SSL_read(vpninfo->https_ssl, 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 = SSL_read(vpninfo->https_ssl, body + done, 16384);
322 body = realloc(body, done + 1);
331 if (closeconn || vpninfo->no_http_keepalive) {
332 SSL_free(vpninfo->https_ssl);
333 vpninfo->https_ssl = NULL;
334 close(vpninfo->ssl_fd);
335 vpninfo->ssl_fd = -1;
344 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
347 struct vpn_option *opt;
348 char buf[MAX_BUF_LEN];
349 char *config_buf = NULL;
351 unsigned char local_sha1_bin[SHA_DIGEST_LENGTH];
352 char local_sha1_ascii[(SHA_DIGEST_LENGTH * 2)+1];
356 sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
357 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
358 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
359 sprintf(buf + strlen(buf), "Accept: */*\r\n");
360 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
362 if (vpninfo->cookies) {
363 sprintf(buf + strlen(buf), "Cookie: ");
364 for (opt = vpninfo->cookies; opt; opt = opt->next)
365 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
366 opt->value, opt->next ? "; " : "\r\n");
368 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
370 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
372 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
374 /* We'll already have complained about whatever offended us */
384 EVP_Digest(config_buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
385 EVP_MD_CTX_cleanup(&c);
387 for (i = 0; i < SHA_DIGEST_LENGTH; i++)
388 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
390 if (strcasecmp(server_sha1, local_sha1_ascii)) {
391 vpn_progress(vpninfo, PRG_ERR,
392 _("Downloaded config file did not match intended SHA1\n"));
397 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
402 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
407 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
408 vpn_progress(vpninfo, PRG_ERR,
409 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
410 "This facility is disabled by default for security reasons, so you may wish to enable it."));
415 vpn_progress(vpninfo, PRG_INFO,
416 _("Trying to run Linux CSD trojan script."));
419 sprintf(fname, "/tmp/csdXXXXXX");
423 vpn_progress(vpninfo, PRG_ERR,
424 _("Failed to open temporary CSD script file: %s\n"),
429 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
431 vpn_progress(vpninfo, PRG_ERR,
432 _("Failed to write temporary CSD script file: %s\n"),
440 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
441 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
442 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
443 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
447 if (vpninfo->uid_csd != getuid()) {
450 if (setuid(vpninfo->uid_csd)) {
451 fprintf(stderr, _("Failed to set uid %ld\n"),
452 (long)vpninfo->uid_csd);
455 if (!(pw = getpwuid(vpninfo->uid_csd))) {
456 fprintf(stderr, _("Invalid user uid=%ld\n"),
457 (long)vpninfo->uid_csd);
460 setenv("HOME", pw->pw_dir, 1);
461 if (chdir(pw->pw_dir)) {
462 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
463 pw->pw_dir, strerror(errno));
467 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
468 fprintf(stderr, _("Warning: you are running insecure "
469 "CSD code with root privileges\n"
470 "\t Use command line option \"--csd-user\"\n"));
472 if (vpninfo->uid_csd_given == 2) {
473 /* The NM tool really needs not to get spurious output
474 on stdout, which the CSD trojan spews. */
477 if (vpninfo->csd_wrapper)
478 csd_argv[i++] = vpninfo->csd_wrapper;
479 csd_argv[i++] = fname;
480 csd_argv[i++]= (char *)"-ticket";
481 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
483 csd_argv[i++]= (char *)"-stub";
484 csd_argv[i++]= (char *)"\"0\"";
485 csd_argv[i++]= (char *)"-group";
486 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
489 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
491 get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
495 csd_argv[i++]= (char *)"-certhash";
496 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
498 csd_argv[i++]= (char *)"-url";
499 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
501 /* WTF would it want to know this for? */
502 csd_argv[i++]= (char *)"-vpnclient";
503 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
504 csd_argv[i++]= (char *)"-connect";
505 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
507 csd_argv[i++]= (char *)"-connectparam";
508 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
510 csd_argv[i++]= (char *)"-langselen";
511 csd_argv[i++] = NULL;
513 execv(csd_argv[0], csd_argv);
514 vpn_progress(vpninfo, PRG_ERR,
515 _("Failed to exec CSD script %s\n"), csd_argv[0]);
519 free(vpninfo->csd_stuburl);
520 vpninfo->csd_stuburl = NULL;
521 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
522 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
523 vpninfo->csd_waiturl = NULL;
524 vpninfo->csd_scriptname = strdup(fname);
526 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
531 #ifndef HAVE_STRCASESTR
532 static char *openconnect__strcasestr(const char *haystack, const char *needle)
534 int hlen = strlen(haystack);
535 int nlen = strlen(needle);
538 for (i = 0; i < hlen - nlen + 1; i++) {
539 for (j = 0; j < nlen; j++) {
540 if (tolower(haystack[i + j]) !=
545 return (char *)haystack + i;
549 #define strcasestr openconnect__strcasestr
553 int internal_parse_url(char *url, char **res_proto, char **res_host,
554 int *res_port, char **res_path, int default_port)
557 char *host, *path, *port_str;
560 host = strstr(url, "://");
565 if (!strcasecmp(proto, "https"))
567 else if (!strcasecmp(proto, "http"))
569 else if (!strcasecmp(proto, "socks") ||
570 !strcasecmp(proto, "socks4") ||
571 !strcasecmp(proto, "socks5"))
574 return -EPROTONOSUPPORT;
584 path = strchr(host, '/');
588 port_str = strrchr(host, ':');
591 int new_port = strtol(port_str + 1, &end, 10);
600 *res_proto = proto ? strdup(proto) : NULL;
602 *res_host = strdup(host);
606 *res_path = (path && *path) ? strdup(path) : NULL;
608 /* Undo the damage we did to the original string */
618 * = 0, no cookie (user cancel)
619 * = 1, obtained cookie
621 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
623 struct vpn_option *opt, *next;
624 char buf[MAX_BUF_LEN];
625 char *form_buf = NULL;
627 char request_body[2048];
628 const char *request_body_type = NULL;
629 const char *method = "GET";
636 if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
637 vpn_progress(vpninfo, PRG_ERR,
638 _("Failed to open HTTPS connection to %s\n"),
643 /* We don't cope with nonblocking mode... yet */
644 fcntl(vpninfo->ssl_fd, F_SETFL, fcntl(vpninfo->ssl_fd, F_GETFL) & ~O_NONBLOCK);
647 * It would be nice to use cURL for this, but we really need to guarantee
648 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
649 * to have any way to let us provide our own socket read/write functions.
650 * We can only provide a socket _open_ function. Which would require having
651 * a socketpair() and servicing the "other" end of it.
653 * So we process the HTTP for ourselves...
655 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
656 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
657 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
658 sprintf(buf + strlen(buf), "Accept: */*\r\n");
659 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
661 if (vpninfo->cookies) {
662 sprintf(buf + strlen(buf), "Cookie: ");
663 for (opt = vpninfo->cookies; opt; opt = opt->next)
664 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
665 opt->value, opt->next ? "; " : "\r\n");
667 if (request_body_type) {
668 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
670 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
671 strlen(request_body));
673 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
674 if (request_body_type)
675 sprintf(buf + strlen(buf), "%s", request_body);
677 if (vpninfo->port == 443)
678 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
679 method, vpninfo->hostname,
680 vpninfo->urlpath ?: "");
682 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
683 method, vpninfo->hostname, vpninfo->port,
684 vpninfo->urlpath ?: "");
686 SSL_write(vpninfo->https_ssl, 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);
715 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
716 free(vpninfo->hostname);
717 vpninfo->hostname = host;
718 vpninfo->port = port;
720 /* Kill the existing connection, and a new one will happen */
721 free(vpninfo->peer_addr);
722 vpninfo->peer_addr = NULL;
723 if (vpninfo->https_ssl) {
724 SSL_free(vpninfo->https_ssl);
725 vpninfo->https_ssl = NULL;
726 close(vpninfo->ssl_fd);
727 vpninfo->ssl_fd = -1;
730 for (opt = vpninfo->cookies; opt; opt = next) {
737 vpninfo->cookies = NULL;
741 free(vpninfo->redirect_url);
742 vpninfo->redirect_url = NULL;
745 } else if (vpninfo->redirect_url[0] == '/') {
746 /* Absolute redirect within same host */
747 free(vpninfo->urlpath);
748 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
749 free(vpninfo->redirect_url);
750 vpninfo->redirect_url = NULL;
753 char *lastslash = NULL;
754 if (vpninfo->urlpath)
755 lastslash = strrchr(vpninfo->urlpath, '/');
757 free(vpninfo->urlpath);
758 vpninfo->urlpath = vpninfo->redirect_url;
759 vpninfo->redirect_url = NULL;
761 char *oldurl = vpninfo->urlpath;
763 vpninfo->urlpath = NULL;
764 if (asprintf(&vpninfo->urlpath, "%s/%s",
765 oldurl, vpninfo->redirect_url) == -1) {
767 vpn_progress(vpninfo, PRG_ERR,
768 _("Allocating new path for relative redirect failed: %s\n"),
773 free(vpninfo->redirect_url);
774 vpninfo->redirect_url = NULL;
779 if (!form_buf || result != 200) {
780 vpn_progress(vpninfo, PRG_ERR,
781 _("Unexpected %d result from server\n"),
786 if (vpninfo->csd_stuburl) {
787 /* This is the CSD stub script, which we now need to run */
788 result = run_csd_script(vpninfo, form_buf, buflen);
794 /* Now we'll be redirected to the waiturl */
797 if (strncmp(form_buf, "<?xml", 5)) {
798 /* Not XML? Perhaps it's HTML with a refresh... */
799 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
800 vpn_progress(vpninfo, PRG_INFO,
801 _("Refreshing %s after 1 second...\n"),
806 vpn_progress(vpninfo, PRG_ERR,
807 _("Unknown response from server\n"));
812 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
813 &method, &request_body_type);
823 /* A return value of 2 means the XML form indicated
824 success. We _should_ have a cookie... */
826 for (opt = vpninfo->cookies; opt; opt = opt->next) {
828 if (!strcmp(opt->option, "webvpn"))
829 vpninfo->cookie = opt->value;
830 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
831 char *tok = opt->value;
832 char *bu = NULL, *fu = NULL, *sha = NULL;
835 if (tok != opt->value)
838 if (!strncmp(tok, "bu:", 3))
840 else if (!strncmp(tok, "fu:", 3))
842 else if (!strncmp(tok, "fh:", 3)) {
843 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
844 SHA_DIGEST_LENGTH * 2))
848 } while ((tok = strchr(tok, '&')));
851 fetch_config(vpninfo, bu, fu, sha);
854 if (vpninfo->csd_scriptname) {
855 unlink(vpninfo->csd_scriptname);
856 free(vpninfo->csd_scriptname);
857 vpninfo->csd_scriptname = NULL;
862 char *openconnect_create_useragent(const char *base)
866 if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
872 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
873 char *buf, size_t len)
881 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
882 if (buf[i] == '\n') {
884 if (i && buf[i-1] == '\r') {
901 static int proxy_write(struct openconnect_info *vpninfo, int fd,
902 unsigned char *buf, size_t len)
906 for (count = 0; count < len; ) {
907 fd_set rd_set, wr_set;
914 if (vpninfo->cancel_fd != -1) {
915 FD_SET(vpninfo->cancel_fd, &rd_set);
916 if (vpninfo->cancel_fd > fd)
917 maxfd = vpninfo->cancel_fd;
920 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
921 if (vpninfo->cancel_fd != -1 &&
922 FD_ISSET(vpninfo->cancel_fd, &rd_set))
925 /* Not that this should ever be able to happen... */
926 if (!FD_ISSET(fd, &wr_set))
929 i = write(fd, buf + count, len - count);
938 static int proxy_read(struct openconnect_info *vpninfo, int fd,
939 unsigned char *buf, size_t len)
943 for (count = 0; count < len; ) {
950 if (vpninfo->cancel_fd != -1) {
951 FD_SET(vpninfo->cancel_fd, &rd_set);
952 if (vpninfo->cancel_fd > fd)
953 maxfd = vpninfo->cancel_fd;
956 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
957 if (vpninfo->cancel_fd != -1 &&
958 FD_ISSET(vpninfo->cancel_fd, &rd_set))
961 /* Not that this should ever be able to happen... */
962 if (!FD_ISSET(fd, &rd_set))
965 i = read(fd, buf + count, len - count);
974 static const char *socks_errors[] = {
975 N_("request granted"),
976 N_("general failure"),
977 N_("connection not allowed by ruleset"),
978 N_("network unreachable"),
979 N_("host unreachable"),
980 N_("connection refused by destination host"),
982 N_("command not supported / protocol error"),
983 N_("address type not supported")
986 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
988 unsigned char buf[1024];
991 buf[0] = 5; /* SOCKS version */
992 buf[1] = 1; /* # auth methods */
993 buf[2] = 0; /* No auth supported */
995 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
996 vpn_progress(vpninfo, PRG_ERR,
997 _("Error writing auth request to SOCKS proxy: %s\n"),
1002 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1003 vpn_progress(vpninfo, PRG_ERR,
1004 _("Error reading auth response from SOCKS proxy: %s\n"),
1009 vpn_progress(vpninfo, PRG_ERR,
1010 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1016 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1017 vpn_progress(vpninfo, PRG_ERR,
1018 _("SOCKS proxy error %02x: %s\n"),
1019 buf[1], _(socks_errors[buf[1]]));
1021 vpn_progress(vpninfo, PRG_ERR,
1022 _("SOCKS proxy error %02x\n"),
1027 vpn_progress(vpninfo, PRG_INFO,
1028 _("Requesting SOCKS proxy connection to %s:%d\n"),
1029 vpninfo->hostname, vpninfo->port);
1031 buf[0] = 5; /* SOCKS version */
1032 buf[1] = 1; /* CONNECT */
1033 buf[2] = 0; /* Reserved */
1034 buf[3] = 3; /* Address type is domain name */
1035 buf[4] = strlen(vpninfo->hostname);
1036 strcpy((char *)buf + 5, vpninfo->hostname);
1037 i = strlen(vpninfo->hostname) + 5;
1038 buf[i++] = vpninfo->port >> 8;
1039 buf[i++] = vpninfo->port & 0xff;
1041 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1042 vpn_progress(vpninfo, PRG_ERR,
1043 _("Error writing connect request to SOCKS proxy: %s\n"),
1047 /* Read 5 bytes -- up to and including the first byte of the returned
1048 address (which might be the length byte of a domain name) */
1049 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1050 vpn_progress(vpninfo, PRG_ERR,
1051 _("Error reading connect response from SOCKS proxy: %s\n"),
1056 vpn_progress(vpninfo, PRG_ERR,
1057 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1064 /* Connect responses contain an address */
1066 case 1: /* Legacy IP */
1069 case 3: /* Domain name */
1076 vpn_progress(vpninfo, PRG_ERR,
1077 _("Unexpected address type %02x in SOCKS connect response\n"),
1082 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1083 vpn_progress(vpninfo, PRG_ERR,
1084 _("Error reading connect response from SOCKS proxy: %s\n"),
1091 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1093 char buf[MAX_BUF_LEN];
1096 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1097 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1098 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1099 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1100 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1101 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1102 sprintf(buf + strlen(buf), "\r\n");
1104 vpn_progress(vpninfo, PRG_INFO,
1105 _("Requesting HTTP proxy connection to %s:%d\n"),
1106 vpninfo->hostname, vpninfo->port);
1108 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1110 vpn_progress(vpninfo, PRG_ERR,
1111 _("Sending proxy request failed: %s\n"),
1116 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1117 vpn_progress(vpninfo, PRG_ERR,
1118 _("Error fetching proxy response\n"));
1122 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1123 buf[8] != ' ' || !(result = atoi(buf+9))) {
1124 vpn_progress(vpninfo, PRG_ERR,
1125 _("Failed to parse proxy response '%s'\n"), buf);
1129 if (result != 200) {
1130 vpn_progress(vpninfo, PRG_ERR,
1131 _("Proxy CONNECT request failed: %s\n"), buf);
1135 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1137 vpn_progress(vpninfo, PRG_ERR,
1138 _("Failed to read proxy response\n"));
1141 vpn_progress(vpninfo, PRG_ERR,
1142 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1149 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1151 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1152 return process_http_proxy(vpninfo, ssl_sock);
1154 if (!strcmp(vpninfo->proxy_type, "socks") ||
1155 !strcmp(vpninfo->proxy_type, "socks5"))
1156 return process_socks_proxy(vpninfo, ssl_sock);
1158 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1159 vpninfo->proxy_type);
1163 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1171 free(vpninfo->proxy_type);
1172 vpninfo->proxy_type = NULL;
1173 free(vpninfo->proxy);
1174 vpninfo->proxy = NULL;
1176 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1177 &vpninfo->proxy_port, NULL, 80);
1181 if (vpninfo->proxy_type &&
1182 strcmp(vpninfo->proxy_type, "http") &&
1183 strcmp(vpninfo->proxy_type, "socks") &&
1184 strcmp(vpninfo->proxy_type, "socks5")) {
1185 vpn_progress(vpninfo, PRG_ERR,
1186 _("Only http or socks(5) proxies supported\n"));
1187 free(vpninfo->proxy_type);
1188 vpninfo->proxy_type = NULL;
1189 free(vpninfo->proxy);
1190 vpninfo->proxy = NULL;