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
35 #include <sys/types.h>
37 #include <openssl/ssl.h>
38 #include <openssl/err.h>
39 #include <openssl/engine.h>
41 #include "openconnect-internal.h"
43 static int proxy_write(int fd, unsigned char *buf, size_t len);
45 #define MAX_BUF_LEN 131072
47 * We didn't really want to have to do this for ourselves -- one might have
48 * thought that it would be available in a library somewhere. But neither
49 * cURL nor Neon have reliable cross-platform ways of either using a cert
50 * from the TPM, or just reading from / writing to a transport which is
51 * provided by their caller.
54 static int http_add_cookie(struct openconnect_info *vpninfo,
55 const char *option, const char *value)
57 struct vpn_option *new, **this;
60 new = malloc(sizeof(*new));
62 vpn_progress(vpninfo, PRG_ERR,
63 _("No memory for allocating cookies\n"));
67 new->option = strdup(option);
68 new->value = strdup(value);
69 if (!new->option || !new->value) {
76 /* Kill cookie; don't replace it */
79 for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
80 if (!strcmp(option, (*this)->option)) {
81 /* Replace existing cookie */
83 new->next = (*this)->next;
87 free((*this)->option);
101 #define BODY_HTTP10 -1
102 #define BODY_CHUNKED -2
104 static int process_http_response(struct openconnect_info *vpninfo, int *result,
105 int (*header_cb)(struct openconnect_info *, char *, char *),
108 char buf[MAX_BUF_LEN];
110 int bodylen = BODY_HTTP10;
116 if (openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)) < 0) {
117 vpn_progress(vpninfo, PRG_ERR,
118 _("Error fetching HTTPS response\n"));
122 if (!strncmp(buf, "HTTP/1.0 ", 9))
125 if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
126 vpn_progress(vpninfo, PRG_ERR,
127 _("Failed to parse HTTP response '%s'\n"), buf);
131 vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
132 _("Got HTTP response: %s\n"), buf);
135 while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
139 vpn_progress(vpninfo, PRG_ERR,
140 _("Error processing HTTP response\n"));
143 colon = strchr(buf, ':');
145 vpn_progress(vpninfo, PRG_ERR,
146 _("Ignoring unknown HTTP response line '%s'\n"), buf);
153 /* Handle Set-Cookie first so that we can avoid printing the
154 webvpn cookie in the verbose debug output */
155 if (!strcasecmp(buf, "Set-Cookie")) {
156 char *semicolon = strchr(colon, ';');
157 const char *print_equals;
158 char *equals = strchr(colon, '=');
165 vpn_progress(vpninfo, PRG_ERR,
166 _("Invalid cookie offered: %s\n"), buf);
171 print_equals = equals;
172 /* Don't print the webvpn cookie unless it's empty; we don't
173 want people posting it in public with debugging output */
174 if (!strcmp(colon, "webvpn") && *equals)
175 print_equals = _("<elided>");
176 vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
177 buf, colon, print_equals, semicolon?";":"",
178 semicolon?(semicolon+1):"");
180 ret = http_add_cookie(vpninfo, colon, equals);
184 vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
187 if (!strcasecmp(buf, "Connection")) {
188 if (!strcasecmp(colon, "Close"))
191 /* This might seem reasonable, but in fact it breaks
192 certificate authentication with some servers. If
193 they give an HTTP/1.0 response, even if they
194 explicitly give a Connection: Keep-Alive header,
195 just close the connection. */
196 else if (!strcasecmp(colon, "Keep-Alive"))
200 if (!strcasecmp(buf, "Location")) {
201 vpninfo->redirect_url = strdup(colon);
202 if (!vpninfo->redirect_url)
205 if (!strcasecmp(buf, "Content-Length")) {
206 bodylen = atoi(colon);
208 vpn_progress(vpninfo, PRG_ERR,
209 _("Response body has negative size (%d)\n"),
214 if (!strcasecmp(buf, "Transfer-Encoding")) {
215 if (!strcasecmp(colon, "chunked"))
216 bodylen = BODY_CHUNKED;
218 vpn_progress(vpninfo, PRG_ERR,
219 _("Unknown Transfer-Encoding: %s\n"),
224 if (header_cb && !strncmp(buf, "X-", 2))
225 header_cb(vpninfo, buf, colon);
228 /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
232 /* Now the body, if there is one */
233 vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"),
234 bodylen==BODY_HTTP10?"http 1.0" :
235 bodylen==BODY_CHUNKED?"chunked" : "length: ",
238 /* If we were given Content-Length, it's nice and easy... */
240 body = malloc(bodylen + 1);
243 while (done < bodylen) {
244 i = SSL_read(vpninfo->https_ssl, body + done, bodylen - done);
246 vpn_progress(vpninfo, PRG_ERR,
247 _("Error reading HTTP response body\n"));
253 } else if (bodylen == BODY_CHUNKED) {
254 /* ... else, chunked */
255 while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
256 int chunklen, lastchunk = 0;
259 vpn_progress(vpninfo, PRG_ERR,
260 _("Error fetching chunk header\n"));
263 chunklen = strtol(buf, NULL, 16);
268 body = realloc(body, done + chunklen + 1);
272 i = SSL_read(vpninfo->https_ssl, body + done, chunklen);
274 vpn_progress(vpninfo, PRG_ERR,
275 _("Error reading HTTP response body\n"));
283 if ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
285 vpn_progress(vpninfo, PRG_ERR,
286 _("Error fetching HTTP response body\n"));
288 vpn_progress(vpninfo, PRG_ERR,
289 _("Error in chunked decoding. Expected '', got: '%s'"),
299 } else if (bodylen == BODY_HTTP10) {
301 vpn_progress(vpninfo, PRG_ERR,
302 _("Cannot receive HTTP 1.0 body without closing connection\n"));
306 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
308 body = realloc(body, done + 16384);
311 i = SSL_read(vpninfo->https_ssl, body + done, 16384);
313 body = realloc(body, done + 1);
322 if (closeconn || vpninfo->no_http_keepalive) {
323 SSL_free(vpninfo->https_ssl);
324 vpninfo->https_ssl = NULL;
325 close(vpninfo->ssl_fd);
326 vpninfo->ssl_fd = -1;
335 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
338 struct vpn_option *opt;
339 char buf[MAX_BUF_LEN];
340 char *config_buf = NULL;
342 unsigned char local_sha1_bin[SHA_DIGEST_LENGTH];
343 char local_sha1_ascii[(SHA_DIGEST_LENGTH * 2)+1];
347 sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
348 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
349 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
350 sprintf(buf + strlen(buf), "Accept: */*\r\n");
351 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
353 if (vpninfo->cookies) {
354 sprintf(buf + strlen(buf), "Cookie: ");
355 for (opt = vpninfo->cookies; opt; opt = opt->next)
356 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
357 opt->value, opt->next ? "; " : "\r\n");
359 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
361 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
363 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
365 /* We'll already have complained about whatever offended us */
375 EVP_Digest(config_buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
376 EVP_MD_CTX_cleanup(&c);
378 for (i = 0; i < SHA_DIGEST_LENGTH; i++)
379 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
381 if (strcasecmp(server_sha1, local_sha1_ascii)) {
382 vpn_progress(vpninfo, PRG_ERR,
383 _("Downloaded config file did not match intended SHA1\n"));
388 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
393 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
398 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
399 vpn_progress(vpninfo, PRG_ERR,
400 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
401 "This facility is disabled by default for security reasons, so you may wish to enable it."));
406 vpn_progress(vpninfo, PRG_INFO,
407 _("Trying to run Linux CSD trojan script."));
410 sprintf(fname, "/tmp/csdXXXXXX");
414 vpn_progress(vpninfo, PRG_ERR,
415 _("Failed to open temporary CSD script file: %s\n"),
420 ret = proxy_write(fd, (void *)buf, buflen);
422 vpn_progress(vpninfo, PRG_ERR,
423 _("Failed to write temporary CSD script file: %s\n"),
431 X509 *scert = SSL_get_peer_certificate(vpninfo->https_ssl);
432 X509 *ccert = SSL_get_certificate(vpninfo->https_ssl);
433 char scertbuf[EVP_MAX_MD_SIZE * 2 + 1];
434 char ccertbuf[EVP_MAX_MD_SIZE * 2 + 1];
438 if (vpninfo->uid_csd != getuid()) {
441 if (setuid(vpninfo->uid_csd)) {
442 fprintf(stderr, _("Failed to set uid %d\n"),
446 if (!(pw = getpwuid(vpninfo->uid_csd))) {
447 fprintf(stderr, _("Invalid user uid=%d\n"),
451 setenv("HOME", pw->pw_dir, 1);
452 if (chdir(pw->pw_dir)) {
453 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
454 pw->pw_dir, strerror(errno));
458 if (vpninfo->uid_csd == 0 && !vpninfo->csd_wrapper) {
459 fprintf(stderr, _("Warning: you are running insecure "
460 "CSD code with root privileges\n"
461 "\t Use command line option \"--csd-user\"\n"));
463 if (vpninfo->uid_csd_given == 2) {
464 /* The NM tool really needs not to get spurious output
465 on stdout, which the CSD trojan spews. */
468 if (vpninfo->csd_wrapper)
469 csd_argv[i++] = vpninfo->csd_wrapper;
470 csd_argv[i++] = fname;
471 csd_argv[i++]= (char *)"-ticket";
472 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
474 csd_argv[i++]= (char *)"-stub";
475 csd_argv[i++]= (char *)"\"0\"";
476 csd_argv[i++]= (char *)"-group";
477 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
480 get_cert_md5_fingerprint(vpninfo, scert, scertbuf);
482 get_cert_md5_fingerprint(vpninfo, ccert, ccertbuf);
486 csd_argv[i++]= (char *)"-certhash";
487 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
489 csd_argv[i++]= (char *)"-url";
490 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
492 /* WTF would it want to know this for? */
493 csd_argv[i++]= (char *)"-vpnclient";
494 csd_argv[i++]= (char *)"\"/opt/cisco/vpn/bin/vpnui";
495 csd_argv[i++]= (char *)"-connect";
496 if (asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl) == -1)
498 csd_argv[i++]= (char *)"-connectparam";
499 if (asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token) == -1)
501 csd_argv[i++]= (char *)"-langselen";
502 csd_argv[i++] = NULL;
504 execv(csd_argv[0], csd_argv);
505 vpn_progress(vpninfo, PRG_ERR,
506 _("Failed to exec CSD script %s\n"), csd_argv[0]);
510 free(vpninfo->csd_stuburl);
511 vpninfo->csd_stuburl = NULL;
512 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
513 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
514 vpninfo->csd_waiturl = NULL;
515 vpninfo->csd_scriptname = strdup(fname);
517 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
523 char *local_strcasestr(const char *haystack, const char *needle)
525 int hlen = strlen(haystack);
526 int nlen = strlen(needle);
529 for (i = 0; i < hlen - nlen + 1; i++) {
530 for (j = 0; j < nlen; j++) {
531 if (tolower(haystack[i + j]) !=
536 return (char *)haystack + i;
540 #define strcasestr local_strcasestr
543 int internal_parse_url(char *url, char **res_proto, char **res_host,
544 int *res_port, char **res_path, int default_port)
547 char *host, *path, *port_str;
550 host = strstr(url, "://");
555 if (!strcasecmp(proto, "https"))
557 else if (!strcasecmp(proto, "http"))
559 else if (!strcasecmp(proto, "socks") ||
560 !strcasecmp(proto, "socks4") ||
561 !strcasecmp(proto, "socks5"))
564 return -EPROTONOSUPPORT;
574 path = strchr(host, '/');
578 port_str = strrchr(host, ':');
581 int new_port = strtol(port_str + 1, &end, 10);
590 *res_proto = proto ? strdup(proto) : NULL;
592 *res_host = strdup(host);
596 *res_path = (path && *path) ? strdup(path) : NULL;
598 /* Undo the damage we did to the original string */
608 * = 0, no cookie (user cancel)
609 * = 1, obtained cookie
611 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
613 struct vpn_option *opt, *next;
614 char buf[MAX_BUF_LEN];
615 char *form_buf = NULL;
617 char request_body[2048];
618 const char *request_body_type = NULL;
619 const char *method = "GET";
626 if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
627 vpn_progress(vpninfo, PRG_ERR,
628 _("Failed to open HTTPS connection to %s\n"),
634 * It would be nice to use cURL for this, but we really need to guarantee
635 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
636 * to have any way to let us provide our own socket read/write functions.
637 * We can only provide a socket _open_ function. Which would require having
638 * a socketpair() and servicing the "other" end of it.
640 * So we process the HTTP for ourselves...
642 sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
643 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
644 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
645 sprintf(buf + strlen(buf), "Accept: */*\r\n");
646 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
648 if (vpninfo->cookies) {
649 sprintf(buf + strlen(buf), "Cookie: ");
650 for (opt = vpninfo->cookies; opt; opt = opt->next)
651 sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
652 opt->value, opt->next ? "; " : "\r\n");
654 if (request_body_type) {
655 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
657 sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
658 strlen(request_body));
660 sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
661 if (request_body_type)
662 sprintf(buf + strlen(buf), "%s", request_body);
664 if (vpninfo->port == 443)
665 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
666 method, vpninfo->hostname,
667 vpninfo->urlpath ?: "");
669 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
670 method, vpninfo->hostname, vpninfo->port,
671 vpninfo->urlpath ?: "");
673 SSL_write(vpninfo->https_ssl, buf, strlen(buf));
675 buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
677 /* We'll already have complained about whatever offended us */
681 if (result != 200 && vpninfo->redirect_url) {
683 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
684 /* New host. Tear down the existing connection and make a new one */
689 free(vpninfo->urlpath);
690 vpninfo->urlpath = NULL;
692 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
694 vpn_progress(vpninfo, PRG_ERR,
695 _("Failed to parse redirected URL '%s': %s\n"),
696 vpninfo->redirect_url, strerror(-ret));
697 free(vpninfo->redirect_url);
702 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
703 free(vpninfo->hostname);
704 vpninfo->hostname = host;
705 vpninfo->port = port;
707 /* Kill the existing connection, and a new one will happen */
708 free(vpninfo->peer_addr);
709 vpninfo->peer_addr = NULL;
710 if (vpninfo->https_ssl) {
711 SSL_free(vpninfo->https_ssl);
712 vpninfo->https_ssl = NULL;
713 close(vpninfo->ssl_fd);
714 vpninfo->ssl_fd = -1;
717 for (opt = vpninfo->cookies; opt; opt = next) {
724 vpninfo->cookies = NULL;
728 free(vpninfo->redirect_url);
729 vpninfo->redirect_url = NULL;
732 } else if (vpninfo->redirect_url[0] == '/') {
733 /* Absolute redirect within same host */
734 free(vpninfo->urlpath);
735 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
736 free(vpninfo->redirect_url);
737 vpninfo->redirect_url = NULL;
740 char *lastslash = NULL;
741 if (vpninfo->urlpath)
742 lastslash = strrchr(vpninfo->urlpath, '/');
744 free(vpninfo->urlpath);
745 vpninfo->urlpath = vpninfo->redirect_url;
746 vpninfo->redirect_url = NULL;
748 char *oldurl = vpninfo->urlpath;
750 vpninfo->urlpath = NULL;
751 if (asprintf(&vpninfo->urlpath, "%s/%s",
752 oldurl, vpninfo->redirect_url) == -1) {
754 vpn_progress(vpninfo, PRG_ERR,
755 _("Allocating new path for relative redirect failed: %s\n"),
760 free(vpninfo->redirect_url);
761 vpninfo->redirect_url = NULL;
766 if (!form_buf || result != 200) {
767 vpn_progress(vpninfo, PRG_ERR,
768 _("Unexpected %d result from server\n"),
773 if (vpninfo->csd_stuburl) {
774 /* This is the CSD stub script, which we now need to run */
775 result = run_csd_script(vpninfo, form_buf, buflen);
781 /* Now we'll be redirected to the waiturl */
784 if (strncmp(form_buf, "<?xml", 5)) {
785 /* Not XML? Perhaps it's HTML with a refresh... */
786 if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
787 vpn_progress(vpninfo, PRG_INFO,
788 _("Refreshing %s after 1 second...\n"),
793 vpn_progress(vpninfo, PRG_ERR,
794 _("Unknown response from server\n"));
799 result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
800 &method, &request_body_type);
810 /* A return value of 2 means the XML form indicated
811 success. We _should_ have a cookie... */
813 for (opt = vpninfo->cookies; opt; opt = opt->next) {
815 if (!strcmp(opt->option, "webvpn"))
816 vpninfo->cookie = opt->value;
817 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
818 char *tok = opt->value;
819 char *bu = NULL, *fu = NULL, *sha = NULL;
822 if (tok != opt->value)
825 if (!strncmp(tok, "bu:", 3))
827 else if (!strncmp(tok, "fu:", 3))
829 else if (!strncmp(tok, "fh:", 3)) {
830 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
831 SHA_DIGEST_LENGTH * 2))
835 } while ((tok = strchr(tok, '&')));
838 fetch_config(vpninfo, bu, fu, sha);
841 if (vpninfo->csd_scriptname) {
842 unlink(vpninfo->csd_scriptname);
843 free(vpninfo->csd_scriptname);
844 vpninfo->csd_scriptname = NULL;
849 char *openconnect_create_useragent(const char *base)
853 if (asprintf(&uagent, "%s %s", base, openconnect_version) < 0)
859 static int proxy_gets(int fd, char *buf, size_t len)
867 while ( (ret = read(fd, buf + i, 1)) == 1) {
868 if (buf[i] == '\n') {
870 if (i && buf[i-1] == '\r') {
890 static int proxy_write(int fd, unsigned char *buf, size_t len)
894 for (count = 0; count < len; ) {
895 int i = write(fd, buf + count, len - count);
904 static int proxy_read(int fd, unsigned char *buf, size_t len)
908 for (count = 0; count < len; ) {
909 int i = read(fd, buf + count, len - count);
918 static const char *socks_errors[] = {
919 N_("request granted"),
920 N_("general failure"),
921 N_("connection not allowed by ruleset"),
922 N_("network unreachable"),
923 N_("host unreachable"),
924 N_("connection refused by destination host"),
926 N_("command not supported / protocol error"),
927 N_("address type not supported")
930 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
932 unsigned char buf[1024];
935 buf[0] = 5; /* SOCKS version */
936 buf[1] = 1; /* # auth methods */
937 buf[2] = 0; /* No auth supported */
939 if ((i = proxy_write(ssl_sock, buf, 3))) {
940 vpn_progress(vpninfo, PRG_ERR,
941 _("Error writing auth request to SOCKS proxy: %s\n"),
946 if ((i = proxy_read(ssl_sock, buf, 2))) {
947 vpn_progress(vpninfo, PRG_ERR,
948 _("Error reading auth response from SOCKS proxy: %s\n"),
953 vpn_progress(vpninfo, PRG_ERR,
954 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
960 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
961 vpn_progress(vpninfo, PRG_ERR,
962 _("SOCKS proxy error %02x: %s\n"),
963 buf[1], _(socks_errors[buf[1]]));
965 vpn_progress(vpninfo, PRG_ERR,
966 _("SOCKS proxy error %02x\n"),
971 vpn_progress(vpninfo, PRG_INFO,
972 _("Requesting SOCKS proxy connection to %s:%d\n"),
973 vpninfo->hostname, vpninfo->port);
975 buf[0] = 5; /* SOCKS version */
976 buf[1] = 1; /* CONNECT */
977 buf[2] = 0; /* Reserved */
978 buf[3] = 3; /* Address type is domain name */
979 buf[4] = strlen(vpninfo->hostname);
980 strcpy((char *)buf + 5, vpninfo->hostname);
981 i = strlen(vpninfo->hostname) + 5;
982 buf[i++] = vpninfo->port >> 8;
983 buf[i++] = vpninfo->port & 0xff;
985 if ((i = proxy_write(ssl_sock, buf, i))) {
986 vpn_progress(vpninfo, PRG_ERR,
987 _("Error writing connect request to SOCKS proxy: %s\n"),
991 /* Read 5 bytes -- up to and including the first byte of the returned
992 address (which might be the length byte of a domain name) */
993 if ((i = proxy_read(ssl_sock, buf, 5))) {
994 vpn_progress(vpninfo, PRG_ERR,
995 _("Error reading connect response from SOCKS proxy: %s\n"),
1000 vpn_progress(vpninfo, PRG_ERR,
1001 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1008 /* Connect responses contain an address */
1010 case 1: /* Legacy IP */
1013 case 3: /* Domain name */
1020 vpn_progress(vpninfo, PRG_ERR,
1021 _("Unexpected address type %02x in SOCKS connect response\n"),
1026 if ((i = proxy_read(ssl_sock, buf, i))) {
1027 vpn_progress(vpninfo, PRG_ERR,
1028 _("Error reading connect response from SOCKS proxy: %s\n"),
1035 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1037 char buf[MAX_BUF_LEN];
1040 sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1041 sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
1042 sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
1043 sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
1044 sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
1045 sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
1046 sprintf(buf + strlen(buf), "\r\n");
1048 vpn_progress(vpninfo, PRG_INFO,
1049 _("Requesting HTTP proxy connection to %s:%d\n"),
1050 vpninfo->hostname, vpninfo->port);
1052 if (proxy_write(ssl_sock, (unsigned char *)buf, strlen(buf))) {
1054 vpn_progress(vpninfo, PRG_ERR,
1055 _("Sending proxy request failed: %s\n"),
1060 if (proxy_gets(ssl_sock, buf, sizeof(buf)) < 0) {
1061 vpn_progress(vpninfo, PRG_ERR,
1062 _("Error fetching proxy response\n"));
1066 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1067 buf[8] != ' ' || !(result = atoi(buf+9))) {
1068 vpn_progress(vpninfo, PRG_ERR,
1069 _("Failed to parse proxy response '%s'\n"), buf);
1073 if (result != 200) {
1074 vpn_progress(vpninfo, PRG_ERR,
1075 _("Proxy CONNECT request failed: %s\n"), buf);
1079 while ((buflen = proxy_gets(ssl_sock, buf, sizeof(buf)))) {
1081 vpn_progress(vpninfo, PRG_ERR,
1082 _("Failed to read proxy response\n"));
1085 vpn_progress(vpninfo, PRG_ERR,
1086 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1093 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1095 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1096 return process_http_proxy(vpninfo, ssl_sock);
1098 if (!strcmp(vpninfo->proxy_type, "socks") ||
1099 !strcmp(vpninfo->proxy_type, "socks5"))
1100 return process_socks_proxy(vpninfo, ssl_sock);
1102 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1103 vpninfo->proxy_type);
1107 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1115 free(vpninfo->proxy_type);
1116 vpninfo->proxy_type = NULL;
1117 free(vpninfo->proxy);
1118 vpninfo->proxy = NULL;
1120 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1121 &vpninfo->proxy_port, NULL, 80);
1125 if (vpninfo->proxy_type &&
1126 strcmp(vpninfo->proxy_type, "http") &&
1127 strcmp(vpninfo->proxy_type, "socks") &&
1128 strcmp(vpninfo->proxy_type, "socks5")) {
1129 vpn_progress(vpninfo, PRG_ERR,
1130 _("Only http or socks(5) proxies supported\n"));
1131 free(vpninfo->proxy_type);
1132 vpninfo->proxy_type = NULL;
1133 free(vpninfo->proxy);
1134 vpninfo->proxy = NULL;