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 int internal_parse_url(char *url, char **res_proto, char **res_host,
530 int *res_port, char **res_path, int default_port)
533 char *host, *path, *port_str;
536 host = strstr(url, "://");
541 if (!strcasecmp(proto, "https"))
543 else if (!strcasecmp(proto, "http"))
545 else if (!strcasecmp(proto, "socks") ||
546 !strcasecmp(proto, "socks4") ||
547 !strcasecmp(proto, "socks5"))
550 return -EPROTONOSUPPORT;
560 path = strchr(host, '/');
564 port_str = strrchr(host, ':');
567 int new_port = strtol(port_str + 1, &end, 10);
576 *res_proto = proto ? strdup(proto) : NULL;
578 *res_host = strdup(host);
582 *res_path = (path && *path) ? strdup(path) : NULL;
584 /* Undo the damage we did to the original string */
594 static void clear_cookies(struct openconnect_info *vpninfo)
596 struct vpn_option *opt, *next;
598 for (opt = vpninfo->cookies; opt; opt = next) {
605 vpninfo->cookies = NULL;
610 * = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
612 static int handle_redirect(struct openconnect_info *vpninfo)
614 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
615 /* New host. Tear down the existing connection and make a new one */
620 free(vpninfo->urlpath);
621 vpninfo->urlpath = NULL;
623 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
625 vpn_progress(vpninfo, PRG_ERR,
626 _("Failed to parse redirected URL '%s': %s\n"),
627 vpninfo->redirect_url, strerror(-ret));
628 free(vpninfo->redirect_url);
629 vpninfo->redirect_url = NULL;
633 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
634 free(vpninfo->hostname);
635 vpninfo->hostname = host;
636 vpninfo->port = port;
638 /* Kill the existing connection, and a new one will happen */
639 free(vpninfo->peer_addr);
640 vpninfo->peer_addr = NULL;
641 openconnect_close_https(vpninfo, 0);
642 clear_cookies(vpninfo);
646 free(vpninfo->redirect_url);
647 vpninfo->redirect_url = NULL;
650 } else if (strstr(vpninfo->redirect_url, "://")) {
651 vpn_progress(vpninfo, PRG_ERR,
652 _("Cannot follow redirection to non-https URL '%s'\n"),
653 vpninfo->redirect_url);
654 free(vpninfo->redirect_url);
655 vpninfo->redirect_url = NULL;
657 } else if (vpninfo->redirect_url[0] == '/') {
658 /* Absolute redirect within same host */
659 free(vpninfo->urlpath);
660 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
661 free(vpninfo->redirect_url);
662 vpninfo->redirect_url = NULL;
665 char *lastslash = NULL;
666 if (vpninfo->urlpath)
667 lastslash = strrchr(vpninfo->urlpath, '/');
669 free(vpninfo->urlpath);
670 vpninfo->urlpath = vpninfo->redirect_url;
671 vpninfo->redirect_url = NULL;
673 char *oldurl = vpninfo->urlpath;
675 vpninfo->urlpath = NULL;
676 if (asprintf(&vpninfo->urlpath, "%s/%s",
677 oldurl, vpninfo->redirect_url) == -1) {
679 vpn_progress(vpninfo, PRG_ERR,
680 _("Allocating new path for relative redirect failed: %s\n"),
685 free(vpninfo->redirect_url);
686 vpninfo->redirect_url = NULL;
694 * > 0, no cookie (user cancel)
695 * = 0, obtained cookie
697 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
699 struct vpn_option *opt;
700 char buf[MAX_BUF_LEN];
701 char *form_buf = NULL;
703 char request_body[2048];
704 const char *request_body_type = NULL;
705 const char *method = "GET";
707 if (vpninfo->use_stoken) {
708 result = prepare_stoken(vpninfo);
718 if (openconnect_open_https(vpninfo)) {
719 vpn_progress(vpninfo, PRG_ERR,
720 _("Failed to open HTTPS connection to %s\n"),
726 * It would be nice to use cURL for this, but we really need to guarantee
727 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
728 * to have any way to let us provide our own socket read/write functions.
729 * We can only provide a socket _open_ function. Which would require having
730 * a socketpair() and servicing the "other" end of it.
732 * So we process the HTTP for ourselves...
734 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
735 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
736 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
737 sprintf(buf + strlen(buf), "Accept: */*\r\n");
738 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
740 if (vpninfo->cookies) {
741 sprintf(buf + strlen(buf), "Cookie: ");
742 for (opt = vpninfo->cookies; opt; opt = opt->next)
743 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
744 opt->value, opt->next ? "; " : "\r\n");
746 if (request_body_type) {
747 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
749 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
750 strlen(request_body));
752 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
753 if (request_body_type)
754 sprintf(buf + strlen(buf), "%s", request_body);
756 if (vpninfo->port == 443)
757 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
758 method, vpninfo->hostname,
759 vpninfo->urlpath ?: "");
761 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
762 method, vpninfo->hostname, vpninfo->port,
763 vpninfo->urlpath ?: "");
765 result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
769 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
771 /* We'll already have complained about whatever offended us */
775 if (result != 200 && vpninfo->redirect_url) {
777 result = handle_redirect(vpninfo);
783 if (!form_buf || result != 200) {
784 vpn_progress(vpninfo, PRG_ERR,
785 _("Unexpected %d result from server\n"),
790 if (vpninfo->csd_stuburl) {
791 /* This is the CSD stub script, which we now need to run */
792 result = run_csd_script(vpninfo, form_buf, buflen);
798 /* Now we'll be redirected to the waiturl */
801 if (strncmp(form_buf, "<?xml", 5)) {
802 /* Not XML? Perhaps it's HTML with a refresh... */
803 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
804 vpn_progress(vpninfo, PRG_INFO,
805 _("Refreshing %s after 1 second...\n"),
810 vpn_progress(vpninfo, PRG_ERR,
811 _("Unknown response from server\n"));
816 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
817 &method, &request_body_type);
827 /* A return value of 2 means the XML form indicated
828 success. We _should_ have a cookie... */
830 for (opt = vpninfo->cookies; opt; opt = opt->next) {
832 if (!strcmp(opt->option, "webvpn"))
833 vpninfo->cookie = opt->value;
834 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
835 char *tok = opt->value;
836 char *bu = NULL, *fu = NULL, *sha = NULL;
839 if (tok != opt->value)
842 if (!strncmp(tok, "bu:", 3))
844 else if (!strncmp(tok, "fu:", 3))
846 else if (!strncmp(tok, "fh:", 3)) {
847 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
852 } while ((tok = strchr(tok, '&')));
855 fetch_config(vpninfo, bu, fu, sha);
858 if (vpninfo->csd_scriptname) {
859 unlink(vpninfo->csd_scriptname);
860 free(vpninfo->csd_scriptname);
861 vpninfo->csd_scriptname = NULL;
866 char *openconnect_create_useragent(const char *base)
870 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
876 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
877 char *buf, size_t len)
885 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
886 if (buf[i] == '\n') {
888 if (i && buf[i-1] == '\r') {
905 static int proxy_write(struct openconnect_info *vpninfo, int fd,
906 unsigned char *buf, size_t len)
910 for (count = 0; count < len; ) {
911 fd_set rd_set, wr_set;
918 if (vpninfo->cancel_fd != -1) {
919 FD_SET(vpninfo->cancel_fd, &rd_set);
920 if (vpninfo->cancel_fd > fd)
921 maxfd = vpninfo->cancel_fd;
924 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
925 if (vpninfo->cancel_fd != -1 &&
926 FD_ISSET(vpninfo->cancel_fd, &rd_set))
929 /* Not that this should ever be able to happen... */
930 if (!FD_ISSET(fd, &wr_set))
933 i = write(fd, buf + count, len - count);
942 static int proxy_read(struct openconnect_info *vpninfo, int fd,
943 unsigned char *buf, size_t len)
947 for (count = 0; count < len; ) {
954 if (vpninfo->cancel_fd != -1) {
955 FD_SET(vpninfo->cancel_fd, &rd_set);
956 if (vpninfo->cancel_fd > fd)
957 maxfd = vpninfo->cancel_fd;
960 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
961 if (vpninfo->cancel_fd != -1 &&
962 FD_ISSET(vpninfo->cancel_fd, &rd_set))
965 /* Not that this should ever be able to happen... */
966 if (!FD_ISSET(fd, &rd_set))
969 i = read(fd, buf + count, len - count);
978 static const char *socks_errors[] = {
979 N_("request granted"),
980 N_("general failure"),
981 N_("connection not allowed by ruleset"),
982 N_("network unreachable"),
983 N_("host unreachable"),
984 N_("connection refused by destination host"),
986 N_("command not supported / protocol error"),
987 N_("address type not supported")
990 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
992 unsigned char buf[1024];
995 buf[0] = 5; /* SOCKS version */
996 buf[1] = 1; /* # auth methods */
997 buf[2] = 0; /* No auth supported */
999 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1000 vpn_progress(vpninfo, PRG_ERR,
1001 _("Error writing auth request to SOCKS proxy: %s\n"),
1006 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1007 vpn_progress(vpninfo, PRG_ERR,
1008 _("Error reading auth response from SOCKS proxy: %s\n"),
1013 vpn_progress(vpninfo, PRG_ERR,
1014 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1020 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1021 vpn_progress(vpninfo, PRG_ERR,
1022 _("SOCKS proxy error %02x: %s\n"),
1023 buf[1], _(socks_errors[buf[1]]));
1025 vpn_progress(vpninfo, PRG_ERR,
1026 _("SOCKS proxy error %02x\n"),
1031 vpn_progress(vpninfo, PRG_INFO,
1032 _("Requesting SOCKS proxy connection to %s:%d\n"),
1033 vpninfo->hostname, vpninfo->port);
1035 buf[0] = 5; /* SOCKS version */
1036 buf[1] = 1; /* CONNECT */
1037 buf[2] = 0; /* Reserved */
1038 buf[3] = 3; /* Address type is domain name */
1039 buf[4] = strlen(vpninfo->hostname);
1040 strcpy((char *)buf + 5, vpninfo->hostname);
1041 i = strlen(vpninfo->hostname) + 5;
1042 buf[i++] = vpninfo->port >> 8;
1043 buf[i++] = vpninfo->port & 0xff;
1045 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1046 vpn_progress(vpninfo, PRG_ERR,
1047 _("Error writing connect request to SOCKS proxy: %s\n"),
1051 /* Read 5 bytes -- up to and including the first byte of the returned
1052 address (which might be the length byte of a domain name) */
1053 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1054 vpn_progress(vpninfo, PRG_ERR,
1055 _("Error reading connect response from SOCKS proxy: %s\n"),
1060 vpn_progress(vpninfo, PRG_ERR,
1061 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1068 /* Connect responses contain an address */
1070 case 1: /* Legacy IP */
1073 case 3: /* Domain name */
1080 vpn_progress(vpninfo, PRG_ERR,
1081 _("Unexpected address type %02x in SOCKS connect response\n"),
1086 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1087 vpn_progress(vpninfo, PRG_ERR,
1088 _("Error reading connect response from SOCKS proxy: %s\n"),
1095 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1097 char buf[MAX_BUF_LEN];
1100 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1101 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1102 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1103 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1104 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1105 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1106 sprintf(buf + strlen(buf), "\r\n");
1108 vpn_progress(vpninfo, PRG_INFO,
1109 _("Requesting HTTP proxy connection to %s:%d\n"),
1110 vpninfo->hostname, vpninfo->port);
1112 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1114 vpn_progress(vpninfo, PRG_ERR,
1115 _("Sending proxy request failed: %s\n"),
1120 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1121 vpn_progress(vpninfo, PRG_ERR,
1122 _("Error fetching proxy response\n"));
1126 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1127 buf[8] != ' ' || !(result = atoi(buf+9))) {
1128 vpn_progress(vpninfo, PRG_ERR,
1129 _("Failed to parse proxy response '%s'\n"), buf);
1133 if (result != 200) {
1134 vpn_progress(vpninfo, PRG_ERR,
1135 _("Proxy CONNECT request failed: %s\n"), buf);
1139 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1141 vpn_progress(vpninfo, PRG_ERR,
1142 _("Failed to read proxy response\n"));
1145 vpn_progress(vpninfo, PRG_ERR,
1146 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1153 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1155 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1156 return process_http_proxy(vpninfo, ssl_sock);
1158 if (!strcmp(vpninfo->proxy_type, "socks") ||
1159 !strcmp(vpninfo->proxy_type, "socks5"))
1160 return process_socks_proxy(vpninfo, ssl_sock);
1162 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1163 vpninfo->proxy_type);
1167 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1175 free(vpninfo->proxy_type);
1176 vpninfo->proxy_type = NULL;
1177 free(vpninfo->proxy);
1178 vpninfo->proxy = NULL;
1180 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1181 &vpninfo->proxy_port, NULL, 80);
1185 if (vpninfo->proxy_type &&
1186 strcmp(vpninfo->proxy_type, "http") &&
1187 strcmp(vpninfo->proxy_type, "socks") &&
1188 strcmp(vpninfo->proxy_type, "socks5")) {
1189 vpn_progress(vpninfo, PRG_ERR,
1190 _("Only http or socks(5) proxies supported\n"));
1191 free(vpninfo->proxy_type);
1192 vpninfo->proxy_type = NULL;
1193 free(vpninfo->proxy);
1194 vpninfo->proxy = NULL;