2 * OpenConnect (SSL + DTLS) VPN client
4 * Copyright © 2008-2012 Intel Corporation.
5 * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
7 * Author: David Woodhouse <dwmw2@infradead.org>
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * version 2.1, as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to:
21 * Free Software Foundation, Inc.
22 * 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301 USA
34 #include <sys/types.h>
39 #include "openconnect-internal.h"
41 static int proxy_write(struct openconnect_info *vpninfo, int fd,
42 unsigned char *buf, size_t len);
43 static int proxy_read(struct openconnect_info *vpninfo, int fd,
44 unsigned char *buf, size_t len);
46 #define MAX_BUF_LEN 131072
48 * We didn't really want to have to do this for ourselves -- one might have
49 * thought that it would be available in a library somewhere. But neither
50 * cURL nor Neon have reliable cross-platform ways of either using a cert
51 * from the TPM, or just reading from / writing to a transport which is
52 * provided by their caller.
55 static int http_add_cookie(struct openconnect_info *vpninfo,
56 const char *option, const char *value)
58 struct vpn_option *new, **this;
61 new = malloc(sizeof(*new));
63 vpn_progress(vpninfo, PRG_ERR,
64 _("No memory for allocating cookies\n"));
68 new->option = strdup(option);
69 new->value = strdup(value);
70 if (!new->option || !new->value) {
77 /* Kill cookie; don't replace it */
80 for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
81 if (!strcmp(option, (*this)->option)) {
82 /* Replace existing cookie */
84 new->next = (*this)->next;
88 free((*this)->option);
102 #define BODY_HTTP10 -1
103 #define BODY_CHUNKED -2
105 static int process_http_response(struct openconnect_info *vpninfo, int *result,
106 int (*header_cb)(struct openconnect_info *, char *, char *),
109 char buf[MAX_BUF_LEN];
111 int bodylen = BODY_HTTP10;
117 if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
118 vpn_progress(vpninfo, PRG_ERR,
119 _("Error fetching HTTPS response\n"));
123 if (!strncmp(buf, "HTTP/1.0 ", 9))
126 if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
127 vpn_progress(vpninfo, PRG_ERR,
128 _("Failed to parse HTTP response '%s'\n"), buf);
132 vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
133 _("Got HTTP response: %s\n"), buf);
136 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
140 vpn_progress(vpninfo, PRG_ERR,
141 _("Error processing HTTP response\n"));
144 colon = strchr(buf, ':');
146 vpn_progress(vpninfo, PRG_ERR,
147 _("Ignoring unknown HTTP response line '%s'\n"), buf);
154 /* Handle Set-Cookie first so that we can avoid printing the
155 webvpn cookie in the verbose debug output */
156 if (!strcasecmp(buf, "Set-Cookie")) {
157 char *semicolon = strchr(colon, ';');
158 const char *print_equals;
159 char *equals = strchr(colon, '=');
166 vpn_progress(vpninfo, PRG_ERR,
167 _("Invalid cookie offered: %s\n"), buf);
172 print_equals = equals;
173 /* Don't print the webvpn cookie unless it's empty; we don't
174 want people posting it in public with debugging output */
175 if (!strcmp(colon, "webvpn") && *equals)
176 print_equals = _("<elided>");
177 vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
178 buf, colon, print_equals, semicolon?";":"",
179 semicolon?(semicolon+1):"");
181 /* The server tends to ask for the username and password as
182 usual, even if we've already failed because it didn't like
183 our cert. Thankfully it does give us this hint... */
184 if (!strcmp(colon, "ClientCertAuthFailed"))
185 vpn_progress(vpninfo, PRG_ERR,
186 _("SSL certificate authentication failed\n"));
188 ret = http_add_cookie(vpninfo, colon, equals);
192 vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
195 if (!strcasecmp(buf, "Connection")) {
196 if (!strcasecmp(colon, "Close"))
199 /* This might seem reasonable, but in fact it breaks
200 certificate authentication with some servers. If
201 they give an HTTP/1.0 response, even if they
202 explicitly give a Connection: Keep-Alive header,
203 just close the connection. */
204 else if (!strcasecmp(colon, "Keep-Alive"))
208 if (!strcasecmp(buf, "Location")) {
209 vpninfo->redirect_url = strdup(colon);
210 if (!vpninfo->redirect_url)
213 if (!strcasecmp(buf, "Content-Length")) {
214 bodylen = atoi(colon);
216 vpn_progress(vpninfo, PRG_ERR,
217 _("Response body has negative size (%d)\n"),
222 if (!strcasecmp(buf, "Transfer-Encoding")) {
223 if (!strcasecmp(colon, "chunked"))
224 bodylen = BODY_CHUNKED;
226 vpn_progress(vpninfo, PRG_ERR,
227 _("Unknown Transfer-Encoding: %s\n"),
232 if (header_cb && !strncmp(buf, "X-", 2))
233 header_cb(vpninfo, buf, colon);
236 /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
240 /* Now the body, if there is one */
241 vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"),
242 bodylen==BODY_HTTP10?"http 1.0" :
243 bodylen==BODY_CHUNKED?"chunked" : "length: ",
246 /* If we were given Content-Length, it's nice and easy... */
248 body = malloc(bodylen + 1);
251 while (done < bodylen) {
252 i = openconnect_SSL_read(vpninfo, body + done, bodylen - done);
254 vpn_progress(vpninfo, PRG_ERR,
255 _("Error reading HTTP response body\n"));
261 } else if (bodylen == BODY_CHUNKED) {
262 /* ... else, chunked */
263 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
264 int chunklen, lastchunk = 0;
267 vpn_progress(vpninfo, PRG_ERR,
268 _("Error fetching chunk header\n"));
271 chunklen = strtol(buf, NULL, 16);
276 body = realloc(body, done + chunklen + 1);
280 i = openconnect_SSL_read(vpninfo, body + done, chunklen);
282 vpn_progress(vpninfo, PRG_ERR,
283 _("Error reading HTTP response body\n"));
291 if ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
293 vpn_progress(vpninfo, PRG_ERR,
294 _("Error fetching HTTP response body\n"));
296 vpn_progress(vpninfo, PRG_ERR,
297 _("Error in chunked decoding. Expected '', got: '%s'"),
307 } else if (bodylen == BODY_HTTP10) {
309 vpn_progress(vpninfo, PRG_ERR,
310 _("Cannot receive HTTP 1.0 body without closing connection\n"));
314 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
316 body = realloc(body, done + 16384);
319 i = openconnect_SSL_read(vpninfo, body + done, 16384);
328 /* Connection closed. Reduce allocation to just what we need */
329 body = realloc(body, done + 1);
337 if (closeconn || vpninfo->no_http_keepalive)
338 openconnect_close_https(vpninfo, 0);
346 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
349 struct vpn_option *opt;
350 char buf[MAX_BUF_LEN];
351 char *config_buf = NULL;
353 unsigned char local_sha1_bin[SHA1_SIZE];
354 char local_sha1_ascii[(SHA1_SIZE * 2)+1];
357 if (openconnect_open_https(vpninfo)) {
358 vpn_progress(vpninfo, PRG_ERR,
359 _("Failed to open HTTPS connection to %s\n"),
364 sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
365 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
366 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
367 sprintf(buf + strlen(buf), "Accept: */*\r\n");
368 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
370 if (vpninfo->cookies) {
371 sprintf(buf + strlen(buf), "Cookie: ");
372 for (opt = vpninfo->cookies; opt; opt = opt->next)
373 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
374 opt->value, opt->next ? "; " : "\r\n");
376 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
378 if (openconnect_SSL_write(vpninfo, buf, strlen(buf)) != strlen(buf)) {
379 vpn_progress(vpninfo, PRG_ERR,
380 _("Failed to send GET request for new config\n"));
384 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
386 /* We'll already have complained about whatever offended us */
395 openconnect_sha1(local_sha1_bin, config_buf, buflen);
397 for (i = 0; i < SHA1_SIZE; i++)
398 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
400 if (strcasecmp(server_sha1, local_sha1_ascii)) {
401 vpn_progress(vpninfo, PRG_ERR,
402 _("Downloaded config file did not match intended SHA1\n"));
407 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
412 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
417 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
418 vpn_progress(vpninfo, PRG_ERR,
419 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
420 "This facility is disabled by default for security reasons, so you may wish to enable it."));
425 vpn_progress(vpninfo, PRG_INFO,
426 _("Trying to run Linux CSD trojan script."));
429 sprintf(fname, "/tmp/csdXXXXXX");
433 vpn_progress(vpninfo, PRG_ERR,
434 _("Failed to open temporary CSD script file: %s\n"),
439 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
441 vpn_progress(vpninfo, PRG_ERR,
442 _("Failed to write temporary CSD script file: %s\n"),
450 char scertbuf[MD5_SIZE * 2 + 1];
451 char ccertbuf[MD5_SIZE * 2 + 1];
455 if (vpninfo->uid_csd_given && vpninfo->uid_csd != getuid()) {
458 if (setuid(vpninfo->uid_csd)) {
459 fprintf(stderr, _("Failed to set uid %ld\n"),
460 (long)vpninfo->uid_csd);
463 if (!(pw = getpwuid(vpninfo->uid_csd))) {
464 fprintf(stderr, _("Invalid user uid=%ld\n"),
465 (long)vpninfo->uid_csd);
468 setenv("HOME", pw->pw_dir, 1);
469 if (chdir(pw->pw_dir)) {
470 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
471 pw->pw_dir, strerror(errno));
475 if (getuid() == 0 && !vpninfo->csd_wrapper) {
476 fprintf(stderr, _("Warning: you are running insecure "
477 "CSD code with root privileges\n"
478 "\t Use command line option \"--csd-user\"\n"));
480 if (vpninfo->uid_csd_given == 2) {
481 /* The NM tool really needs not to get spurious output
482 on stdout, which the CSD trojan spews. */
485 if (vpninfo->csd_wrapper)
486 csd_argv[i++] = vpninfo->csd_wrapper;
487 csd_argv[i++] = fname;
488 csd_argv[i++]= (char *)"-ticket";
489 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
491 csd_argv[i++]= (char *)"-stub";
492 csd_argv[i++]= (char *)"\"0\"";
493 csd_argv[i++]= (char *)"-group";
494 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
497 openconnect_local_cert_md5(vpninfo, ccertbuf);
499 get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
500 csd_argv[i++]= (char *)"-certhash";
501 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
504 csd_argv[i++]= (char *)"-url";
505 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
508 csd_argv[i++]= (char *)"-langselen";
509 csd_argv[i++] = NULL;
511 execv(csd_argv[0], csd_argv);
512 vpn_progress(vpninfo, PRG_ERR,
513 _("Failed to exec CSD script %s\n"), csd_argv[0]);
517 free(vpninfo->csd_stuburl);
518 vpninfo->csd_stuburl = NULL;
519 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
520 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
521 vpninfo->csd_waiturl = NULL;
522 vpninfo->csd_scriptname = strdup(fname);
524 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
529 #ifndef HAVE_STRCASESTR
530 static char *openconnect__strcasestr(const char *haystack, const char *needle)
532 int hlen = strlen(haystack);
533 int nlen = strlen(needle);
536 for (i = 0; i < hlen - nlen + 1; i++) {
537 for (j = 0; j < nlen; j++) {
538 if (tolower(haystack[i + j]) !=
543 return (char *)haystack + i;
547 #define strcasestr openconnect__strcasestr
551 int internal_parse_url(char *url, char **res_proto, char **res_host,
552 int *res_port, char **res_path, int default_port)
555 char *host, *path, *port_str;
558 host = strstr(url, "://");
563 if (!strcasecmp(proto, "https"))
565 else if (!strcasecmp(proto, "http"))
567 else if (!strcasecmp(proto, "socks") ||
568 !strcasecmp(proto, "socks4") ||
569 !strcasecmp(proto, "socks5"))
572 return -EPROTONOSUPPORT;
582 path = strchr(host, '/');
586 port_str = strrchr(host, ':');
589 int new_port = strtol(port_str + 1, &end, 10);
598 *res_proto = proto ? strdup(proto) : NULL;
600 *res_host = strdup(host);
604 *res_path = (path && *path) ? strdup(path) : NULL;
606 /* 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 (openconnect_open_https(vpninfo)) {
637 vpn_progress(vpninfo, PRG_ERR,
638 _("Failed to open HTTPS connection to %s\n"),
644 * It would be nice to use cURL for this, but we really need to guarantee
645 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
646 * to have any way to let us provide our own socket read/write functions.
647 * We can only provide a socket _open_ function. Which would require having
648 * a socketpair() and servicing the "other" end of it.
650 * So we process the HTTP for ourselves...
652 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
653 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
654 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
655 sprintf(buf + strlen(buf), "Accept: */*\r\n");
656 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
658 if (vpninfo->cookies) {
659 sprintf(buf + strlen(buf), "Cookie: ");
660 for (opt = vpninfo->cookies; opt; opt = opt->next)
661 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
662 opt->value, opt->next ? "; " : "\r\n");
664 if (request_body_type) {
665 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
667 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
668 strlen(request_body));
670 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
671 if (request_body_type)
672 sprintf(buf + strlen(buf), "%s", request_body);
674 if (vpninfo->port == 443)
675 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
676 method, vpninfo->hostname,
677 vpninfo->urlpath ?: "");
679 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
680 method, vpninfo->hostname, vpninfo->port,
681 vpninfo->urlpath ?: "");
683 result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
687 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
689 /* We'll already have complained about whatever offended us */
693 if (result != 200 && vpninfo->redirect_url) {
695 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
696 /* New host. Tear down the existing connection and make a new one */
701 free(vpninfo->urlpath);
702 vpninfo->urlpath = NULL;
704 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
706 vpn_progress(vpninfo, PRG_ERR,
707 _("Failed to parse redirected URL '%s': %s\n"),
708 vpninfo->redirect_url, strerror(-ret));
709 free(vpninfo->redirect_url);
710 vpninfo->redirect_url = NULL;
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 openconnect_close_https(vpninfo, 0);
725 for (opt = vpninfo->cookies; opt; opt = next) {
732 vpninfo->cookies = NULL;
736 free(vpninfo->redirect_url);
737 vpninfo->redirect_url = NULL;
740 } else if (strstr(vpninfo->redirect_url, "://")) {
741 vpn_progress(vpninfo, PRG_ERR,
742 _("Cannot follow redirection to non-https URL '%s'\n"),
743 vpninfo->redirect_url);
744 free(vpninfo->redirect_url);
745 vpninfo->redirect_url = NULL;
748 } else if (vpninfo->redirect_url[0] == '/') {
749 /* Absolute redirect within same host */
750 free(vpninfo->urlpath);
751 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
752 free(vpninfo->redirect_url);
753 vpninfo->redirect_url = NULL;
756 char *lastslash = NULL;
757 if (vpninfo->urlpath)
758 lastslash = strrchr(vpninfo->urlpath, '/');
760 free(vpninfo->urlpath);
761 vpninfo->urlpath = vpninfo->redirect_url;
762 vpninfo->redirect_url = NULL;
764 char *oldurl = vpninfo->urlpath;
766 vpninfo->urlpath = NULL;
767 if (asprintf(&vpninfo->urlpath, "%s/%s",
768 oldurl, vpninfo->redirect_url) == -1) {
770 vpn_progress(vpninfo, PRG_ERR,
771 _("Allocating new path for relative redirect failed: %s\n"),
776 free(vpninfo->redirect_url);
777 vpninfo->redirect_url = NULL;
782 if (!form_buf || result != 200) {
783 vpn_progress(vpninfo, PRG_ERR,
784 _("Unexpected %d result from server\n"),
789 if (vpninfo->csd_stuburl) {
790 /* This is the CSD stub script, which we now need to run */
791 result = run_csd_script(vpninfo, form_buf, buflen);
797 /* Now we'll be redirected to the waiturl */
800 if (strncmp(form_buf, "<?xml", 5)) {
801 /* Not XML? Perhaps it's HTML with a refresh... */
802 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
803 vpn_progress(vpninfo, PRG_INFO,
804 _("Refreshing %s after 1 second...\n"),
809 vpn_progress(vpninfo, PRG_ERR,
810 _("Unknown response from server\n"));
815 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
816 &method, &request_body_type);
826 /* A return value of 2 means the XML form indicated
827 success. We _should_ have a cookie... */
829 for (opt = vpninfo->cookies; opt; opt = opt->next) {
831 if (!strcmp(opt->option, "webvpn"))
832 vpninfo->cookie = opt->value;
833 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
834 char *tok = opt->value;
835 char *bu = NULL, *fu = NULL, *sha = NULL;
838 if (tok != opt->value)
841 if (!strncmp(tok, "bu:", 3))
843 else if (!strncmp(tok, "fu:", 3))
845 else if (!strncmp(tok, "fh:", 3)) {
846 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
851 } while ((tok = strchr(tok, '&')));
854 fetch_config(vpninfo, bu, fu, sha);
857 if (vpninfo->csd_scriptname) {
858 unlink(vpninfo->csd_scriptname);
859 free(vpninfo->csd_scriptname);
860 vpninfo->csd_scriptname = NULL;
865 char *openconnect_create_useragent(const char *base)
869 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
875 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
876 char *buf, size_t len)
884 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
885 if (buf[i] == '\n') {
887 if (i && buf[i-1] == '\r') {
904 static int proxy_write(struct openconnect_info *vpninfo, int fd,
905 unsigned char *buf, size_t len)
909 for (count = 0; count < len; ) {
910 fd_set rd_set, wr_set;
917 if (vpninfo->cancel_fd != -1) {
918 FD_SET(vpninfo->cancel_fd, &rd_set);
919 if (vpninfo->cancel_fd > fd)
920 maxfd = vpninfo->cancel_fd;
923 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
924 if (vpninfo->cancel_fd != -1 &&
925 FD_ISSET(vpninfo->cancel_fd, &rd_set))
928 /* Not that this should ever be able to happen... */
929 if (!FD_ISSET(fd, &wr_set))
932 i = write(fd, buf + count, len - count);
941 static int proxy_read(struct openconnect_info *vpninfo, int fd,
942 unsigned char *buf, size_t len)
946 for (count = 0; count < len; ) {
953 if (vpninfo->cancel_fd != -1) {
954 FD_SET(vpninfo->cancel_fd, &rd_set);
955 if (vpninfo->cancel_fd > fd)
956 maxfd = vpninfo->cancel_fd;
959 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
960 if (vpninfo->cancel_fd != -1 &&
961 FD_ISSET(vpninfo->cancel_fd, &rd_set))
964 /* Not that this should ever be able to happen... */
965 if (!FD_ISSET(fd, &rd_set))
968 i = read(fd, buf + count, len - count);
977 static const char *socks_errors[] = {
978 N_("request granted"),
979 N_("general failure"),
980 N_("connection not allowed by ruleset"),
981 N_("network unreachable"),
982 N_("host unreachable"),
983 N_("connection refused by destination host"),
985 N_("command not supported / protocol error"),
986 N_("address type not supported")
989 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
991 unsigned char buf[1024];
994 buf[0] = 5; /* SOCKS version */
995 buf[1] = 1; /* # auth methods */
996 buf[2] = 0; /* No auth supported */
998 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
999 vpn_progress(vpninfo, PRG_ERR,
1000 _("Error writing auth request to SOCKS proxy: %s\n"),
1005 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1006 vpn_progress(vpninfo, PRG_ERR,
1007 _("Error reading auth response from SOCKS proxy: %s\n"),
1012 vpn_progress(vpninfo, PRG_ERR,
1013 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1019 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1020 vpn_progress(vpninfo, PRG_ERR,
1021 _("SOCKS proxy error %02x: %s\n"),
1022 buf[1], _(socks_errors[buf[1]]));
1024 vpn_progress(vpninfo, PRG_ERR,
1025 _("SOCKS proxy error %02x\n"),
1030 vpn_progress(vpninfo, PRG_INFO,
1031 _("Requesting SOCKS proxy connection to %s:%d\n"),
1032 vpninfo->hostname, vpninfo->port);
1034 buf[0] = 5; /* SOCKS version */
1035 buf[1] = 1; /* CONNECT */
1036 buf[2] = 0; /* Reserved */
1037 buf[3] = 3; /* Address type is domain name */
1038 buf[4] = strlen(vpninfo->hostname);
1039 strcpy((char *)buf + 5, vpninfo->hostname);
1040 i = strlen(vpninfo->hostname) + 5;
1041 buf[i++] = vpninfo->port >> 8;
1042 buf[i++] = vpninfo->port & 0xff;
1044 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1045 vpn_progress(vpninfo, PRG_ERR,
1046 _("Error writing connect request to SOCKS proxy: %s\n"),
1050 /* Read 5 bytes -- up to and including the first byte of the returned
1051 address (which might be the length byte of a domain name) */
1052 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1053 vpn_progress(vpninfo, PRG_ERR,
1054 _("Error reading connect response from SOCKS proxy: %s\n"),
1059 vpn_progress(vpninfo, PRG_ERR,
1060 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1067 /* Connect responses contain an address */
1069 case 1: /* Legacy IP */
1072 case 3: /* Domain name */
1079 vpn_progress(vpninfo, PRG_ERR,
1080 _("Unexpected address type %02x in SOCKS connect response\n"),
1085 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1086 vpn_progress(vpninfo, PRG_ERR,
1087 _("Error reading connect response from SOCKS proxy: %s\n"),
1094 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1096 char buf[MAX_BUF_LEN];
1099 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1100 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1101 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1102 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1103 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1104 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1105 sprintf(buf + strlen(buf), "\r\n");
1107 vpn_progress(vpninfo, PRG_INFO,
1108 _("Requesting HTTP proxy connection to %s:%d\n"),
1109 vpninfo->hostname, vpninfo->port);
1111 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1113 vpn_progress(vpninfo, PRG_ERR,
1114 _("Sending proxy request failed: %s\n"),
1119 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1120 vpn_progress(vpninfo, PRG_ERR,
1121 _("Error fetching proxy response\n"));
1125 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1126 buf[8] != ' ' || !(result = atoi(buf+9))) {
1127 vpn_progress(vpninfo, PRG_ERR,
1128 _("Failed to parse proxy response '%s'\n"), buf);
1132 if (result != 200) {
1133 vpn_progress(vpninfo, PRG_ERR,
1134 _("Proxy CONNECT request failed: %s\n"), buf);
1138 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1140 vpn_progress(vpninfo, PRG_ERR,
1141 _("Failed to read proxy response\n"));
1144 vpn_progress(vpninfo, PRG_ERR,
1145 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1152 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1154 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1155 return process_http_proxy(vpninfo, ssl_sock);
1157 if (!strcmp(vpninfo->proxy_type, "socks") ||
1158 !strcmp(vpninfo->proxy_type, "socks5"))
1159 return process_socks_proxy(vpninfo, ssl_sock);
1161 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1162 vpninfo->proxy_type);
1166 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1174 free(vpninfo->proxy_type);
1175 vpninfo->proxy_type = NULL;
1176 free(vpninfo->proxy);
1177 vpninfo->proxy = NULL;
1179 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1180 &vpninfo->proxy_port, NULL, 80);
1184 if (vpninfo->proxy_type &&
1185 strcmp(vpninfo->proxy_type, "http") &&
1186 strcmp(vpninfo->proxy_type, "socks") &&
1187 strcmp(vpninfo->proxy_type, "socks5")) {
1188 vpn_progress(vpninfo, PRG_ERR,
1189 _("Only http or socks(5) proxies supported\n"));
1190 free(vpninfo->proxy_type);
1191 vpninfo->proxy_type = NULL;
1192 free(vpninfo->proxy);
1193 vpninfo->proxy = NULL;