2 * OpenConnect (SSL + DTLS) VPN client
4 * Copyright © 2008-2012 Intel Corporation.
5 * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
7 * Author: David Woodhouse <dwmw2@infradead.org>
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * version 2.1, as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to:
21 * Free Software Foundation, Inc.
22 * 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301 USA
34 #include <sys/types.h>
40 #include "openconnect-internal.h"
42 static int proxy_write(struct openconnect_info *vpninfo, int fd,
43 unsigned char *buf, size_t len);
44 static int proxy_read(struct openconnect_info *vpninfo, int fd,
45 unsigned char *buf, size_t len);
47 #define MAX_BUF_LEN 131072
48 #define BUF_CHUNK_SIZE 4096
57 static struct oc_text_buf *buf_alloc(void)
59 return calloc(1, sizeof(struct oc_text_buf));
62 static void buf_append(struct oc_text_buf *buf, const char *fmt, ...)
66 if (!buf || buf->error)
70 buf->data = malloc(BUF_CHUNK_SIZE);
75 buf->buf_len = BUF_CHUNK_SIZE;
79 int max_len = buf->buf_len - buf->pos, ret;
82 ret = vsnprintf(buf->data + buf->pos, max_len, fmt, ap);
87 } else if (ret < max_len) {
91 int new_buf_len = buf->buf_len + BUF_CHUNK_SIZE;
93 if (new_buf_len > MAX_BUF_LEN) {
94 /* probably means somebody is messing with us */
99 buf->data = realloc(buf->data, new_buf_len);
101 buf->error = -ENOMEM;
104 buf->buf_len = new_buf_len;
109 static int buf_error(struct oc_text_buf *buf)
111 return buf ? buf->error : -ENOMEM;
114 static int buf_free(struct oc_text_buf *buf)
116 int error = buf_error(buf);
128 * We didn't really want to have to do this for ourselves -- one might have
129 * thought that it would be available in a library somewhere. But neither
130 * cURL nor Neon have reliable cross-platform ways of either using a cert
131 * from the TPM, or just reading from / writing to a transport which is
132 * provided by their caller.
135 static int http_add_cookie(struct openconnect_info *vpninfo,
136 const char *option, const char *value)
138 struct vpn_option *new, **this;
141 new = malloc(sizeof(*new));
143 vpn_progress(vpninfo, PRG_ERR,
144 _("No memory for allocating cookies\n"));
148 new->option = strdup(option);
149 new->value = strdup(value);
150 if (!new->option || !new->value) {
157 /* Kill cookie; don't replace it */
160 for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
161 if (!strcmp(option, (*this)->option)) {
162 /* Replace existing cookie */
164 new->next = (*this)->next;
168 free((*this)->option);
169 free((*this)->value);
182 #define BODY_HTTP10 -1
183 #define BODY_CHUNKED -2
185 static int process_http_response(struct openconnect_info *vpninfo, int *result,
186 int (*header_cb)(struct openconnect_info *, char *, char *),
189 char buf[MAX_BUF_LEN];
191 int bodylen = BODY_HTTP10;
197 if (openconnect_SSL_gets(vpninfo, buf, sizeof(buf)) < 0) {
198 vpn_progress(vpninfo, PRG_ERR,
199 _("Error fetching HTTPS response\n"));
203 if (!strncmp(buf, "HTTP/1.0 ", 9))
206 if ((!closeconn && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
207 vpn_progress(vpninfo, PRG_ERR,
208 _("Failed to parse HTTP response '%s'\n"), buf);
212 vpn_progress(vpninfo, (*result==200)?PRG_TRACE:PRG_INFO,
213 _("Got HTTP response: %s\n"), buf);
216 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
220 vpn_progress(vpninfo, PRG_ERR,
221 _("Error processing HTTP response\n"));
224 colon = strchr(buf, ':');
226 vpn_progress(vpninfo, PRG_ERR,
227 _("Ignoring unknown HTTP response line '%s'\n"), buf);
234 /* Handle Set-Cookie first so that we can avoid printing the
235 webvpn cookie in the verbose debug output */
236 if (!strcasecmp(buf, "Set-Cookie")) {
237 char *semicolon = strchr(colon, ';');
238 const char *print_equals;
239 char *equals = strchr(colon, '=');
246 vpn_progress(vpninfo, PRG_ERR,
247 _("Invalid cookie offered: %s\n"), buf);
252 print_equals = equals;
253 /* Don't print the webvpn cookie unless it's empty; we don't
254 want people posting it in public with debugging output */
255 if (!strcmp(colon, "webvpn") && *equals)
256 print_equals = _("<elided>");
257 vpn_progress(vpninfo, PRG_TRACE, "%s: %s=%s%s%s\n",
258 buf, colon, print_equals, semicolon?";":"",
259 semicolon?(semicolon+1):"");
261 /* The server tends to ask for the username and password as
262 usual, even if we've already failed because it didn't like
263 our cert. Thankfully it does give us this hint... */
264 if (!strcmp(colon, "ClientCertAuthFailed"))
265 vpn_progress(vpninfo, PRG_ERR,
266 _("SSL certificate authentication failed\n"));
268 ret = http_add_cookie(vpninfo, colon, equals);
272 vpn_progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
275 if (!strcasecmp(buf, "Connection")) {
276 if (!strcasecmp(colon, "Close"))
279 /* This might seem reasonable, but in fact it breaks
280 certificate authentication with some servers. If
281 they give an HTTP/1.0 response, even if they
282 explicitly give a Connection: Keep-Alive header,
283 just close the connection. */
284 else if (!strcasecmp(colon, "Keep-Alive"))
288 if (!strcasecmp(buf, "Location")) {
289 vpninfo->redirect_url = strdup(colon);
290 if (!vpninfo->redirect_url)
293 if (!strcasecmp(buf, "Content-Length")) {
294 bodylen = atoi(colon);
296 vpn_progress(vpninfo, PRG_ERR,
297 _("Response body has negative size (%d)\n"),
302 if (!strcasecmp(buf, "Transfer-Encoding")) {
303 if (!strcasecmp(colon, "chunked"))
304 bodylen = BODY_CHUNKED;
306 vpn_progress(vpninfo, PRG_ERR,
307 _("Unknown Transfer-Encoding: %s\n"),
312 if (header_cb && !strncmp(buf, "X-", 2))
313 header_cb(vpninfo, buf, colon);
316 /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
320 /* Now the body, if there is one */
321 vpn_progress(vpninfo, PRG_TRACE, _("HTTP body %s (%d)\n"),
322 bodylen==BODY_HTTP10?"http 1.0" :
323 bodylen==BODY_CHUNKED?"chunked" : "length: ",
326 /* If we were given Content-Length, it's nice and easy... */
328 body = malloc(bodylen + 1);
331 while (done < bodylen) {
332 i = openconnect_SSL_read(vpninfo, body + done, bodylen - done);
334 vpn_progress(vpninfo, PRG_ERR,
335 _("Error reading HTTP response body\n"));
341 } else if (bodylen == BODY_CHUNKED) {
342 /* ... else, chunked */
343 while ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
344 int chunklen, lastchunk = 0;
347 vpn_progress(vpninfo, PRG_ERR,
348 _("Error fetching chunk header\n"));
351 chunklen = strtol(buf, NULL, 16);
356 body = realloc(body, done + chunklen + 1);
360 i = openconnect_SSL_read(vpninfo, body + done, chunklen);
362 vpn_progress(vpninfo, PRG_ERR,
363 _("Error reading HTTP response body\n"));
371 if ((i = openconnect_SSL_gets(vpninfo, buf, sizeof(buf)))) {
373 vpn_progress(vpninfo, PRG_ERR,
374 _("Error fetching HTTP response body\n"));
376 vpn_progress(vpninfo, PRG_ERR,
377 _("Error in chunked decoding. Expected '', got: '%s'"),
387 } else if (bodylen == BODY_HTTP10) {
389 vpn_progress(vpninfo, PRG_ERR,
390 _("Cannot receive HTTP 1.0 body without closing connection\n"));
394 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
396 body = realloc(body, done + 16384);
399 i = openconnect_SSL_read(vpninfo, body + done, 16384);
408 /* Connection closed. Reduce allocation to just what we need */
409 body = realloc(body, done + 1);
417 if (closeconn || vpninfo->no_http_keepalive)
418 openconnect_close_https(vpninfo, 0);
426 static void add_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
428 struct vpn_option *opt;
430 buf_append(buf, "Host: %s\r\n", vpninfo->hostname);
431 buf_append(buf, "User-Agent: %s\r\n", vpninfo->useragent);
432 buf_append(buf, "Accept: */*\r\n");
433 buf_append(buf, "Accept-Encoding: identity\r\n");
435 if (vpninfo->cookies) {
436 buf_append(buf, "Cookie: ");
437 for (opt = vpninfo->cookies; opt; opt = opt->next)
438 buf_append(buf, "%s=%s%s", opt->option,
439 opt->value, opt->next ? "; " : "\r\n");
441 buf_append(buf, "X-Transcend-Version: 1\r\n");
442 buf_append(buf, "X-Aggregate-Auth: 1\r\n");
443 buf_append(buf, "X-AnyConnect-Platform: %s\r\n", vpninfo->platname);
446 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
449 struct oc_text_buf *buf;
450 char *config_buf = NULL;
452 unsigned char local_sha1_bin[SHA1_SIZE];
453 char local_sha1_ascii[(SHA1_SIZE * 2)+1];
456 if (openconnect_open_https(vpninfo)) {
457 vpn_progress(vpninfo, PRG_ERR,
458 _("Failed to open HTTPS connection to %s\n"),
464 buf_append(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
465 add_common_headers(vpninfo, buf);
466 buf_append(buf, "\r\n");
469 return buf_free(buf);
471 if (openconnect_SSL_write(vpninfo, buf->data, buf->pos) != buf->pos) {
472 vpn_progress(vpninfo, PRG_ERR,
473 _("Failed to send GET request for new config\n"));
479 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
481 /* We'll already have complained about whatever offended us */
490 openconnect_sha1(local_sha1_bin, config_buf, buflen);
492 for (i = 0; i < SHA1_SIZE; i++)
493 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
495 if (strcasecmp(server_sha1, local_sha1_ascii)) {
496 vpn_progress(vpninfo, PRG_ERR,
497 _("Downloaded config file did not match intended SHA1\n"));
502 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
507 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
512 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
513 vpn_progress(vpninfo, PRG_ERR,
514 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
515 "This facility is disabled by default for security reasons, so you may wish to enable it."));
520 vpn_progress(vpninfo, PRG_INFO,
521 _("Trying to run Linux CSD trojan script."));
524 sprintf(fname, "/tmp/csdXXXXXX");
528 vpn_progress(vpninfo, PRG_ERR,
529 _("Failed to open temporary CSD script file: %s\n"),
534 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
536 vpn_progress(vpninfo, PRG_ERR,
537 _("Failed to write temporary CSD script file: %s\n"),
545 char scertbuf[MD5_SIZE * 2 + 1];
546 char ccertbuf[MD5_SIZE * 2 + 1];
550 if (vpninfo->uid_csd_given && vpninfo->uid_csd != getuid()) {
553 if (setuid(vpninfo->uid_csd)) {
554 fprintf(stderr, _("Failed to set uid %ld\n"),
555 (long)vpninfo->uid_csd);
558 if (!(pw = getpwuid(vpninfo->uid_csd))) {
559 fprintf(stderr, _("Invalid user uid=%ld\n"),
560 (long)vpninfo->uid_csd);
563 setenv("HOME", pw->pw_dir, 1);
564 if (chdir(pw->pw_dir)) {
565 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
566 pw->pw_dir, strerror(errno));
570 if (getuid() == 0 && !vpninfo->csd_wrapper) {
571 fprintf(stderr, _("Warning: you are running insecure "
572 "CSD code with root privileges\n"
573 "\t Use command line option \"--csd-user\"\n"));
575 if (vpninfo->uid_csd_given == 2) {
576 /* The NM tool really needs not to get spurious output
577 on stdout, which the CSD trojan spews. */
580 if (vpninfo->csd_wrapper)
581 csd_argv[i++] = vpninfo->csd_wrapper;
582 csd_argv[i++] = fname;
583 csd_argv[i++]= (char *)"-ticket";
584 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
586 csd_argv[i++]= (char *)"-stub";
587 csd_argv[i++]= (char *)"\"0\"";
588 csd_argv[i++]= (char *)"-group";
589 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
592 openconnect_local_cert_md5(vpninfo, ccertbuf);
594 get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
595 csd_argv[i++]= (char *)"-certhash";
596 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
599 csd_argv[i++]= (char *)"-url";
600 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
603 csd_argv[i++]= (char *)"-langselen";
604 csd_argv[i++] = NULL;
606 if (setenv("CSD_TOKEN", vpninfo->csd_token, 1))
608 if (setenv("CSD_HOSTNAME", vpninfo->hostname, 1))
611 execv(csd_argv[0], csd_argv);
614 vpn_progress(vpninfo, PRG_ERR,
615 _("Failed to exec CSD script %s\n"), csd_argv[0]);
619 free(vpninfo->csd_stuburl);
620 vpninfo->csd_stuburl = NULL;
621 free(vpninfo->urlpath);
622 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
623 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
624 free(vpninfo->csd_waiturl);
625 vpninfo->csd_waiturl = NULL;
626 vpninfo->csd_scriptname = strdup(fname);
628 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
633 int internal_parse_url(char *url, char **res_proto, char **res_host,
634 int *res_port, char **res_path, int default_port)
637 char *host, *path, *port_str;
640 host = strstr(url, "://");
645 if (!strcasecmp(proto, "https"))
647 else if (!strcasecmp(proto, "http"))
649 else if (!strcasecmp(proto, "socks") ||
650 !strcasecmp(proto, "socks4") ||
651 !strcasecmp(proto, "socks5"))
654 return -EPROTONOSUPPORT;
664 path = strchr(host, '/');
668 port_str = strrchr(host, ':');
671 int new_port = strtol(port_str + 1, &end, 10);
680 *res_proto = proto ? strdup(proto) : NULL;
682 *res_host = strdup(host);
686 *res_path = (path && *path) ? strdup(path) : NULL;
688 /* Undo the damage we did to the original string */
698 static void clear_cookies(struct openconnect_info *vpninfo)
700 struct vpn_option *opt, *next;
702 for (opt = vpninfo->cookies; opt; opt = next) {
709 vpninfo->cookies = NULL;
714 * = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
716 static int handle_redirect(struct openconnect_info *vpninfo)
718 vpninfo->redirect_type = REDIR_TYPE_LOCAL;
720 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
721 /* New host. Tear down the existing connection and make a new one */
726 free(vpninfo->urlpath);
727 vpninfo->urlpath = NULL;
729 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
731 vpn_progress(vpninfo, PRG_ERR,
732 _("Failed to parse redirected URL '%s': %s\n"),
733 vpninfo->redirect_url, strerror(-ret));
734 free(vpninfo->redirect_url);
735 vpninfo->redirect_url = NULL;
739 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
740 free(vpninfo->hostname);
741 vpninfo->hostname = host;
742 vpninfo->port = port;
744 /* Kill the existing connection, and a new one will happen */
745 free(vpninfo->peer_addr);
746 vpninfo->peer_addr = NULL;
747 openconnect_close_https(vpninfo, 0);
748 clear_cookies(vpninfo);
749 vpninfo->redirect_type = REDIR_TYPE_NEWHOST;
753 free(vpninfo->redirect_url);
754 vpninfo->redirect_url = NULL;
757 } else if (strstr(vpninfo->redirect_url, "://")) {
758 vpn_progress(vpninfo, PRG_ERR,
759 _("Cannot follow redirection to non-https URL '%s'\n"),
760 vpninfo->redirect_url);
761 free(vpninfo->redirect_url);
762 vpninfo->redirect_url = NULL;
764 } else if (vpninfo->redirect_url[0] == '/') {
765 /* Absolute redirect within same host */
766 free(vpninfo->urlpath);
767 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
768 free(vpninfo->redirect_url);
769 vpninfo->redirect_url = NULL;
772 char *lastslash = NULL;
773 if (vpninfo->urlpath)
774 lastslash = strrchr(vpninfo->urlpath, '/');
776 free(vpninfo->urlpath);
777 vpninfo->urlpath = vpninfo->redirect_url;
778 vpninfo->redirect_url = NULL;
780 char *oldurl = vpninfo->urlpath;
782 vpninfo->urlpath = NULL;
783 if (asprintf(&vpninfo->urlpath, "%s/%s",
784 oldurl, vpninfo->redirect_url) == -1) {
786 vpn_progress(vpninfo, PRG_ERR,
787 _("Allocating new path for relative redirect failed: %s\n"),
792 free(vpninfo->redirect_url);
793 vpninfo->redirect_url = NULL;
800 * method: GET or POST
801 * vpninfo->hostname: Host DNS name
802 * vpninfo->port: TCP port, typically 443
803 * vpninfo->urlpath: Relative path, e.g. /+webvpn+/foo.html
804 * request_body_type: Content type for a POST (e.g. text/html). Can be NULL.
805 * request_body: POST content
806 * form_buf: Callee-allocated buffer for server content
810 * >=0, on success, indicating the length of the data in *form_buf
812 static int do_https_request(struct openconnect_info *vpninfo, const char *method,
813 const char *request_body_type, const char *request_body,
814 char **form_buf, int fetch_redirect)
816 struct oc_text_buf *buf;
820 vpninfo->redirect_type = REDIR_TYPE_NONE;
826 if (openconnect_open_https(vpninfo)) {
827 vpn_progress(vpninfo, PRG_ERR,
828 _("Failed to open HTTPS connection to %s\n"),
834 * It would be nice to use cURL for this, but we really need to guarantee
835 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
836 * to have any way to let us provide our own socket read/write functions.
837 * We can only provide a socket _open_ function. Which would require having
838 * a socketpair() and servicing the "other" end of it.
840 * So we process the HTTP for ourselves...
843 buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
844 add_common_headers(vpninfo, buf);
846 if (request_body_type) {
847 buf_append(buf, "Content-Type: %s\r\n", request_body_type);
848 buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
850 buf_append(buf, "\r\n");
852 if (request_body_type)
853 buf_append(buf, "%s", request_body);
855 if (vpninfo->port == 443)
856 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
857 method, vpninfo->hostname,
858 vpninfo->urlpath ?: "");
860 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
861 method, vpninfo->hostname, vpninfo->port,
862 vpninfo->urlpath ?: "");
865 return buf_free(buf);
867 result = openconnect_SSL_write(vpninfo, buf->data, buf->pos);
872 buflen = process_http_response(vpninfo, &result, NULL, form_buf);
874 /* We'll already have complained about whatever offended us */
878 if (result != 200 && vpninfo->redirect_url) {
879 result = handle_redirect(vpninfo);
887 if (!*form_buf || result != 200) {
888 vpn_progress(vpninfo, PRG_ERR,
889 _("Unexpected %d result from server\n"),
904 * < 0, if the data is unrecognized
905 * = 0, if the page contains an XML document
906 * = 1, if the page is a wait/refresh HTML page
908 static int check_response_type(struct openconnect_info *vpninfo, char *form_buf)
910 if (strncmp(form_buf, "<?xml", 5)) {
911 /* Not XML? Perhaps it's HTML with a refresh... */
912 if (strcasestr(form_buf, "http-equiv=\"refresh\""))
914 vpn_progress(vpninfo, PRG_ERR,
915 _("Unknown response from server\n"));
923 * > 0, no cookie (user cancel)
924 * = 0, obtained cookie
926 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
928 struct vpn_option *opt;
929 char *form_buf = NULL;
930 struct oc_auth_form *form = NULL;
931 int result, buflen, tries;
932 char request_body[2048];
933 const char *request_body_type = "application/x-www-form-urlencoded";
934 const char *method = "POST";
937 /* Step 1: Unlock software token (if applicable) */
938 if (vpninfo->use_stoken) {
939 result = prepare_stoken(vpninfo);
945 * Step 2: Probe for XML POST compatibility
947 * This can get stuck in a redirect loop, so give up after any of:
949 * a) HTTP error (e.g. 400 Bad Request)
950 * b) Same-host redirect (e.g. Location: /foo/bar)
951 * c) Three redirects without seeing a plausible login form
953 result = xmlpost_initial_req(vpninfo, request_body, sizeof(request_body));
957 for (tries = 0; ; tries++) {
960 buflen = do_https_request(vpninfo, method, request_body_type, request_body,
962 if (buflen == -EINVAL)
967 if (vpninfo->redirect_type == REDIR_TYPE_LOCAL)
969 else if (vpninfo->redirect_type != REDIR_TYPE_NONE)
972 result = parse_xml_response(vpninfo, form_buf, &form);
977 vpn_progress(vpninfo, PRG_INFO, _("XML POST enabled\n"));
981 /* Step 3: Fetch and parse the login form, if not using XML POST */
983 buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
987 result = parse_xml_response(vpninfo, form_buf, &form);
994 /* Step 4: Run the CSD trojan, if applicable */
995 if (vpninfo->csd_starturl) {
996 char *form_path = NULL;
998 if (vpninfo->urlpath) {
999 form_path = strdup(vpninfo->urlpath);
1006 /* fetch the CSD program, if available */
1007 if (vpninfo->csd_stuburl) {
1008 buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
1015 /* This is the CSD stub script, which we now need to run */
1016 result = run_csd_script(vpninfo, form_buf, buflen);
1020 /* vpninfo->urlpath now points to the wait page */
1022 result = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
1026 result = check_response_type(vpninfo, form_buf);
1030 vpn_progress(vpninfo, PRG_INFO,
1031 _("Refreshing %s after 1 second...\n"),
1038 /* refresh the form page, to see if we're authorized now */
1039 free(vpninfo->urlpath);
1040 vpninfo->urlpath = form_path;
1042 result = do_https_request(vpninfo, xmlpost ? "POST" : "GET",
1043 request_body_type, request_body, &form_buf, 1);
1047 result = parse_xml_response(vpninfo, form_buf, &form);
1052 /* Step 5: Ask the user to fill in the auth form; repeat as necessary */
1054 request_body[0] = 0;
1055 result = handle_auth_form(vpninfo, form, request_body, sizeof(request_body),
1056 &method, &request_body_type, xmlpost);
1057 if (result < 0 || result == 1)
1062 result = do_https_request(vpninfo, method, request_body_type, request_body,
1067 result = parse_xml_response(vpninfo, form_buf, &form);
1072 /* A return value of 2 means the XML form indicated
1073 success. We _should_ have a cookie... */
1075 for (opt = vpninfo->cookies; opt; opt = opt->next) {
1077 if (!strcmp(opt->option, "webvpn"))
1078 vpninfo->cookie = opt->value;
1079 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
1080 char *tok = opt->value;
1081 char *bu = NULL, *fu = NULL, *sha = NULL;
1084 if (tok != opt->value)
1087 if (!strncmp(tok, "bu:", 3))
1089 else if (!strncmp(tok, "fu:", 3))
1091 else if (!strncmp(tok, "fh:", 3)) {
1092 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
1097 } while ((tok = strchr(tok, '&')));
1099 if (bu && fu && sha)
1100 fetch_config(vpninfo, bu, fu, sha);
1107 free_auth_form(form);
1109 if (vpninfo->csd_scriptname) {
1110 unlink(vpninfo->csd_scriptname);
1111 free(vpninfo->csd_scriptname);
1112 vpninfo->csd_scriptname = NULL;
1118 char *openconnect_create_useragent(const char *base)
1122 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
1128 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
1129 char *buf, size_t len)
1137 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
1138 if (buf[i] == '\n') {
1140 if (i && buf[i-1] == '\r') {
1157 static int proxy_write(struct openconnect_info *vpninfo, int fd,
1158 unsigned char *buf, size_t len)
1162 for (count = 0; count < len; ) {
1163 fd_set rd_set, wr_set;
1169 FD_SET(fd, &wr_set);
1170 if (vpninfo->cancel_fd != -1) {
1171 FD_SET(vpninfo->cancel_fd, &rd_set);
1172 if (vpninfo->cancel_fd > fd)
1173 maxfd = vpninfo->cancel_fd;
1176 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
1177 if (vpninfo->cancel_fd != -1 &&
1178 FD_ISSET(vpninfo->cancel_fd, &rd_set))
1181 /* Not that this should ever be able to happen... */
1182 if (!FD_ISSET(fd, &wr_set))
1185 i = write(fd, buf + count, len - count);
1194 static int proxy_read(struct openconnect_info *vpninfo, int fd,
1195 unsigned char *buf, size_t len)
1199 for (count = 0; count < len; ) {
1205 FD_SET(fd, &rd_set);
1206 if (vpninfo->cancel_fd != -1) {
1207 FD_SET(vpninfo->cancel_fd, &rd_set);
1208 if (vpninfo->cancel_fd > fd)
1209 maxfd = vpninfo->cancel_fd;
1212 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
1213 if (vpninfo->cancel_fd != -1 &&
1214 FD_ISSET(vpninfo->cancel_fd, &rd_set))
1217 /* Not that this should ever be able to happen... */
1218 if (!FD_ISSET(fd, &rd_set))
1221 i = read(fd, buf + count, len - count);
1230 static const char *socks_errors[] = {
1231 N_("request granted"),
1232 N_("general failure"),
1233 N_("connection not allowed by ruleset"),
1234 N_("network unreachable"),
1235 N_("host unreachable"),
1236 N_("connection refused by destination host"),
1238 N_("command not supported / protocol error"),
1239 N_("address type not supported")
1242 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1244 unsigned char buf[1024];
1247 buf[0] = 5; /* SOCKS version */
1248 buf[1] = 1; /* # auth methods */
1249 buf[2] = 0; /* No auth supported */
1251 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1252 vpn_progress(vpninfo, PRG_ERR,
1253 _("Error writing auth request to SOCKS proxy: %s\n"),
1258 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1259 vpn_progress(vpninfo, PRG_ERR,
1260 _("Error reading auth response from SOCKS proxy: %s\n"),
1265 vpn_progress(vpninfo, PRG_ERR,
1266 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1272 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1273 vpn_progress(vpninfo, PRG_ERR,
1274 _("SOCKS proxy error %02x: %s\n"),
1275 buf[1], _(socks_errors[buf[1]]));
1277 vpn_progress(vpninfo, PRG_ERR,
1278 _("SOCKS proxy error %02x\n"),
1283 vpn_progress(vpninfo, PRG_INFO,
1284 _("Requesting SOCKS proxy connection to %s:%d\n"),
1285 vpninfo->hostname, vpninfo->port);
1287 buf[0] = 5; /* SOCKS version */
1288 buf[1] = 1; /* CONNECT */
1289 buf[2] = 0; /* Reserved */
1290 buf[3] = 3; /* Address type is domain name */
1291 buf[4] = strlen(vpninfo->hostname);
1292 strcpy((char *)buf + 5, vpninfo->hostname);
1293 i = strlen(vpninfo->hostname) + 5;
1294 buf[i++] = vpninfo->port >> 8;
1295 buf[i++] = vpninfo->port & 0xff;
1297 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1298 vpn_progress(vpninfo, PRG_ERR,
1299 _("Error writing connect request to SOCKS proxy: %s\n"),
1303 /* Read 5 bytes -- up to and including the first byte of the returned
1304 address (which might be the length byte of a domain name) */
1305 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1306 vpn_progress(vpninfo, PRG_ERR,
1307 _("Error reading connect response from SOCKS proxy: %s\n"),
1312 vpn_progress(vpninfo, PRG_ERR,
1313 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1320 /* Connect responses contain an address */
1322 case 1: /* Legacy IP */
1325 case 3: /* Domain name */
1332 vpn_progress(vpninfo, PRG_ERR,
1333 _("Unexpected address type %02x in SOCKS connect response\n"),
1338 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1339 vpn_progress(vpninfo, PRG_ERR,
1340 _("Error reading connect response from SOCKS proxy: %s\n"),
1347 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1349 char buf[MAX_BUF_LEN];
1350 struct oc_text_buf *reqbuf;
1353 reqbuf = buf_alloc();
1354 buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1355 buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
1356 buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
1357 buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
1358 buf_append(reqbuf, "Connection: keep-alive\r\n");
1359 buf_append(reqbuf, "Accept-Encoding: identity\r\n");
1360 buf_append(reqbuf, "\r\n");
1362 if (buf_error(reqbuf))
1363 return buf_free(reqbuf);
1365 vpn_progress(vpninfo, PRG_INFO,
1366 _("Requesting HTTP proxy connection to %s:%d\n"),
1367 vpninfo->hostname, vpninfo->port);
1369 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)reqbuf->data, reqbuf->pos);
1373 vpn_progress(vpninfo, PRG_ERR,
1374 _("Sending proxy request failed: %s\n"),
1379 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1380 vpn_progress(vpninfo, PRG_ERR,
1381 _("Error fetching proxy response\n"));
1385 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1386 buf[8] != ' ' || !(result = atoi(buf+9))) {
1387 vpn_progress(vpninfo, PRG_ERR,
1388 _("Failed to parse proxy response '%s'\n"), buf);
1392 if (result != 200) {
1393 vpn_progress(vpninfo, PRG_ERR,
1394 _("Proxy CONNECT request failed: %s\n"), buf);
1398 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1400 vpn_progress(vpninfo, PRG_ERR,
1401 _("Failed to read proxy response\n"));
1404 vpn_progress(vpninfo, PRG_ERR,
1405 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1412 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1414 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1415 return process_http_proxy(vpninfo, ssl_sock);
1417 if (!strcmp(vpninfo->proxy_type, "socks") ||
1418 !strcmp(vpninfo->proxy_type, "socks5"))
1419 return process_socks_proxy(vpninfo, ssl_sock);
1421 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1422 vpninfo->proxy_type);
1426 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1434 free(vpninfo->proxy_type);
1435 vpninfo->proxy_type = NULL;
1436 free(vpninfo->proxy);
1437 vpninfo->proxy = NULL;
1439 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1440 &vpninfo->proxy_port, NULL, 80);
1444 if (vpninfo->proxy_type &&
1445 strcmp(vpninfo->proxy_type, "http") &&
1446 strcmp(vpninfo->proxy_type, "socks") &&
1447 strcmp(vpninfo->proxy_type, "socks5")) {
1448 vpn_progress(vpninfo, PRG_ERR,
1449 _("Only http or socks(5) proxies supported\n"));
1450 free(vpninfo->proxy_type);
1451 vpninfo->proxy_type = NULL;
1452 free(vpninfo->proxy);
1453 vpninfo->proxy = NULL;