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 %ld\n"),
449 (long)vpninfo->uid_csd);
452 if (!(pw = getpwuid(vpninfo->uid_csd))) {
453 fprintf(stderr, _("Invalid user uid=%ld\n"),
454 (long)vpninfo->uid_csd);
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);
528 #ifndef HAVE_STRCASESTR
529 static char *openconnect__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 openconnect__strcasestr
550 int internal_parse_url(char *url, char **res_proto, char **res_host,
551 int *res_port, char **res_path, int default_port)
554 char *host, *path, *port_str;
557 host = strstr(url, "://");
562 if (!strcasecmp(proto, "https"))
564 else if (!strcasecmp(proto, "http"))
566 else if (!strcasecmp(proto, "socks") ||
567 !strcasecmp(proto, "socks4") ||
568 !strcasecmp(proto, "socks5"))
571 return -EPROTONOSUPPORT;
581 path = strchr(host, '/');
585 port_str = strrchr(host, ':');
588 int new_port = strtol(port_str + 1, &end, 10);
597 *res_proto = proto ? strdup(proto) : NULL;
599 *res_host = strdup(host);
603 *res_path = (path && *path) ? strdup(path) : NULL;
605 /* Undo the damage we did to the original string */
615 * = 0, no cookie (user cancel)
616 * = 1, obtained cookie
618 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
620 struct vpn_option *opt, *next;
621 char buf[MAX_BUF_LEN];
622 char *form_buf = NULL;
624 char request_body[2048];
625 const char *request_body_type = NULL;
626 const char *method = "GET";
633 if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
634 vpn_progress(vpninfo, PRG_ERR,
635 _("Failed to open HTTPS connection to %s\n"),
641 * It would be nice to use cURL for this, but we really need to guarantee
642 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
643 * to have any way to let us provide our own socket read/write functions.
644 * We can only provide a socket _open_ function. Which would require having
645 * a socketpair() and servicing the "other" end of it.
647 * So we process the HTTP for ourselves...
649 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
650 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
651 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
652 sprintf(buf + strlen(buf), "Accept: */*\r\n");
653 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
655 if (vpninfo->cookies) {
656 sprintf(buf + strlen(buf), "Cookie: ");
657 for (opt = vpninfo->cookies; opt; opt = opt->next)
658 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
659 opt->value, opt->next ? "; " : "\r\n");
661 if (request_body_type) {
662 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
664 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
665 strlen(request_body));
667 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
668 if (request_body_type)
669 sprintf(buf + strlen(buf), "%s", request_body);
671 if (vpninfo->port == 443)
672 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
673 method, vpninfo->hostname,
674 vpninfo->urlpath ?: "");
676 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
677 method, vpninfo->hostname, vpninfo->port,
678 vpninfo->urlpath ?: "");
680 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
682 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
684 /* We'll already have complained about whatever offended us */
688 if (result != 200 && vpninfo->redirect_url) {
690 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
691 /* New host. Tear down the existing connection and make a new one */
696 free(vpninfo->urlpath);
697 vpninfo->urlpath = NULL;
699 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
701 vpn_progress(vpninfo, PRG_ERR,
702 _("Failed to parse redirected URL '%s': %s\n"),
703 vpninfo->redirect_url, strerror(-ret));
704 free(vpninfo->redirect_url);
709 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
710 free(vpninfo->hostname);
711 vpninfo->hostname = host;
712 vpninfo->port = port;
714 /* Kill the existing connection, and a new one will happen */
715 free(vpninfo->peer_addr);
716 vpninfo->peer_addr = NULL;
717 if (vpninfo->https_ssl) {
718 SSL_free(vpninfo->https_ssl);
719 vpninfo->https_ssl = NULL;
720 close(vpninfo->ssl_fd);
721 vpninfo->ssl_fd = -1;
724 for (opt = vpninfo->cookies; opt; opt = next) {
731 vpninfo->cookies = NULL;
735 free(vpninfo->redirect_url);
736 vpninfo->redirect_url = NULL;
739 } else if (vpninfo->redirect_url[0] == '/') {
740 /* Absolute redirect within same host */
741 free(vpninfo->urlpath);
742 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
743 free(vpninfo->redirect_url);
744 vpninfo->redirect_url = NULL;
747 char *lastslash = NULL;
748 if (vpninfo->urlpath)
749 lastslash = strrchr(vpninfo->urlpath, '/');
751 free(vpninfo->urlpath);
752 vpninfo->urlpath = vpninfo->redirect_url;
753 vpninfo->redirect_url = NULL;
755 char *oldurl = vpninfo->urlpath;
757 vpninfo->urlpath = NULL;
758 if (asprintf(&vpninfo->urlpath, "%s/%s",
759 oldurl, vpninfo->redirect_url) == -1) {
761 vpn_progress(vpninfo, PRG_ERR,
762 _("Allocating new path for relative redirect failed: %s\n"),
767 free(vpninfo->redirect_url);
768 vpninfo->redirect_url = NULL;
773 if (!form_buf || result != 200) {
774 vpn_progress(vpninfo, PRG_ERR,
775 _("Unexpected %d result from server\n"),
780 if (vpninfo->csd_stuburl) {
781 /* This is the CSD stub script, which we now need to run */
782 result = run_csd_script(vpninfo, form_buf, buflen);
788 /* Now we'll be redirected to the waiturl */
791 if (strncmp(form_buf, "<?xml", 5)) {
792 /* Not XML? Perhaps it's HTML with a refresh... */
793 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
794 vpn_progress(vpninfo, PRG_INFO,
795 _("Refreshing %s after 1 second...\n"),
800 vpn_progress(vpninfo, PRG_ERR,
801 _("Unknown response from server\n"));
806 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
807 &method, &request_body_type);
817 /* A return value of 2 means the XML form indicated
818 success. We _should_ have a cookie... */
820 for (opt = vpninfo->cookies; opt; opt = opt->next) {
822 if (!strcmp(opt->option, "webvpn"))
823 vpninfo->cookie = opt->value;
824 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
825 char *tok = opt->value;
826 char *bu = NULL, *fu = NULL, *sha = NULL;
829 if (tok != opt->value)
832 if (!strncmp(tok, "bu:", 3))
834 else if (!strncmp(tok, "fu:", 3))
836 else if (!strncmp(tok, "fh:", 3)) {
837 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
838 SHA_DIGEST_LENGTH * 2))
842 } while ((tok = strchr(tok, '&')));
845 fetch_config(vpninfo, bu, fu, sha);
848 if (vpninfo->csd_scriptname) {
849 unlink(vpninfo->csd_scriptname);
850 free(vpninfo->csd_scriptname);
851 vpninfo->csd_scriptname = NULL;
856 char *openconnect_create_useragent(const char *base)
860 if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
866 static int proxy_gets(int fd, char *buf, size_t len)
874 while ( (ret = read(fd, buf + i, 1)) == 1) {
875 if (buf[i] == '\n') {
877 if (i && buf[i-1] == '\r') {
897 static int proxy_write(int fd, unsigned char *buf, size_t len)
901 for (count = 0; count < len; ) {
902 int i = write(fd, buf + count, len - count);
911 static int proxy_read(int fd, unsigned char *buf, size_t len)
915 for (count = 0; count < len; ) {
916 int i = read(fd, buf + count, len - count);
925 static const char *socks_errors[] = {
926 N_("request granted"),
927 N_("general failure"),
928 N_("connection not allowed by ruleset"),
929 N_("network unreachable"),
930 N_("host unreachable"),
931 N_("connection refused by destination host"),
933 N_("command not supported / protocol error"),
934 N_("address type not supported")
937 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
939 unsigned char buf[1024];
942 buf[0] = 5; /* SOCKS version */
943 buf[1] = 1; /* # auth methods */
944 buf[2] = 0; /* No auth supported */
946 if ((i = proxy_write(ssl_sock, buf, 3))) {
947 vpn_progress(vpninfo, PRG_ERR,
948 _("Error writing auth request to SOCKS proxy: %s\n"),
953 if ((i = proxy_read(ssl_sock, buf, 2))) {
954 vpn_progress(vpninfo, PRG_ERR,
955 _("Error reading auth response from SOCKS proxy: %s\n"),
960 vpn_progress(vpninfo, PRG_ERR,
961 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
967 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
968 vpn_progress(vpninfo, PRG_ERR,
969 _("SOCKS proxy error %02x: %s\n"),
970 buf[1], _(socks_errors[buf[1]]));
972 vpn_progress(vpninfo, PRG_ERR,
973 _("SOCKS proxy error %02x\n"),
978 vpn_progress(vpninfo, PRG_INFO,
979 _("Requesting SOCKS proxy connection to %s:%d\n"),
980 vpninfo->hostname, vpninfo->port);
982 buf[0] = 5; /* SOCKS version */
983 buf[1] = 1; /* CONNECT */
984 buf[2] = 0; /* Reserved */
985 buf[3] = 3; /* Address type is domain name */
986 buf[4] = strlen(vpninfo->hostname);
987 strcpy((char *)buf + 5, vpninfo->hostname);
988 i = strlen(vpninfo->hostname) + 5;
989 buf[i++] = vpninfo->port >> 8;
990 buf[i++] = vpninfo->port & 0xff;
992 if ((i = proxy_write(ssl_sock, buf, i))) {
993 vpn_progress(vpninfo, PRG_ERR,
994 _("Error writing connect request to SOCKS proxy: %s\n"),
998 /* Read 5 bytes -- up to and including the first byte of the returned
999 address (which might be the length byte of a domain name) */
1000 if ((i = proxy_read(ssl_sock, buf, 5))) {
1001 vpn_progress(vpninfo, PRG_ERR,
1002 _("Error reading connect response from SOCKS proxy: %s\n"),
1007 vpn_progress(vpninfo, PRG_ERR,
1008 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1015 /* Connect responses contain an address */
1017 case 1: /* Legacy IP */
1020 case 3: /* Domain name */
1027 vpn_progress(vpninfo, PRG_ERR,
1028 _("Unexpected address type %02x in SOCKS connect response\n"),
1033 if ((i = proxy_read(ssl_sock, buf, i))) {
1034 vpn_progress(vpninfo, PRG_ERR,
1035 _("Error reading connect response from SOCKS proxy: %s\n"),
1042 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1044 char buf[MAX_BUF_LEN];
1047 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1048 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1049 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1050 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1051 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1052 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1053 sprintf(buf + strlen(buf), "\r\n");
1055 vpn_progress(vpninfo, PRG_INFO,
1056 _("Requesting HTTP proxy connection to %s:%d\n"),
1057 vpninfo->hostname, vpninfo->port);
1059 if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
1061 vpn_progress(vpninfo, PRG_ERR,
1062 _("Sending proxy request failed: %s\n"),
1067 if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
1068 vpn_progress(vpninfo, PRG_ERR,
1069 _("Error fetching proxy response\n"));
1073 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1074 buf[8] != ' ' || !(result = atoi(buf+9))) {
1075 vpn_progress(vpninfo, PRG_ERR,
1076 _("Failed to parse proxy response '%s'\n"), buf);
1080 if (result != 200) {
1081 vpn_progress(vpninfo, PRG_ERR,
1082 _("Proxy CONNECT request failed: %s\n"), buf);
1086 while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
1088 vpn_progress(vpninfo, PRG_ERR,
1089 _("Failed to read proxy response\n"));
1092 vpn_progress(vpninfo, PRG_ERR,
1093 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1100 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1102 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1103 return process_http_proxy(vpninfo, ssl_sock);
1105 if (!strcmp(vpninfo->proxy_type, "socks") ||
1106 !strcmp(vpninfo->proxy_type, "socks5"))
1107 return process_socks_proxy(vpninfo, ssl_sock);
1109 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1110 vpninfo->proxy_type);
1114 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1122 free(vpninfo->proxy_type);
1123 vpninfo->proxy_type = NULL;
1124 free(vpninfo->proxy);
1125 vpninfo->proxy = NULL;
1127 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1128 &vpninfo->proxy_port, NULL, 80);
1132 if (vpninfo->proxy_type &&
1133 strcmp(vpninfo->proxy_type, "http") &&
1134 strcmp(vpninfo->proxy_type, "socks") &&
1135 strcmp(vpninfo->proxy_type, "socks5")) {
1136 vpn_progress(vpninfo, PRG_ERR,
1137 _("Only http or socks(5) proxies supported\n"));
1138 free(vpninfo->proxy_type);
1139 vpninfo->proxy_type = NULL;
1140 free(vpninfo->proxy);
1141 vpninfo->proxy = NULL;