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);
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
549 #ifndef HAVE_ASPRINTF
552 static int oc_vasprintf(char **strp, const char *fmt, va_list ap)
558 int errno_save = -ENOMEM;
564 /* Use a copy of 'ap', preserving it in case we need to retry into
565 a larger buffer. 160 characters should be sufficient for most
566 strings in openconnect. */
569 #elif defined (HAVE___VA_COPY)
573 // You could try this.
578 len = vsnprintf(res, 160, fmt, ap2);
588 if (len >=0 && len < 160)
596 len2 = vsnprintf(res, len+1, fmt, ap);
597 if (len2 < 0 || len2 > len)
611 int openconnect__asprintf(char **strp, const char *fmt, ...)
617 ret = oc_vasprintf(strp, fmt, ap);
623 int internal_parse_url(char *url, char **res_proto, char **res_host,
624 int *res_port, char **res_path, int default_port)
627 char *host, *path, *port_str;
630 host = strstr(url, "://");
635 if (!strcasecmp(proto, "https"))
637 else if (!strcasecmp(proto, "http"))
639 else if (!strcasecmp(proto, "socks") ||
640 !strcasecmp(proto, "socks4") ||
641 !strcasecmp(proto, "socks5"))
644 return -EPROTONOSUPPORT;
654 path = strchr(host, '/');
658 port_str = strrchr(host, ':');
661 int new_port = strtol(port_str + 1, &end, 10);
670 *res_proto = proto ? strdup(proto) : NULL;
672 *res_host = strdup(host);
676 *res_path = (path && *path) ? strdup(path) : NULL;
678 /* Undo the damage we did to the original string */
688 * = 0, no cookie (user cancel)
689 * = 1, obtained cookie
691 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
693 struct vpn_option *opt, *next;
694 char buf[MAX_BUF_LEN];
695 char *form_buf = NULL;
697 char request_body[2048];
698 const char *request_body_type = NULL;
699 const char *method = "GET";
706 if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
707 vpn_progress(vpninfo, PRG_ERR,
708 _("Failed to open HTTPS connection to %s\n"),
714 * It would be nice to use cURL for this, but we really need to guarantee
715 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
716 * to have any way to let us provide our own socket read/write functions.
717 * We can only provide a socket _open_ function. Which would require having
718 * a socketpair() and servicing the "other" end of it.
720 * So we process the HTTP for ourselves...
722 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
723 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
724 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
725 sprintf(buf + strlen(buf), "Accept: */*\r\n");
726 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
728 if (vpninfo->cookies) {
729 sprintf(buf + strlen(buf), "Cookie: ");
730 for (opt = vpninfo->cookies; opt; opt = opt->next)
731 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
732 opt->value, opt->next ? "; " : "\r\n");
734 if (request_body_type) {
735 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
737 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
738 strlen(request_body));
740 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
741 if (request_body_type)
742 sprintf(buf + strlen(buf), "%s", request_body);
744 if (vpninfo->port == 443)
745 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
746 method, vpninfo->hostname,
747 vpninfo->urlpath ?: "");
749 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
750 method, vpninfo->hostname, vpninfo->port,
751 vpninfo->urlpath ?: "");
753 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
755 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
757 /* We'll already have complained about whatever offended us */
761 if (result != 200 && vpninfo->redirect_url) {
763 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
764 /* New host. Tear down the existing connection and make a new one */
769 free(vpninfo->urlpath);
770 vpninfo->urlpath = NULL;
772 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
774 vpn_progress(vpninfo, PRG_ERR,
775 _("Failed to parse redirected URL '%s': %s\n"),
776 vpninfo->redirect_url, strerror(-ret));
777 free(vpninfo->redirect_url);
782 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
783 free(vpninfo->hostname);
784 vpninfo->hostname = host;
785 vpninfo->port = port;
787 /* Kill the existing connection, and a new one will happen */
788 free(vpninfo->peer_addr);
789 vpninfo->peer_addr = NULL;
790 if (vpninfo->https_ssl) {
791 SSL_free(vpninfo->https_ssl);
792 vpninfo->https_ssl = NULL;
793 close(vpninfo->ssl_fd);
794 vpninfo->ssl_fd = -1;
797 for (opt = vpninfo->cookies; opt; opt = next) {
804 vpninfo->cookies = NULL;
808 free(vpninfo->redirect_url);
809 vpninfo->redirect_url = NULL;
812 } else if (vpninfo->redirect_url[0] == '/') {
813 /* Absolute redirect within same host */
814 free(vpninfo->urlpath);
815 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
816 free(vpninfo->redirect_url);
817 vpninfo->redirect_url = NULL;
820 char *lastslash = NULL;
821 if (vpninfo->urlpath)
822 lastslash = strrchr(vpninfo->urlpath, '/');
824 free(vpninfo->urlpath);
825 vpninfo->urlpath = vpninfo->redirect_url;
826 vpninfo->redirect_url = NULL;
828 char *oldurl = vpninfo->urlpath;
830 vpninfo->urlpath = NULL;
831 if (asprintf(&vpninfo->urlpath, "%s/%s",
832 oldurl, vpninfo->redirect_url) == -1) {
834 vpn_progress(vpninfo, PRG_ERR,
835 _("Allocating new path for relative redirect failed: %s\n"),
840 free(vpninfo->redirect_url);
841 vpninfo->redirect_url = NULL;
846 if (!form_buf || result != 200) {
847 vpn_progress(vpninfo, PRG_ERR,
848 _("Unexpected %d result from server\n"),
853 if (vpninfo->csd_stuburl) {
854 /* This is the CSD stub script, which we now need to run */
855 result = run_csd_script(vpninfo, form_buf, buflen);
861 /* Now we'll be redirected to the waiturl */
864 if (strncmp(form_buf, "<?xml", 5)) {
865 /* Not XML? Perhaps it's HTML with a refresh... */
866 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
867 vpn_progress(vpninfo, PRG_INFO,
868 _("Refreshing %s after 1 second...\n"),
873 vpn_progress(vpninfo, PRG_ERR,
874 _("Unknown response from server\n"));
879 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
880 &method, &request_body_type);
890 /* A return value of 2 means the XML form indicated
891 success. We _should_ have a cookie... */
893 for (opt = vpninfo->cookies; opt; opt = opt->next) {
895 if (!strcmp(opt->option, "webvpn"))
896 vpninfo->cookie = opt->value;
897 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
898 char *tok = opt->value;
899 char *bu = NULL, *fu = NULL, *sha = NULL;
902 if (tok != opt->value)
905 if (!strncmp(tok, "bu:", 3))
907 else if (!strncmp(tok, "fu:", 3))
909 else if (!strncmp(tok, "fh:", 3)) {
910 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
911 SHA_DIGEST_LENGTH * 2))
915 } while ((tok = strchr(tok, '&')));
918 fetch_config(vpninfo, bu, fu, sha);
921 if (vpninfo->csd_scriptname) {
922 unlink(vpninfo->csd_scriptname);
923 free(vpninfo->csd_scriptname);
924 vpninfo->csd_scriptname = NULL;
929 char *openconnect_create_useragent(const char *base)
933 if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
939 static int proxy_gets(int fd, char *buf, size_t len)
947 while ( (ret = read(fd, buf + i, 1)) == 1) {
948 if (buf[i] == '\n') {
950 if (i && buf[i-1] == '\r') {
970 static int proxy_write(int fd, unsigned char *buf, size_t len)
974 for (count = 0; count < len; ) {
975 int i = write(fd, buf + count, len - count);
984 static int proxy_read(int fd, unsigned char *buf, size_t len)
988 for (count = 0; count < len; ) {
989 int i = read(fd, buf + count, len - count);
998 static const char *socks_errors[] = {
999 N_("request granted"),
1000 N_("general failure"),
1001 N_("connection not allowed by ruleset"),
1002 N_("network unreachable"),
1003 N_("host unreachable"),
1004 N_("connection refused by destination host"),
1006 N_("command not supported / protocol error"),
1007 N_("address type not supported")
1010 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1012 unsigned char buf[1024];
1015 buf[0] = 5; /* SOCKS version */
1016 buf[1] = 1; /* # auth methods */
1017 buf[2] = 0; /* No auth supported */
1019 if ((i = proxy_write(ssl_sock, buf, 3))) {
1020 vpn_progress(vpninfo, PRG_ERR,
1021 _("Error writing auth request to SOCKS proxy: %s\n"),
1026 if ((i = proxy_read(ssl_sock, buf, 2))) {
1027 vpn_progress(vpninfo, PRG_ERR,
1028 _("Error reading auth response from SOCKS proxy: %s\n"),
1033 vpn_progress(vpninfo, PRG_ERR,
1034 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1040 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1041 vpn_progress(vpninfo, PRG_ERR,
1042 _("SOCKS proxy error %02x: %s\n"),
1043 buf[1], _(socks_errors[buf[1]]));
1045 vpn_progress(vpninfo, PRG_ERR,
1046 _("SOCKS proxy error %02x\n"),
1051 vpn_progress(vpninfo, PRG_INFO,
1052 _("Requesting SOCKS proxy connection to %s:%d\n"),
1053 vpninfo->hostname, vpninfo->port);
1055 buf[0] = 5; /* SOCKS version */
1056 buf[1] = 1; /* CONNECT */
1057 buf[2] = 0; /* Reserved */
1058 buf[3] = 3; /* Address type is domain name */
1059 buf[4] = strlen(vpninfo->hostname);
1060 strcpy((char *)buf + 5, vpninfo->hostname);
1061 i = strlen(vpninfo->hostname) + 5;
1062 buf[i++] = vpninfo->port >> 8;
1063 buf[i++] = vpninfo->port & 0xff;
1065 if ((i = proxy_write(ssl_sock, buf, i))) {
1066 vpn_progress(vpninfo, PRG_ERR,
1067 _("Error writing connect request to SOCKS proxy: %s\n"),
1071 /* Read 5 bytes -- up to and including the first byte of the returned
1072 address (which might be the length byte of a domain name) */
1073 if ((i = proxy_read(ssl_sock, buf, 5))) {
1074 vpn_progress(vpninfo, PRG_ERR,
1075 _("Error reading connect response from SOCKS proxy: %s\n"),
1080 vpn_progress(vpninfo, PRG_ERR,
1081 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1088 /* Connect responses contain an address */
1090 case 1: /* Legacy IP */
1093 case 3: /* Domain name */
1100 vpn_progress(vpninfo, PRG_ERR,
1101 _("Unexpected address type %02x in SOCKS connect response\n"),
1106 if ((i = proxy_read(ssl_sock, buf, i))) {
1107 vpn_progress(vpninfo, PRG_ERR,
1108 _("Error reading connect response from SOCKS proxy: %s\n"),
1115 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1117 char buf[MAX_BUF_LEN];
1120 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1121 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1122 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1123 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1124 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1125 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1126 sprintf(buf + strlen(buf), "\r\n");
1128 vpn_progress(vpninfo, PRG_INFO,
1129 _("Requesting HTTP proxy connection to %s:%d\n"),
1130 vpninfo->hostname, vpninfo->port);
1132 if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
1134 vpn_progress(vpninfo, PRG_ERR,
1135 _("Sending proxy request failed: %s\n"),
1140 if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
1141 vpn_progress(vpninfo, PRG_ERR,
1142 _("Error fetching proxy response\n"));
1146 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1147 buf[8] != ' ' || !(result = atoi(buf+9))) {
1148 vpn_progress(vpninfo, PRG_ERR,
1149 _("Failed to parse proxy response '%s'\n"), buf);
1153 if (result != 200) {
1154 vpn_progress(vpninfo, PRG_ERR,
1155 _("Proxy CONNECT request failed: %s\n"), buf);
1159 while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
1161 vpn_progress(vpninfo, PRG_ERR,
1162 _("Failed to read proxy response\n"));
1165 vpn_progress(vpninfo, PRG_ERR,
1166 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1173 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1175 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1176 return process_http_proxy(vpninfo, ssl_sock);
1178 if (!strcmp(vpninfo->proxy_type, "socks") ||
1179 !strcmp(vpninfo->proxy_type, "socks5"))
1180 return process_socks_proxy(vpninfo, ssl_sock);
1182 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1183 vpninfo->proxy_type);
1187 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1195 free(vpninfo->proxy_type);
1196 vpninfo->proxy_type = NULL;
1197 free(vpninfo->proxy);
1198 vpninfo->proxy = NULL;
1200 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1201 &vpninfo->proxy_port, NULL, 80);
1205 if (vpninfo->proxy_type &&
1206 strcmp(vpninfo->proxy_type, "http") &&
1207 strcmp(vpninfo->proxy_type, "socks") &&
1208 strcmp(vpninfo->proxy_type, "socks5")) {
1209 vpn_progress(vpninfo, PRG_ERR,
1210 _("Only http or socks(5) proxies supported\n"));
1211 free(vpninfo->proxy_type);
1212 vpninfo->proxy_type = NULL;
1213 free(vpninfo->proxy);
1214 vpninfo->proxy = NULL;