[PATCH 19/24] http: Rewrite openconnect_obtain_cookie() loop
Kevin Cernekee
cernekee at gmail.com
Sat Nov 3 13:23:01 EDT 2012
First try XML POST, then fall back to the old method if that breaks.
Signed-off-by: Kevin Cernekee <cernekee at gmail.com>
---
http.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 148 insertions(+), 45 deletions(-)
diff --git a/http.c b/http.c
index d3eca46..fdac639 100644
--- a/http.c
+++ b/http.c
@@ -809,7 +809,7 @@ static int handle_redirect(struct openconnect_info *vpninfo)
*/
static int do_https_request(struct openconnect_info *vpninfo, const char *method,
const char *request_body_type, const char *request_body,
- char **form_buf)
+ char **form_buf, int fetch_redirect)
{
struct oc_text_buf *buf;
int result, buflen;
@@ -875,8 +875,11 @@ static int do_https_request(struct openconnect_info *vpninfo, const char *method
if (result != 200 && vpninfo->redirect_url) {
result = handle_redirect(vpninfo);
- if (result == 0)
+ if (result == 0) {
+ if (!fetch_redirect)
+ return 0;
goto retry;
+ }
goto out;
}
if (!*form_buf || result != 200) {
@@ -896,6 +899,24 @@ static int do_https_request(struct openconnect_info *vpninfo, const char *method
}
/* Return value:
+ * < 0, if the data is unrecognized
+ * = 0, if the page contains an XML document
+ * = 1, if the page is a wait/refresh HTML page
+ */
+static int check_response_type(struct openconnect_info *vpninfo, char *form_buf)
+{
+ if (strncmp(form_buf, "<?xml", 5)) {
+ /* Not XML? Perhaps it's HTML with a refresh... */
+ if (strcasestr(form_buf, "http-equiv=\"refresh\""))
+ return 1;
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Unknown response from server\n"));
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* Return value:
* < 0, on error
* > 0, no cookie (user cancel)
* = 0, obtained cookie
@@ -904,70 +925,147 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
{
struct vpn_option *opt;
char *form_buf = NULL;
- struct oc_auth_form *form;
- int result, buflen;
+ struct oc_auth_form *form = NULL;
+ int result, buflen, tries;
char request_body[2048];
- const char *request_body_type = NULL;
- const char *method = "GET";
+ const char *request_body_type = "application/x-www-form-urlencoded";
+ const char *method = "POST";
+ int xmlpost = 0;
+ /* Step 1: Unlock software token (if applicable) */
if (vpninfo->use_stoken) {
result = prepare_stoken(vpninfo);
if (result)
return result;
}
- retry:
- buflen = do_https_request(vpninfo, method, request_body_type, request_body, &form_buf);
- if (buflen < 0)
- return buflen;
+ /*
+ * Step 2: Probe for XML POST compatibility
+ *
+ * This can get stuck in a redirect loop, so give up after any of:
+ *
+ * a) HTTP error (e.g. 400 Bad Request)
+ * b) Same-host redirect (e.g. Location: /foo/bar)
+ * c) Three redirects without seeing a plausible login form
+ */
+ result = xmlpost_initial_req(vpninfo, request_body, sizeof(request_body));
+ if (result < 0)
+ return result;
- if (vpninfo->csd_stuburl) {
- /* This is the CSD stub script, which we now need to run */
- result = run_csd_script(vpninfo, form_buf, buflen);
- if (result) {
+ for (tries = 0; ; tries++) {
+ if (tries == 3)
+ break;
+ buflen = do_https_request(vpninfo, method, request_body_type, request_body,
+ &form_buf, 0);
+ if (buflen == -EINVAL)
+ break;
+ if (buflen < 0)
+ return buflen;
+
+ if (vpninfo->redirect_type == REDIR_TYPE_LOCAL)
+ break;
+ else if (vpninfo->redirect_type != REDIR_TYPE_NONE)
+ continue;
+
+ result = parse_xml_response(vpninfo, form_buf, &form);
+ if (result < 0)
+ break;
+
+ xmlpost = 1;
+ vpn_progress(vpninfo, PRG_INFO, _("XML POST enabled\n"));
+ break;
+ }
+
+ /* Step 3: Fetch and parse the login form, if not using XML POST */
+ if (!xmlpost) {
+ buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
+ if (buflen < 0)
+ return buflen;
+
+ result = parse_xml_response(vpninfo, form_buf, &form);
+ if (result < 0) {
free(form_buf);
return result;
}
-
- /* Now we'll be redirected to the waiturl */
- goto retry;
}
- if (strncmp(form_buf, "<?xml", 5)) {
- /* Not XML? Perhaps it's HTML with a refresh... */
- if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
+
+ /* Step 4: Run the CSD trojan, if applicable */
+ if (vpninfo->csd_starturl) {
+ char *form_path = NULL;
+
+ if (vpninfo->urlpath) {
+ form_path = strdup(vpninfo->urlpath);
+ if (!form_path) {
+ result = -ENOMEM;
+ goto out;
+ }
+ }
+
+ /* fetch the CSD program, if available */
+ if (vpninfo->csd_stuburl) {
+ buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
+ if (buflen <= 0) {
+ result = -EINVAL;
+ goto out;
+ }
+ }
+
+ /* This is the CSD stub script, which we now need to run */
+ result = run_csd_script(vpninfo, form_buf, buflen);
+ if (result)
+ goto out;
+
+ /* vpninfo->urlpath now points to the wait page */
+ while (1) {
+ result = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
+ if (result <= 0)
+ break;
+
+ result = check_response_type(vpninfo, form_buf);
+ if (result <= 0)
+ break;
+
vpn_progress(vpninfo, PRG_INFO,
_("Refreshing %s after 1 second...\n"),
vpninfo->urlpath);
sleep(1);
- goto retry;
}
- vpn_progress(vpninfo, PRG_ERR,
- _("Unknown response from server\n"));
- free(form_buf);
- return -EINVAL;
- }
- result = parse_xml_response(vpninfo, form_buf, &form);
- if (result) {
- free(form_buf);
- return -ENOMEM;
- }
- request_body[0] = 0;
- result = handle_auth_form(vpninfo, form, request_body, sizeof(request_body),
- &method, &request_body_type, 0);
- free_auth_form(form);
+ if (result < 0)
+ goto out;
- free(form_buf);
- form_buf = NULL;
+ /* refresh the form page, to see if we're authorized now */
+ free(vpninfo->urlpath);
+ vpninfo->urlpath = form_path;
- if (!result) {
- result = handle_redirect(vpninfo);
- if (result == 0)
- goto retry;
- return result;
+ result = do_https_request(vpninfo, xmlpost ? "POST" : "GET",
+ request_body_type, request_body, &form_buf, 1);
+ if (result < 0)
+ goto out;
+
+ result = parse_xml_response(vpninfo, form_buf, &form);
+ if (result < 0)
+ goto out;
}
- if (result != 2)
- return result;
+ /* Step 5: Ask the user to fill in the auth form; repeat as necessary */
+ while (1) {
+ request_body[0] = 0;
+ result = handle_auth_form(vpninfo, form, request_body, sizeof(request_body),
+ &method, &request_body_type, xmlpost);
+ if (result < 0 || result == 1)
+ goto out;
+ if (result == 2)
+ break;
+
+ result = do_https_request(vpninfo, method, request_body_type, request_body,
+ &form_buf, 1);
+ if (result < 0)
+ goto out;
+
+ result = parse_xml_response(vpninfo, form_buf, &form);
+ if (result < 0)
+ goto out;
+ }
/* A return value of 2 means the XML form indicated
success. We _should_ have a cookie... */
@@ -1005,7 +1103,12 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
free(vpninfo->csd_scriptname);
vpninfo->csd_scriptname = NULL;
}
- return 0;
+ result = 0;
+
+out:
+ free(form_buf);
+ free_auth_form(form);
+ return result;
}
char *openconnect_create_useragent(const char *base)
--
1.7.10.4
More information about the openconnect-devel
mailing list