Commit 227526e3 authored by Daniel Lenski's avatar Daniel Lenski

simplify gpst_xml_or_error handling and config parsing

* replace GPST-specific xmlnode_get_text() with global xmlnode_get_val(), and make it free existing values (simplifies config loading)
* don't deal with numeric error codes in gpst_xml_or_error, only parsing
* make gpst_xml_or_error take 2 calbacks: one for XML, one for challenge (whether in XML or JavaScript form)
* allow callbacks to take cb_data
* use all this to simplify form handling
Signed-off-by: default avatarDaniel Lenski <dlenski@gmail.com>
parent 879e25ad
......@@ -35,6 +35,24 @@ int xmlnode_is_named(xmlNode *xml_node, const char *name)
return !strcmp((char *)xml_node->name, name);
}
/* similar to auth.c's xmlnode_get_text, including that *var should be freed by the caller,
but without the hackish param / %s handling that Cisco needs. */
int xmlnode_get_val(xmlNode *xml_node, const char *name, char **var)
{
char *str;
if (name && !xmlnode_is_named(xml_node, name))
return -EINVAL;
str = (char *)xmlNodeGetContent(xml_node);
if (!str)
return -ENOENT;
free(*var);
*var = str;
return 0;
}
int xmlnode_get_prop(xmlNode *xml_node, const char *name, char **var)
{
char *str = (char *)xmlGetProp(xml_node, (unsigned char *)name);
......
......@@ -42,11 +42,11 @@ void gpst_common_headers(struct openconnect_info *vpninfo,
* - "challenge" (2FA) password, along with form name in auth_id
* - cookie from external authentication flow (INSTEAD OF password)
*
* This function steals the value of auth_id and prompt and username for
* This function steals the value of username for
* use in the auth form; pw_or_cookie_field is NOT stolen.
*/
static struct oc_auth_form *auth_form(struct openconnect_info *vpninfo,
char *prompt, char *auth_id, char *username, char *pw_or_cookie_field)
static struct oc_auth_form *new_auth_form(struct openconnect_info *vpninfo,
char *prompt, char *auth_id, char *username, char *pw_or_cookie_field)
{
struct oc_auth_form *form;
struct oc_form_opt *opt, *opt2;
......@@ -54,8 +54,8 @@ static struct oc_auth_form *auth_form(struct openconnect_info *vpninfo,
form = calloc(1, sizeof(*form));
if (!form)
return NULL;
form->message = prompt ? : strdup(_("Please enter your username and password"));
form->auth_id = auth_id ? : strdup("_gateway");
form->message = strdup(prompt ? : _("Please enter your username and password"));
form->auth_id = strdup(auth_id ? : "_login");
opt = form->opts = calloc(1, sizeof(*opt));
if (!opt) {
......@@ -86,9 +86,32 @@ static struct oc_auth_form *auth_form(struct openconnect_info *vpninfo,
return form;
}
/* Return value:
* < 0, on error
* = 0, on success; *form is populated
/* Callback function to create a new form from a challenge
*
*/
static int challenge_cb(struct openconnect_info *vpninfo, char *prompt, char *inputStr, void *cb_data)
{
struct oc_auth_form **out_form = cb_data;
struct oc_auth_form *form = *out_form;
char *username;
/* Steal and reuse username from existing form */
username = form->opts ? form->opts->_value : NULL;
form->opts->_value = NULL;
free_auth_form(form);
form = *out_form = new_auth_form(vpninfo, prompt, inputStr, username, NULL);
if (!form)
return -ENOMEM;
return -EAGAIN;
}
/* Parse gateway login response (POST /ssl-vpn/login.esp)
*
* Extracts the relevant arguments from the XML (<jnlp><application-desc><argument>...</argument></application-desc></jnlp>)
* and uses them to build a query string fragment which is usable for subsequent requests.
* This query string fragement is saved as vpninfo->cookie.
*
*/
struct gp_login_arg {
const char *opt;
......@@ -118,7 +141,7 @@ static const struct gp_login_arg gp_login_args[] = {
{ .opt=NULL },
};
static int parse_login_xml(struct openconnect_info *vpninfo, xmlNode *xml_node)
static int parse_login_xml(struct openconnect_info *vpninfo, xmlNode *xml_node, void *cb_data)
{
struct oc_text_buf *cookie = buf_alloc();
char *value = NULL;
......@@ -139,16 +162,14 @@ static int parse_login_xml(struct openconnect_info *vpninfo, xmlNode *xml_node)
while (xml_node && xml_node->type != XML_ELEMENT_NODE)
xml_node = xml_node->next;
if (xml_node && !xmlnode_is_named(xml_node, "argument"))
goto err_out;
else if (xml_node) {
value = (char *)xmlNodeGetContent(xml_node);
if (xml_node && !xmlnode_get_val(xml_node, "argument", &value)) {
if (value && (!value[0] || !strcmp(value, "(null)") || !strcmp(value, "-1"))) {
free(value);
value = NULL;
}
xml_node = xml_node->next;
}
} else if (xml_node)
goto err_out;
if (arg->check && (!value || strcmp(value, arg->check))) {
vpn_progress(vpninfo, arg->err_missing ? PRG_ERR : PRG_DEBUG,
......@@ -187,7 +208,14 @@ err_out:
return -EINVAL;
}
static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node)
/* Parse portal login/config response (POST /ssl-vpn/getconfig.esp)
*
* Extracts the list of gateways from the XML, writes them to the XML config,
* presents the user with a form to choose the gateway, and redirects
* to that gateway.
*
*/
static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node, void *cb_data)
{
struct oc_auth_form *form;
xmlNode *x = NULL;
......@@ -221,10 +249,10 @@ static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node)
*/
if (xmlnode_is_named(xml_node, "policy")) {
for (x = xml_node->children, xml_node = NULL; x; x = x->next) {
if (xmlnode_is_named(x, "portal-name"))
portal = (char *)xmlNodeGetContent(x);
else if (xmlnode_is_named(x, "gateways"))
if (xmlnode_is_named(x, "gateways"))
xml_node = x;
else
xmlnode_get_val(x, "portal-name", &portal);
}
}
......@@ -275,8 +303,7 @@ gateways:
xmlnode_get_prop(xml_node, "name", &choice->name);
for (x = xml_node->children; x; x=x->next)
if (xmlnode_is_named(x, "description")) {
choice->label = (char *)xmlNodeGetContent(x);
if (!xmlnode_get_val(x, "description", &choice->label)) {
if (vpninfo->write_new_config) {
buf_append(buf, " <HostEntry><HostName>");
buf_append_xmlescaped(buf, choice->label);
......@@ -319,6 +346,12 @@ out:
return result;
}
/* Main login entry point
*
* portal: 0 for gateway login, 1 for portal login
* pw_or_cookie_field: "alternate secret" field (see new_auth_form)
*
*/
static int gpst_login(struct openconnect_info *vpninfo, int portal, char *pw_or_cookie_field)
{
int result;
......@@ -327,7 +360,6 @@ static int gpst_login(struct openconnect_info *vpninfo, int portal, char *pw_or_
struct oc_text_buf *request_body = buf_alloc();
const char *request_body_type = "application/x-www-form-urlencoded";
char *xml_buf = NULL, *orig_path;
char *prompt = NULL, *auth_id = NULL, *username = NULL;
#ifdef HAVE_LIBSTOKEN
/* Step 1: Unlock software token (if applicable) */
......@@ -338,20 +370,19 @@ static int gpst_login(struct openconnect_info *vpninfo, int portal, char *pw_or_
}
#endif
form = auth_form(vpninfo, prompt, auth_id, username, pw_or_cookie_field);
if (!form)
return -ENOMEM;
/* Ask the user to fill in the auth form; repeat as necessary */
for (;;) {
/* process auth form (username and password or challenge) */
if (!form)
form = new_auth_form(vpninfo, NULL, NULL, NULL, pw_or_cookie_field);
if (!form)
return -ENOMEM;
/* process auth form */
result = process_auth_form(vpninfo, form);
if (result)
goto out;
reuse_whole_form:
buf_truncate(request_body);
/* generate token code if specified */
result = do_gen_tokencode(vpninfo, form);
if (result) {
......@@ -382,31 +413,20 @@ static int gpst_login(struct openconnect_info *vpninfo, int portal, char *pw_or_
vpninfo->urlpath = orig_path;
/* Result could be either a JavaScript challenge or XML */
result = gpst_xml_or_error(vpninfo, result, xml_buf,
portal ? parse_portal_xml : parse_login_xml, &prompt, &auth_id);
if (result >= 0)
result = gpst_xml_or_error(vpninfo, xml_buf, portal ? parse_portal_xml : parse_login_xml,
challenge_cb, &form);
if (result == -EAGAIN) {
reuse_username:
/* Steal and reuse username from first form */
username = form->opts ? form->opts->_value : NULL;
form->opts->_value = NULL;
free_auth_form(form);
form = auth_form(vpninfo, prompt, auth_id, username, pw_or_cookie_field);
prompt = auth_id = username = NULL;
if (!form)
return -ENOMEM;
} else if (portal && result == 0) {
/* Portal login succeeded; reuse same credentials to login to gateway,
* unless it was a challenge auth form, in which case we only
* reuse the username.
*/
portal = 0;
if (form->auth_id && form->auth_id[0] != '_')
goto reuse_username;
else
goto reuse_whole_form;
/* New form is already populated from the challenge */
continue;
} else if (result == -EACCES) {
/* Invalid username/password; reuse same form, but blank */
nuke_opt_values(form->opts);
} else if (portal && result == 0) {
/* Portal login succeeded; now login to gateway */
portal = 0;
free_auth_form(form);
form = NULL;
} else
break;
}
......@@ -423,7 +443,8 @@ int gpst_obtain_cookie(struct openconnect_info *vpninfo)
char *pw_or_cookie_field = NULL;
int result;
/* An alternate password/secret field may be specified in the "URL path". Known possibilities:
/* An alternate password/secret field may be specified in the "URL path" (or --usergroup).
* Known possibilities are:
* /portal:portal-userauthcookie
* /gateway:prelogin-cookie
*/
......@@ -492,7 +513,9 @@ int gpst_bye(struct openconnect_info *vpninfo, const char *reason)
/* logout.esp returns HTTP status 200 and <response status="success"> when
* successful, and all manner of malformed junk when unsuccessful.
*/
result = gpst_xml_or_error(vpninfo, result, xml_buf, NULL, NULL, NULL);
if (result >= 0)
result = gpst_xml_or_error(vpninfo, xml_buf, NULL, NULL, NULL);
if (result < 0)
vpn_progress(vpninfo, PRG_ERR, _("Logout failed.\n"));
else
......
This diff is collapsed.
......@@ -874,9 +874,10 @@ void gpst_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *b
int gpst_bye(struct openconnect_info *vpninfo, const char *reason);
/* gpst.c */
int gpst_xml_or_error(struct openconnect_info *vpninfo, int result, char *response,
int (*xml_cb)(struct openconnect_info *, xmlNode *xml_node),
char **prompt, char **inputStr);
int gpst_xml_or_error(struct openconnect_info *vpninfo, char *response,
int (*xml_cb)(struct openconnect_info *, xmlNode *xml_node, void *cb_data),
int (*challenge_cb)(struct openconnect_info *, char *prompt, char *inputStr, void *cb_data),
void *cb_data);
int gpst_setup(struct openconnect_info *vpninfo);
int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout);
int gpst_esp_send_probes(struct openconnect_info *vpninfo);
......@@ -1014,6 +1015,7 @@ int set_csd_user(struct openconnect_info *vpninfo);
/* auth-common.c */
int xmlnode_is_named(xmlNode *xml_node, const char *name);
int xmlnode_get_val(xmlNode *xml_node, const char *name, char **var);
int xmlnode_get_prop(xmlNode *xml_node, const char *name, char **var);
int xmlnode_match_prop(xmlNode *xml_node, const char *name, const char *match);
int append_opt(struct oc_text_buf *body, const char *opt, const char *name);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment