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 void add_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
428 struct vpn_option *opt;
430 buf_append(buf, "Host: %s\r\n", vpninfo->hostname);
431 buf_append(buf, "User-Agent: %s\r\n", vpninfo->useragent);
432 buf_append(buf, "Accept: */*\r\n");
433 buf_append(buf, "Accept-Encoding: identity\r\n");
435 if (vpninfo->cookies) {
436 buf_append(buf, "Cookie: ");
437 for (opt = vpninfo->cookies; opt; opt = opt->next)
438 buf_append(buf, "%s=%s%s", opt->option,
439 opt->value, opt->next ? "; " : "\r\n");
441 buf_append(buf, "X-Transcend-Version: 1\r\n");
444 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
447 struct oc_text_buf *buf;
448 char *config_buf = NULL;
450 unsigned char local_sha1_bin[SHA1_SIZE];
451 char local_sha1_ascii[(SHA1_SIZE * 2)+1];
454 if (openconnect_open_https(vpninfo)) {
455 vpn_progress(vpninfo, PRG_ERR,
456 _("Failed to open HTTPS connection to %s\n"),
462 buf_append(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
463 add_common_headers(vpninfo, buf);
464 buf_append(buf, "\r\n");
467 return buf_free(buf);
469 if (openconnect_SSL_write(vpninfo, buf->data, buf->pos) != buf->pos) {
470 vpn_progress(vpninfo, PRG_ERR,
471 _("Failed to send GET request for new config\n"));
477 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
479 /* We'll already have complained about whatever offended us */
488 openconnect_sha1(local_sha1_bin, config_buf, buflen);
490 for (i = 0; i < SHA1_SIZE; i++)
491 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
493 if (strcasecmp(server_sha1, local_sha1_ascii)) {
494 vpn_progress(vpninfo, PRG_ERR,
495 _("Downloaded config file did not match intended SHA1\n"));
500 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
505 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
510 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
511 vpn_progress(vpninfo, PRG_ERR,
512 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
513 "This facility is disabled by default for security reasons, so you may wish to enable it."));
518 vpn_progress(vpninfo, PRG_INFO,
519 _("Trying to run Linux CSD trojan script."));
522 sprintf(fname, "/tmp/csdXXXXXX");
526 vpn_progress(vpninfo, PRG_ERR,
527 _("Failed to open temporary CSD script file: %s\n"),
532 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
534 vpn_progress(vpninfo, PRG_ERR,
535 _("Failed to write temporary CSD script file: %s\n"),
543 char scertbuf[MD5_SIZE * 2 + 1];
544 char ccertbuf[MD5_SIZE * 2 + 1];
548 if (vpninfo->uid_csd_given && vpninfo->uid_csd != getuid()) {
551 if (setuid(vpninfo->uid_csd)) {
552 fprintf(stderr, _("Failed to set uid %ld\n"),
553 (long)vpninfo->uid_csd);
556 if (!(pw = getpwuid(vpninfo->uid_csd))) {
557 fprintf(stderr, _("Invalid user uid=%ld\n"),
558 (long)vpninfo->uid_csd);
561 setenv("HOME", pw->pw_dir, 1);
562 if (chdir(pw->pw_dir)) {
563 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
564 pw->pw_dir, strerror(errno));
568 if (getuid() == 0 && !vpninfo->csd_wrapper) {
569 fprintf(stderr, _("Warning: you are running insecure "
570 "CSD code with root privileges\n"
571 "\t Use command line option \"--csd-user\"\n"));
573 if (vpninfo->uid_csd_given == 2) {
574 /* The NM tool really needs not to get spurious output
575 on stdout, which the CSD trojan spews. */
578 if (vpninfo->csd_wrapper)
579 csd_argv[i++] = vpninfo->csd_wrapper;
580 csd_argv[i++] = fname;
581 csd_argv[i++]= (char *)"-ticket";
582 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
584 csd_argv[i++]= (char *)"-stub";
585 csd_argv[i++]= (char *)"\"0\"";
586 csd_argv[i++]= (char *)"-group";
587 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
590 openconnect_local_cert_md5(vpninfo, ccertbuf);
592 get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
593 csd_argv[i++]= (char *)"-certhash";
594 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
597 csd_argv[i++]= (char *)"-url";
598 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
601 csd_argv[i++]= (char *)"-langselen";
602 csd_argv[i++] = NULL;
604 execv(csd_argv[0], csd_argv);
605 vpn_progress(vpninfo, PRG_ERR,
606 _("Failed to exec CSD script %s\n"), csd_argv[0]);
610 free(vpninfo->csd_stuburl);
611 vpninfo->csd_stuburl = NULL;
612 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
613 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
614 vpninfo->csd_waiturl = NULL;
615 vpninfo->csd_scriptname = strdup(fname);
617 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
622 int internal_parse_url(char *url, char **res_proto, char **res_host,
623 int *res_port, char **res_path, int default_port)
626 char *host, *path, *port_str;
629 host = strstr(url, "://");
634 if (!strcasecmp(proto, "https"))
636 else if (!strcasecmp(proto, "http"))
638 else if (!strcasecmp(proto, "socks") ||
639 !strcasecmp(proto, "socks4") ||
640 !strcasecmp(proto, "socks5"))
643 return -EPROTONOSUPPORT;
653 path = strchr(host, '/');
657 port_str = strrchr(host, ':');
660 int new_port = strtol(port_str + 1, &end, 10);
669 *res_proto = proto ? strdup(proto) : NULL;
671 *res_host = strdup(host);
675 *res_path = (path && *path) ? strdup(path) : NULL;
677 /* Undo the damage we did to the original string */
687 static void clear_cookies(struct openconnect_info *vpninfo)
689 struct vpn_option *opt, *next;
691 for (opt = vpninfo->cookies; opt; opt = next) {
698 vpninfo->cookies = NULL;
703 * = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
705 static int handle_redirect(struct openconnect_info *vpninfo)
707 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
708 /* New host. Tear down the existing connection and make a new one */
713 free(vpninfo->urlpath);
714 vpninfo->urlpath = NULL;
716 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
718 vpn_progress(vpninfo, PRG_ERR,
719 _("Failed to parse redirected URL '%s': %s\n"),
720 vpninfo->redirect_url, strerror(-ret));
721 free(vpninfo->redirect_url);
722 vpninfo->redirect_url = NULL;
726 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
727 free(vpninfo->hostname);
728 vpninfo->hostname = host;
729 vpninfo->port = port;
731 /* Kill the existing connection, and a new one will happen */
732 free(vpninfo->peer_addr);
733 vpninfo->peer_addr = NULL;
734 openconnect_close_https(vpninfo, 0);
735 clear_cookies(vpninfo);
739 free(vpninfo->redirect_url);
740 vpninfo->redirect_url = NULL;
743 } else if (strstr(vpninfo->redirect_url, "://")) {
744 vpn_progress(vpninfo, PRG_ERR,
745 _("Cannot follow redirection to non-https URL '%s'\n"),
746 vpninfo->redirect_url);
747 free(vpninfo->redirect_url);
748 vpninfo->redirect_url = NULL;
750 } else if (vpninfo->redirect_url[0] == '/') {
751 /* Absolute redirect within same host */
752 free(vpninfo->urlpath);
753 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
754 free(vpninfo->redirect_url);
755 vpninfo->redirect_url = NULL;
758 char *lastslash = NULL;
759 if (vpninfo->urlpath)
760 lastslash = strrchr(vpninfo->urlpath, '/');
762 free(vpninfo->urlpath);
763 vpninfo->urlpath = vpninfo->redirect_url;
764 vpninfo->redirect_url = NULL;
766 char *oldurl = vpninfo->urlpath;
768 vpninfo->urlpath = NULL;
769 if (asprintf(&vpninfo->urlpath, "%s/%s",
770 oldurl, vpninfo->redirect_url) == -1) {
772 vpn_progress(vpninfo, PRG_ERR,
773 _("Allocating new path for relative redirect failed: %s\n"),
778 free(vpninfo->redirect_url);
779 vpninfo->redirect_url = NULL;
786 * method: GET or POST
787 * vpninfo->hostname: Host DNS name
788 * vpninfo->port: TCP port, typically 443
789 * vpninfo->urlpath: Relative path, e.g. /+webvpn+/foo.html
790 * request_body_type: Content type for a POST (e.g. text/html). Can be NULL.
791 * request_body: POST content
792 * form_buf: Callee-allocated buffer for server content
796 * >=0, on success, indicating the length of the data in *form_buf
798 static int do_https_request(struct openconnect_info *vpninfo, const char *method,
799 const char *request_body_type, const char *request_body,
802 struct oc_text_buf *buf;
810 if (openconnect_open_https(vpninfo)) {
811 vpn_progress(vpninfo, PRG_ERR,
812 _("Failed to open HTTPS connection to %s\n"),
818 * It would be nice to use cURL for this, but we really need to guarantee
819 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
820 * to have any way to let us provide our own socket read/write functions.
821 * We can only provide a socket _open_ function. Which would require having
822 * a socketpair() and servicing the "other" end of it.
824 * So we process the HTTP for ourselves...
827 buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
828 add_common_headers(vpninfo, buf);
830 if (request_body_type) {
831 buf_append(buf, "Content-Type: %s\r\n", request_body_type);
832 buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
834 buf_append(buf, "\r\n");
836 if (request_body_type)
837 buf_append(buf, "%s", request_body);
839 if (vpninfo->port == 443)
840 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
841 method, vpninfo->hostname,
842 vpninfo->urlpath ?: "");
844 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
845 method, vpninfo->hostname, vpninfo->port,
846 vpninfo->urlpath ?: "");
849 return buf_free(buf);
851 result = openconnect_SSL_write(vpninfo, buf->data, buf->pos);
856 buflen = process_http_response(vpninfo, &result, NULL, form_buf);
858 /* We'll already have complained about whatever offended us */
862 if (result != 200 && vpninfo->redirect_url) {
863 result = handle_redirect(vpninfo);
868 if (!*form_buf || result != 200) {
869 vpn_progress(vpninfo, PRG_ERR,
870 _("Unexpected %d result from server\n"),
886 * > 0, no cookie (user cancel)
887 * = 0, obtained cookie
889 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
891 struct vpn_option *opt;
892 char *form_buf = NULL;
893 struct oc_auth_form *form;
895 char request_body[2048];
896 const char *request_body_type = NULL;
897 const char *method = "GET";
899 if (vpninfo->use_stoken) {
900 result = prepare_stoken(vpninfo);
906 buflen = do_https_request(vpninfo, method, request_body_type, request_body, &form_buf);
910 if (vpninfo->csd_stuburl) {
911 /* This is the CSD stub script, which we now need to run */
912 result = run_csd_script(vpninfo, form_buf, buflen);
918 /* Now we'll be redirected to the waiturl */
921 if (strncmp(form_buf, "<?xml", 5)) {
922 /* Not XML? Perhaps it's HTML with a refresh... */
923 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
924 vpn_progress(vpninfo, PRG_INFO,
925 _("Refreshing %s after 1 second...\n"),
930 vpn_progress(vpninfo, PRG_ERR,
931 _("Unknown response from server\n"));
935 result = parse_xml_response(vpninfo, form_buf, &form);
941 result = handle_auth_form(vpninfo, form, request_body, sizeof(request_body),
942 &method, &request_body_type, 0);
943 free_auth_form(form);
949 result = handle_redirect(vpninfo);
958 /* A return value of 2 means the XML form indicated
959 success. We _should_ have a cookie... */
961 for (opt = vpninfo->cookies; opt; opt = opt->next) {
963 if (!strcmp(opt->option, "webvpn"))
964 vpninfo->cookie = opt->value;
965 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
966 char *tok = opt->value;
967 char *bu = NULL, *fu = NULL, *sha = NULL;
970 if (tok != opt->value)
973 if (!strncmp(tok, "bu:", 3))
975 else if (!strncmp(tok, "fu:", 3))
977 else if (!strncmp(tok, "fh:", 3)) {
978 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
983 } while ((tok = strchr(tok, '&')));
986 fetch_config(vpninfo, bu, fu, sha);
989 if (vpninfo->csd_scriptname) {
990 unlink(vpninfo->csd_scriptname);
991 free(vpninfo->csd_scriptname);
992 vpninfo->csd_scriptname = NULL;
997 char *openconnect_create_useragent(const char *base)
1001 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
1007 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
1008 char *buf, size_t len)
1016 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
1017 if (buf[i] == '\n') {
1019 if (i && buf[i-1] == '\r') {
1036 static int proxy_write(struct openconnect_info *vpninfo, int fd,
1037 unsigned char *buf, size_t len)
1041 for (count = 0; count < len; ) {
1042 fd_set rd_set, wr_set;
1048 FD_SET(fd, &wr_set);
1049 if (vpninfo->cancel_fd != -1) {
1050 FD_SET(vpninfo->cancel_fd, &rd_set);
1051 if (vpninfo->cancel_fd > fd)
1052 maxfd = vpninfo->cancel_fd;
1055 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
1056 if (vpninfo->cancel_fd != -1 &&
1057 FD_ISSET(vpninfo->cancel_fd, &rd_set))
1060 /* Not that this should ever be able to happen... */
1061 if (!FD_ISSET(fd, &wr_set))
1064 i = write(fd, buf + count, len - count);
1073 static int proxy_read(struct openconnect_info *vpninfo, int fd,
1074 unsigned char *buf, size_t len)
1078 for (count = 0; count < len; ) {
1084 FD_SET(fd, &rd_set);
1085 if (vpninfo->cancel_fd != -1) {
1086 FD_SET(vpninfo->cancel_fd, &rd_set);
1087 if (vpninfo->cancel_fd > fd)
1088 maxfd = vpninfo->cancel_fd;
1091 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
1092 if (vpninfo->cancel_fd != -1 &&
1093 FD_ISSET(vpninfo->cancel_fd, &rd_set))
1096 /* Not that this should ever be able to happen... */
1097 if (!FD_ISSET(fd, &rd_set))
1100 i = read(fd, buf + count, len - count);
1109 static const char *socks_errors[] = {
1110 N_("request granted"),
1111 N_("general failure"),
1112 N_("connection not allowed by ruleset"),
1113 N_("network unreachable"),
1114 N_("host unreachable"),
1115 N_("connection refused by destination host"),
1117 N_("command not supported / protocol error"),
1118 N_("address type not supported")
1121 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1123 unsigned char buf[1024];
1126 buf[0] = 5; /* SOCKS version */
1127 buf[1] = 1; /* # auth methods */
1128 buf[2] = 0; /* No auth supported */
1130 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1131 vpn_progress(vpninfo, PRG_ERR,
1132 _("Error writing auth request to SOCKS proxy: %s\n"),
1137 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1138 vpn_progress(vpninfo, PRG_ERR,
1139 _("Error reading auth response from SOCKS proxy: %s\n"),
1144 vpn_progress(vpninfo, PRG_ERR,
1145 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1151 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1152 vpn_progress(vpninfo, PRG_ERR,
1153 _("SOCKS proxy error %02x: %s\n"),
1154 buf[1], _(socks_errors[buf[1]]));
1156 vpn_progress(vpninfo, PRG_ERR,
1157 _("SOCKS proxy error %02x\n"),
1162 vpn_progress(vpninfo, PRG_INFO,
1163 _("Requesting SOCKS proxy connection to %s:%d\n"),
1164 vpninfo->hostname, vpninfo->port);
1166 buf[0] = 5; /* SOCKS version */
1167 buf[1] = 1; /* CONNECT */
1168 buf[2] = 0; /* Reserved */
1169 buf[3] = 3; /* Address type is domain name */
1170 buf[4] = strlen(vpninfo->hostname);
1171 strcpy((char *)buf + 5, vpninfo->hostname);
1172 i = strlen(vpninfo->hostname) + 5;
1173 buf[i++] = vpninfo->port >> 8;
1174 buf[i++] = vpninfo->port & 0xff;
1176 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1177 vpn_progress(vpninfo, PRG_ERR,
1178 _("Error writing connect request to SOCKS proxy: %s\n"),
1182 /* Read 5 bytes -- up to and including the first byte of the returned
1183 address (which might be the length byte of a domain name) */
1184 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1185 vpn_progress(vpninfo, PRG_ERR,
1186 _("Error reading connect response from SOCKS proxy: %s\n"),
1191 vpn_progress(vpninfo, PRG_ERR,
1192 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1199 /* Connect responses contain an address */
1201 case 1: /* Legacy IP */
1204 case 3: /* Domain name */
1211 vpn_progress(vpninfo, PRG_ERR,
1212 _("Unexpected address type %02x in SOCKS connect response\n"),
1217 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1218 vpn_progress(vpninfo, PRG_ERR,
1219 _("Error reading connect response from SOCKS proxy: %s\n"),
1226 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1228 char buf[MAX_BUF_LEN];
1229 struct oc_text_buf *reqbuf;
1232 reqbuf = buf_alloc();
1233 buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1234 buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
1235 buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
1236 buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
1237 buf_append(reqbuf, "Connection: keep-alive\r\n");
1238 buf_append(reqbuf, "Accept-Encoding: identity\r\n");
1239 buf_append(reqbuf, "\r\n");
1241 if (buf_error(reqbuf))
1242 return buf_free(reqbuf);
1244 vpn_progress(vpninfo, PRG_INFO,
1245 _("Requesting HTTP proxy connection to %s:%d\n"),
1246 vpninfo->hostname, vpninfo->port);
1248 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)reqbuf->data, reqbuf->pos);
1252 vpn_progress(vpninfo, PRG_ERR,
1253 _("Sending proxy request failed: %s\n"),
1258 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1259 vpn_progress(vpninfo, PRG_ERR,
1260 _("Error fetching proxy response\n"));
1264 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1265 buf[8] != ' ' || !(result = atoi(buf+9))) {
1266 vpn_progress(vpninfo, PRG_ERR,
1267 _("Failed to parse proxy response '%s'\n"), buf);
1271 if (result != 200) {
1272 vpn_progress(vpninfo, PRG_ERR,
1273 _("Proxy CONNECT request failed: %s\n"), buf);
1277 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1279 vpn_progress(vpninfo, PRG_ERR,
1280 _("Failed to read proxy response\n"));
1283 vpn_progress(vpninfo, PRG_ERR,
1284 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1291 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1293 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1294 return process_http_proxy(vpninfo, ssl_sock);
1296 if (!strcmp(vpninfo->proxy_type, "socks") ||
1297 !strcmp(vpninfo->proxy_type, "socks5"))
1298 return process_socks_proxy(vpninfo, ssl_sock);
1300 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1301 vpninfo->proxy_type);
1305 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1313 free(vpninfo->proxy_type);
1314 vpninfo->proxy_type = NULL;
1315 free(vpninfo->proxy);
1316 vpninfo->proxy = NULL;
1318 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1319 &vpninfo->proxy_port, NULL, 80);
1323 if (vpninfo->proxy_type &&
1324 strcmp(vpninfo->proxy_type, "http") &&
1325 strcmp(vpninfo->proxy_type, "socks") &&
1326 strcmp(vpninfo->proxy_type, "socks5")) {
1327 vpn_progress(vpninfo, PRG_ERR,
1328 _("Only http or socks(5) proxies supported\n"));
1329 free(vpninfo->proxy_type);
1330 vpninfo->proxy_type = NULL;
1331 free(vpninfo->proxy);
1332 vpninfo->proxy = NULL;