From cb56ec21e7895e6b532563c49bcd94d5e097f781 Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Sun, 7 Feb 2021 13:50:12 -0800 Subject: [PATCH] auth-juniper.c simplifications (including ignoring submit_button if NULL) Signed-off-by: Daniel Lenski --- Makefile.am | 2 +- auth-html.c | 242 +++++++++++++++++++++++++++++++++++++ auth-juniper.c | 264 ++--------------------------------------- openconnect-internal.h | 12 ++ 4 files changed, 266 insertions(+), 254 deletions(-) create mode 100644 auth-html.c diff --git a/Makefile.am b/Makefile.am index 38a1fc46..636e183f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,7 +30,7 @@ openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIB if OPENCONNECT_WIN32 openconnect_SOURCES += openconnect.rc endif -library_srcs = ssl.c http.c http-auth.c auth-common.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c openconnect-internal.h +library_srcs = ssl.c http.c http-auth.c auth-common.c auth-html.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c openconnect-internal.h lib_srcs_cisco = auth.c cstp.c lib_srcs_juniper = oncp.c lzo.c auth-juniper.c lib_srcs_pulse = pulse.c diff --git a/auth-html.c b/auth-html.c new file mode 100644 index 00000000..148ff3c5 --- /dev/null +++ b/auth-html.c @@ -0,0 +1,242 @@ +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright © 2015-2021 David Woodhouse, Daniel Lenski + * + * Author: David Woodhouse , Daniel Lenski + * + * 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 + +#include + +#include +#include + +#include "openconnect-internal.h" + +xmlNodePtr htmlnode_next(xmlNodePtr top, xmlNodePtr node) +{ + if (node->children) + return node->children; + + while (!node->next) { + node = node->parent; + if (!node || node == top) + return NULL; + } + return node->next; +} + +xmlNodePtr find_form_node(xmlDocPtr doc) +{ + xmlNodePtr root, node; + + for (root = node = xmlDocGetRootElement(doc); node; node = htmlnode_next(root, node)) { + if (node->name && !strcasecmp((char *)node->name, "form")) + return node; + } + return NULL; +} + +int parse_input_node(struct openconnect_info *vpninfo, struct oc_auth_form *form, + xmlNodePtr node, const char *submit_button, + int (*can_gen_tokencode)(struct openconnect_info *vpninfo, struct oc_auth_form *form, struct oc_form_opt *opt)) +{ + char *type = (char *)xmlGetProp(node, (unsigned char *)"type"); + struct oc_form_opt **p = &form->opts; + struct oc_form_opt *opt; + int ret = 0; + + if (!type) + return -EINVAL; + + opt = calloc(1, sizeof(*opt)); + if (!opt) { + ret = -ENOMEM; + goto out; + } + + if (!strcasecmp(type, "hidden")) { + opt->type = OC_FORM_OPT_HIDDEN; + xmlnode_get_prop(node, "name", &opt->name); + xmlnode_get_prop(node, "value", &opt->_value); + /* XXX: Handle tz_offset / tz */ + } else if (!strcasecmp(type, "password")) { + opt->type = OC_FORM_OPT_PASSWORD; + xmlnode_get_prop(node, "name", &opt->name); + if (asprintf(&opt->label, "%s:", opt->name) == -1) { + ret = -ENOMEM; + goto out; + } + if (can_gen_tokencode && !can_gen_tokencode(vpninfo, form, opt)) + opt->type = OC_FORM_OPT_TOKEN; + } else if (!strcasecmp(type, "text") || !strcasecmp(type, "username") || !strcasecmp(type, "email")) { + opt->type = OC_FORM_OPT_TEXT; + xmlnode_get_prop(node, "name", &opt->name); + if (asprintf(&opt->label, "%s:", opt->name) == -1) { + ret = -ENOMEM; + goto out; + } + if (!strcmp(form->auth_id, "loginForm") && + !strcmp(opt->name, "VerificationCode") && + can_gen_tokencode && !can_gen_tokencode(vpninfo, form, opt)) + opt->type = OC_FORM_OPT_TOKEN; + } else if (!strcasecmp(type, "submit")) { + xmlnode_get_prop(node, "name", &opt->name); + if (opt->name && submit_button && (!strcmp(opt->name, submit_button) || + /* XX: Juniper-specific */ + !strcmp(opt->name, "sn-postauth-proceed") || + !strcmp(opt->name, "sn-preauth-proceed") || + !strcmp(opt->name, "secidactionEnter"))) { + /* Use this as the 'Submit' action for the form, by + implicitly adding it as a hidden option. */ + xmlnode_get_prop(node, "value", &opt->_value); + opt->type = OC_FORM_OPT_HIDDEN; + } else { + vpn_progress(vpninfo, PRG_DEBUG, + _("Ignoring unknown form submit item '%s'\n"), + opt->name); + ret = -EINVAL; + goto out; + } + } else if (!strcasecmp(type, "checkbox")) { + opt->type = OC_FORM_OPT_HIDDEN; + xmlnode_get_prop(node, "name", &opt->name); + xmlnode_get_prop(node, "value", &opt->_value); + } else { + vpn_progress(vpninfo, PRG_DEBUG, + _("Ignoring unknown form input type '%s'\n"), + type); + ret = -EINVAL; + goto out; + } + + /* Append to the existing list */ + while (*p) { + if (!strcmp((*p)->name, opt->name)) { + vpn_progress(vpninfo, PRG_DEBUG, + _("Discarding duplicate option '%s'\n"), + opt->name); + free_opt(opt); + goto out; + } + p = &(*p)->next; + } + *p = opt; + out: + if (ret) + free_opt(opt); + free(type); + return ret; +} + +int parse_select_node(struct openconnect_info *vpninfo, struct oc_auth_form *form, + xmlNodePtr node) +{ + xmlNodePtr child; + struct oc_form_opt_select *opt; + struct oc_choice *choice; + + opt = calloc(1, sizeof(*opt)); + if (!opt) + return -ENOMEM; + + xmlnode_get_prop(node, "name", &opt->form.name); + opt->form.label = strdup(opt->form.name); + opt->form.type = OC_FORM_OPT_SELECT; + if (!strcmp(opt->form.name, "realm")) /* XX: Juniper-specific */ + form->authgroup_opt = opt; + + for (child = node->children; child; child = child->next) { + struct oc_choice **new_choices; + if (!child->name || strcasecmp((const char *)child->name, "option")) + continue; + + choice = calloc(1, sizeof(*choice)); + if (!choice) { + free_opt((void *)opt); + return -ENOMEM; + } + + xmlnode_get_prop(node, "name", &choice->name); + choice->label = (char *)xmlNodeGetContent(child); + choice->name = strdup(choice->label); + new_choices = realloc(opt->choices, sizeof(opt->choices[0]) * (opt->nr_choices+1)); + if (!new_choices) { + free_opt((void *)opt); + free(choice); + return -ENOMEM; + } + opt->choices = new_choices; + opt->choices[opt->nr_choices++] = choice; + } + + /* Prepend to the existing list */ + opt->form.next = form->opts; + form->opts = &opt->form; + return 0; +} + +struct oc_auth_form *parse_form_node(struct openconnect_info *vpninfo, + xmlNodePtr node, const char *submit_button, + int (*can_gen_tokencode)(struct openconnect_info *vpninfo, struct oc_auth_form *form, struct oc_form_opt *opt)) +{ + struct oc_auth_form *form = calloc(1, sizeof(*form)); + xmlNodePtr child; + + if (!form) + return NULL; + + xmlnode_get_prop(node, "method", &form->method); + xmlnode_get_prop(node, "action", &form->action); + if (!form->method || strcasecmp(form->method, "POST") || + !form->action || !form->action[0]) { + vpn_progress(vpninfo, PRG_ERR, + _("Cannot handle form method='%s', action='%s'\n"), + form->method, form->action); + free(form); + return NULL; + } + xmlnode_get_prop(node, "name", &form->auth_id); + form->banner = strdup(form->auth_id); + + for (child = htmlnode_next(node, node); child && child != node; child = htmlnode_next(node, child)) { + if (!child->name) + continue; + + if (!strcasecmp((char *)child->name, "input")) + parse_input_node(vpninfo, form, child, submit_button, can_gen_tokencode); + else if (!strcasecmp((char *)child->name, "select")) { + parse_select_node(vpninfo, form, child); + /* Skip its children */ + while (child->children) + child = child->last; + } else if (!strcasecmp((char *)child->name, "textarea")) { + /* display the post sign-in message, if any */ + char *fieldname = (char *)xmlGetProp(child, (unsigned char *)"name"); + if (fieldname && (!strcasecmp(fieldname, "sn-postauth-text") || /* XX: Juniper-specific */ + !strcasecmp(fieldname, "sn-preauth-text"))) { + char *postauth_msg = (char *)xmlNodeGetContent(child); + if (postauth_msg) { + free(form->banner); + form->banner = postauth_msg; + } + } else { + vpn_progress(vpninfo, PRG_ERR, + _("Unknown textarea field: '%s'\n"), fieldname); + } + free(fieldname); + } + } + return form; +} diff --git a/auth-juniper.c b/auth-juniper.c index edcd1e8c..17c0b707 100644 --- a/auth-juniper.c +++ b/auth-juniper.c @@ -52,20 +52,6 @@ void oncp_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *b // buf_append(buf, "Accept-Encoding: gzip\r\n"); } - -static xmlNodePtr htmlnode_next(xmlNodePtr top, xmlNodePtr node) -{ - if (node->children) - return node->children; - - while (!node->next) { - node = node->parent; - if (!node || node == top) - return NULL; - } - return node->next; -} - static int oncp_can_gen_tokencode(struct openconnect_info *vpninfo, struct oc_auth_form *form, struct oc_form_opt *opt) @@ -97,215 +83,6 @@ static int oncp_can_gen_tokencode(struct openconnect_info *vpninfo, } -static int parse_input_node(struct openconnect_info *vpninfo, struct oc_auth_form *form, - xmlNodePtr node, const char *submit_button) -{ - char *type = (char *)xmlGetProp(node, (unsigned char *)"type"); - struct oc_form_opt **p = &form->opts; - struct oc_form_opt *opt; - int ret = 0; - - if (!type) - return -EINVAL; - - opt = calloc(1, sizeof(*opt)); - if (!opt) { - ret = -ENOMEM; - goto out; - } - - if (!strcasecmp(type, "hidden")) { - opt->type = OC_FORM_OPT_HIDDEN; - xmlnode_get_prop(node, "name", &opt->name); - xmlnode_get_prop(node, "value", &opt->_value); - /* XXX: Handle tz_offset / tz */ - } else if (!strcasecmp(type, "password")) { - opt->type = OC_FORM_OPT_PASSWORD; - xmlnode_get_prop(node, "name", &opt->name); - if (asprintf(&opt->label, "%s:", opt->name) == -1) { - ret = -ENOMEM; - goto out; - } - if (!oncp_can_gen_tokencode(vpninfo, form, opt)) - opt->type = OC_FORM_OPT_TOKEN; - } else if (!strcasecmp(type, "text")) { - opt->type = OC_FORM_OPT_TEXT; - xmlnode_get_prop(node, "name", &opt->name); - if (asprintf(&opt->label, "%s:", opt->name) == -1) { - ret = -ENOMEM; - goto out; - } - if (!strcmp(form->auth_id, "loginForm") && - !strcmp(opt->name, "VerificationCode") && - !can_gen_tokencode(vpninfo, form, opt)) - opt->type = OC_FORM_OPT_TOKEN; - } else if (!strcasecmp(type, "username") || !strcasecmp(type, "email")) { - opt->type = OC_FORM_OPT_TEXT; - xmlnode_get_prop(node, "name", &opt->name); - if (asprintf(&opt->label, "%s:", opt->name) == -1) { - ret = -ENOMEM; - goto out; - } - } else if (!strcasecmp(type, "submit")) { - xmlnode_get_prop(node, "name", &opt->name); - if (opt->name && (!strcmp(opt->name, submit_button) || - !strcmp(opt->name, "sn-postauth-proceed") || - !strcmp(opt->name, "sn-preauth-proceed") || - !strcmp(opt->name, "secidactionEnter"))) { - /* Use this as the 'Submit' action for the form, by - implicitly adding it as a hidden option. */ - xmlnode_get_prop(node, "value", &opt->_value); - opt->type = OC_FORM_OPT_HIDDEN; - } else { - vpn_progress(vpninfo, PRG_DEBUG, - _("Ignoring unknown form submit item '%s'\n"), - opt->name); - ret = -EINVAL; - goto out; - } - } else if (!strcasecmp(type, "checkbox")) { - opt->type = OC_FORM_OPT_HIDDEN; - xmlnode_get_prop(node, "name", &opt->name); - xmlnode_get_prop(node, "value", &opt->_value); - } else { - vpn_progress(vpninfo, PRG_DEBUG, - _("Ignoring unknown form input type '%s'\n"), - type); - ret = -EINVAL; - goto out; - } - - /* Append to the existing list */ - while (*p) { - if (!strcmp((*p)->name, opt->name)) { - vpn_progress(vpninfo, PRG_DEBUG, - _("Discarding duplicate option '%s'\n"), - opt->name); - free_opt(opt); - goto out; - } - p = &(*p)->next; - } - *p = opt; - out: - if (ret) - free_opt(opt); - free(type); - return ret; -} - -static int parse_select_node(struct openconnect_info *vpninfo, struct oc_auth_form *form, - xmlNodePtr node) -{ - xmlNodePtr child; - struct oc_form_opt_select *opt; - struct oc_choice *choice; - - opt = calloc(1, sizeof(*opt)); - if (!opt) - return -ENOMEM; - - xmlnode_get_prop(node, "name", &opt->form.name); - opt->form.label = strdup(opt->form.name); - opt->form.type = OC_FORM_OPT_SELECT; - if (!strcmp(opt->form.name, "realm")) - form->authgroup_opt = opt; - - for (child = node->children; child; child = child->next) { - struct oc_choice **new_choices; - if (!child->name || strcasecmp((const char *)child->name, "option")) - continue; - - choice = calloc(1, sizeof(*choice)); - if (!choice) { - free_opt((void *)opt); - return -ENOMEM; - } - - xmlnode_get_prop(node, "name", &choice->name); - choice->label = (char *)xmlNodeGetContent(child); - choice->name = strdup(choice->label); - new_choices = realloc(opt->choices, sizeof(opt->choices[0]) * (opt->nr_choices+1)); - if (!new_choices) { - free_opt((void *)opt); - free(choice); - return -ENOMEM; - } - opt->choices = new_choices; - opt->choices[opt->nr_choices++] = choice; - } - - /* Prepend to the existing list */ - opt->form.next = form->opts; - form->opts = &opt->form; - return 0; -} - -static struct oc_auth_form *parse_form_node(struct openconnect_info *vpninfo, - xmlNodePtr node, const char *submit_button) -{ - struct oc_auth_form *form = calloc(1, sizeof(*form)); - xmlNodePtr child; - - if (!form) - return NULL; - - xmlnode_get_prop(node, "method", &form->method); - xmlnode_get_prop(node, "action", &form->action); - if (!form->method || strcasecmp(form->method, "POST")) { - vpn_progress(vpninfo, PRG_ERR, - _("Cannot handle form method='%s', action='%s'\n"), - form->method, form->action); - free(form); - return NULL; - } - /* XX: some forms have 'id', but no 'name' */ - if (xmlnode_get_prop(node, "name", &form->auth_id)) - xmlnode_get_prop(node, "id", &form->auth_id); - form->banner = strdup(form->auth_id); - - for (child = htmlnode_next(node, node); child && child != node; child = htmlnode_next(node, child)) { - if (!child->name) - continue; - - if (!strcasecmp((char *)child->name, "input")) - parse_input_node(vpninfo, form, child, submit_button); - else if (!strcasecmp((char *)child->name, "select")) { - parse_select_node(vpninfo, form, child); - /* Skip its children */ - while (child->children) - child = child->last; - } else if (!strcasecmp((char *)child->name, "textarea")) { - /* display the post sign-in message, if any */ - char *fieldname = (char *)xmlGetProp(child, (unsigned char *)"name"); - if (fieldname && (!strcasecmp(fieldname, "sn-postauth-text") || - !strcasecmp(fieldname, "sn-preauth-text"))) { - char *postauth_msg = (char *)xmlNodeGetContent(child); - if (postauth_msg) { - free(form->banner); - form->banner = postauth_msg; - } - } else { - vpn_progress(vpninfo, PRG_ERR, - _("Unknown textarea field: '%s'\n"), fieldname); - } - free(fieldname); - } - } - return form; -} - -static xmlNodePtr find_form_node(xmlDocPtr doc) -{ - xmlNodePtr root, node; - - for (root = node = xmlDocGetRootElement(doc); node; node = htmlnode_next(root, node)) { - if (node->name && !strcasecmp((char *)node->name, "form")) - return node; - } - return NULL; -} - int oncp_send_tncc_command(struct openconnect_info *vpninfo, int start) { const char *dspreauth = vpninfo->csd_token, *dsurl = vpninfo->csd_starturl ? : "null"; @@ -746,26 +523,14 @@ int oncp_obtain_cookie(struct openconnect_info *vpninfo) _("Encountered form with no 'name' or 'id'\n")); goto dump_form; } else if (form_name && !strcmp(form_name, "frmLogin")) { - form = parse_form_node(vpninfo, node, "btnSubmit"); - if (!form) { - ret = -EINVAL; - break; - } + form = parse_form_node(vpninfo, node, "btnSubmit", oncp_can_gen_tokencode); } else if (form_id && !strcmp(form_id, "loginForm")) { - form = parse_form_node(vpninfo, node, "submitButton"); - if (!form) { - ret = -EINVAL; - break; - } + form = parse_form_node(vpninfo, node, "submitButton", oncp_can_gen_tokencode); } else if ((form_name && !strcmp(form_name, "frmDefender")) || (form_name && !strcmp(form_name, "frmNextToken"))) { - form = parse_form_node(vpninfo, node, "btnAction"); - if (!form) { - ret = -EINVAL; - break; - } + form = parse_form_node(vpninfo, node, "btnAction", oncp_can_gen_tokencode); } else if (form_name && !strcmp(form_name, "frmConfirmation")) { - form = parse_form_node(vpninfo, node, "btnContinue"); + form = parse_form_node(vpninfo, node, "btnContinue", oncp_can_gen_tokencode); if (!form) { ret = -EINVAL; break; @@ -774,24 +539,12 @@ int oncp_obtain_cookie(struct openconnect_info *vpninfo) goto form_done; } else if (form_name && !strcmp(form_name, "frmSelectRoles")) { form = parse_roles_form_node(node); - if (!form) { - ret = -EINVAL; - break; - } role_select = 1; } else if (form_name && !strcmp(form_name, "frmTotpToken")) { - form = parse_form_node(vpninfo, node, "totpactionEnter"); - if (!form) { - ret = -EINVAL; - break; - } + form = parse_form_node(vpninfo, node, "totpactionEnter", oncp_can_gen_tokencode); } else if ((form_name && !strcmp(form_name, "hiddenform")) || (form_id && !strcmp(form_id, "formSAMLSSO"))) { - form = parse_form_node(vpninfo, node, "submit"); - if (!form) { - ret = -EINVAL; - break; - } + form = parse_form_node(vpninfo, node, "submit", oncp_can_gen_tokencode); } else { char *form_action = (char *)xmlGetProp(node, (unsigned char *)"action"); if (form_action && strstr(form_action, "remediate.cgi")) { @@ -811,6 +564,11 @@ int oncp_obtain_cookie(struct openconnect_info *vpninfo) break; } + if (!form) { + ret = -EINVAL; + break; + } + do { ret = process_auth_form(vpninfo, form); } while (ret == OC_FORM_RESULT_NEWGROUP); diff --git a/openconnect-internal.h b/openconnect-internal.h index 04776bdb..b3e084f8 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -920,6 +920,18 @@ int decompress_and_queue_packet(struct openconnect_info *vpninfo, int compr_type unsigned char *buf, int len); int compress_packet(struct openconnect_info *vpninfo, int compr_type, struct pkt *this); +/* html-auth.c */ +xmlNodePtr htmlnode_next(xmlNodePtr top, xmlNodePtr node); +xmlNodePtr find_form_node(xmlDocPtr doc); +int parse_input_node(struct openconnect_info *vpninfo, struct oc_auth_form *form, + xmlNodePtr node, const char *submit_button, + int (*can_gen_tokencode)(struct openconnect_info *vpninfo, struct oc_auth_form *form, struct oc_form_opt *opt)); +int parse_select_node(struct openconnect_info *vpninfo, struct oc_auth_form *form, + xmlNodePtr node); +struct oc_auth_form *parse_form_node(struct openconnect_info *vpninfo, + xmlNodePtr node, const char *submit_button, + int (*can_gen_tokencode)(struct openconnect_info *vpninfo, struct oc_auth_form *form, struct oc_form_opt *opt)); + /* auth-juniper.c */ int oncp_obtain_cookie(struct openconnect_info *vpninfo); int oncp_send_tncc_command(struct openconnect_info *vpninfo, int first); -- 2.49.0