From: Dan Lenski Date: Wed, 15 Feb 2017 17:18:20 +0000 (-0800) Subject: working on support for challenge (= secondary login form) X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=6951a167e60d1f9d3bcb4f3d80677a85d09f7ba4;p=users%2Fdwmw2%2Fopenconnect.git working on support for challenge (= secondary login form) --- diff --git a/auth-globalprotect.c b/auth-globalprotect.c index b4b4527f..7bfe62d6 100644 --- a/auth-globalprotect.c +++ b/auth-globalprotect.c @@ -27,17 +27,36 @@ void gpst_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *b http_common_headers(vpninfo, buf); } -/* our "auth form" is just a static combination of username and password */ -static struct oc_auth_form *gp_auth_form(struct openconnect_info *vpninfo) +/* our "auth form" always has a username and password or challenge */ +static struct oc_auth_form *auth_form(struct openconnect_info *vpninfo, char *prompt, char *user, char *inputStr) { - static struct oc_form_opt password = {.type=OC_FORM_OPT_PASSWORD, .name=(char *)"password", .label=(char *)"Password: "}; - static struct oc_form_opt username = {.next=&password, .type=OC_FORM_OPT_TEXT, .name=(char *)"username", .label=(char *)"Username: "}; - static struct oc_auth_form form = {.opts=&username, .message=(char *)"Please enter your username and password." }; - - if (vpninfo->token_mode!=OC_TOKEN_MODE_NONE) - password.type = OC_FORM_OPT_TOKEN; - - return &form; + static struct oc_auth_form *form; + static 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 = inputStr; + + opt = form->opts = calloc(1, sizeof(*opt)); + if (!opt) + return NULL; + opt->name=strdup("username"); + opt->label=strdup(_("Username: ")); + opt->type = user ? OC_FORM_OPT_HIDDEN : OC_FORM_OPT_TEXT; + opt->_value = user; + + opt2 = opt->next = calloc(1, sizeof(*opt)); + if (!opt2) + return NULL; + opt2->name = strdup("password"); + opt2->label = inputStr ? strdup(_("Challenge: ")) : strdup(_("Password: ")); + opt2->type = vpninfo->token_mode!=OC_TOKEN_MODE_NONE ? OC_FORM_OPT_TOKEN : OC_FORM_OPT_PASSWORD; + + form->opts = opt; + return form; } /* Return value: @@ -117,11 +136,12 @@ int gpst_obtain_cookie(struct openconnect_info *vpninfo) int result; struct oc_form_opt *opt; - struct oc_auth_form *form = gp_auth_form(vpninfo); + struct oc_auth_form *form; struct oc_text_buf *request_body = buf_alloc(); const char *request_body_type = "application/x-www-form-urlencoded"; const char *method = "POST"; char *xml_buf=NULL, *orig_path, *orig_ua; + char *prompt=NULL, *inputStr=NULL; #ifdef HAVE_LIBSTOKEN /* Step 1: Unlock software token (if applicable) */ @@ -135,7 +155,11 @@ int gpst_obtain_cookie(struct openconnect_info *vpninfo) /* Ask the user to fill in the auth form; repeat as necessary */ do { - /* process static auth form (username and password) */ + form = auth_form(vpninfo, prompt, NULL, inputStr); + if (!form) + return -ENOMEM; + + /* process auth form (username, password or challenge, and hidden inputStr) */ result = process_auth_form(vpninfo, form); if (result) goto out; @@ -153,12 +177,14 @@ int gpst_obtain_cookie(struct openconnect_info *vpninfo) buf_append(request_body, "jnlpReady=jnlpReady&ok=Login&direct=yes&clientVer=4100&prot=https:"); append_opt(request_body, "server", vpninfo->hostname); append_opt(request_body, "computer", vpninfo->localname); + append_opt(request_body, "inputStr", form->auth_id); for (opt=form->opts; opt; opt=opt->next) { if (!strcmp(opt->name, "username")) append_opt(request_body, "user", opt->_value); else if (!strcmp(opt->name, "password")) append_opt(request_body, "passwd", opt->_value); } + free_auth_form(form); orig_path = vpninfo->urlpath; orig_ua = vpninfo->useragent; @@ -170,11 +196,11 @@ int gpst_obtain_cookie(struct openconnect_info *vpninfo) vpninfo->urlpath = orig_path; vpninfo->useragent = orig_ua; - result = gpst_xml_or_error(vpninfo, result, xml_buf, parse_login_xml); - + /* Result could be either a JavaScript challenge or XML */ + result = gpst_xml_or_error(vpninfo, result, xml_buf, parse_login_xml, &prompt, &inputStr); } - /* repeat on invalid username or password */ - while (result == -512); + /* repeat on invalid username or password, or challenge */ + while (result == -512 || result == 2); out: buf_free(request_body); @@ -212,8 +238,8 @@ int gpst_bye(struct openconnect_info *vpninfo, const char *reason) /* logout.esp returns HTTP status 200 and when * successful, and all manner of malformed junk when unsuccessful. - */ - result = gpst_xml_or_error(vpninfo, result, xml_buf, NULL); + */ + result = gpst_xml_or_error(vpninfo, result, xml_buf, NULL, NULL, NULL); if (result < 0) vpn_progress(vpninfo, PRG_ERR, _("Logout failed.\n")); else diff --git a/gpst.c b/gpst.c index 31f3f1b5..6d727f91 100644 --- a/gpst.c +++ b/gpst.c @@ -66,12 +66,86 @@ static int xmlnode_get_text(xmlNode *xml_node, const char *name, const char **va return 0; } +/* Parse this JavaScript-y mess: + + "var respStatus = \"\";\n" + "var respMsg = \"\";\n" + "thisForm.inputStr.value = "";\n" +*/ +int gpst_scrape_javascript(char *javascript, char **prompt, char **inputStr) +{ + const char *start, *end = javascript; + int status; + + const char *pre_status = "var respStatus = \"", + *pre_prompt = "var respMsg = \"", + *pre_inputStr = "thisForm.inputStr.value = \""; + + /* Status */ + while (isspace(*end)) + end++; + if (strncmp(end, pre_status, strlen(pre_status))) + goto err; + + start = end+strlen(pre_status); + end = strchr(start, '\n'); + if (!end || end[-1] != ';' || end[-2] != '"') + goto err; + + if (!strncmp(start, "Challenge", 8)) status = 2; + else if (!strncmp(start, "Error", 5)) status = 1; + else if (!strncmp(start, "Success", 7)) status = 0; + else goto err; + + /* Prompt */ + while (isspace(*end)) + end++; + if (strncmp(end, pre_prompt, strlen(pre_prompt))) + goto err; + + start = end+strlen(pre_prompt); + end = strchr(start, '\n'); + if (!end || end[-1] != ';' || end[-2] != '"') + goto err; + + if (prompt) + *prompt = strndup(start, end-start-2); + + /* inputStr */ + while (isspace(*end)) + end++; + if (strncmp(end, pre_inputStr, strlen(pre_inputStr))) + goto err2; + + start = end+strlen(pre_inputStr); + end = strchr(start, '\n'); + if (!end || end[-1] != ';' || end[-2] != '"') + goto err2; + + if (inputStr) + *inputStr = strndup(start, end-start-2); + + while (isspace(*end)) + end++; + if (*end != '\0') + goto err3; + + return status; + +err3: + if (inputStr) free((void *)inputStr); +err2: + if (prompt) free((void *)prompt); +err: + return -EINVAL; +} + int gpst_xml_or_error(struct openconnect_info *vpninfo, int result, char *response, - int (*xml_cb)(struct openconnect_info *, xmlNode *xml_node)) + int (*xml_cb)(struct openconnect_info *, xmlNode *xml_node), + char **prompt, char **inputStr) { xmlDocPtr xml_doc; xmlNode *xml_node; - int errlen; const char *err; /* custom error codes returned by /ssl-vpn/login.esp and maybe others */ @@ -93,21 +167,32 @@ int gpst_xml_or_error(struct openconnect_info *vpninfo, int result, char *respon xml_doc = xmlReadMemory(response, strlen(response), "noname.xml", NULL, XML_PARSE_NOERROR); if (!xml_doc) { - /* nope, but maybe it looks like this JavaScript-y blob: - var respStatus = "Error"; - var respMsg = ""; - thisForm.inputStr.value = ""; */ - if ((err = strstr(response, "respMsg = \"")) != NULL) { - err += 11; - errlen = (strchr(err, ';') ? : err + strlen(err)) - err - 1; - vpn_progress(vpninfo, PRG_ERR, - _("%.*s\n"), errlen, err); - goto out; + char *p, *i; + result = gpst_scrape_javascript(response, &p, &i); + switch (result) { + case 0: + vpn_progress(vpninfo, PRG_ERR, _("Success: %s\n"), p); + break; + case 1: + vpn_progress(vpninfo, PRG_ERR, _("%s\n"), p); + break; + case 2: + vpn_progress(vpninfo, PRG_ERR, _("Challenge: %s\n"), p); + if (prompt && inputStr) { + *prompt=p; + *inputStr=i; + return 2; + } + break; + default: + goto bad_xml; } - goto bad_xml; + free((char *)p); + free((char *)i); + goto out; } - xml_node = xmlDocGetRootElement(xml_doc); + xml_node = xmlDocGetRootElement(xml_doc); /* is it .. ? */ if (xmlnode_is_named(xml_node, "response") @@ -370,7 +455,7 @@ static int gpst_get_config(struct openconnect_info *vpninfo) goto out; /* parse getconfig result */ - result = gpst_xml_or_error(vpninfo, result, xml_buf, gpst_parse_config_xml); + result = gpst_xml_or_error(vpninfo, result, xml_buf, gpst_parse_config_xml, NULL, NULL); if (result) return result; diff --git a/openconnect-internal.h b/openconnect-internal.h index ee4edbdf..dc57ab1e 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -864,8 +864,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_scrape_javascript(char *javascript, char **prompt, char **inputStr); int gpst_xml_or_error(struct openconnect_info *vpninfo, int result, char *response, - int (*xml_cb)(struct openconnect_info *, xmlNode *xml_node)); + int (*xml_cb)(struct openconnect_info *, xmlNode *xml_node), + char **prompt, char **inputStr); int gpst_setup(struct openconnect_info *vpninfo); int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout);