[PATCH 1/2] Added support for HTTP authentication in the CSTP channel.

Nikos Mavrogiannopoulos nmav at redhat.com
Fri Feb 20 05:05:02 PST 2015


Signed-off-by: Nikos Mavrogiannopoulos <nmav at redhat.com>
---
 auth.c                 |  20 +++++
 cstp.c                 |   1 +
 http.c                 | 201 ++++++++++++++++++++++++++++++++++++++++++-------
 openconnect-internal.h |  11 +++
 4 files changed, 204 insertions(+), 29 deletions(-)

diff --git a/auth.c b/auth.c
index f055c7a..0636c47 100644
--- a/auth.c
+++ b/auth.c
@@ -1151,6 +1151,7 @@ static int check_response_type(struct openconnect_info *vpninfo, char *form_buf)
 	return 0;
 }
 
+
 /* Return value:
  *  < 0, on error
  *  > 0, no cookie (user cancel)
@@ -1168,6 +1169,7 @@ int cstp_obtain_cookie(struct openconnect_info *vpninfo)
 	char *orig_host = NULL, *orig_path = NULL, *form_path = NULL;
 	int orig_port = 0;
 	int cert_rq, cert_sent = !vpninfo->cert;
+	int max_newgroup_tries = 4;
 
 #ifdef HAVE_LIBSTOKEN
 	/* Step 1: Unlock software token (if applicable) */
@@ -1191,6 +1193,10 @@ int cstp_obtain_cookie(struct openconnect_info *vpninfo)
 	 * c) Three redirects without seeing a plausible login form
 	 */
 newgroup:
+	if (max_newgroup_tries-- <= 0) {
+		goto out;
+	}
+
 	buf_truncate(request_body);
 	result = xmlpost_initial_req(vpninfo, request_body, 0);
 	if (result < 0)
@@ -1235,6 +1241,19 @@ newgroup:
 			result = 1;
 			goto out;
 		}
+
+		if (result == -EPERM) {
+			result = process_auth(vpninfo);
+			if (result > 0) {
+				goto out;
+			} else if (result == 0) {
+				goto read_cookies;
+			} else {
+				openconnect_close_https(vpninfo, 0);
+				goto newgroup;
+			}
+		}
+
 		if (result == -EINVAL)
 			goto fail;
 		if (result < 0)
@@ -1386,6 +1405,7 @@ newgroup:
 		}
 	}
 
+ read_cookies:
 	/* A return value of 2 means the XML form indicated
 	   success. We _should_ have a cookie... */
 
diff --git a/cstp.c b/cstp.c
index 2b89648..b142982 100644
--- a/cstp.c
+++ b/cstp.c
@@ -1147,6 +1147,7 @@ void cstp_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *b
 		buf_append(buf, "X-Aggregate-Auth: 1\r\n");
 		buf_append(buf, "X-AnyConnect-Platform: %s\r\n",
 			   vpninfo->platname);
+		buf_append(buf, "X-Support-HTTP-Auth: true\r\n");
 	}
 
 	append_mobile_headers(vpninfo, buf);
diff --git a/http.c b/http.c
index ed86f7d..8a3a4a4 100644
--- a/http.c
+++ b/http.c
@@ -30,8 +30,38 @@
 
 #include "openconnect-internal.h"
 
+struct auth_method {
+	int state_index;
+	const char *name;
+	int (*authorization)(struct openconnect_info *, int, struct http_auth_state *, struct oc_text_buf *);
+	void (*cleanup)(struct openconnect_info *, int, struct http_auth_state *);
+};
+
 static int proxy_write(struct openconnect_info *vpninfo, char *buf, size_t len);
 static int proxy_read(struct openconnect_info *vpninfo, char *buf, size_t len);
