2 * OpenConnect (SSL + DTLS) VPN client
4 * Copyright © 2008-2010 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>
36 #include <openssl/ssl.h>
37 #include <openssl/err.h>
38 #include <openssl/engine.h>
40 #include "openconnect-internal.h"
42 static int proxy_write(int fd, unsigned char *buf, size_t len);
44 #define MAX_BUF_LEN 131072
46 * We didn't really want to have to do this for ourselves -- one might have
47 * thought that it would be available in a library somewhere. But neither
48 * cURL nor Neon have reliable cross-platform ways of either using a cert
49 * from the TPM, or just reading from / writing to a transport which is
50 * provided by their caller.
53 static int http_add_cookie(struct openconnect_info *vpninfo,
54 const char *option, const char *value)
56 struct vpn_option *new, **this;
59 new = malloc(sizeof(*new));
61 vpn_progress(vpninfo, PRG_ERR,
62 _("No memory for allocating cookies\n"));
66 new->option = strdup(option);
67 new->value = strdup(value);
68 if (!new->option || !new->value) {
75 /* Kill cookie; don't replace it */
78 for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
79 if (!strcmp(option, (*this)->option)) {
80 /* Replace existing cookie */
82 new->next = (*this)->next;
86 free((*this)->option);
100 #define BODY_HTTP10 -1
101 #define BODY_CHUNKED -2
103 static int process_http_response(struct openconnect_info *vpninfo, int *result,
104 int (*header_cb)(struct openconnect_info *, char *, char *),
107 char buf[MAX_BUF_LEN];
109 int bodylen = BODY_HTTP10;
115 if (openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)) < 0) {
116 vpn_progress(vpninfo, PRG_ERR,
117 _("Error fetching HTTPS response\n"));
121 if (!strncmp(buf, "HTTP/1.0 ", 9))
124 if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
125 vpn_progress(vpninfo, PRG_ERR,
126 _("Failed to parse HTTP response '%s'\n"), buf);
130 vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
131 _("Got HTTP response: %s\n"), buf);
134 while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
138 vpn_progress(vpninfo, PRG_ERR,
139 _("Error processing HTTP response\n"));
142 colon = strchr(buf, ':');
144 vpn_progress(vpninfo, PRG_ERR,
145 _("Ignoring unknown HTTP response line '%s'\n"), buf);
152 /* Handle Set-Cookie first so that we can avoid printing the
153 webvpn cookie in the verbose debug output */
154 if (!strcasecmp(buf, "Set-Cookie")) {
155 char *semicolon = strchr(colon, ';');
156 const char *print_equals;
157 char *equals = strchr(colon, '=');
164 vpn_progress(vpninfo, PRG_ERR,
165 _("Invalid cookie offered: %s\n"), buf);
170 print_equals = equals;
171 /* Don't print the webvpn cookie unless it's empty; we don't
172 want people posting it in public with debugging output */
173 if (!strcmp(colon, "webvpn") && *equals)
174 print_equals = _("<elided>");
175 vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
176 buf, colon, print_equals, semicolon?";":"",
177 semicolon?(semicolon+1):"");
179 /* The server tends to ask for the username and password as
180 usual, even if we've already failed because it didn't like
181 our cert. Thankfully it does give us this hint... */
182 if (!strcmp(colon, "ClientCertAuthFailed"))
183 vpn_progress(vpninfo, PRG_ERR,
184 _("SSL certificate authentication failed\n"));
186 ret = http_add_cookie(vpninfo, colon, equals);
190 vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
193 if (!strcasecmp(buf, "Connection")) {
194 if (!strcasecmp(colon, "Close"))
197 /* This might seem reasonable, but in fact it breaks
198 certificate authentication with some servers. If
199 they give an HTTP/1.0 response, even if they
200 explicitly give a Connection: Keep-Alive header,
201 just close the connection. */
202 else if (!strcasecmp(colon, "Keep-Alive"))
206 if (!strcasecmp(buf, "Location")) {
207 vpninfo->redirect_url = strdup(colon);
208 if (!vpninfo->redirect_url)
211 if (!strcasecmp(buf, "Content-Length")) {
212 bodylen = atoi(colon);
214 vpn_progress(vpninfo, PRG_ERR,
215 _("Response body has negative size (%d)\n"),
220 if (!strcasecmp(buf, "Transfer-Encoding")) {
221 if (!strcasecmp(colon, "chunked"))
222 bodylen = BODY_CHUNKED;
224 vpn_progress(vpninfo, PRG_ERR,
225 _("Unknown Transfer-Encoding: %s\n"),
230 if (header_cb && !strncmp(buf, "X-", 2))
231 header_cb(vpninfo, buf, colon);
234 /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
238 /* Now the body, if there is one */
239 vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"),
240 bodylen==BODY_HTTP10?"http 1.0" :
241 bodylen==BODY_CHUNKED?"chunked" : "length: ",
244 /* If we were given Content-Length, it's nice and easy... */
246 body = malloc(bodylen + 1);
249 while (done < bodylen) {
250 i = SSL_read(vpninfo->https_ssl, body + done, bodylen - done);
252 vpn_progress(vpninfo, PRG_ERR,
253 _("Error reading HTTP response body\n"));
259 } else if (bodylen == BODY_CHUNKED) {
260 /* ... else, chunked */
261 while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
262 int chunklen, lastchunk = 0;
265 vpn_progress(vpninfo, PRG_ERR,
266 _("Error fetching chunk header\n"));
269 chunklen = strtol(buf, NULL, 16);
274 body = realloc(body, done + chunklen + 1);
278 i = SSL_read(vpninfo->https_ssl, body + done, chunklen);
280 vpn_progress(vpninfo, PRG_ERR,
281 _("Error reading HTTP response body\n"));
289 if ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
291 vpn_progress(vpninfo, PRG_ERR,
292 _("Error fetching HTTP response body\n"));
294 vpn_progress(vpninfo, PRG_ERR,
295 _("Error in chunked decoding. Expected '', got: '%s'"),
305 } else if (bodylen == BODY_HTTP10) {
307 vpn_progress(vpninfo, PRG_ERR,
308 _("Cannot receive HTTP 1.0 body without closing connection\n"));
312 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
314 body = realloc(body, done + 16384);
317 i = SSL_read(vpninfo->https_ssl, body + done, 16384);
319 body = realloc(body, done + 1);
328 if (closeconn || vpninfo->no_http_keepalive) {
329 SSL_free(vpninfo->https_ssl);
330 vpninfo->https_ssl = NULL;
331 close(vpninfo->ssl_fd);
332 vpninfo->ssl_fd = -1;
341 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
344 struct vpn_option *opt;
345 char buf[MAX_BUF_LEN];
346 char *config_buf = NULL;
348 unsigned char local_sha1_bin[SHA_DIGEST_LENGTH];
349 char local_sha1_ascii[(SHA_DIGEST_LENGTH * 2)+1];
353 sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
354 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
355 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
356 sprintf(buf + strlen(buf), "Accept: */*\r\n");
357 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
359 if (vpninfo->cookies) {
360 sprintf(buf + strlen(buf), "Cookie: ");
361 for (opt = vpninfo->cookies; opt; opt = opt->next)
362 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
363 opt->value, opt->next ? "; " : "\r\n");
365 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
367 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
369 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
371 /* We'll already have complained about whatever offended us */
381 EVP_Digest(config_buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
382 EVP_MD_CTX_cleanup(&c);
384 for (i = 0; i < SHA_DIGEST_LENGTH; i++)
385 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
387 if (strcasecmp(server_sha1, local_sha1_ascii)) {
388 vpn_progress(vpninfo, PRG_ERR,
389 _("Downloaded config file did not match intended SHA1\n"));
394 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
399 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
404 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
405 vpn_progress(vpninfo, PRG_ERR,
406 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
407 "This facility is disabled by default for security reasons, so you may wish to enable it."));
412 vpn_progress(vpninfo, PRG_INFO,
413 _("Trying to run Linux CSD trojan script."));
416 sprintf(fname, "/tmp/csdXXXXXX");
420 vpn_progress(vpninfo, PRG_ERR,
421 _("Failed to open temporary CSD script file: %s\n"),
426 ret = proxy_write(fd, (void *)buf, buflen);
428 vpn_progress(vpninfo, PRG_ERR,
429 _("Failed to write temporary CSD script file: %s\n"),
437 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
438 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
439 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
440 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
444 if (vpninfo->uid_csd != getuid()) {
447 if (setuid(vpninfo->uid_csd)) {
448 fprintf(stderr, _("Failed to set uid %d\n"),
452 if (!(pw = getpwuid(vpninfo->uid_csd))) {
453 fprintf(stderr, _("Invalid user uid=%d\n"),
457 setenv("HOME", pw->pw_dir, 1);
458 if (chdir(pw->pw_dir)) {
459 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
460 pw->pw_dir, strerror(errno));
464 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
465 fprintf(stderr, _("Warning: you are running insecure "
466 "CSD code with root privileges\n"
467 "\t Use command line option \"--csd-user\"\n"));
469 if (vpninfo->uid_csd_given == 2) {
470 /* The NM tool really needs not to get spurious output
471 on stdout, which the CSD trojan spews. */
474 if (vpninfo->csd_wrapper)
475 csd_argv[i++] = vpninfo->csd_wrapper;
476 csd_argv[i++] = fname;
477 csd_argv[i++]= (char *)"-ticket";
478 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
480 csd_argv[i++]= (char *)"-stub";
481 csd_argv[i++]= (char *)"\"0\"";
482 csd_argv[i++]= (char *)"-group";
483 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
486 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
488 get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
492 csd_argv[i++]= (char *)"-certhash";
493 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
495 csd_argv[i++]= (char *)"-url";
496 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
498 /* WTF would it want to know this for? */
499 csd_argv[i++]= (char *)"-vpnclient";
500 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
501 csd_argv[i++]= (char *)"-connect";
502 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
504 csd_argv[i++]= (char *)"-connectparam";
505 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
507 csd_argv[i++]= (char *)"-langselen";
508 csd_argv[i++] = NULL;
510 execv(csd_argv[0], csd_argv);
511 vpn_progress(vpninfo, PRG_ERR,
512 _("Failed to exec CSD script %s\n"), csd_argv[0]);
516 free(vpninfo->csd_stuburl);
517 vpninfo->csd_stuburl = NULL;
518 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
519 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
520 vpninfo->csd_waiturl = NULL;
521 vpninfo->csd_scriptname = strdup(fname);
523 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
529 char *local_strcasestr(const char *haystack, const char *needle)
531 int hlen = strlen(haystack);
532 int nlen = strlen(needle);
535 for (i = 0; i < hlen - nlen + 1; i++) {
536 for (j = 0; j < nlen; j++) {
537 if (tolower(haystack[i + j]) !=
542 return (char *)haystack + i;
546 #define strcasestr local_strcasestr
549 int internal_parse_url(char *url, char **res_proto, char **res_host,
550 int *res_port, char **res_path, int default_port)
553 char *host, *path, *port_str;
556 host = strstr(url, "://");
561 if (!strcasecmp(proto, "https"))
563 else if (!strcasecmp(proto, "http"))
565 else if (!strcasecmp(proto, "socks") ||
566 !strcasecmp(proto, "socks4") ||
567 !strcasecmp(proto, "socks5"))
570 return -EPROTONOSUPPORT;
580 path = strchr(host, '/');
584 port_str = strrchr(host, ':');
587 int new_port = strtol(port_str + 1, &end, 10);
596 *res_proto = proto ? strdup(proto) : NULL;
598 *res_host = strdup(host);
602 *res_path = (path && *path) ? strdup(path) : NULL;
604 /* Undo the damage we did to the original string */
614 * = 0, no cookie (user cancel)
615 * = 1, obtained cookie
617 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
619 struct vpn_option *opt, *next;
620 char buf[MAX_BUF_LEN];
621 char *form_buf = NULL;
623 char request_body[2048];
624 const char *request_body_type = NULL;
625 const char *method = "GET";
632 if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
633 vpn_progress(vpninfo, PRG_ERR,
634 _("Failed to open HTTPS connection to %s\n"),
640 * It would be nice to use cURL for this, but we really need to guarantee
641 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
642 * to have any way to let us provide our own socket read/write functions.
643 * We can only provide a socket _open_ function. Which would require having
644 * a socketpair() and servicing the "other" end of it.
646 * So we process the HTTP for ourselves...
648 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
649 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
650 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
651 sprintf(buf + strlen(buf), "Accept: */*\r\n");
652 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
654 if (vpninfo->cookies) {
655 sprintf(buf + strlen(buf), "Cookie: ");
656 for (opt = vpninfo->cookies; opt; opt = opt->next)
657 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
658 opt->value, opt->next ? "; " : "\r\n");
660 if (request_body_type) {
661 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
663 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
664 strlen(request_body));
666 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
667 if (request_body_type)
668 sprintf(buf + strlen(buf), "%s", request_body);
670 if (vpninfo->port == 443)
671 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
672 method, vpninfo->hostname,
673 vpninfo->urlpath ?: "");
675 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
676 method, vpninfo->hostname, vpninfo->port,
677 vpninfo->urlpath ?: "");
679 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
681 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
683 /* We'll already have complained about whatever offended us */
687 if (result != 200 && vpninfo->redirect_url) {
689 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
690 /* New host. Tear down the existing connection and make a new one */
695 free(vpninfo->urlpath);
696 vpninfo->urlpath = NULL;
698 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
700 vpn_progress(vpninfo, PRG_ERR,
701 _("Failed to parse redirected URL '%s': %s\n"),
702 vpninfo->redirect_url, strerror(-ret));
703 free(vpninfo->redirect_url);
708 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
709 free(vpninfo->hostname);
710 vpninfo->hostname = host;
711 vpninfo->port = port;
713 /* Kill the existing connection, and a new one will happen */
714 free(vpninfo->peer_addr);
715 vpninfo->peer_addr = NULL;
716 if (vpninfo->https_ssl) {
717 SSL_free(vpninfo->https_ssl);
718 vpninfo->https_ssl = NULL;
719 close(vpninfo->ssl_fd);
720 vpninfo->ssl_fd = -1;
723 for (opt = vpninfo->cookies; opt; opt = next) {
730 vpninfo->cookies = NULL;
734 free(vpninfo->redirect_url);
735 vpninfo->redirect_url = NULL;
738 } else if (vpninfo->redirect_url[0] == '/') {
739 /* Absolute redirect within same host */
740 free(vpninfo->urlpath);
741 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
742 free(vpninfo->redirect_url);
743 vpninfo->redirect_url = NULL;
746 char *lastslash = NULL;
747 if (vpninfo->urlpath)
748 lastslash = strrchr(vpninfo->urlpath, '/');
750 free(vpninfo->urlpath);
751 vpninfo->urlpath = vpninfo->redirect_url;
752 vpninfo->redirect_url = NULL;
754 char *oldurl = vpninfo->urlpath;
756 vpninfo->urlpath = NULL;
757 if (asprintf(&vpninfo->urlpath, "%s/%s",
758 oldurl, vpninfo->redirect_url) == -1) {
760 vpn_progress(vpninfo, PRG_ERR,
761 _("Allocating new path for relative redirect failed: %s\n"),
766 free(vpninfo->redirect_url);
767 vpninfo->redirect_url = NULL;
772 if (!form_buf || result != 200) {
773 vpn_progress(vpninfo, PRG_ERR,
774 _("Unexpected %d result from server\n"),
779 if (vpninfo->csd_stuburl) {
780 /* This is the CSD stub script, which we now need to run */
781 result = run_csd_script(vpninfo, form_buf, buflen);
787 /* Now we'll be redirected to the waiturl */
790 if (strncmp(form_buf, "<?xml", 5)) {
791 /* Not XML? Perhaps it's HTML with a refresh... */
792 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
793 vpn_progress(vpninfo, PRG_INFO,
794 _("Refreshing %s after 1 second...\n"),
799 vpn_progress(vpninfo, PRG_ERR,
800 _("Unknown response from server\n"));
805 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
806 &method, &request_body_type);
816 /* A return value of 2 means the XML form indicated
817 success. We _should_ have a cookie... */
819 for (opt = vpninfo->cookies; opt; opt = opt->next) {
821 if (!strcmp(opt->option, "webvpn"))
822 vpninfo->cookie = opt->value;
823 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
824 char *tok = opt->value;
825 char *bu = NULL, *fu = NULL, *sha = NULL;
828 if (tok != opt->value)
831 if (!strncmp(tok, "bu:", 3))
833 else if (!strncmp(tok, "fu:", 3))
835 else if (!strncmp(tok, "fh:", 3)) {
836 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
837 SHA_DIGEST_LENGTH * 2))
841 } while ((tok = strchr(tok, '&')));
844 fetch_config(vpninfo, bu, fu, sha);
847 if (vpninfo->csd_scriptname) {
848 unlink(vpninfo->csd_scriptname);
849 free(vpninfo->csd_scriptname);
850 vpninfo->csd_scriptname = NULL;
855 char *openconnect_create_useragent(const char *base)
859 if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
865 static int proxy_gets(int fd, char *buf, size_t len)
873 while ( (ret = read(fd, buf + i, 1)) == 1) {
874 if (buf[i] == '\n') {
876 if (i && buf[i-1] == '\r') {
896 static int proxy_write(int fd, unsigned char *buf, size_t len)
900 for (count = 0; count < len; ) {
901 int i = write(fd, buf + count, len - count);
910 static int proxy_read(int fd, unsigned char *buf, size_t len)
914 for (count = 0; count < len; ) {
915 int i = read(fd, buf + count, len - count);
924 static const char *socks_errors[] = {
925 N_("request granted"),
926 N_("general failure"),
927 N_("connection not allowed by ruleset"),
928 N_("network unreachable"),
929 N_("host unreachable"),
930 N_("connection refused by destination host"),
932 N_("command not supported / protocol error"),
933 N_("address type not supported")
936 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
938 unsigned char buf[1024];
941 buf[0] = 5; /* SOCKS version */
942 buf[1] = 1; /* # auth methods */
943 buf[2] = 0; /* No auth supported */
945 if ((i = proxy_write(ssl_sock, buf, 3))) {
946 vpn_progress(vpninfo, PRG_ERR,
947 _("Error writing auth request to SOCKS proxy: %s\n"),
952 if ((i = proxy_read(ssl_sock, buf, 2))) {
953 vpn_progress(vpninfo, PRG_ERR,
954 _("Error reading auth response from SOCKS proxy: %s\n"),
959 vpn_progress(vpninfo, PRG_ERR,
960 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
966 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
967 vpn_progress(vpninfo, PRG_ERR,
968 _("SOCKS proxy error %02x: %s\n"),
969 buf[1], _(socks_errors[buf[1]]));
971 vpn_progress(vpninfo, PRG_ERR,
972 _("SOCKS proxy error %02x\n"),
977 vpn_progress(vpninfo, PRG_INFO,
978 _("Requesting SOCKS proxy connection to %s:%d\n"),
979 vpninfo->hostname, vpninfo->port);
981 buf[0] = 5; /* SOCKS version */
982 buf[1] = 1; /* CONNECT */
983 buf[2] = 0; /* Reserved */
984 buf[3] = 3; /* Address type is domain name */
985 buf[4] = strlen(vpninfo->hostname);
986 strcpy((char *)buf + 5, vpninfo->hostname);
987 i = strlen(vpninfo->hostname) + 5;
988 buf[i++] = vpninfo->port >> 8;
989 buf[i++] = vpninfo->port & 0xff;
991 if ((i = proxy_write(ssl_sock, buf, i))) {
992 vpn_progress(vpninfo, PRG_ERR,
993 _("Error writing connect request to SOCKS proxy: %s\n"),
997 /* Read 5 bytes -- up to and including the first byte of the returned
998 address (which might be the length byte of a domain name) */
999 if ((i = proxy_read(ssl_sock, buf, 5))) {
1000 vpn_progress(vpninfo, PRG_ERR,
1001 _("Error reading connect response from SOCKS proxy: %s\n"),
1006 vpn_progress(vpninfo, PRG_ERR,
1007 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1014 /* Connect responses contain an address */
1016 case 1: /* Legacy IP */
1019 case 3: /* Domain name */
1026 vpn_progress(vpninfo, PRG_ERR,
1027 _("Unexpected address type %02x in SOCKS connect response\n"),
1032 if ((i = proxy_read(ssl_sock, buf, i))) {
1033 vpn_progress(vpninfo, PRG_ERR,
1034 _("Error reading connect response from SOCKS proxy: %s\n"),
1041 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1043 char buf[MAX_BUF_LEN];
1046 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1047 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1048 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1049 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1050 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1051 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1052 sprintf(buf + strlen(buf), "\r\n");
1054 vpn_progress(vpninfo, PRG_INFO,
1055 _("Requesting HTTP proxy connection to %s:%d\n"),
1056 vpninfo->hostname, vpninfo->port);
1058 if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
1060 vpn_progress(vpninfo, PRG_ERR,
1061 _("Sending proxy request failed: %s\n"),
1066 if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
1067 vpn_progress(vpninfo, PRG_ERR,
1068 _("Error fetching proxy response\n"));
1072 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1073 buf[8] != ' ' || !(result = atoi(buf+9))) {
1074 vpn_progress(vpninfo, PRG_ERR,
1075 _("Failed to parse proxy response '%s'\n"), buf);
1079 if (result != 200) {
1080 vpn_progress(vpninfo, PRG_ERR,
1081 _("Proxy CONNECT request failed: %s\n"), buf);
1085 while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
1087 vpn_progress(vpninfo, PRG_ERR,
1088 _("Failed to read proxy response\n"));
1091 vpn_progress(vpninfo, PRG_ERR,
1092 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1099 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1101 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1102 return process_http_proxy(vpninfo, ssl_sock);
1104 if (!strcmp(vpninfo->proxy_type, "socks") ||
1105 !strcmp(vpninfo->proxy_type, "socks5"))
1106 return process_socks_proxy(vpninfo, ssl_sock);
1108 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1109 vpninfo->proxy_type);
1113 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1121 free(vpninfo->proxy_type);
1122 vpninfo->proxy_type = NULL;
1123 free(vpninfo->proxy);
1124 vpninfo->proxy = NULL;
1126 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1127 &vpninfo->proxy_port, NULL, 80);
1131 if (vpninfo->proxy_type &&
1132 strcmp(vpninfo->proxy_type, "http") &&
1133 strcmp(vpninfo->proxy_type, "socks") &&
1134 strcmp(vpninfo->proxy_type, "socks5")) {
1135 vpn_progress(vpninfo, PRG_ERR,
1136 _("Only http or socks(5) proxies supported\n"));
1137 free(vpninfo->proxy_type);
1138 vpninfo->proxy_type = NULL;
1139 free(vpninfo->proxy);
1140 vpninfo->proxy = NULL;