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>
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
48 #define BUF_CHUNK_SIZE 4096
57 static struct oc_text_buf *buf_alloc(void)
59 return calloc(1, sizeof(struct oc_text_buf));
62 static void buf_append(struct oc_text_buf *buf, const char *fmt, ...)
66 if (!buf || buf->error)
70 buf->data = malloc(BUF_CHUNK_SIZE);
75 buf->buf_len = BUF_CHUNK_SIZE;
79 int max_len = buf->buf_len - buf->pos, ret;
82 ret = vsnprintf(buf->data + buf->pos, max_len, fmt, ap);
87 } else if (ret < max_len) {
91 int new_buf_len = buf->buf_len + BUF_CHUNK_SIZE;
93 if (new_buf_len > MAX_BUF_LEN) {
94 /* probably means somebody is messing with us */
99 buf->data = realloc(buf->data, new_buf_len);
101 buf->error = -ENOMEM;
104 buf->buf_len = new_buf_len;
109 static int buf_error(struct oc_text_buf *buf)
111 return buf ? buf->error : -ENOMEM;
114 static int buf_free(struct oc_text_buf *buf)
116 int error = buf_error(buf);
128 * We didn't really want to have to do this for ourselves -- one might have
129 * thought that it would be available in a library somewhere. But neither
130 * cURL nor Neon have reliable cross-platform ways of either using a cert
131 * from the TPM, or just reading from / writing to a transport which is
132 * provided by their caller.
135 static int http_add_cookie(struct openconnect_info *vpninfo,
136 const char *option, const char *value)
138 struct vpn_option *new, **this;
141 new = malloc(sizeof(*new));
143 vpn_progress(vpninfo, PRG_ERR,
144 _("No memory for allocating cookies\n"));
148 new->option = strdup(option);
149 new->value = strdup(value);
150 if (!new->option || !new->value) {
157 /* Kill cookie; don't replace it */
160 for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
161 if (!strcmp(option, (*this)->option)) {
162 /* Replace existing cookie */
164 new->next = (*this)->next;
168 free((*this)->option);
169 free((*this)->value);
182 #define BODY_HTTP10 -1
183 #define BODY_CHUNKED -2
185 static int process_http_response(struct openconnect_info *vpninfo, int *result,
186 int (*header_cb)(struct openconnect_info *, char *, char *),
189 char buf[MAX_BUF_LEN];
191 int bodylen = BODY_HTTP10;
197 if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
198 vpn_progress(vpninfo, PRG_ERR,
199 _("Error fetching HTTPS response\n"));
203 if (!strncmp(buf, "HTTP/1.0 ", 9))
206 if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
207 vpn_progress(vpninfo, PRG_ERR,
208 _("Failed to parse HTTP response '%s'\n"), buf);
212 vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
213 _("Got HTTP response: %s\n"), buf);
216 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
220 vpn_progress(vpninfo, PRG_ERR,
221 _("Error processing HTTP response\n"));
224 colon = strchr(buf, ':');
226 vpn_progress(vpninfo, PRG_ERR,
227 _("Ignoring unknown HTTP response line '%s'\n"), buf);
234 /* Handle Set-Cookie first so that we can avoid printing the
235 webvpn cookie in the verbose debug output */
236 if (!strcasecmp(buf, "Set-Cookie")) {
237 char *semicolon = strchr(colon, ';');
238 const char *print_equals;
239 char *equals = strchr(colon, '=');
246 vpn_progress(vpninfo, PRG_ERR,
247 _("Invalid cookie offered: %s\n"), buf);
252 print_equals = equals;
253 /* Don't print the webvpn cookie unless it's empty; we don't
254 want people posting it in public with debugging output */
255 if (!strcmp(colon, "webvpn") && *equals)
256 print_equals = _("<elided>");
257 vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
258 buf, colon, print_equals, semicolon?";":"",
259 semicolon?(semicolon+1):"");
261 /* The server tends to ask for the username and password as
262 usual, even if we've already failed because it didn't like
263 our cert. Thankfully it does give us this hint... */
264 if (!strcmp(colon, "ClientCertAuthFailed"))
265 vpn_progress(vpninfo, PRG_ERR,
266 _("SSL certificate authentication failed\n"));
268 ret = http_add_cookie(vpninfo, colon, equals);
272 vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
275 if (!strcasecmp(buf, "Connection")) {
276 if (!strcasecmp(colon, "Close"))
279 /* This might seem reasonable, but in fact it breaks
280 certificate authentication with some servers. If
281 they give an HTTP/1.0 response, even if they
282 explicitly give a Connection: Keep-Alive header,
283 just close the connection. */
284 else if (!strcasecmp(colon, "Keep-Alive"))
288 if (!strcasecmp(buf, "Location")) {
289 vpninfo->redirect_url = strdup(colon);
290 if (!vpninfo->redirect_url)
293 if (!strcasecmp(buf, "Content-Length")) {
294 bodylen = atoi(colon);
296 vpn_progress(vpninfo, PRG_ERR,
297 _("Response body has negative size (%d)\n"),
302 if (!strcasecmp(buf, "Transfer-Encoding")) {
303 if (!strcasecmp(colon, "chunked"))
304 bodylen = BODY_CHUNKED;
306 vpn_progress(vpninfo, PRG_ERR,
307 _("Unknown Transfer-Encoding: %s\n"),
312 if (header_cb && !strncmp(buf, "X-", 2))
313 header_cb(vpninfo, buf, colon);
316 /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
320 /* Now the body, if there is one */
321 vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"),
322 bodylen==BODY_HTTP10?"http 1.0" :
323 bodylen==BODY_CHUNKED?"chunked" : "length: ",
326 /* If we were given Content-Length, it's nice and easy... */
328 body = malloc(bodylen + 1);
331 while (done < bodylen) {
332 i = openconnect_SSL_read(vpninfo, body + done, bodylen - done);
334 vpn_progress(vpninfo, PRG_ERR,
335 _("Error reading HTTP response body\n"));
341 } else if (bodylen == BODY_CHUNKED) {
342 /* ... else, chunked */
343 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
344 int chunklen, lastchunk = 0;
347 vpn_progress(vpninfo, PRG_ERR,
348 _("Error fetching chunk header\n"));
351 chunklen = strtol(buf, NULL, 16);
356 body = realloc(body, done + chunklen + 1);
360 i = openconnect_SSL_read(vpninfo, body + done, chunklen);
362 vpn_progress(vpninfo, PRG_ERR,
363 _("Error reading HTTP response body\n"));
371 if ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
373 vpn_progress(vpninfo, PRG_ERR,
374 _("Error fetching HTTP response body\n"));
376 vpn_progress(vpninfo, PRG_ERR,
377 _("Error in chunked decoding. Expected '', got: '%s'"),
387 } else if (bodylen == BODY_HTTP10) {
389 vpn_progress(vpninfo, PRG_ERR,
390 _("Cannot receive HTTP 1.0 body without closing connection\n"));
394 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
396 body = realloc(body, done + 16384);
399 i = openconnect_SSL_read(vpninfo, body + done, 16384);
408 /* Connection closed. Reduce allocation to just what we need */
409 body = realloc(body, done + 1);
417 if (closeconn || vpninfo->no_http_keepalive)
418 openconnect_close_https(vpninfo, 0);
426 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
429 struct vpn_option *opt;
430 struct oc_text_buf *buf;
431 char *config_buf = NULL;
433 unsigned char local_sha1_bin[SHA1_SIZE];
434 char local_sha1_ascii[(SHA1_SIZE * 2)+1];
437 if (openconnect_open_https(vpninfo)) {
438 vpn_progress(vpninfo, PRG_ERR,
439 _("Failed to open HTTPS connection to %s\n"),
445 buf_append(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
446 buf_append(buf, "Host: %s\r\n", vpninfo->hostname);
447 buf_append(buf, "User-Agent: %s\r\n", vpninfo->useragent);
448 buf_append(buf, "Accept: */*\r\n");
449 buf_append(buf, "Accept-Encoding: identity\r\n");
451 if (vpninfo->cookies) {
452 buf_append(buf, "Cookie: ");
453 for (opt = vpninfo->cookies; opt; opt = opt->next)
454 buf_append(buf, "%s=%s%s", opt->option,
455 opt->value, opt->next ? "; " : "\r\n");
457 buf_append(buf, "X-Transcend-Version: 1\r\n\r\n");
460 return buf_free(buf);
462 if (openconnect_SSL_write(vpninfo, buf->data, buf->pos) != buf->pos) {
463 vpn_progress(vpninfo, PRG_ERR,
464 _("Failed to send GET request for new config\n"));
470 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
472 /* We'll already have complained about whatever offended us */
481 openconnect_sha1(local_sha1_bin, config_buf, buflen);
483 for (i = 0; i < SHA1_SIZE; i++)
484 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
486 if (strcasecmp(server_sha1, local_sha1_ascii)) {
487 vpn_progress(vpninfo, PRG_ERR,
488 _("Downloaded config file did not match intended SHA1\n"));
493 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
498 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
503 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
504 vpn_progress(vpninfo, PRG_ERR,
505 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
506 "This facility is disabled by default for security reasons, so you may wish to enable it."));
511 vpn_progress(vpninfo, PRG_INFO,
512 _("Trying to run Linux CSD trojan script."));
515 sprintf(fname, "/tmp/csdXXXXXX");
519 vpn_progress(vpninfo, PRG_ERR,
520 _("Failed to open temporary CSD script file: %s\n"),
525 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
527 vpn_progress(vpninfo, PRG_ERR,
528 _("Failed to write temporary CSD script file: %s\n"),
536 char scertbuf[MD5_SIZE * 2 + 1];
537 char ccertbuf[MD5_SIZE * 2 + 1];
541 if (vpninfo->uid_csd_given && vpninfo->uid_csd != getuid()) {
544 if (setuid(vpninfo->uid_csd)) {
545 fprintf(stderr, _("Failed to set uid %ld\n"),
546 (long)vpninfo->uid_csd);
549 if (!(pw = getpwuid(vpninfo->uid_csd))) {
550 fprintf(stderr, _("Invalid user uid=%ld\n"),
551 (long)vpninfo->uid_csd);
554 setenv("HOME", pw->pw_dir, 1);
555 if (chdir(pw->pw_dir)) {
556 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
557 pw->pw_dir, strerror(errno));
561 if (getuid() == 0 && !vpninfo->csd_wrapper) {
562 fprintf(stderr, _("Warning: you are running insecure "
563 "CSD code with root privileges\n"
564 "\t Use command line option \"--csd-user\"\n"));
566 if (vpninfo->uid_csd_given == 2) {
567 /* The NM tool really needs not to get spurious output
568 on stdout, which the CSD trojan spews. */
571 if (vpninfo->csd_wrapper)
572 csd_argv[i++] = vpninfo->csd_wrapper;
573 csd_argv[i++] = fname;
574 csd_argv[i++]= (char *)"-ticket";
575 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
577 csd_argv[i++]= (char *)"-stub";
578 csd_argv[i++]= (char *)"\"0\"";
579 csd_argv[i++]= (char *)"-group";
580 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
583 openconnect_local_cert_md5(vpninfo, ccertbuf);
585 get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
586 csd_argv[i++]= (char *)"-certhash";
587 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
590 csd_argv[i++]= (char *)"-url";
591 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
594 csd_argv[i++]= (char *)"-langselen";
595 csd_argv[i++] = NULL;
597 execv(csd_argv[0], csd_argv);
598 vpn_progress(vpninfo, PRG_ERR,
599 _("Failed to exec CSD script %s\n"), csd_argv[0]);
603 free(vpninfo->csd_stuburl);
604 vpninfo->csd_stuburl = NULL;
605 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
606 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
607 vpninfo->csd_waiturl = NULL;
608 vpninfo->csd_scriptname = strdup(fname);
610 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
615 int internal_parse_url(char *url, char **res_proto, char **res_host,
616 int *res_port, char **res_path, int default_port)
619 char *host, *path, *port_str;
622 host = strstr(url, "://");
627 if (!strcasecmp(proto, "https"))
629 else if (!strcasecmp(proto, "http"))
631 else if (!strcasecmp(proto, "socks") ||
632 !strcasecmp(proto, "socks4") ||
633 !strcasecmp(proto, "socks5"))
636 return -EPROTONOSUPPORT;
646 path = strchr(host, '/');
650 port_str = strrchr(host, ':');
653 int new_port = strtol(port_str + 1, &end, 10);
662 *res_proto = proto ? strdup(proto) : NULL;
664 *res_host = strdup(host);
668 *res_path = (path && *path) ? strdup(path) : NULL;
670 /* Undo the damage we did to the original string */
680 static void clear_cookies(struct openconnect_info *vpninfo)
682 struct vpn_option *opt, *next;
684 for (opt = vpninfo->cookies; opt; opt = next) {
691 vpninfo->cookies = NULL;
696 * = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
698 static int handle_redirect(struct openconnect_info *vpninfo)
700 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
701 /* New host. Tear down the existing connection and make a new one */
706 free(vpninfo->urlpath);
707 vpninfo->urlpath = NULL;
709 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
711 vpn_progress(vpninfo, PRG_ERR,
712 _("Failed to parse redirected URL '%s': %s\n"),
713 vpninfo->redirect_url, strerror(-ret));
714 free(vpninfo->redirect_url);
715 vpninfo->redirect_url = NULL;
719 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
720 free(vpninfo->hostname);
721 vpninfo->hostname = host;
722 vpninfo->port = port;
724 /* Kill the existing connection, and a new one will happen */
725 free(vpninfo->peer_addr);
726 vpninfo->peer_addr = NULL;
727 openconnect_close_https(vpninfo, 0);
728 clear_cookies(vpninfo);
732 free(vpninfo->redirect_url);
733 vpninfo->redirect_url = NULL;
736 } else if (strstr(vpninfo->redirect_url, "://")) {
737 vpn_progress(vpninfo, PRG_ERR,
738 _("Cannot follow redirection to non-https URL '%s'\n"),
739 vpninfo->redirect_url);
740 free(vpninfo->redirect_url);
741 vpninfo->redirect_url = NULL;
743 } else if (vpninfo->redirect_url[0] == '/') {
744 /* Absolute redirect within same host */
745 free(vpninfo->urlpath);
746 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
747 free(vpninfo->redirect_url);
748 vpninfo->redirect_url = NULL;
751 char *lastslash = NULL;
752 if (vpninfo->urlpath)
753 lastslash = strrchr(vpninfo->urlpath, '/');
755 free(vpninfo->urlpath);
756 vpninfo->urlpath = vpninfo->redirect_url;
757 vpninfo->redirect_url = NULL;
759 char *oldurl = vpninfo->urlpath;
761 vpninfo->urlpath = NULL;
762 if (asprintf(&vpninfo->urlpath, "%s/%s",
763 oldurl, vpninfo->redirect_url) == -1) {
765 vpn_progress(vpninfo, PRG_ERR,
766 _("Allocating new path for relative redirect failed: %s\n"),
771 free(vpninfo->redirect_url);
772 vpninfo->redirect_url = NULL;
780 * > 0, no cookie (user cancel)
781 * = 0, obtained cookie
783 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
785 struct vpn_option *opt;
786 struct oc_text_buf *buf;
787 char *form_buf = NULL;
789 char request_body[2048];
790 const char *request_body_type = NULL;
791 const char *method = "GET";
793 if (vpninfo->use_stoken) {
794 result = prepare_stoken(vpninfo);
804 if (openconnect_open_https(vpninfo)) {
805 vpn_progress(vpninfo, PRG_ERR,
806 _("Failed to open HTTPS connection to %s\n"),
812 * It would be nice to use cURL for this, but we really need to guarantee
813 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
814 * to have any way to let us provide our own socket read/write functions.
815 * We can only provide a socket _open_ function. Which would require having
816 * a socketpair() and servicing the "other" end of it.
818 * So we process the HTTP for ourselves...
821 buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
822 buf_append(buf, "Host: %s\r\n", vpninfo->hostname);
823 buf_append(buf, "User-Agent: %s\r\n", vpninfo->useragent);
824 buf_append(buf, "Accept: */*\r\n");
825 buf_append(buf, "Accept-Encoding: identity\r\n");
827 if (vpninfo->cookies) {
828 buf_append(buf, "Cookie: ");
829 for (opt = vpninfo->cookies; opt; opt = opt->next)
830 buf_append(buf, "%s=%s%s", opt->option,
831 opt->value, opt->next ? "; " : "\r\n");
833 if (request_body_type) {
834 buf_append(buf, "Content-Type: %s\r\n", request_body_type);
835 buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
837 buf_append(buf, "X-Transcend-Version: 1\r\n\r\n");
838 if (request_body_type)
839 buf_append(buf, "%s", request_body);
841 if (vpninfo->port == 443)
842 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
843 method, vpninfo->hostname,
844 vpninfo->urlpath ?: "");
846 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
847 method, vpninfo->hostname, vpninfo->port,
848 vpninfo->urlpath ?: "");
851 return buf_free(buf);
853 result = openconnect_SSL_write(vpninfo, buf->data, buf->pos);
858 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
860 /* We'll already have complained about whatever offended us */
864 if (result != 200 && vpninfo->redirect_url) {
866 result = handle_redirect(vpninfo);
872 if (!form_buf || result != 200) {
873 vpn_progress(vpninfo, PRG_ERR,
874 _("Unexpected %d result from server\n"),
879 if (vpninfo->csd_stuburl) {
880 /* This is the CSD stub script, which we now need to run */
881 result = run_csd_script(vpninfo, form_buf, buflen);
887 /* Now we'll be redirected to the waiturl */
890 if (strncmp(form_buf, "<?xml", 5)) {
891 /* Not XML? Perhaps it's HTML with a refresh... */
892 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
893 vpn_progress(vpninfo, PRG_INFO,
894 _("Refreshing %s after 1 second...\n"),
899 vpn_progress(vpninfo, PRG_ERR,
900 _("Unknown response from server\n"));
905 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
906 &method, &request_body_type);
916 /* A return value of 2 means the XML form indicated
917 success. We _should_ have a cookie... */
919 for (opt = vpninfo->cookies; opt; opt = opt->next) {
921 if (!strcmp(opt->option, "webvpn"))
922 vpninfo->cookie = opt->value;
923 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
924 char *tok = opt->value;
925 char *bu = NULL, *fu = NULL, *sha = NULL;
928 if (tok != opt->value)
931 if (!strncmp(tok, "bu:", 3))
933 else if (!strncmp(tok, "fu:", 3))
935 else if (!strncmp(tok, "fh:", 3)) {
936 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
941 } while ((tok = strchr(tok, '&')));
944 fetch_config(vpninfo, bu, fu, sha);
947 if (vpninfo->csd_scriptname) {
948 unlink(vpninfo->csd_scriptname);
949 free(vpninfo->csd_scriptname);
950 vpninfo->csd_scriptname = NULL;
955 char *openconnect_create_useragent(const char *base)
959 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
965 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
966 char *buf, size_t len)
974 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
975 if (buf[i] == '\n') {
977 if (i && buf[i-1] == '\r') {
994 static int proxy_write(struct openconnect_info *vpninfo, int fd,
995 unsigned char *buf, size_t len)
999 for (count = 0; count < len; ) {
1000 fd_set rd_set, wr_set;
1006 FD_SET(fd, &wr_set);
1007 if (vpninfo->cancel_fd != -1) {
1008 FD_SET(vpninfo->cancel_fd, &rd_set);
1009 if (vpninfo->cancel_fd > fd)
1010 maxfd = vpninfo->cancel_fd;
1013 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
1014 if (vpninfo->cancel_fd != -1 &&
1015 FD_ISSET(vpninfo->cancel_fd, &rd_set))
1018 /* Not that this should ever be able to happen... */
1019 if (!FD_ISSET(fd, &wr_set))
1022 i = write(fd, buf + count, len - count);
1031 static int proxy_read(struct openconnect_info *vpninfo, int fd,
1032 unsigned char *buf, size_t len)
1036 for (count = 0; count < len; ) {
1042 FD_SET(fd, &rd_set);
1043 if (vpninfo->cancel_fd != -1) {
1044 FD_SET(vpninfo->cancel_fd, &rd_set);
1045 if (vpninfo->cancel_fd > fd)
1046 maxfd = vpninfo->cancel_fd;
1049 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
1050 if (vpninfo->cancel_fd != -1 &&
1051 FD_ISSET(vpninfo->cancel_fd, &rd_set))
1054 /* Not that this should ever be able to happen... */
1055 if (!FD_ISSET(fd, &rd_set))
1058 i = read(fd, buf + count, len - count);
1067 static const char *socks_errors[] = {
1068 N_("request granted"),
1069 N_("general failure"),
1070 N_("connection not allowed by ruleset"),
1071 N_("network unreachable"),
1072 N_("host unreachable"),
1073 N_("connection refused by destination host"),
1075 N_("command not supported / protocol error"),
1076 N_("address type not supported")
1079 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1081 unsigned char buf[1024];
1084 buf[0] = 5; /* SOCKS version */
1085 buf[1] = 1; /* # auth methods */
1086 buf[2] = 0; /* No auth supported */
1088 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1089 vpn_progress(vpninfo, PRG_ERR,
1090 _("Error writing auth request to SOCKS proxy: %s\n"),
1095 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1096 vpn_progress(vpninfo, PRG_ERR,
1097 _("Error reading auth response from SOCKS proxy: %s\n"),
1102 vpn_progress(vpninfo, PRG_ERR,
1103 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1109 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1110 vpn_progress(vpninfo, PRG_ERR,
1111 _("SOCKS proxy error %02x: %s\n"),
1112 buf[1], _(socks_errors[buf[1]]));
1114 vpn_progress(vpninfo, PRG_ERR,
1115 _("SOCKS proxy error %02x\n"),
1120 vpn_progress(vpninfo, PRG_INFO,
1121 _("Requesting SOCKS proxy connection to %s:%d\n"),
1122 vpninfo->hostname, vpninfo->port);
1124 buf[0] = 5; /* SOCKS version */
1125 buf[1] = 1; /* CONNECT */
1126 buf[2] = 0; /* Reserved */
1127 buf[3] = 3; /* Address type is domain name */
1128 buf[4] = strlen(vpninfo->hostname);
1129 strcpy((char *)buf + 5, vpninfo->hostname);
1130 i = strlen(vpninfo->hostname) + 5;
1131 buf[i++] = vpninfo->port >> 8;
1132 buf[i++] = vpninfo->port & 0xff;
1134 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1135 vpn_progress(vpninfo, PRG_ERR,
1136 _("Error writing connect request to SOCKS proxy: %s\n"),
1140 /* Read 5 bytes -- up to and including the first byte of the returned
1141 address (which might be the length byte of a domain name) */
1142 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1143 vpn_progress(vpninfo, PRG_ERR,
1144 _("Error reading connect response from SOCKS proxy: %s\n"),
1149 vpn_progress(vpninfo, PRG_ERR,
1150 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1157 /* Connect responses contain an address */
1159 case 1: /* Legacy IP */
1162 case 3: /* Domain name */
1169 vpn_progress(vpninfo, PRG_ERR,
1170 _("Unexpected address type %02x in SOCKS connect response\n"),
1175 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1176 vpn_progress(vpninfo, PRG_ERR,
1177 _("Error reading connect response from SOCKS proxy: %s\n"),
1184 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1186 char buf[MAX_BUF_LEN];
1187 struct oc_text_buf *reqbuf;
1190 reqbuf = buf_alloc();
1191 buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1192 buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
1193 buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
1194 buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
1195 buf_append(reqbuf, "Connection: keep-alive\r\n");
1196 buf_append(reqbuf, "Accept-Encoding: identity\r\n");
1197 buf_append(reqbuf, "\r\n");
1199 if (buf_error(reqbuf))
1200 return buf_free(reqbuf);
1202 vpn_progress(vpninfo, PRG_INFO,
1203 _("Requesting HTTP proxy connection to %s:%d\n"),
1204 vpninfo->hostname, vpninfo->port);
1206 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)reqbuf->data, reqbuf->pos);
1210 vpn_progress(vpninfo, PRG_ERR,
1211 _("Sending proxy request failed: %s\n"),
1216 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1217 vpn_progress(vpninfo, PRG_ERR,
1218 _("Error fetching proxy response\n"));
1222 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1223 buf[8] != ' ' || !(result = atoi(buf+9))) {
1224 vpn_progress(vpninfo, PRG_ERR,
1225 _("Failed to parse proxy response '%s'\n"), buf);
1229 if (result != 200) {
1230 vpn_progress(vpninfo, PRG_ERR,
1231 _("Proxy CONNECT request failed: %s\n"), buf);
1235 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1237 vpn_progress(vpninfo, PRG_ERR,
1238 _("Failed to read proxy response\n"));
1241 vpn_progress(vpninfo, PRG_ERR,
1242 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1249 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1251 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1252 return process_http_proxy(vpninfo, ssl_sock);
1254 if (!strcmp(vpninfo->proxy_type, "socks") ||
1255 !strcmp(vpninfo->proxy_type, "socks5"))
1256 return process_socks_proxy(vpninfo, ssl_sock);
1258 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1259 vpninfo->proxy_type);
1263 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1271 free(vpninfo->proxy_type);
1272 vpninfo->proxy_type = NULL;
1273 free(vpninfo->proxy);
1274 vpninfo->proxy = NULL;
1276 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1277 &vpninfo->proxy_port, NULL, 80);
1281 if (vpninfo->proxy_type &&
1282 strcmp(vpninfo->proxy_type, "http") &&
1283 strcmp(vpninfo->proxy_type, "socks") &&
1284 strcmp(vpninfo->proxy_type, "socks5")) {
1285 vpn_progress(vpninfo, PRG_ERR,
1286 _("Only http or socks(5) proxies supported\n"));
1287 free(vpninfo->proxy_type);
1288 vpninfo->proxy_type = NULL;
1289 free(vpninfo->proxy);
1290 vpninfo->proxy = NULL;