123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766 |
- /*
- * OpenConnect (SSL + DTLS) VPN client
- *
- * Copyright © 2020-2021 David Woodhouse, Daniel Lenski
- *
- * Author: David Woodhouse <dwmw2@infradead.org>, Daniel Lenski <dlenski@gmail.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * version 2.1, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- */
- #include <config.h>
- #include "openconnect-internal.h"
- #include "ppp.h"
- #include <libxml/HTMLparser.h>
- #include <libxml/HTMLtree.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include <sys/types.h>
- #include <time.h>
- #include <string.h>
- #include <ctype.h>
- #include <errno.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <stdarg.h>
- #define XCAST(x) ((const xmlChar *)(x))
- /* XX: some F5 VPNs simply do not have a static HTML form to parse, but
- * only a mess of JavaScript which creates a dynamic form that's
- * functionally equivalent to the following:
- *
- * <form id="auth_form" method="post">
- * <input type="text" name="username"/>
- * <input type="password" name="password"/>
- * </form>
- */
- static struct oc_auth_form *plain_auth_form(void)
- {
- struct oc_auth_form *form;
- struct oc_form_opt *opt, *opt2;
- form = calloc(1, sizeof(*form));
- if (!form) {
- nomem:
- free_auth_form(form);
- return NULL;
- }
- if ((form->auth_id = strdup("auth_form")) == NULL)
- goto nomem;
- opt = form->opts = calloc(1, sizeof(*opt));
- if (!opt)
- goto nomem;
- opt->label = strdup("username:");
- opt->name = strdup("username");
- opt->type = OC_FORM_OPT_TEXT;
- opt2 = opt->next = calloc(1, sizeof(*opt2));
- if (!opt2)
- goto nomem;
- opt2->label = strdup("password:");
- opt2->name = strdup("password");
- opt2->type = OC_FORM_OPT_PASSWORD;
- return form;
- }
- static int check_cookie_success(struct openconnect_info *vpninfo)
- {
- struct oc_vpn_option *cookie;
- const char *session = NULL, *f5_st = NULL;
- /* XX: The MRHSession cookie is often set repeatedly to new values prior
- * to the completion of authentication, so it is not a sufficient
- * indicator of successfully completed authentication by itself.
- *
- * The combination of the MRHSession and F5_ST cookies appears to be
- * reliable indicator. F5_ST is a "session timeout" cookie (see
- * https://support.f5.com/csp/article/K15387).
- */
- for (cookie = vpninfo->cookies; cookie; cookie = cookie->next) {
- if (!strcmp(cookie->option, "MRHSession"))
- session = cookie->value;
- else if (!strcmp(cookie->option, "F5_ST"))
- f5_st = cookie->value;
- }
- if (session && f5_st) {
- free(vpninfo->cookie);
- if (asprintf(&vpninfo->cookie, "MRHSession=%s; F5_ST=%s", session, f5_st) <= 0)
- return -ENOMEM;
- return 0;
- }
- return -ENOENT;
- }
- int f5_obtain_cookie(struct openconnect_info *vpninfo)
- {
- int ret, form_order=0;
- xmlDocPtr doc = NULL;
- xmlNode *node;
- struct oc_text_buf *req_buf = NULL;
- struct oc_auth_form *form = NULL;
- char *form_id = NULL;
- req_buf = buf_alloc();
- if ((ret = buf_error(req_buf)))
- goto out;
- while (++form_order) {
- char *resp_buf = NULL;
- char *url;
- if (req_buf && req_buf->pos)
- ret = do_https_request(vpninfo, "POST",
- "application/x-www-form-urlencoded",
- req_buf, &resp_buf, NULL, HTTP_REDIRECT_TO_GET);
- else
- ret = do_https_request(vpninfo, "GET", NULL, NULL, &resp_buf, NULL, HTTP_REDIRECT_TO_GET);
- if (!check_cookie_success(vpninfo)) {
- free(resp_buf);
- ret = 0;
- break;
- }
- if (ret < 0)
- break;
- url = internal_get_url(vpninfo);
- if (!url) {
- free(resp_buf);
- nomem:
- ret = -ENOMEM;
- break;
- }
- doc = htmlReadMemory(resp_buf, ret, url, NULL,
- HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING|HTML_PARSE_NONET);
- free(url);
- free(resp_buf);
- if (!doc) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to parse HTML document\n"));
- ret = -EINVAL;
- break;
- }
- buf_truncate(req_buf);
- node = find_form_node(doc);
- if (!node && form_order==1) {
- /* XX: some F5 VPNs simply do not have a static HTML form to parse */
- vpn_progress(vpninfo, PRG_ERR,
- _("WARNING: no HTML login form found; assuming username and password fields\n"));
- if ((form = plain_auth_form()) == NULL)
- goto nomem;
- } else {
- form = parse_form_node(vpninfo, node, NULL, NULL);
- if (form_order==1 && (xmlnode_get_prop(node, "id", &form_id) || strcmp(form_id, "auth_form"))) {
- vpn_progress(vpninfo, PRG_ERR, _("Unknown form ID '%s' (expected 'auth_form')\n"),
- form_id);
- fprintf(stderr, _("Dumping unknown HTML form:\n"));
- htmlNodeDumpFileFormat(stderr, node->doc, node, NULL, 1);
- ret = -EINVAL;
- break;
- }
- }
- if (!form) {
- ret = -EINVAL;
- break;
- }
- /* XX: do_gen_tokencode would go here, if we knew of any
- * token-based 2FA options for F5.
- */
- do {
- ret = process_auth_form(vpninfo, form);
- } while (ret == OC_FORM_RESULT_NEWGROUP);
- if (ret)
- goto out;
- append_form_opts(vpninfo, form, req_buf);
- if ((ret = buf_error(req_buf)))
- goto out;
- if (form->action) {
- vpninfo->redirect_url = form->action;
- form->action = NULL;
- }
- free_auth_form(form);
- form = NULL;
- if (vpninfo->redirect_url)
- handle_redirect(vpninfo);
- xmlFreeDoc(doc);
- doc = NULL;
- }
- out:
- if (doc)
- xmlFreeDoc(doc);
- free(form_id);
- if (form) free_auth_form(form);
- if (req_buf) buf_free(req_buf);
- return ret;
- }
- /*
- * Parse the 'favorites' profile information from
- * /vdesk/vpn/index.php3?outform=xml&client_version=2.0
- * which looks something like this:
- *
- * <?xml version="1.0" encoding="utf-8"?>
- * <favorites type="VPN" limited="YES">
- * <favorite id="/Common/demo_vpn_resource">
- * <caption>demo_vpn_resource</caption>
- * <name>/Common/demo_vpn_resource</name>
- * <params>resourcename=/Common/demo_vpn_resource</params>
- * </favorite>
- * </favorites>
- *
- * Extract the content of the "params" node which is needed for the
- * next request.
- */
- static int parse_profile(struct openconnect_info *vpninfo, char *buf, int len,
- char **params)
- {
- xmlDocPtr xml_doc;
- xmlNode *xml_node, *xml_node2, *xml_node3;
- char *type = NULL;
- int ret;
- if (!buf || !len)
- return -EINVAL;
- xml_doc = xmlReadMemory(buf, len, NULL, NULL,
- XML_PARSE_NOERROR|XML_PARSE_RECOVER);
- if (!xml_doc) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to parse F5 profile response\n"));
- vpn_progress(vpninfo, PRG_DEBUG,
- _("Response was:%s\n"), buf);
- return -EINVAL;
- }
- xml_node = xmlDocGetRootElement(xml_doc);
- for (; xml_node; xml_node = xml_node->next) {
- if (xml_node->type != XML_ELEMENT_NODE)
- continue;
- if (!xmlnode_is_named(xml_node, "favorites"))
- continue;
- type = (char *)xmlGetProp(xml_node, XCAST("type"));
- if (!type)
- continue;
- if (strcmp(type, "VPN")) {
- free(type);
- continue;
- }
- free(type);
- for (xml_node2 = xmlFirstElementChild(xml_node);
- xml_node2;
- xml_node2 = xmlNextElementSibling(xml_node2)) {
- if (!xmlnode_is_named(xml_node2, "favorite"))
- continue;
- for (xml_node3 = xmlFirstElementChild(xml_node2);
- xml_node3;
- xml_node3 = xmlNextElementSibling(xml_node3)) {
- if (!xmlnode_is_named(xml_node3, "params"))
- continue;
- *params = (char *)xmlNodeGetContent(xml_node3);
- ret = 0;
- goto out;
- }
- }
- }
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to find VPN profile parameters\n"));
- vpn_progress(vpninfo, PRG_DEBUG,
- _("Response was:%s\n"), buf);
- ret = -EINVAL;
- out:
- xmlFreeDoc(xml_doc);
- return ret;
- }
- static int xmlnode_bool_or_int_value(struct openconnect_info *vpninfo, xmlNode *node)
- {
- int ret = -1;
- char *content = (char *)xmlNodeGetContent(node);
- if (!content)
- return -1;
- if (isdigit(content[0]))
- ret = atoi(content);
- if (!strcasecmp(content, "yes") || !strcasecmp(content, "on"))
- ret = 1;
- if (!strcasecmp(content, "no") || !strcasecmp(content, "off"))
- ret = 0;
- free(content);
- return ret;
- }
- static int parse_options(struct openconnect_info *vpninfo, char *buf, int len,
- char **session_id, char **ur_z, int *ipv4, int *ipv6, int *hdlc)
- {
- xmlNode *fav_node, *obj_node, *xml_node;
- xmlDocPtr xml_doc;
- int ret = 0, n_dns = 0, n_nbns = 0, default_route = 0, dtls = 0, dtls_port = 0;
- char *s = NULL;
- struct oc_text_buf *domains = NULL;
- if (!buf || !len)
- return -EINVAL;
- xml_doc = xmlReadMemory(buf, len, NULL, NULL,
- XML_PARSE_NOERROR|XML_PARSE_RECOVER);
- if (!xml_doc) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to parse F5 options response\n"));
- vpn_progress(vpninfo, PRG_DEBUG,
- _("Response was:%s\n"), buf);
- return -EINVAL;
- }
- fav_node = xmlDocGetRootElement(xml_doc);
- if (!xmlnode_is_named(fav_node, "favorite"))
- goto err;
- obj_node = xmlFirstElementChild(fav_node);
- if (!xmlnode_is_named(obj_node, "object"))
- goto err;
- struct oc_vpn_option *new_opts = NULL;
- struct oc_ip_info new_ip_info = {};
- domains = buf_alloc();
- for (xml_node = xmlFirstElementChild(obj_node);
- xml_node;
- xml_node = xmlNextElementSibling(xml_node)) {
- if (xmlnode_is_named(xml_node, "ur_Z"))
- *ur_z = (char *)xmlNodeGetContent(xml_node);
- else if (xmlnode_is_named(xml_node, "Session_ID"))
- *session_id = (char *)xmlNodeGetContent(xml_node);
- else if (xmlnode_is_named(xml_node, "IPV4_0"))
- *ipv4 = xmlnode_bool_or_int_value(vpninfo, xml_node);
- else if (xmlnode_is_named(xml_node, "IPV6_0")) {
- if (!vpninfo->disable_ipv6)
- *ipv6 = xmlnode_bool_or_int_value(vpninfo, xml_node);
- } else if (xmlnode_is_named(xml_node, "hdlc_framing"))
- *hdlc = xmlnode_bool_or_int_value(vpninfo, xml_node);
- else if (xmlnode_is_named(xml_node, "idle_session_timeout")) {
- int sec = vpninfo->idle_timeout = xmlnode_bool_or_int_value(vpninfo, xml_node);
- vpn_progress(vpninfo, PRG_INFO, _("Idle timeout is %d minutes\n"), sec/60);
- } else if (xmlnode_is_named(xml_node, "tunnel_dtls"))
- dtls = xmlnode_bool_or_int_value(vpninfo, xml_node);
- else if (xmlnode_is_named(xml_node, "tunnel_port_dtls"))
- dtls_port = xmlnode_bool_or_int_value(vpninfo, xml_node);
- else if (xmlnode_is_named(xml_node, "dtls_v1_2_supported"))
- vpninfo->dtls12 = xmlnode_bool_or_int_value(vpninfo, xml_node);
- else if (xmlnode_is_named(xml_node, "UseDefaultGateway0")) {
- default_route = xmlnode_bool_or_int_value(vpninfo, xml_node);
- if (default_route)
- vpn_progress(vpninfo, PRG_INFO, _("Got default routes\n"));
- } else if (xmlnode_is_named(xml_node, "SplitTunneling0")) {
- int st = xmlnode_bool_or_int_value(vpninfo, xml_node);
- vpn_progress(vpninfo, PRG_INFO, _("Got SplitTunneling0 value of %d\n"), st);
- /* XX: Should we ignore split-{in,ex}cludes if this is zero? */
- }
- /* XX: This is an objectively stupid way to use XML, a hierarchical data format. */
- else if ( (!strncmp((char *)xml_node->name, "DNS", 3) && isdigit(xml_node->name[3]))
- || (!strncmp((char *)xml_node->name, "DNS6_", 5) && isdigit(xml_node->name[5])) ) {
- free(s);
- s = (char *)xmlNodeGetContent(xml_node);
- if (s && *s) {
- vpn_progress(vpninfo, PRG_INFO, _("Got DNS server %s\n"), s);
- if (n_dns < 3) new_ip_info.dns[n_dns++] = add_option_steal(&new_opts, "DNS", &s);
- }
- } else if (!strncmp((char *)xml_node->name, "WINS", 4) && isdigit(xml_node->name[4])) {
- free(s);
- s = (char *)xmlNodeGetContent(xml_node);
- if (s && *s) {
- vpn_progress(vpninfo, PRG_INFO, _("Got WINS/NBNS server %s\n"), s);
- if (n_nbns < 3) new_ip_info.dns[n_nbns++] = add_option_steal(&new_opts, "WINS", &s);
- }
- } else if (!strncmp((char *)xml_node->name, "DNSSuffix", 9) && isdigit(xml_node->name[9])) {
- free(s);
- s = (char *)xmlNodeGetContent(xml_node);
- if (s && *s) {
- vpn_progress(vpninfo, PRG_INFO, _("Got search domain %s\n"), s);
- buf_append(domains, "%s ", s);
- }
- }
- /* XX: Like the above, but even stupider because one tag can contain multiple space-separated values. */
- else if ( (!strncmp((char *)xml_node->name, "LAN", 3) && isdigit((char)xml_node->name[3]))
- || (!strncmp((char *)xml_node->name, "LAN6_", 5) && isdigit((char)xml_node->name[5]))
- || (!strncmp((char *)xml_node->name, "ExcludeSubnets", 14) && isdigit((char)xml_node->name[14]))
- || (!strncmp((char *)xml_node->name, "ExcludeSubnets6_", 16) && isdigit((char)xml_node->name[16]))) {
- free(s);
- s = (char *)xmlNodeGetContent(xml_node);
- if (s && *s) {
- char *word, *next;
- int is_exclude = (xml_node->name[0] == 'E');
- const char *option = is_exclude ? "split-exclude" : "split-include";
- for (word = s; *word; word = next) {
- for (next = word; *next && !isspace(*next); next++);
- if (*next)
- *next++ = 0;
- if (next == word + 1)
- continue;
- struct oc_split_include *inc = malloc(sizeof(*inc));
- if (!inc)
- continue;
- inc->route = add_option_dup(&new_opts, option, word, -1);
- if (is_exclude) {
- inc->next = new_ip_info.split_excludes;
- new_ip_info.split_excludes = inc;
- vpn_progress(vpninfo, PRG_INFO, _("Got split exclude route %s\n"), word);
- } else {
- inc->next = new_ip_info.split_includes;
- new_ip_info.split_includes = inc;
- vpn_progress(vpninfo, PRG_INFO, _("Got split include route %s\n"), word);
- }
- }
- }
- }
- }
- if (dtls && dtls_port && vpninfo->dtls_state == DTLS_NOSECRET) {
- vpn_progress(vpninfo, PRG_INFO, _("DTLS is enabled on port %d\n"), dtls_port);
- if (!*hdlc) {
- udp_sockaddr(vpninfo, dtls_port);
- vpninfo->dtls_state = DTLS_SECRET;
- } else {
- /* XX: HDLC-like framing (RFC1662) means that tunneled packets may double in size as part of
- * their on-the-wire encapsulation, while efficient datagram transport requires calculation
- * of a predictable maximum transfer unit.
- *
- * We hope no servers expect us to combine them. If they do, we should reject DTLS.
- */
- vpn_progress(vpninfo, PRG_ERR,
- _("WARNING: Server enables DTLS, but also requires HDLC. Disabling DTLS,\n"
- " because HDLC prevents determination of efficient and consistent MTU.\n"));
- }
- }
- if (default_route && *ipv4)
- new_ip_info.netmask = add_option_dup(&new_opts, "netmask", "0.0.0.0", -1);
- if (default_route && *ipv6)
- new_ip_info.netmask6 = add_option_dup(&new_opts, "netmask6", "::/0", -1);
- if (buf_error(domains) == 0 && domains->pos > 0) {
- domains->data[domains->pos-1] = '\0';
- new_ip_info.domain = add_option_steal(&new_opts, "search", &domains->data);
- }
- buf_free(domains);
- ret = install_vpn_opts(vpninfo, new_opts, &new_ip_info);
- if (ret || (*ipv4 < 1 && *ipv6 < 1) || !*ur_z || !*session_id) {
- free_optlist(new_opts);
- free_split_routes(&new_ip_info);
- err:
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to find VPN options\n"));
- vpn_progress(vpninfo, PRG_DEBUG,
- _("Response was:%s\n"), buf);
- ret = -EINVAL;
- }
- xmlFreeDoc(xml_doc);
- free(s);
- return ret;
- }
- static int get_ip_address(struct openconnect_info *vpninfo, char *header, char *val)
- {
- struct oc_ppp *ppp = vpninfo->ppp;
- if (!ppp || ppp->ppp_state != PPPS_DEAD)
- return 0;
- /* If the addresses were already negotiated once in PPP and this
- * is a reconnect, they'll be in vpninfo->ip_info.addr*. In that
- * case don't overwrite them, and let it correctly abort if the
- * server rejects the same addresses this time round. */
- if (!strcasecmp(header, "X-VPN-client-IP")) {
- vpn_progress(vpninfo, PRG_INFO,
- _("Got Legacy IP address %s\n"), val);
- if (!vpninfo->ip_info.addr)
- ppp->out_ipv4_addr.s_addr = inet_addr(val);
- } else if (!strcasecmp(header, "X-VPN-client-IPv6")) {
- vpn_progress(vpninfo, PRG_INFO,
- _("Got IPv6 address %s\n"), val);
- if (!vpninfo->ip_info.addr6 && !vpninfo->ip_info.netmask6)
- inet_pton(AF_INET6, val, &ppp->out_ipv6_addr);
- }
- /* XX: The server's IP address(es) X-VPN-server-{IP,IPv6} are also
- * sent, but the utility of these is unclear. As remarked in oncp.c,
- * "this is a tunnel; having a gateway is meaningless." */
- return 0;
- }
- static int f5_configure(struct openconnect_info *vpninfo)
- {
- int ret;
- struct oc_text_buf *reqbuf = NULL;
- struct oc_vpn_option *cookie;
- char *profile_params = NULL;
- char *sid = NULL, *ur_z = NULL;
- int ipv4 = -1, ipv6 = -1, hdlc = 0;
- char *res_buf = NULL;
- if (!vpninfo->cookies) {
- /* XX: This will happen if authentication was separate/external */
- ret = internal_split_cookies(vpninfo, 1, "MRHSession");
- if (ret)
- return ret;
- }
- /* XX: parse "session timeout" cookie to get auth expiration */
- for (cookie = vpninfo->cookies; cookie; cookie = cookie->next) {
- if (!strcmp(cookie->option, "F5_ST")) {
- int junk, start, dur;
- char c = 0;
- if (sscanf(cookie->value, "%dz%dz%dz%dz%d%c", &junk, &junk, &junk, &start, &dur, &c) >= 5
- && (c == 0 || c == 'z'))
- vpninfo->auth_expiration = start + dur;
- break;
- }
- }
- free(vpninfo->urlpath);
- vpninfo->urlpath = strdup("vdesk/vpn/index.php3?outform=xml&client_version=2.0");
- ret = do_https_request(vpninfo, "GET", NULL, NULL, &res_buf, NULL, HTTP_NO_FLAGS);
- if (ret < 0)
- goto out;
- ret = parse_profile(vpninfo, res_buf, ret, &profile_params);
- if (ret)
- goto out;
- vpn_progress(vpninfo, PRG_DEBUG,
- _("Got profile parameters '%s'\n"), profile_params);
- free(res_buf);
- res_buf = NULL;
- free(vpninfo->urlpath);
- if (asprintf(&vpninfo->urlpath, "vdesk/vpn/connect.php3?%s&outform=xml&client_version=2.0",
- profile_params) == -1) {
- ret = -ENOMEM;
- goto out;
- }
- ret = do_https_request(vpninfo, "GET", NULL, NULL, &res_buf, NULL, HTTP_NO_FLAGS);
- if (ret < 0)
- goto out;
- ret = parse_options(vpninfo, res_buf, ret, &sid, &ur_z, &ipv4, &ipv6, &hdlc);
- if (ret)
- goto out;
- vpn_progress(vpninfo, PRG_DEBUG,
- _("Got ipv4 %d ipv6 %d hdlc %d ur_Z '%s'\n"), ipv4, ipv6, hdlc, ur_z);
- if (ipv4 == -1)
- ipv4 = 0;
- if (ipv6 == -1)
- ipv6 = 0;
- /* XX: This buffer is used to initiate the connection over either TLS or DTLS.
- * Cookies are not needed for it to succeed, and can potentially grow without bound,
- * which would make it too big to fit in a single DTLS packet (ick, HTTP over DTLS).
- *
- * Don't blame me. I didn't design this.
- */
- reqbuf = vpninfo->ppp_dtls_connect_req;
- if (!reqbuf)
- reqbuf = buf_alloc();
- buf_truncate(reqbuf);
- buf_append(reqbuf, "GET /myvpn?sess=%s&hdlc_framing=%s&ipv4=%s&ipv6=%s&Z=%s&hostname=",
- sid, hdlc?"yes":"no", ipv4?"yes":"no", ipv6?"yes":"no", ur_z);
- buf_append_base64(reqbuf, vpninfo->localname, strlen(vpninfo->localname), 0);
- buf_append(reqbuf, " HTTP/1.1\r\n");
- struct oc_vpn_option *saved_cookies = vpninfo->cookies;
- vpninfo->cookies = NULL; /* hide cookies */
- http_common_headers(vpninfo, reqbuf);
- vpninfo->cookies = saved_cookies; /* restore cookies */
- buf_append(reqbuf, "\r\n");
- if (buf_error(reqbuf)) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Error establishing F5 connection\n"));
- ret = buf_error(reqbuf);
- goto out;
- }
- vpninfo->ppp_dtls_connect_req = reqbuf;
- reqbuf = NULL;
- ret = openconnect_ppp_new(vpninfo, hdlc ? PPP_ENCAP_F5_HDLC : PPP_ENCAP_F5, ipv4, ipv6);
- out:
- free(res_buf);
- free(profile_params);
- free(sid);
- free(ur_z);
- buf_free(reqbuf);
- return ret;
- }
- int f5_connect(struct openconnect_info *vpninfo)
- {
- int ret = 0;
- if (!vpninfo->ppp) {
- /* Initial connection */
- ret = f5_configure(vpninfo);
- } else if (vpninfo->ppp->ppp_state != PPPS_DEAD) {
- /* TLS/DTLS reconnection with already-established PPP session
- * (PPP session will persist past reconnect.)
- */
- ret = ppp_reset(vpninfo);
- }
- if (ret) {
- err:
- openconnect_close_https(vpninfo, 0);
- return ret;
- }
- ret = ppp_tcp_should_connect(vpninfo);
- if (ret <= 0)
- goto err;
- ret = openconnect_open_https(vpninfo);
- if (ret)
- goto err;
- if (vpninfo->dump_http_traffic)
- dump_buf(vpninfo, '>', vpninfo->ppp_dtls_connect_req->data);
- ret = vpninfo->ssl_write(vpninfo, vpninfo->ppp_dtls_connect_req->data,
- vpninfo->ppp_dtls_connect_req->pos);
- if (ret < 0)
- goto err;
- struct oc_text_buf *resp_buf = buf_alloc();
- if (buf_error(resp_buf)) {
- ret = buf_free(resp_buf);
- goto err;
- }
- ret = process_http_response(vpninfo, 1, get_ip_address, resp_buf);
- buf_free(resp_buf);
- if (ret < 0)
- goto err;
- if (ret != 201 && ret != 200) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Unexpected %d result from server\n"),
- ret);
- ret = (ret == 504) ? -EPERM : -EINVAL;
- goto err;
- }
- /* Trigger the first PPP negotiations and ensure the PPP state
- * is PPPS_ESTABLISH so that our mainloop knows we've started. */
- ppp_start_tcp_mainloop(vpninfo);
- monitor_fd_new(vpninfo, ssl);
- monitor_read_fd(vpninfo, ssl);
- monitor_except_fd(vpninfo, ssl);
- return 0;
- }
- int f5_bye(struct openconnect_info *vpninfo, const char *reason)
- {
- char *orig_path;
- char *res_buf = NULL;
- int ret;
- /* We need to close and reopen the HTTPS connection (to kill
- * the f5 tunnel) and submit a new HTTPS request to logout.
- */
- openconnect_close_https(vpninfo, 0);
- orig_path = vpninfo->urlpath;
- vpninfo->urlpath = strdup("vdesk/hangup.php3?hangup_error=1"); /* redirect segfaults without strdup */
- ret = do_https_request(vpninfo, "GET", NULL, NULL, &res_buf, NULL, HTTP_NO_FLAGS);
- free(vpninfo->urlpath);
- vpninfo->urlpath = orig_path;
- if (ret < 0)
- vpn_progress(vpninfo, PRG_ERR, _("Logout failed.\n"));
- else
- vpn_progress(vpninfo, PRG_INFO, _("Logout successful.\n"));
- free(res_buf);
- return ret;
- }
- int f5_dtls_catch_probe(struct openconnect_info *vpninfo, struct pkt *pkt)
- {
- char *line = (void *)pkt->data, *cr, *colon;
- int first = 1, status = -1;
- pkt->data[pkt->len] = 0;
- while (line && *line) {
- if (*line == '\n') {
- line++;
- continue;
- }
- cr = strchr(line, '\r');
- if (!cr)
- break;
- *cr = 0;
- if (first) {
- char junk, c = 0;
- if (sscanf(line, "HTTP/%c.%c %d%c", &junk, &junk, &status, &c) >= 3
- && (c == 0 || isspace(c))
- && status == 200) {
- first = 0;
- } else
- return (status >= 400 && status <= 499) ? -EPERM : -EINVAL;
- } else {
- colon = strchr(line, ':');
- if (colon) {
- *colon = 0;
- colon++;
- while (isspace(*colon))
- colon++;
- get_ip_address(vpninfo, line, colon);
- }
- }
- line = cr + 1;
- }
- return 1;
- }
|