Newer AnyConnect installations use a different XML document tree to pass information to the client. This patch allows OpenConnect to parse the new format, and attempts to document both the old format and the new format in the comments. Signed-off-by: Kevin Cernekee <cernekee at gmail.com> --- auth.c | 127 ++++++++++++++++++++++++++++++++++++++++++++---- openconnect-internal.h | 3 ++ 2 files changed, 121 insertions(+), 9 deletions(-) diff --git a/auth.c b/auth.c index 4cfc1e1..79c9bc1 100644 --- a/auth.c +++ b/auth.c @@ -348,6 +348,63 @@ static int xmlnode_get_text(xmlNode *xml_node, const char *name, char **var) return 0; } +/* + * Legacy server response looks like: + * + * <auth id="<!-- "main" for initial attempt, "success" means we have a cookie -->"> + * <title><!-- title to display to user --></title> + * <csd token="<!-- save to vpninfo->csd_token -->" + * ticket="<!-- save to vpninfo->csd_ticket -->" /> + * <csd stuburl="<!-- ignore -->" + * starturl="<!-- ignore -->" + * waiturl="<!-- ignore -->" + * <csdMac + * stuburl="<!-- save to vpninfo->csd_stuburl on Mac only -->" + * starturl="<!-- save to vpninfo->csd_starturl on Mac only -->" + * waiturl="<!-- save to vpninfo->csd_waiturl on Mac only -->" /> + * <csdLinux + * stuburl="<!-- same as above, for Linux -->" + * starturl="<!-- same as above, for Linux -->" + * waiturl="<!-- same as above, for Linux -->" /> + * <banner><!-- display this to the user --></banner> + * <message>Please enter your username and password.</message> + * <form method="post" action="/+webvpn+/index.html"> + * <input type="text" name="username" label="Username:" /> + * <input type="password" name="password" label="Password:" /> + * <input type="hidden" name="<!-- save these -->" value="<!-- ... -->" /> + * <input type="submit" name="Login" value="Login" /> + * <input type="reset" name="Clear" value="Clear" /> + * </form> + * </auth> + * + * New server response looks like: + * + * <config-auth> + * <version><!-- whatever --></version> + * <session-token><!-- if present, save to vpninfo->cookie --></session-token> + * <opaque> + * <!-- this could contain anything; copy to vpninfo->opaque_srvdata --> + * <tunnel-group>foobar</tunnel-group> + * <config-hash>1234567</config-hash> + * </opaque> + * <auth id="<!-- see above --> + * <!-- all of our old familiar fields --> + * </auth> + * <host-scan> + * <host-scan-ticket><!-- save to vpninfo->csd_ticket --></host-scan-ticket> + * <host-scan-token><!-- save to vpninfo->csd_token --></host-scan-token> + * <host-scan-base-uri><!-- save to vpninfo->csd_starturl --></host-scan-base-uri> + * <host-scan-wait-uri><!-- save to vpninfo->csd_waiturl --></host-scan-wait-uri> + * </host-scan> + * </config-auth> + * + * Notes: + * + * 1) The new host-scan-*-uri nodes do not map directly to the old CSD fields. + * + * 2) The new <form> tag tends to omit the method/action properties. + */ + static int parse_auth_node(struct openconnect_info *vpninfo, xmlNode *xml_node, struct oc_auth_form *form) { @@ -363,6 +420,10 @@ static int parse_auth_node(struct openconnect_info *vpninfo, xmlNode *xml_node, if (xmlnode_is_named(xml_node, "form")) { + /* defaults for new XML POST */ + form->method = strdup("POST"); + form->action = strdup("/"); + xmlnode_get_prop(xml_node, "method", &form->method); xmlnode_get_prop(xml_node, "action", &form->action); @@ -394,6 +455,24 @@ out: return ret; } +static int parse_host_scan_node(struct openconnect_info *vpninfo, xmlNode *xml_node) +{ + /* ignore this whole section if the CSD trojan has already run */ + if (vpninfo->csd_scriptname) + return 0; + + for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE) + continue; + + xmlnode_get_text(xml_node, "host-scan-ticket", &vpninfo->csd_ticket); + xmlnode_get_text(xml_node, "host-scan-token", &vpninfo->csd_token); + xmlnode_get_text(xml_node, "host-scan-base-uri", &vpninfo->csd_starturl); + xmlnode_get_text(xml_node, "host-scan-wait-uri", &vpninfo->csd_waiturl); + } + return 0; +} + /* Return value: * < 0, on error * = 0, on success; *form is populated @@ -403,6 +482,7 @@ int parse_xml_response(struct openconnect_info *vpninfo, char *response, struct struct oc_auth_form *form; xmlDocPtr xml_doc; xmlNode *xml_node; + int ret = -EINVAL; if (*formp) { free_auth_form(*formp); @@ -424,23 +504,52 @@ int parse_xml_response(struct openconnect_info *vpninfo, char *response, struct } xml_node = xmlDocGetRootElement(xml_doc); - if (xml_node->type != XML_ELEMENT_NODE || strcmp((char *)xml_node->name, "auth")) { + while (xml_node) { + int ret = 0; + + if (xml_node->type != XML_ELEMENT_NODE) { + xml_node = xml_node->next; + continue; + } + if (xmlnode_is_named(xml_node, "config-auth")) { + /* if we do have a config-auth node, it is the root element */ + xml_node = xml_node->children; + continue; + } else if (xmlnode_is_named(xml_node, "auth")) { + xmlnode_get_prop(xml_node, "id", &form->auth_id); + ret = parse_auth_node(vpninfo, xml_node, form); + } else if (xmlnode_is_named(xml_node, "opaque")) { + if (vpninfo->opaque_srvdata) + xmlFreeNode(vpninfo->opaque_srvdata); + vpninfo->opaque_srvdata = xmlCopyNode(xml_node, 1); + if (!vpninfo->opaque_srvdata) + ret = -ENOMEM; + } else if (xmlnode_is_named(xml_node, "host-scan")) { + ret = parse_host_scan_node(vpninfo, xml_node); + } else { + xmlnode_get_text(xml_node, "session-token", &vpninfo->cookie); + xmlnode_get_text(xml_node, "error", &form->error); + } + + if (ret) + goto out; + xml_node = xml_node->next; + } + + if (!form->auth_id) { vpn_progress(vpninfo, PRG_ERR, - _("XML response has no \"auth\" root node\n")); + _("XML response has no \"auth\" node\n")); goto out; } - form->auth_id = (char *)xmlGetProp(xml_node, (unsigned char *)"id"); - if (parse_auth_node(vpninfo, xml_node, form) == 0) { - xmlFreeDoc(xml_doc); - *formp = form; - return 0; - } + *formp = form; + xmlFreeDoc(xml_doc); + return 0; out: xmlFreeDoc(xml_doc); free_auth_form(form); - return -EINVAL; + return ret; } /* Return value: diff --git a/openconnect-internal.h b/openconnect-internal.h index 1349e9e..cd0e7e7 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -74,6 +74,8 @@ #endif #define N_(s) s +#include <libxml/tree.h> + #define SHA1_SIZE 20 #define MD5_SIZE 16 @@ -139,6 +141,7 @@ struct openconnect_info { char *csd_preurl; char *csd_scriptname; + xmlNode *opaque_srvdata; #ifdef LIBPROXY_HDR pxProxyFactory *proxy_factory; -- 1.7.10.4