* - "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;
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) {
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;
{ .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;
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,
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;
*/
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);
}
}
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);
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;
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) */
}
#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) {
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;
}
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
*/
/* 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
{ .gpst.hdr = { 0x1a, 0x2b, 0x3c, 0x4d } }
};
-/* 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. And without freeing up
- the old contents of *var, which is likely to lead to bugs? */
-static int xmlnode_get_text(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;
-
- *var = str;
- return 0;
-}
-
/* We behave like CSTP — create a linked list in vpninfo->cstp_options
* with the strings containing the information we got from the server,
* and oc_ip_info contains const copies of those pointers.
*
* (unlike version in oncp.c, val is stolen rather than strdup'ed) */
-static const char *add_option(struct openconnect_info *vpninfo, const char *opt, char *val)
+static const char *add_option(struct openconnect_info *vpninfo, const char *opt, char **val)
{
struct oc_vpn_option *new = malloc(sizeof(*new));
if (!new)
free(new);
return NULL;
}
- new->value = val;
+ new->value = *val;
+ *val = NULL;
new->next = vpninfo->cstp_options;
vpninfo->cstp_options = new;
return -EINVAL;
}
-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)
{
xmlDocPtr xml_doc;
xmlNode *xml_node;
char *err = NULL;
-
- /* custom error code returned by /ssl-vpn/login.esp and maybe others */
- if (result == -EACCES)
- vpn_progress(vpninfo, PRG_ERR, _("Invalid username or password.\n"));
-
- if (result < 0)
- return result;
+ char *prompt = NULL, *inputStr = NULL;
+ int result = -EINVAL;
if (!response) {
- vpn_progress(vpninfo, PRG_DEBUG,
+ vpn_progress(vpninfo, PRG_ERR,
_("Empty response from server\n"));
return -EINVAL;
}
XML_PARSE_NOERROR);
if (!xml_doc) {
/* is it Javascript? */
- char *p, *i;
- result = parse_javascript(response, &p, &i);
+ result = parse_javascript(response, &prompt, &inputStr);
switch (result) {
case 1:
- vpn_progress(vpninfo, PRG_ERR, _("%s\n"), p);
+ vpn_progress(vpninfo, PRG_ERR, _("%s\n"), prompt);
break;
case 0:
- vpn_progress(vpninfo, PRG_INFO, _("Challenge: %s\n"), p);
- if (prompt && inputStr) {
- *prompt=p;
- *inputStr=i;
- return -EAGAIN;
- }
+ vpn_progress(vpninfo, PRG_INFO, _("Challenge: %s\n"), prompt);
+ result = challenge_cb ? challenge_cb(vpninfo, prompt, inputStr, cb_data) : -EINVAL;
break;
default:
goto bad_xml;
}
- free((char *)p);
- free((char *)i);
- goto out;
+ free(prompt);
+ free(inputStr);
+ goto bad_xml;
}
xml_node = xmlDocGetRootElement(xml_doc);
if (xmlnode_is_named(xml_node, "response")
&& !xmlnode_match_prop(xml_node, "status", "error")) {
for (xml_node=xml_node->children; xml_node; xml_node=xml_node->next) {
- if (!xmlnode_get_text(xml_node, "error", &err))
+ if (!xmlnode_get_val(xml_node, "error", &err))
goto out;
}
goto bad_xml;
/* is it <challenge><user>user.name</user><inputstr>...</inputstr><respmsg>...</respmsg></challenge> */
if (xmlnode_is_named(xml_node, "challenge")) {
for (xml_node=xml_node->children; xml_node; xml_node=xml_node->next) {
- if (inputStr && xmlnode_is_named(xml_node, "inputstr"))
- xmlnode_get_text(xml_node, "inputstr", inputStr);
- else if (prompt && xmlnode_is_named(xml_node, "respmsg"))
- xmlnode_get_text(xml_node, "respmsg", prompt);
- else if (xmlnode_is_named(xml_node, "user"))
- {} /* XXX: override the username passed to the next form? */
+ xmlnode_get_val(xml_node, "inputstr", &inputStr);
+ xmlnode_get_val(xml_node, "respmsg", &prompt);
+ /* XXX: override the username passed to the next form from <user> ? */
}
- result = -EAGAIN;
- goto out;
+ result = challenge_cb ? challenge_cb(vpninfo, prompt, inputStr, cb_data) : -EINVAL;
+ free(prompt);
+ free(inputStr);
+ goto bad_xml;
}
- if (xml_cb)
- result = xml_cb(vpninfo, xml_node);
+ /* if it's XML, invoke callback (or default to success) */
+ result = xml_cb ? xml_cb(vpninfo, xml_node, cb_data) : 0;
+bad_xml:
if (result == -EINVAL) {
- bad_xml:
vpn_progress(vpninfo, PRG_ERR,
_("Failed to parse server response\n"));
vpn_progress(vpninfo, PRG_DEBUG,
{
int bits = -1;
xmlNode *child;
- char *s, *p;
+ char *p, *s = NULL;
for (child = xml_node->children; child; child=child->next) {
- if (xmlnode_get_text(child, "bits", &s) == 0) {
+ if (xmlnode_get_val(child, "bits", &s) == 0) {
bits = atoi(s);
- free(s);
- } else if (xmlnode_get_text(child, "val", &s) == 0) {
+ } else if (xmlnode_get_val(child, "val", &s) == 0) {
for (p=s; *p && *(p+1) && (bits-=8)>=0; p+=2)
*dest++ = unhex(p);
- free(s);
}
}
+ free(s);
return (bits == 0) ? 0 : -EINVAL;
}
#endif
* < 0, on error
* = 0, on success; *form is populated
*/
-static int gpst_parse_config_xml(struct openconnect_info *vpninfo, xmlNode *xml_node)
+static int gpst_parse_config_xml(struct openconnect_info *vpninfo, xmlNode *xml_node, void *cb_data)
{
xmlNode *member;
- char *s;
+ char *s = NULL;
int ii;
if (!xml_node || !xmlnode_is_named(xml_node, "response"))
/* Parse config */
for (xml_node = xml_node->children; xml_node; xml_node=xml_node->next) {
- if (!xmlnode_get_text(xml_node, "ip-address", &s))
- vpninfo->ip_info.addr = add_option(vpninfo, "ipaddr", s);
- else if (!xmlnode_get_text(xml_node, "netmask", &s))
- vpninfo->ip_info.netmask = add_option(vpninfo, "netmask", s);
- else if (!xmlnode_get_text(xml_node, "mtu", &s)) {
+ if (!xmlnode_get_val(xml_node, "ip-address", &s))
+ vpninfo->ip_info.addr = add_option(vpninfo, "ipaddr", &s);
+ else if (!xmlnode_get_val(xml_node, "netmask", &s))
+ vpninfo->ip_info.netmask = add_option(vpninfo, "netmask", &s);
+ else if (!xmlnode_get_val(xml_node, "mtu", &s))
vpninfo->ip_info.mtu = atoi(s);
- free(s);
- } else if (!xmlnode_get_text(xml_node, "disconnect-on-idle", &s)) {
+ else if (!xmlnode_get_val(xml_node, "disconnect-on-idle", &s)) {
int sec = atoi(s);
vpn_progress(vpninfo, PRG_INFO, _("Idle timeout is %d minutes.\n"), sec/60);
vpninfo->idle_timeout = sec;
- free(s);
- } else if (!xmlnode_get_text(xml_node, "ssl-tunnel-url", &s)) {
+ } else if (!xmlnode_get_val(xml_node, "ssl-tunnel-url", &s)) {
free(vpninfo->urlpath);
vpninfo->urlpath = s;
if (strcmp(s, "/ssl-tunnel-connect.sslvpn"))
vpn_progress(vpninfo, PRG_INFO, _("Non-standard SSL tunnel path: %s\n"), s);
- } else if (!xmlnode_get_text(xml_node, "timeout", &s)) {
+ s = NULL;
+ } else if (!xmlnode_get_val(xml_node, "timeout", &s)) {
int sec = atoi(s);
vpn_progress(vpninfo, PRG_INFO, _("Tunnel timeout (rekey interval) is %d minutes.\n"), sec/60);
vpninfo->ssl_times.last_rekey = time(NULL);
vpninfo->ssl_times.rekey = sec - 60;
vpninfo->ssl_times.rekey_method = REKEY_TUNNEL;
- free(s);
- } else if (!xmlnode_get_text(xml_node, "gw-address", &s)) {
+ } else if (!xmlnode_get_val(xml_node, "gw-address", &s)) {
/* As remarked in oncp.c, "this is a tunnel; having a
* gateway is meaningless." See esp_send_probes_gp for the
* gory details of what this field actually means.
vpn_progress(vpninfo, PRG_DEBUG,
_("Gateway address in config XML (%s) differs from external gateway address (%s).\n"), s, vpninfo->ip_info.gateway_addr);
vpninfo->esp_magic = inet_addr(s);
- free(s);
} else if (xmlnode_is_named(xml_node, "dns")) {
for (ii=0, member = xml_node->children; member && ii<3; member=member->next)
- if (!xmlnode_get_text(member, "member", &s))
- vpninfo->ip_info.dns[ii++] = add_option(vpninfo, "DNS", s);
+ if (!xmlnode_get_val(member, "member", &s))
+ vpninfo->ip_info.dns[ii++] = add_option(vpninfo, "DNS", &s);
} else if (xmlnode_is_named(xml_node, "wins")) {
for (ii=0, member = xml_node->children; member && ii<3; member=member->next)
- if (!xmlnode_get_text(member, "member", &s))
- vpninfo->ip_info.nbns[ii++] = add_option(vpninfo, "WINS", s);
+ if (!xmlnode_get_val(member, "member", &s))
+ vpninfo->ip_info.nbns[ii++] = add_option(vpninfo, "WINS", &s);
} else if (xmlnode_is_named(xml_node, "dns-suffix")) {
struct oc_text_buf *domains = buf_alloc();
for (member = xml_node->children; member; member=member->next)
- if (!xmlnode_get_text(member, "member", &s))
+ if (!xmlnode_get_val(member, "member", &s))
buf_append(domains, "%s ", s);
if (buf_error(domains) == 0) {
domains->data[domains->pos-1] = '\0';
- vpninfo->ip_info.domain = add_option(vpninfo, "search", domains->data);
- domains->data = NULL;
+ vpninfo->ip_info.domain = add_option(vpninfo, "search", &domains->data);
}
buf_free(domains);
} else if (xmlnode_is_named(xml_node, "access-routes")) {
for (member = xml_node->children; member; member=member->next) {
- if (!xmlnode_get_text(member, "member", &s)) {
+ if (!xmlnode_get_val(member, "member", &s)) {
struct oc_split_include *inc = malloc(sizeof(*inc));
if (!inc)
continue;
- inc->route = add_option(vpninfo, "split-include", s);
+ inc->route = add_option(vpninfo, "split-include", &s);
inc->next = vpninfo->ip_info.split_includes;
vpninfo->ip_info.split_includes = inc;
}
int c = (vpninfo->current_esp_in ^= 1);
vpninfo->old_esp_maxseq = vpninfo->esp_in[c^1].seq + 32;
for (member = xml_node->children; member; member=member->next) {
- s = NULL;
- if (!xmlnode_get_text(member, "udp-port", &s)) udp_sockaddr(vpninfo, atoi(s));
- else if (!xmlnode_get_text(member, "enc-algo", &s)) set_esp_algo(vpninfo, s, 0);
- else if (!xmlnode_get_text(member, "hmac-algo", &s)) set_esp_algo(vpninfo, s, 1);
- else if (!xmlnode_get_text(member, "c2s-spi", &s)) vpninfo->esp_out.spi = htonl(strtoul(s, NULL, 16));
- else if (!xmlnode_get_text(member, "s2c-spi", &s)) vpninfo->esp_in[c].spi = htonl(strtoul(s, NULL, 16));
+ if (!xmlnode_get_val(member, "udp-port", &s)) udp_sockaddr(vpninfo, atoi(s));
+ else if (!xmlnode_get_val(member, "enc-algo", &s)) set_esp_algo(vpninfo, s, 0);
+ else if (!xmlnode_get_val(member, "hmac-algo", &s)) set_esp_algo(vpninfo, s, 1);
+ else if (!xmlnode_get_val(member, "c2s-spi", &s)) vpninfo->esp_out.spi = htonl(strtoul(s, NULL, 16));
+ else if (!xmlnode_get_val(member, "s2c-spi", &s)) vpninfo->esp_in[c].spi = htonl(strtoul(s, NULL, 16));
else if (xmlnode_is_named(member, "ekey-c2s")) get_key_bits(member, vpninfo->esp_out.enc_key);
else if (xmlnode_is_named(member, "ekey-s2c")) get_key_bits(member, vpninfo->esp_in[c].enc_key);
else if (xmlnode_is_named(member, "akey-c2s")) get_key_bits(member, vpninfo->esp_out.hmac_key);
else if (xmlnode_is_named(member, "akey-s2c")) get_key_bits(member, vpninfo->esp_in[c].hmac_key);
- else if (!xmlnode_get_text(member, "ipsec-mode", &s) && strcmp(s, "esp-tunnel"))
+ else if (!xmlnode_get_val(member, "ipsec-mode", &s) && strcmp(s, "esp-tunnel"))
vpn_progress(vpninfo, PRG_ERR, _("GlobalProtect config sent ipsec-mode=%s (expected esp-tunnel)\n"), s);
- free(s);
}
if (setup_esp_keys(vpninfo, 0))
vpn_progress(vpninfo, PRG_ERR, "Failed to setup ESP keys.\n");
vpninfo->ssl_times.dpd = 10;
vpninfo->ssl_times.keepalive = vpninfo->esp_ssl_fallback = vpninfo->ssl_times.dpd;
+ free(s);
return 0;
}
free(vpninfo->urlpath);
vpninfo->urlpath = orig_path;
- if (result < 0)
- goto pre_opt_out;
-
/* parse getconfig result */
- result = gpst_xml_or_error(vpninfo, result, xml_buf, gpst_parse_config_xml, NULL, NULL);
+ if (result >= 0)
+ result = gpst_xml_or_error(vpninfo, xml_buf, gpst_parse_config_xml, NULL, NULL);
if (result)
- return result;
+ goto out;
if (!vpninfo->ip_info.mtu) {
/* FIXME: GP gateway config always seems to be <mtu>0</mtu> */
out:
free_optlist(old_cstp_opts);
-pre_opt_out:
buf_free(request_body);
free(xml_buf);
return result;
return ret;
}
-static int parse_hip_report_check(struct openconnect_info *vpninfo, xmlNode *xml_node)
+static int parse_hip_report_check(struct openconnect_info *vpninfo, xmlNode *xml_node, void *cb_data)
{
- char *s;
+ char *s = NULL;
int result = -EINVAL;
if (!xml_node || !xmlnode_is_named(xml_node, "response"))
goto out;
for (xml_node = xml_node->children; xml_node; xml_node=xml_node->next) {
- if (!xmlnode_get_text(xml_node, "hip-report-needed", &s)) {
+ if (!xmlnode_get_val(xml_node, "hip-report-needed", &s)) {
if (!strcmp(s, "no"))
result = 0;
else if (!strcmp(s, "yes"))
result = -EAGAIN;
else
result = -EINVAL;
- free(s);
goto out;
}
}
out:
+ free(s);
return result;
}
free(vpninfo->urlpath);
vpninfo->urlpath = orig_path;
- result = gpst_xml_or_error(vpninfo, result, xml_buf, report ? NULL : parse_hip_report_check, NULL, NULL);
+ if (result >= 0)
+ result = gpst_xml_or_error(vpninfo, xml_buf, report ? NULL : parse_hip_report_check, NULL, NULL);
out:
buf_free(request_body);