+static int http_auth_hdrs(struct openconnect_info *vpninfo, char *hdr, char *val);
+static int basic_authorization(struct openconnect_info *vpninfo, int proxy,
+			       struct http_auth_state *auth_state,
+			       struct oc_text_buf *hdrbuf);
+static void clear_auth_state(struct openconnect_info *vpninfo, int proxy,
+			     struct auth_method *method, int reset);
+#if !defined(HAVE_GSSAPI) && !defined(_WIN32)
+static int no_gssapi_authorization(struct openconnect_info *vpninfo,
+				   struct http_auth_state *auth_state,
+				   struct oc_text_buf *hdrbuf);
+#endif
+
+struct auth_method auth_methods[] = {
+#if defined(HAVE_GSSAPI) || defined(_WIN32)
+	{ AUTH_TYPE_GSSAPI, "Negotiate", gssapi_authorization, cleanup_gssapi_auth },
+#endif
+	{ AUTH_TYPE_NTLM, "NTLM", ntlm_authorization, cleanup_ntlm_auth },
+	{ AUTH_TYPE_DIGEST, "Digest", digest_authorization, NULL },
+	{ AUTH_TYPE_BASIC, "Basic", basic_authorization, NULL },
+#if !defined(HAVE_GSSAPI) && !defined(_WIN32)
+	{ AUTH_TYPE_GSSAPI, "Negotiate", no_gssapi_authorization, NULL }
+#endif
+};
 
 #define MAX_BUF_LEN 131072
 #define BUF_CHUNK_SIZE 4096
@@ -337,6 +367,73 @@ int http_add_cookie(struct openconnect_info *vpninfo, const char *option,
 	return 0;
 }
 
+/* Return value:
+ *  < 0, on error
+ *  > 0, no cookie (user cancel)
+ *  = 0, obtained cookie
+ */
+int process_auth(struct openconnect_info *vpninfo)
+{
+	struct oc_text_buf *reqbuf;
+	int result;
+	char *form_buf = NULL;
+	int i;
+
+	vpninfo->http_close_during_auth = 0;
+
+	vpn_progress(vpninfo, PRG_INFO,
+		     _("Requesting HTTP authentication from %s:%d\n"),
+		     vpninfo->hostname, vpninfo->port);
+
+	do {
+		reqbuf = buf_alloc();
+
+		result = gen_authorization_hdr(vpninfo, vpninfo->http_auth, reqbuf);
+		if (result) {
+			buf_free(reqbuf);
+			return result;
+		}
+		/* Forget existing challenges */
+		for (i = 0; i < sizeof(auth_methods) / sizeof(auth_methods[0]); i++) {
+			clear_auth_state(vpninfo, 0, &auth_methods[i], 0);
+		}
+		buf_append(reqbuf, "\r\n");
+
+		if (buf_error(reqbuf))
+			return buf_free(reqbuf);
+
+		if (vpninfo->dump_http_traffic)
+			dump_buf(vpninfo, '>', reqbuf->data);
+
+		result = do_https_request_head(vpninfo, "GET", NULL, reqbuf, NULL, &form_buf, 0);
+		free(form_buf);
+		form_buf = NULL;
+	
+		if (vpninfo->got_cancel_cmd) {
+			result = 1;
+			goto out;
+		}
+
+		if (result == -EPERM && vpninfo->http_close_during_auth)
+			return -EAGAIN;
+
+		if (result < 0 && result != -EPERM) {
+			vpn_progress(vpninfo, PRG_ERR,
+			     _("Sending headers failed: %d\n"), result);
+			goto out;
+		}
+	} while(result == -EPERM);
+
+	if (result >= 0)
+		return 0;
+
+	result = -EIO;
+ out:
+	vpn_progress(vpninfo, PRG_ERR,
+		     _("Authentication failed: %d\n"), result);
+	return result;
+}
+
 #define BODY_HTTP10 -1
 #define BODY_CHUNKED -2
 
