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"));
391 openconnect_close_https(vpninfo, 0);
395 /* HTTP 1.0 response. Just eat all we can in 16KiB chunks */
397 body = realloc(body, done + 16384);
400 i = openconnect_SSL_read(vpninfo, body + done, 16384);
409 /* Connection closed. Reduce allocation to just what we need */
410 body = realloc(body, done + 1);
418 if (closeconn || vpninfo->no_http_keepalive)
419 openconnect_close_https(vpninfo, 0);
427 static void add_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
429 struct vpn_option *opt;
431 buf_append(buf, "Host: %s\r\n", vpninfo->hostname);
432 buf_append(buf, "User-Agent: %s\r\n", vpninfo->useragent);
433 buf_append(buf, "Accept: */*\r\n");
434 buf_append(buf, "Accept-Encoding: identity\r\n");
436 if (vpninfo->cookies) {
437 buf_append(buf, "Cookie: ");
438 for (opt = vpninfo->cookies; opt; opt = opt->next)
439 buf_append(buf, "%s=%s%s", opt->option,
440 opt->value, opt->next ? "; " : "\r\n");
442 buf_append(buf, "X-Transcend-Version: 1\r\n");
443 buf_append(buf, "X-Aggregate-Auth: 1\r\n");
444 buf_append(buf, "X-AnyConnect-Platform: %s\r\n", vpninfo->platname);
447 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
450 struct oc_text_buf *buf;
451 char *config_buf = NULL;
453 unsigned char local_sha1_bin[SHA1_SIZE];
454 char local_sha1_ascii[(SHA1_SIZE * 2)+1];
457 if (openconnect_open_https(vpninfo)) {
458 vpn_progress(vpninfo, PRG_ERR,
459 _("Failed to open HTTPS connection to %s\n"),
465 buf_append(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
466 add_common_headers(vpninfo, buf);
467 buf_append(buf, "\r\n");
470 return buf_free(buf);
472 if (openconnect_SSL_write(vpninfo, buf->data, buf->pos) != buf->pos) {
473 vpn_progress(vpninfo, PRG_ERR,
474 _("Failed to send GET request for new config\n"));
480 buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
482 /* We'll already have complained about whatever offended us */
491 openconnect_sha1(local_sha1_bin, config_buf, buflen);
493 for (i = 0; i < SHA1_SIZE; i++)
494 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
496 if (strcasecmp(server_sha1, local_sha1_ascii)) {
497 vpn_progress(vpninfo, PRG_ERR,
498 _("Downloaded config file did not match intended SHA1\n"));
503 result = vpninfo->write_new_config(vpninfo->cbdata, config_buf, buflen);
508 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
513 if (!vpninfo->uid_csd_given && !vpninfo->csd_wrapper) {
514 vpn_progress(vpninfo, PRG_ERR,
515 _("Error: Server asked us to download and run a 'Cisco Secure Desktop' trojan.\n"
516 "This facility is disabled by default for security reasons, so you may wish to enable it."));
521 vpn_progress(vpninfo, PRG_INFO,
522 _("Trying to run Linux CSD trojan script."));
525 sprintf(fname, "/tmp/csdXXXXXX");
529 vpn_progress(vpninfo, PRG_ERR,
530 _("Failed to open temporary CSD script file: %s\n"),
535 ret = proxy_write(vpninfo, fd, (void *)buf, buflen);
537 vpn_progress(vpninfo, PRG_ERR,
538 _("Failed to write temporary CSD script file: %s\n"),
546 char scertbuf[MD5_SIZE * 2 + 1];
547 char ccertbuf[MD5_SIZE * 2 + 1];
551 if (vpninfo->uid_csd_given && vpninfo->uid_csd != getuid()) {
554 if (setuid(vpninfo->uid_csd)) {
555 fprintf(stderr, _("Failed to set uid %ld\n"),
556 (long)vpninfo->uid_csd);
559 if (!(pw = getpwuid(vpninfo->uid_csd))) {
560 fprintf(stderr, _("Invalid user uid=%ld\n"),
561 (long)vpninfo->uid_csd);
564 setenv("HOME", pw->pw_dir, 1);
565 if (chdir(pw->pw_dir)) {
566 fprintf(stderr, _("Failed to change to CSD home directory '%s': %s\n"),
567 pw->pw_dir, strerror(errno));
571 if (getuid() == 0 && !vpninfo->csd_wrapper) {
572 fprintf(stderr, _("Warning: you are running insecure "
573 "CSD code with root privileges\n"
574 "\t Use command line option \"--csd-user\"\n"));
576 if (vpninfo->uid_csd_given == 2) {
577 /* The NM tool really needs not to get spurious output
578 on stdout, which the CSD trojan spews. */
581 if (vpninfo->csd_wrapper)
582 csd_argv[i++] = vpninfo->csd_wrapper;
583 csd_argv[i++] = fname;
584 csd_argv[i++]= (char *)"-ticket";
585 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
587 csd_argv[i++]= (char *)"-stub";
588 csd_argv[i++]= (char *)"\"0\"";
589 csd_argv[i++]= (char *)"-group";
590 if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
593 openconnect_local_cert_md5(vpninfo, ccertbuf);
595 get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
596 csd_argv[i++]= (char *)"-certhash";
597 if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
600 csd_argv[i++]= (char *)"-url";
601 if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
604 csd_argv[i++]= (char *)"-langselen";
605 csd_argv[i++] = NULL;
607 if (setenv("CSD_TOKEN", vpninfo->csd_token, 1))
609 if (setenv("CSD_HOSTNAME", vpninfo->hostname, 1))
612 execv(csd_argv[0], csd_argv);
615 vpn_progress(vpninfo, PRG_ERR,
616 _("Failed to exec CSD script %s\n"), csd_argv[0]);
620 free(vpninfo->csd_stuburl);
621 vpninfo->csd_stuburl = NULL;
622 free(vpninfo->urlpath);
623 vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
624 (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
625 free(vpninfo->csd_waiturl);
626 vpninfo->csd_waiturl = NULL;
627 vpninfo->csd_scriptname = strdup(fname);
629 http_add_cookie(vpninfo, "sdesktop", vpninfo->csd_token);
634 int internal_parse_url(char *url, char **res_proto, char **res_host,
635 int *res_port, char **res_path, int default_port)
638 char *host, *path, *port_str;
641 host = strstr(url, "://");
646 if (!strcasecmp(proto, "https"))
648 else if (!strcasecmp(proto, "http"))
650 else if (!strcasecmp(proto, "socks") ||
651 !strcasecmp(proto, "socks4") ||
652 !strcasecmp(proto, "socks5"))
655 return -EPROTONOSUPPORT;
665 path = strchr(host, '/');
669 port_str = strrchr(host, ':');
672 int new_port = strtol(port_str + 1, &end, 10);
681 *res_proto = proto ? strdup(proto) : NULL;
683 *res_host = strdup(host);
687 *res_path = (path && *path) ? strdup(path) : NULL;
689 /* Undo the damage we did to the original string */
699 static void clear_cookies(struct openconnect_info *vpninfo)
701 struct vpn_option *opt, *next;
703 for (opt = vpninfo->cookies; opt; opt = next) {
710 vpninfo->cookies = NULL;
715 * = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
717 static int handle_redirect(struct openconnect_info *vpninfo)
719 vpninfo->redirect_type = REDIR_TYPE_LOCAL;
721 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
722 /* New host. Tear down the existing connection and make a new one */
727 free(vpninfo->urlpath);
728 vpninfo->urlpath = NULL;
730 ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
732 vpn_progress(vpninfo, PRG_ERR,
733 _("Failed to parse redirected URL '%s': %s\n"),
734 vpninfo->redirect_url, strerror(-ret));
735 free(vpninfo->redirect_url);
736 vpninfo->redirect_url = NULL;
740 if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
741 free(vpninfo->hostname);
742 vpninfo->hostname = host;
743 vpninfo->port = port;
745 /* Kill the existing connection, and a new one will happen */
746 free(vpninfo->peer_addr);
747 vpninfo->peer_addr = NULL;
748 openconnect_close_https(vpninfo, 0);
749 clear_cookies(vpninfo);
750 vpninfo->redirect_type = REDIR_TYPE_NEWHOST;
754 free(vpninfo->redirect_url);
755 vpninfo->redirect_url = NULL;
758 } else if (strstr(vpninfo->redirect_url, "://")) {
759 vpn_progress(vpninfo, PRG_ERR,
760 _("Cannot follow redirection to non-https URL '%s'\n"),
761 vpninfo->redirect_url);
762 free(vpninfo->redirect_url);
763 vpninfo->redirect_url = NULL;
765 } else if (vpninfo->redirect_url[0] == '/') {
766 /* Absolute redirect within same host */
767 free(vpninfo->urlpath);
768 vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
769 free(vpninfo->redirect_url);
770 vpninfo->redirect_url = NULL;
773 char *lastslash = NULL;
774 if (vpninfo->urlpath)
775 lastslash = strrchr(vpninfo->urlpath, '/');
777 free(vpninfo->urlpath);
778 vpninfo->urlpath = vpninfo->redirect_url;
779 vpninfo->redirect_url = NULL;
781 char *oldurl = vpninfo->urlpath;
783 vpninfo->urlpath = NULL;
784 if (asprintf(&vpninfo->urlpath, "%s/%s",
785 oldurl, vpninfo->redirect_url) == -1) {
787 vpn_progress(vpninfo, PRG_ERR,
788 _("Allocating new path for relative redirect failed: %s\n"),
793 free(vpninfo->redirect_url);
794 vpninfo->redirect_url = NULL;
801 * method: GET or POST
802 * vpninfo->hostname: Host DNS name
803 * vpninfo->port: TCP port, typically 443
804 * vpninfo->urlpath: Relative path, e.g. /+webvpn+/foo.html
805 * request_body_type: Content type for a POST (e.g. text/html). Can be NULL.
806 * request_body: POST content
807 * form_buf: Callee-allocated buffer for server content
811 * >=0, on success, indicating the length of the data in *form_buf
813 static int do_https_request(struct openconnect_info *vpninfo, const char *method,
814 const char *request_body_type, const char *request_body,
815 char **form_buf, int fetch_redirect)
817 struct oc_text_buf *buf;
821 vpninfo->redirect_type = REDIR_TYPE_NONE;
827 if (openconnect_open_https(vpninfo)) {
828 vpn_progress(vpninfo, PRG_ERR,
829 _("Failed to open HTTPS connection to %s\n"),
835 * It would be nice to use cURL for this, but we really need to guarantee
836 * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
837 * to have any way to let us provide our own socket read/write functions.
838 * We can only provide a socket _open_ function. Which would require having
839 * a socketpair() and servicing the "other" end of it.
841 * So we process the HTTP for ourselves...
844 buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
845 add_common_headers(vpninfo, buf);
847 if (request_body_type) {
848 buf_append(buf, "Content-Type: %s\r\n", request_body_type);
849 buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
851 buf_append(buf, "\r\n");
853 if (request_body_type)
854 buf_append(buf, "%s", request_body);
856 if (vpninfo->port == 443)
857 vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
858 method, vpninfo->hostname,
859 vpninfo->urlpath ?: "");
861 vpn_progress(vpninfo, PRG_INFO, "%s https://%s:%d/%s\n",
862 method, vpninfo->hostname, vpninfo->port,
863 vpninfo->urlpath ?: "");
866 return buf_free(buf);
868 result = openconnect_SSL_write(vpninfo, buf->data, buf->pos);
873 buflen = process_http_response(vpninfo, &result, NULL, form_buf);
875 /* We'll already have complained about whatever offended us */
879 if (result != 200 && vpninfo->redirect_url) {
880 result = handle_redirect(vpninfo);
888 if (!*form_buf || result != 200) {
889 vpn_progress(vpninfo, PRG_ERR,
890 _("Unexpected %d result from server\n"),
905 * < 0, if the data is unrecognized
906 * = 0, if the page contains an XML document
907 * = 1, if the page is a wait/refresh HTML page
909 static int check_response_type(struct openconnect_info *vpninfo, char *form_buf)
911 if (strncmp(form_buf, "<?xml", 5)) {
912 /* Not XML? Perhaps it's HTML with a refresh... */
913 if (strcasestr(form_buf, "http-equiv=\"refresh\""))
915 vpn_progress(vpninfo, PRG_ERR,
916 _("Unknown response from server\n"));
924 * > 0, no cookie (user cancel)
925 * = 0, obtained cookie
927 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
929 struct vpn_option *opt;
930 char *form_buf = NULL;
931 struct oc_auth_form *form = NULL;
932 int result, buflen, tries;
933 char request_body[2048];
934 const char *request_body_type = "application/x-www-form-urlencoded";
935 const char *method = "POST";
938 /* Step 1: Unlock software token (if applicable) */
939 if (vpninfo->use_stoken) {
940 result = prepare_stoken(vpninfo);
946 * Step 2: Probe for XML POST compatibility
948 * This can get stuck in a redirect loop, so give up after any of:
950 * a) HTTP error (e.g. 400 Bad Request)
951 * b) Same-host redirect (e.g. Location: /foo/bar)
952 * c) Three redirects without seeing a plausible login form
954 result = xmlpost_initial_req(vpninfo, request_body, sizeof(request_body));
958 for (tries = 0; ; tries++) {
961 buflen = do_https_request(vpninfo, method, request_body_type, request_body,
963 if (buflen == -EINVAL)
968 if (vpninfo->redirect_type == REDIR_TYPE_LOCAL)
970 else if (vpninfo->redirect_type != REDIR_TYPE_NONE)
973 result = parse_xml_response(vpninfo, form_buf, &form);
978 vpn_progress(vpninfo, PRG_INFO, _("XML POST enabled\n"));
982 /* Step 3: Fetch and parse the login form, if not using XML POST */
984 buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
988 result = parse_xml_response(vpninfo, form_buf, &form);
994 vpninfo->redirect_url = strdup(form->action);
995 handle_redirect(vpninfo);
999 /* Step 4: Run the CSD trojan, if applicable */
1000 if (vpninfo->csd_starturl) {
1001 char *form_path = NULL;
1003 if (vpninfo->urlpath) {
1004 form_path = strdup(vpninfo->urlpath);
1011 /* fetch the CSD program, if available */
1012 if (vpninfo->csd_stuburl) {
1013 buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
1020 /* This is the CSD stub script, which we now need to run */
1021 result = run_csd_script(vpninfo, form_buf, buflen);
1025 /* vpninfo->urlpath now points to the wait page */
1027 result = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
1031 result = check_response_type(vpninfo, form_buf);
1035 vpn_progress(vpninfo, PRG_INFO,
1036 _("Refreshing %s after 1 second...\n"),
1043 /* refresh the form page, to see if we're authorized now */
1044 free(vpninfo->urlpath);
1045 vpninfo->urlpath = form_path;
1047 result = do_https_request(vpninfo, xmlpost ? "POST" : "GET",
1048 request_body_type, request_body, &form_buf, 1);
1052 result = parse_xml_response(vpninfo, form_buf, &form);
1057 /* Step 5: Ask the user to fill in the auth form; repeat as necessary */
1059 request_body[0] = 0;
1060 result = handle_auth_form(vpninfo, form, request_body, sizeof(request_body),
1061 &method, &request_body_type, xmlpost);
1062 if (result < 0 || result == 1)
1067 result = do_https_request(vpninfo, method, request_body_type, request_body,
1072 result = parse_xml_response(vpninfo, form_buf, &form);
1077 /* A return value of 2 means the XML form indicated
1078 success. We _should_ have a cookie... */
1080 for (opt = vpninfo->cookies; opt; opt = opt->next) {
1082 if (!strcmp(opt->option, "webvpn"))
1083 vpninfo->cookie = opt->value;
1084 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
1085 char *tok = opt->value;
1086 char *bu = NULL, *fu = NULL, *sha = NULL;
1089 if (tok != opt->value)
1092 if (!strncmp(tok, "bu:", 3))
1094 else if (!strncmp(tok, "fu:", 3))
1096 else if (!strncmp(tok, "fh:", 3)) {
1097 if (!strncasecmp(tok+3, vpninfo->xmlsha1,
1102 } while ((tok = strchr(tok, '&')));
1104 if (bu && fu && sha)
1105 fetch_config(vpninfo, bu, fu, sha);
1112 free_auth_form(form);
1114 if (vpninfo->csd_scriptname) {
1115 unlink(vpninfo->csd_scriptname);
1116 free(vpninfo->csd_scriptname);
1117 vpninfo->csd_scriptname = NULL;
1123 char *openconnect_create_useragent(const char *base)
1127 if (asprintf(&uagent, "%s %s", base, openconnect_version_str) < 0)
1133 static int proxy_gets(struct openconnect_info *vpninfo, int fd,
1134 char *buf, size_t len)
1142 while ( (ret = proxy_read(vpninfo, fd, (void *)(buf + i), 1)) == 0) {
1143 if (buf[i] == '\n') {
1145 if (i && buf[i-1] == '\r') {
1162 static int proxy_write(struct openconnect_info *vpninfo, int fd,
1163 unsigned char *buf, size_t len)
1167 for (count = 0; count < len; ) {
1168 fd_set rd_set, wr_set;
1174 FD_SET(fd, &wr_set);
1175 if (vpninfo->cancel_fd != -1) {
1176 FD_SET(vpninfo->cancel_fd, &rd_set);
1177 if (vpninfo->cancel_fd > fd)
1178 maxfd = vpninfo->cancel_fd;
1181 select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
1182 if (vpninfo->cancel_fd != -1 &&
1183 FD_ISSET(vpninfo->cancel_fd, &rd_set))
1186 /* Not that this should ever be able to happen... */
1187 if (!FD_ISSET(fd, &wr_set))
1190 i = write(fd, buf + count, len - count);
1199 static int proxy_read(struct openconnect_info *vpninfo, int fd,
1200 unsigned char *buf, size_t len)
1204 for (count = 0; count < len; ) {
1210 FD_SET(fd, &rd_set);
1211 if (vpninfo->cancel_fd != -1) {
1212 FD_SET(vpninfo->cancel_fd, &rd_set);
1213 if (vpninfo->cancel_fd > fd)
1214 maxfd = vpninfo->cancel_fd;
1217 select(maxfd + 1, &rd_set, NULL, NULL, NULL);
1218 if (vpninfo->cancel_fd != -1 &&
1219 FD_ISSET(vpninfo->cancel_fd, &rd_set))
1222 /* Not that this should ever be able to happen... */
1223 if (!FD_ISSET(fd, &rd_set))
1226 i = read(fd, buf + count, len - count);
1235 static const char *socks_errors[] = {
1236 N_("request granted"),
1237 N_("general failure"),
1238 N_("connection not allowed by ruleset"),
1239 N_("network unreachable"),
1240 N_("host unreachable"),
1241 N_("connection refused by destination host"),
1243 N_("command not supported / protocol error"),
1244 N_("address type not supported")
1247 static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1249 unsigned char buf[1024];
1252 buf[0] = 5; /* SOCKS version */
1253 buf[1] = 1; /* # auth methods */
1254 buf[2] = 0; /* No auth supported */
1256 if ((i = proxy_write(vpninfo, ssl_sock, buf, 3))) {
1257 vpn_progress(vpninfo, PRG_ERR,
1258 _("Error writing auth request to SOCKS proxy: %s\n"),
1263 if ((i = proxy_read(vpninfo, ssl_sock, buf, 2))) {
1264 vpn_progress(vpninfo, PRG_ERR,
1265 _("Error reading auth response from SOCKS proxy: %s\n"),
1270 vpn_progress(vpninfo, PRG_ERR,
1271 _("Unexpected auth response from SOCKS proxy: %02x %02x\n"),
1277 if (buf[1] < sizeof(socks_errors) / sizeof(socks_errors[0]))
1278 vpn_progress(vpninfo, PRG_ERR,
1279 _("SOCKS proxy error %02x: %s\n"),
1280 buf[1], _(socks_errors[buf[1]]));
1282 vpn_progress(vpninfo, PRG_ERR,
1283 _("SOCKS proxy error %02x\n"),
1288 vpn_progress(vpninfo, PRG_INFO,
1289 _("Requesting SOCKS proxy connection to %s:%d\n"),
1290 vpninfo->hostname, vpninfo->port);
1292 buf[0] = 5; /* SOCKS version */
1293 buf[1] = 1; /* CONNECT */
1294 buf[2] = 0; /* Reserved */
1295 buf[3] = 3; /* Address type is domain name */
1296 buf[4] = strlen(vpninfo->hostname);
1297 strcpy((char *)buf + 5, vpninfo->hostname);
1298 i = strlen(vpninfo->hostname) + 5;
1299 buf[i++] = vpninfo->port >> 8;
1300 buf[i++] = vpninfo->port & 0xff;
1302 if ((i = proxy_write(vpninfo, ssl_sock, buf, i))) {
1303 vpn_progress(vpninfo, PRG_ERR,
1304 _("Error writing connect request to SOCKS proxy: %s\n"),
1308 /* Read 5 bytes -- up to and including the first byte of the returned
1309 address (which might be the length byte of a domain name) */
1310 if ((i = proxy_read(vpninfo, ssl_sock, buf, 5))) {
1311 vpn_progress(vpninfo, PRG_ERR,
1312 _("Error reading connect response from SOCKS proxy: %s\n"),
1317 vpn_progress(vpninfo, PRG_ERR,
1318 _("Unexpected connect response from SOCKS proxy: %02x %02x...\n"),
1325 /* Connect responses contain an address */
1327 case 1: /* Legacy IP */
1330 case 3: /* Domain name */
1337 vpn_progress(vpninfo, PRG_ERR,
1338 _("Unexpected address type %02x in SOCKS connect response\n"),
1343 if ((i = proxy_read(vpninfo, ssl_sock, buf, i))) {
1344 vpn_progress(vpninfo, PRG_ERR,
1345 _("Error reading connect response from SOCKS proxy: %s\n"),
1352 static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1354 char buf[MAX_BUF_LEN];
1355 struct oc_text_buf *reqbuf;
1358 reqbuf = buf_alloc();
1359 buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
1360 buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
1361 buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
1362 buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
1363 buf_append(reqbuf, "Connection: keep-alive\r\n");
1364 buf_append(reqbuf, "Accept-Encoding: identity\r\n");
1365 buf_append(reqbuf, "\r\n");
1367 if (buf_error(reqbuf))
1368 return buf_free(reqbuf);
1370 vpn_progress(vpninfo, PRG_INFO,
1371 _("Requesting HTTP proxy connection to %s:%d\n"),
1372 vpninfo->hostname, vpninfo->port);
1374 result = proxy_write(vpninfo, ssl_sock, (unsigned char *)reqbuf->data, reqbuf->pos);
1378 vpn_progress(vpninfo, PRG_ERR,
1379 _("Sending proxy request failed: %s\n"),
1384 if (proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)) < 0) {
1385 vpn_progress(vpninfo, PRG_ERR,
1386 _("Error fetching proxy response\n"));
1390 if (strncmp(buf, "HTTP/1.", 7) || (buf[7] != '0' && buf[7] != '1') ||
1391 buf[8] != ' ' || !(result = atoi(buf+9))) {
1392 vpn_progress(vpninfo, PRG_ERR,
1393 _("Failed to parse proxy response '%s'\n"), buf);
1397 if (result != 200) {
1398 vpn_progress(vpninfo, PRG_ERR,
1399 _("Proxy CONNECT request failed: %s\n"), buf);
1403 while ((buflen = proxy_gets(vpninfo, ssl_sock, buf, sizeof(buf)))) {
1405 vpn_progress(vpninfo, PRG_ERR,
1406 _("Failed to read proxy response\n"));
1409 vpn_progress(vpninfo, PRG_ERR,
1410 _("Unexpected continuation line after CONNECT response: '%s'\n"),
1417 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock)
1419 if (!vpninfo->proxy_type || !strcmp(vpninfo->proxy_type, "http"))
1420 return process_http_proxy(vpninfo, ssl_sock);
1422 if (!strcmp(vpninfo->proxy_type, "socks") ||
1423 !strcmp(vpninfo->proxy_type, "socks5"))
1424 return process_socks_proxy(vpninfo, ssl_sock);
1426 vpn_progress(vpninfo, PRG_ERR, _("Unknown proxy type '%s'\n"),
1427 vpninfo->proxy_type);
1431 int openconnect_set_http_proxy(struct openconnect_info *vpninfo, char *proxy)
1439 free(vpninfo->proxy_type);
1440 vpninfo->proxy_type = NULL;
1441 free(vpninfo->proxy);
1442 vpninfo->proxy = NULL;
1444 ret = internal_parse_url(url, &vpninfo->proxy_type, &vpninfo->proxy,
1445 &vpninfo->proxy_port, NULL, 80);
1449 if (vpninfo->proxy_type &&
1450 strcmp(vpninfo->proxy_type, "http") &&
1451 strcmp(vpninfo->proxy_type, "socks") &&
1452 strcmp(vpninfo->proxy_type, "socks5")) {
1453 vpn_progress(vpninfo, PRG_ERR,
1454 _("Only http or socks(5) proxies supported\n"));
1455 free(vpninfo->proxy_type);
1456 vpninfo->proxy_type = NULL;
1457 free(vpninfo->proxy);
1458 vpninfo->proxy = NULL;