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 */
596 * = 0, no cookie (user cancel)
597 * = 1, obtained cookie
599 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
601 struct vpn_option *opt, *next;
602 char buf[MAX_BUF_LEN];
603 char *form_buf = NULL;
605 char request_body[2048];
606 const char *request_body_type = NULL;
607 const char *method = "GET";
614 if (openconnect_open_https(vpninfo)) {
615 vpn_progress(vpninfo, PRG_ERR,
616 _("Failed to open HTTPS connection to %s\n"),
622 * It would be nice to use cURL for this, but we really need to guarantee
623 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
624 * to have any way to let us provide our own socket read/write functions.
625 * We can only provide a socket _open_ function. Which would require having
626 * a socketpair() and servicing the "other" end of it.
628 * So we process the HTTP for ourselves...
630 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
631 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
632 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
633 sprintf(buf + strlen(buf), "Accept: */*\r\n");
634 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
636 if (vpninfo->cookies) {
637 sprintf(buf + strlen(buf), "Cookie: ");
638 for (opt = vpninfo->cookies; opt; opt = opt->next)
639 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
640 opt->value, opt->next ? "; " : "\r\n");
642 if (request_body_type) {
643 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
645 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
646 strlen(request_body));
648 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
649 if (request_body_type)
650 sprintf(buf + strlen(buf), "%s", request_body);
652 if (vpninfo->port == 443)
653 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
654 method, vpninfo->hostname,
655 vpninfo->urlpath ?: "");
657 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
658 method, vpninfo->hostname, vpninfo->port,
659 vpninfo->urlpath ?: "");
661 result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
665 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
667 /* We'll already have complained about whatever offended us */
671 if (result != 200 && vpninfo->redirect_url) {
673 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
674 /* New host. Tear down the existing connection and make a new one */
679 free(vpninfo->urlpath);
680 vpninfo->urlpath = NULL;
682 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
684 vpn_progress(vpninfo, PRG_ERR,
685 _("Failed to parse redirected URL '%s': %s\n"),
686 vpninfo->redirect_url, strerror(-ret));
687 free(vpninfo->redirect_url);
688 vpninfo->redirect_url = NULL;
693 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
694 free(vpninfo->hostname);
695 vpninfo->hostname = host;
696 vpninfo->port = port;
698 /* Kill the existing connection, and a new one will happen */
699 free(vpninfo->peer_addr);
700 vpninfo->peer_addr = NULL;
701 openconnect_close_https(vpninfo, 0);
703 for (opt = vpninfo->cookies; opt; opt = next) {
710 vpninfo->cookies = NULL;
714 free(vpninfo->redirect_url);
715 vpninfo->redirect_url = NULL;
718 } else if (strstr(vpninfo->redirect_url, "://")) {
719 vpn_progress(vpninfo, PRG_ERR,
720 _("Cannot follow redirection to non-https URL '%s'\n"),
721 vpninfo->redirect_url);
722 free(vpninfo->redirect_url);
723 vpninfo->redirect_url = NULL;
726 } else if (vpninfo->redirect_url[0] == '/') {
727 /* Absolute redirect within same host */
728 free(vpninfo->urlpath);
729 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
730 free(vpninfo->redirect_url);
731 vpninfo->redirect_url = NULL;
734 char *lastslash = NULL;
735 if (vpninfo->urlpath)
736 lastslash = strrchr(vpninfo->urlpath, '/');
738 free(vpninfo->urlpath);
739 vpninfo->urlpath = vpninfo->redirect_url;
740 vpninfo->redirect_url = NULL;
742 char *oldurl = vpninfo->urlpath;
744 vpninfo->urlpath = NULL;
745 if (asprintf(&vpninfo->urlpath, "%s/%s",
746 oldurl, vpninfo->redirect_url) == -1) {
748 vpn_progress(vpninfo, PRG_ERR,
749 _("Allocating new path for relative redirect failed: %s\n"),
754 free(vpninfo->redirect_url);
755 vpninfo->redirect_url = NULL;
760 if (!form_buf || result != 200) {
761 vpn_progress(vpninfo, PRG_ERR,
762 _("Unexpected %d result from server\n"),
767 if (vpninfo->csd_stuburl) {
768 /* This is the CSD stub script, which we now need to run */
769 result = run_csd_script(vpninfo, form_buf, buflen);
775 /* Now we'll be redirected to the waiturl */
778 if (strncmp(form_buf, "<?xml", 5)) {
779 /* Not XML? Perhaps it's HTML with a refresh... */
780 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
781 vpn_progress(vpninfo, PRG_INFO,
782 _("Refreshing %s after 1 second...\n"),
787 vpn_progress(vpninfo, PRG_ERR,
788 _("Unknown response from server\n"));
793 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
794 &method, &request_body_type);
804 /* A return value of 2 means the XML form indicated
805 success. We _should_ have a cookie... */
807 for (opt = vpninfo->cookies; opt; opt = opt->next) {
809 if (!strcmp(opt->option, "webvpn"))
810 vpninfo->cookie = opt->value;
811 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
812 char *tok = opt->value;
813 char *bu = NULL, *fu = NULL, *sha = NULL;
816 if (tok != opt->value)
819 if (!strncmp(tok, "bu:", 3))
821 else if (!strncmp(tok, "fu:", 3))
823 else if (!strncmp(tok, "fh:", 3)) {
824 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
829 } while ((tok = strchr(tok, '&')));
832 fetch_config(vpninfo, bu, fu, sha);
835 if (vpninfo->csd_scriptname) {
836 unlink(vpninfo->csd_scriptname);
837 free(vpninfo->csd_scriptname);
838 vpninfo->csd_scriptname = NULL;
843 char *openconnect_create_useragent(const char *base)
847 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
853 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
854 char *buf, size_t len)
862 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
863 if (buf[i] == '\n') {
865 if (i && buf[i-1] == '\r') {
882 static int proxy_write(struct openconnect_info *vpninfo, int fd,
883 unsigned char *buf, size_t len)
887 for (count = 0; count < len; ) {
888 fd_set rd_set, wr_set;
895 if (vpninfo->cancel_fd != -1) {
896 FD_SET(vpninfo->cancel_fd, &rd_set);
897 if (vpninfo->cancel_fd > fd)
898 maxfd = vpninfo->cancel_fd;
901 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
902 if (vpninfo->cancel_fd != -1 &&
903 FD_ISSET(vpninfo->cancel_fd, &rd_set))
906 /* Not that this should ever be able to happen... */
907 if (!FD_ISSET(fd, &wr_set))
910 i = write(fd, buf + count, len - count);
919 static int proxy_read(struct openconnect_info *vpninfo, int fd,
920 unsigned char *buf, size_t len)
924 for (count = 0; count < len; ) {
931 if (vpninfo->cancel_fd != -1) {
932 FD_SET(vpninfo->cancel_fd, &rd_set);
933 if (vpninfo->cancel_fd > fd)
934 maxfd = vpninfo->cancel_fd;
937 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
938 if (vpninfo->cancel_fd != -1 &&
939 FD_ISSET(vpninfo->cancel_fd, &rd_set))
942 /* Not that this should ever be able to happen... */
943 if (!FD_ISSET(fd, &rd_set))
946 i = read(fd, buf + count, len - count);
955 static const char *socks_errors[] = {
956 N_("request granted"),
957 N_("general failure"),
958 N_("connection not allowed by ruleset"),
959 N_("network unreachable"),
960 N_("host unreachable"),
961 N_("connection refused by destination host"),
963 N_("command not supported / protocol error"),
964 N_("address type not supported")
967 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
969 unsigned char buf[1024];
972 buf[0] = 5; /* SOCKS version */
973 buf[1] = 1; /* # auth methods */
974 buf[2] = 0; /* No auth supported */
976 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
977 vpn_progress(vpninfo, PRG_ERR,
978 _("Error writing auth request to SOCKS proxy: %s\n"),
983 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
984 vpn_progress(vpninfo, PRG_ERR,
985 _("Error reading auth response from SOCKS proxy: %s\n"),
990 vpn_progress(vpninfo, PRG_ERR,
991 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
997 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
998 vpn_progress(vpninfo, PRG_ERR,
999 _("SOCKS proxy error %02x: %s\n"),
1000 buf[1], _(socks_errors[buf[1]]));
1002 vpn_progress(vpninfo, PRG_ERR,
1003 _("SOCKS proxy error %02x\n"),
1008 vpn_progress(vpninfo, PRG_INFO,
1009 _("Requesting SOCKS proxy connection to %s:%d\n"),
1010 vpninfo->hostname, vpninfo->port);
1012 buf[0] = 5; /* SOCKS version */
1013 buf[1] = 1; /* CONNECT */
1014 buf[2] = 0; /* Reserved */
1015 buf[3] = 3; /* Address type is domain name */
1016 buf[4] = strlen(vpninfo->hostname);
1017 strcpy((char *)buf + 5, vpninfo->hostname);
1018 i = strlen(vpninfo->hostname) + 5;
1019 buf[i++] = vpninfo->port >> 8;
1020 buf[i++] = vpninfo->port & 0xff;
1022 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1023 vpn_progress(vpninfo, PRG_ERR,
1024 _("Error writing connect request to SOCKS proxy: %s\n"),
1028 /* Read 5 bytes -- up to and including the first byte of the returned
1029 address (which might be the length byte of a domain name) */
1030 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1031 vpn_progress(vpninfo, PRG_ERR,
1032 _("Error reading connect response from SOCKS proxy: %s\n"),
1037 vpn_progress(vpninfo, PRG_ERR,
1038 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1045 /* Connect responses contain an address */
1047 case 1: /* Legacy IP */
1050 case 3: /* Domain name */
1057 vpn_progress(vpninfo, PRG_ERR,
1058 _("Unexpected address type %02x in SOCKS connect response\n"),
1063 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1064 vpn_progress(vpninfo, PRG_ERR,
1065 _("Error reading connect response from SOCKS proxy: %s\n"),
1072 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1074 char buf[MAX_BUF_LEN];
1077 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1078 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1079 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1080 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1081 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1082 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1083 sprintf(buf + strlen(buf), "\r\n");
1085 vpn_progress(vpninfo, PRG_INFO,
1086 _("Requesting HTTP proxy connection to %s:%d\n"),
1087 vpninfo->hostname, vpninfo->port);
1089 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
1091 vpn_progress(vpninfo, PRG_ERR,
1092 _("Sending proxy request failed: %s\n"),
1097 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1098 vpn_progress(vpninfo, PRG_ERR,
1099 _("Error fetching proxy response\n"));
1103 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1104 buf[8] != ' ' || !(result = atoi(buf+9))) {
1105 vpn_progress(vpninfo, PRG_ERR,
1106 _("Failed to parse proxy response '%s'\n"), buf);
1110 if (result != 200) {
1111 vpn_progress(vpninfo, PRG_ERR,
1112 _("Proxy CONNECT request failed: %s\n"), buf);
1116 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1118 vpn_progress(vpninfo, PRG_ERR,
1119 _("Failed to read proxy response\n"));
1122 vpn_progress(vpninfo, PRG_ERR,
1123 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1130 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1132 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1133 return process_http_proxy(vpninfo, ssl_sock);
1135 if (!strcmp(vpninfo->proxy_type, "socks") ||
1136 !strcmp(vpninfo->proxy_type, "socks5"))
1137 return process_socks_proxy(vpninfo, ssl_sock);
1139 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1140 vpninfo->proxy_type);
1144 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1152 free(vpninfo->proxy_type);
1153 vpninfo->proxy_type = NULL;
1154 free(vpninfo->proxy);
1155 vpninfo->proxy = NULL;
1157 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1158 &vpninfo->proxy_port, NULL, 80);
1162 if (vpninfo->proxy_type &&
1163 strcmp(vpninfo->proxy_type, "http") &&
1164 strcmp(vpninfo->proxy_type, "socks") &&
1165 strcmp(vpninfo->proxy_type, "socks5")) {
1166 vpn_progress(vpninfo, PRG_ERR,
1167 _("Only http or socks(5) proxies supported\n"));
1168 free(vpninfo->proxy_type);
1169 vpninfo->proxy_type = NULL;
1170 free(vpninfo->proxy);
1171 vpninfo->proxy = NULL;