@@ -351,7 +448,6 @@ int process_http_response(struct openconnect_info *vpninfo, int connect,
 	int i;
 
 	buf_truncate(body);
-
  cont:
 	if (vpninfo->ssl_gets(vpninfo, buf, sizeof(buf)) < 0) {
 		vpn_progress(vpninfo, PRG_ERR,
@@ -477,6 +573,7 @@ int process_http_response(struct openconnect_info *vpninfo, int connect,
 			header_cb(vpninfo, buf, colon);
 	}
 
+
 	/* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
 	if (result == 100)
 		goto cont;
@@ -794,6 +891,30 @@ int do_https_request(struct openconnect_info *vpninfo, const char *method,
 		     const char *request_body_type, struct oc_text_buf *request_body,
 		     char **form_buf, int fetch_redirect)
 {
+	return do_https_request_head(vpninfo, method, request_body_type,
+		NULL, request_body, form_buf, fetch_redirect);
+}
+
+/* Inputs:
+ *  method:             GET or POST
+ *  vpninfo->hostname:  Host DNS name
+ *  vpninfo->port:      TCP port, typically 443
+ *  vpninfo->urlpath:   Relative path, e.g. /+webvpn+/foo.html
+ *  request_body_type:  Content type for a POST (e.g. text/html).  Can be NULL.
+ *  request_headers:    Additional headers. Can be NULL
+ *  request_body:       POST content
+ *  form_buf:           Callee-allocated buffer for server content
+ *
+ * Return value:
+ *  < 0, on error
+ *  >=0, on success, indicating the length of the data in *form_buf
+ */
+int do_https_request_head(struct openconnect_info *vpninfo, const char *method,
+		     const char *request_body_type, 
+		     struct oc_text_buf *request_headers,
+		     struct oc_text_buf *request_body,
+		     char **form_buf, int fetch_redirect)
+{
 	struct oc_text_buf *buf;
 	int result;
 	int rq_retry;
@@ -835,6 +956,10 @@ int do_https_request(struct openconnect_info *vpninfo, const char *method,
 		buf_append(buf, "Content-Type: %s\r\n", request_body_type);
 		buf_append(buf, "Content-Length: %d\r\n", (int)rlen);
 	}
+
+	if (request_headers && request_headers->buf_len > 0) {
+		buf_append(buf, "%s", request_headers->data);
+	}
 	buf_append(buf, "\r\n");
 
 	if (request_body_type)
@@ -884,11 +1009,19 @@ int do_https_request(struct openconnect_info *vpninfo, const char *method,
 	if (result < 0)
 		goto out;
 
-	result = process_http_response(vpninfo, 0, NULL, buf);
+	result = process_http_response(vpninfo, 0, http_auth_hdrs, buf);
 	if (result < 0) {
 		/* We'll already have complained about whatever offended us */
 		goto out;
 	}
+
+	if (result == 401) {
+		if (vpninfo->http_close_during_auth)
+			return -EAGAIN;
+		result = -EPERM;
+		goto out;
+	}
+
 	if (vpninfo->dump_http_traffic && buf->pos)
 		dump_buf(vpninfo, '<', buf->data);
 
@@ -1441,36 +1574,24 @@ static int no_gssapi_authorization(struct openconnect_info *vpninfo,
 }
 #endif
 
-struct auth_method {
-	int state_index;
-	const char *name;
-	int (*authorization)(struct openconnect_info *, int, struct http_auth_state *, struct oc_text_buf *);
-	void (*cleanup)(struct openconnect_info *, int, struct http_auth_state *);
-} auth_methods[] = {
-#if defined(HAVE_GSSAPI) || defined(_WIN32)
-	{ AUTH_TYPE_GSSAPI, "Negotiate", gssapi_authorization, cleanup_gssapi_auth },
-#endif
-	{ AUTH_TYPE_NTLM, "NTLM", ntlm_authorization, cleanup_ntlm_auth },
-	{ AUTH_TYPE_DIGEST, "Digest", digest_authorization, NULL },
-	{ AUTH_TYPE_BASIC, "Basic", basic_authorization, NULL },
-#if !defined(HAVE_GSSAPI) && !defined(_WIN32)
-	{ AUTH_TYPE_GSSAPI, "Negotiate", no_gssapi_authorization, NULL }
-#endif
-};
-
 /* Generate Proxy-Authorization: header for request if appropriate */
-static int gen_authorization_hdr(struct openconnect_info *vpninfo, int proxy,
-				 struct oc_text_buf *buf)
+int gen_authorization_hdr(struct openconnect_info *vpninfo,
+			  struct http_auth_state *auth_states,
+			  struct oc_text_buf *buf)
 {
 	int ret;
 	int i;
+	int proxy;
+
+	if (auth_states == vpninfo->http_auth)
+		proxy = 0;
+	else
+		proxy = 1;
 
 	for (i = 0; i < sizeof(auth_methods) / sizeof(auth_methods[0]); i++) {
 		struct http_auth_state *auth_state;
-		if (proxy)
-			auth_state = &vpninfo->proxy_auth[auth_methods[i].state_index];
-		else
-			auth_state = &vpninfo->http_auth[auth_methods[i].state_index];
+		auth_state = &auth_states[auth_methods[i].state_index];
+
 		if (auth_state->state > AUTH_UNSEEN) {
 			ret = auth_methods[i].authorization(vpninfo, proxy, auth_state, buf);
 			if (ret == -EAGAIN || !ret)
@@ -1482,10 +1603,10 @@ static int gen_authorization_hdr(struct openconnect_info *vpninfo, int proxy,
 }
 
 /* Returns non-zero if it matched */
-static int handle_auth_proto(struct openconnect_info *vpninfo,
+static int handle_auth_proto(struct http_auth_state auth_methods[MAX_AUTH_TYPES],
 			     struct auth_method *method, char *hdr)
 {
-	struct http_auth_state *auth = &vpninfo->proxy_auth[method->state_index];
+	struct http_auth_state *auth = &auth_methods[method->state_index];
 	int l = strlen(method->name);
 
 	if (auth->state <= AUTH_FAILED)
@@ -1508,6 +1629,28 @@ static int handle_auth_proto(struct openconnect_info *vpninfo,
 	return 1;
 }
 
+static int http_auth_hdrs(struct openconnect_info *vpninfo, char *hdr, char *val)
+{
+	int i;
+
+	if (!strcasecmp(hdr, "Connection")) {
+		if (!strcasecmp(val, "close"))
+			vpninfo->http_close_during_auth = 1;
+		return 0;
+	}
+	if (strcasecmp(hdr, "WWW-Authenticate"))
+		return 0;
+
+	for (i = 0; i < sizeof(auth_methods) / sizeof(auth_methods[0]); i++) {
+		/* Return once we've found a match */
+		if (handle_auth_proto(vpninfo->http_auth, &auth_methods[i], val)) {
+			return 0;
+		}
+	}
+
+	return 0;
+}
+
 static int proxy_hdrs(struct openconnect_info *vpninfo, char *hdr, char *val)
 {
 	int i;
@@ -1524,7 +1667,7 @@ static int proxy_hdrs(struct openconnect_info *vpninfo, char *hdr, char *val)
 
 	for (i = 0; i < sizeof(auth_methods) / sizeof(auth_methods[0]); i++) {
 		/* Return once we've found a match */
-		if (handle_auth_proto(vpninfo, &auth_methods[i], val))
+		if (handle_auth_proto(vpninfo->proxy_auth, &auth_methods[i], val))
 			return 0;
 	}
 
@@ -1580,7 +1723,7 @@ static int process_http_proxy(struct openconnect_info *vpninfo)
 	if (auth) {
 		int i;
 
-		result = gen_authorization_hdr(vpninfo, 1, reqbuf);
+		result = gen_authorization_hdr(vpninfo, vpninfo->proxy_auth, reqbuf);
 		if (result) {
 			buf_free(reqbuf);
 			return result;
diff --git a/openconnect-internal.h b/openconnect-internal.h
index 966b940..1d9bd54 100644
--- a/openconnect-internal.h
+++ b/openconnect-internal.h
@@ -939,6 +939,12 @@ int internal_parse_url(const char *url, char **res_proto, char **res_host,
 int do_https_request(struct openconnect_info *vpninfo, const char *method,
 		     const char *request_body_type, struct oc_text_buf *request_body,
 		     char **form_buf, int fetch_redirect);
+int do_https_request_head(struct openconnect_info *vpninfo, const char *method,
+		     const char *request_body_type, 
+		     struct oc_text_buf *request_headers,
+		     struct oc_text_buf *request_body,
+		     char **form_buf, int fetch_redirect);
+
 int http_add_cookie(struct openconnect_info *vpninfo, const char *option,
 		    const char *value, int replace);
 int process_http_response(struct openconnect_info *vpninfo, int connect,
@@ -947,6 +953,11 @@ int process_http_response(struct openconnect_info *vpninfo, int connect,
 int handle_redirect(struct openconnect_info *vpninfo);
 void http_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf);
 
+int process_auth(struct openconnect_info *vpninfo);
+int gen_authorization_hdr(struct openconnect_info *vpninfo,
+			  struct http_auth_state *auth_states,
+			  struct oc_text_buf *buf);
+
 /* ntlm.c */
 int ntlm_authorization(struct openconnect_info *vpninfo, int proxy, struct http_auth_state *auth_state, struct oc_text_buf *buf);
 void cleanup_ntlm_auth(struct openconnect_info *vpninfo, int proxy, struct http_auth_state *auth_state);
-- 
2.1.0




More information about the openconnect-devel mailing list