From 0d16466cfa2165839b83718f4264a68f932a4f8d Mon Sep 17 00:00:00 2001 From: Nikos Mavrogiannopoulos Date: Fri, 13 Feb 2015 18:50:29 +0100 Subject: [PATCH] Added support for SPNEGO in the CSTP channel Signed-off-by: Nikos Mavrogiannopoulos --- auth.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ cstp.c | 2 ++ gssapi.c | 14 ++++++----- http.c | 44 +++++++++++++++++++++++++++++++++- openconnect-internal.h | 13 +++++++++- 5 files changed, 129 insertions(+), 8 deletions(-) diff --git a/auth.c b/auth.c index f055c7a..8101eb3 100644 --- a/auth.c +++ b/auth.c @@ -1153,6 +1153,56 @@ static int check_response_type(struct openconnect_info *vpninfo, char *form_buf) /* Return value: * < 0, on error + * > 0, user cancel + * = 0, completed auth + */ +static +int do_spnego(struct openconnect_info *vpninfo) +{ + struct oc_text_buf *reqbuf; + char *form_buf; + int result; + + vpninfo->auth[AUTH_TYPE_GSSAPI].state = AUTH_AVAILABLE; + do { + reqbuf = buf_alloc(); + + result = gssapi_authorization(vpninfo, reqbuf, 0); + if (result < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("GSSAPI auth failed\n")); + goto out; + } + + result = do_https_request_head(vpninfo, "GET", NULL, reqbuf, + NULL, &form_buf, 0); + if (vpninfo->got_cancel_cmd) { + result = 1; + goto out; + } + + if (result < 0) { + goto out; + } + + if (vpninfo->auth[AUTH_TYPE_GSSAPI].state == GSSAPI_COMPLETE) { + result = 0; + break; + } else if (vpninfo->auth[AUTH_TYPE_GSSAPI].state != GSSAPI_CONTINUE) { + result = -1; + break; + } + buf_free(reqbuf); + } while(1); + + out: + buf_free(reqbuf); + return result; + +} + +/* Return value: + * < 0, on error * > 0, no cookie (user cancel) * = 0, obtained cookie */ @@ -1240,6 +1290,19 @@ newgroup: if (result < 0) goto out; + if (vpninfo->spnego > 0) { + result = do_spnego(vpninfo); + if (result > 0) + goto out; + else if (result == 0) { + vpninfo->spnego = -1; + goto read_cookies; + } else { /* fallback to normal auth */ + vpninfo->spnego = -1; + goto newgroup; + } + } + /* Some ASAs forget to send the TLS cert request on the initial connection. * If we have a client cert, disable HTTP keepalive until we get a real * login form (not a redirect). */ @@ -1386,6 +1449,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 eed45f6..d8c5e2c 100644 --- a/cstp.c +++ b/cstp.c @@ -1134,6 +1134,8 @@ void cstp_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *b buf_append(buf, "X-Transcend-Version: 1\r\n"); if (vpninfo->xmlpost) { buf_append(buf, "X-Aggregate-Auth: 1\r\n"); + if (vpninfo->spnego != -1) + buf_append(buf, "X-Support-SPNEGO: true\r\n"); buf_append(buf, "X-AnyConnect-Platform: %s\r\n", vpninfo->platname); } diff --git a/gssapi.c b/gssapi.c index 7272970..49a497d 100644 --- a/gssapi.c +++ b/gssapi.c @@ -77,10 +77,7 @@ static int gssapi_setup(struct openconnect_info *vpninfo, const char *service) return 0; } -#define GSSAPI_CONTINUE 2 -#define GSSAPI_COMPLETE 3 - -int gssapi_authorization(struct openconnect_info *vpninfo, struct oc_text_buf *hdrbuf) +int gssapi_authorization(struct openconnect_info *vpninfo, struct oc_text_buf *hdrbuf, unsigned proxy) { OM_uint32 major, minor; gss_buffer_desc in = GSS_C_EMPTY_BUFFER; @@ -127,17 +124,22 @@ int gssapi_authorization(struct openconnect_info *vpninfo, struct oc_text_buf *h auth method try without having to reconnect first. */ return in.value ? -EAGAIN : -ENOENT; } - buf_append(hdrbuf, "Proxy-Authorization: Negotiate "); + buf_append(hdrbuf, "%s: Negotiate ", proxy?"Proxy-Authorization":"Authorization"); buf_append_base64(hdrbuf, out.value, out.length); buf_append(hdrbuf, "\r\n"); gss_release_buffer(&minor, &out); if (!vpninfo->auth[AUTH_TYPE_GSSAPI].challenge) vpn_progress(vpninfo, PRG_INFO, - _("Attempting GSSAPI authentication to proxy\n")); + _("Attempting GSSAPI authentication\n")); return 0; } +int gssapi_proxy_authorization(struct openconnect_info *vpninfo, struct oc_text_buf *hdrbuf) +{ + return gssapi_authorization(vpninfo, hdrbuf, 1); +} + void cleanup_gssapi_auth(struct openconnect_info *vpninfo) { OM_uint32 minor; diff --git a/http.c b/http.c index 8a459d9..7276780 100644 --- a/http.c +++ b/http.c @@ -452,6 +452,16 @@ int process_http_response(struct openconnect_info *vpninfo, int connect, if (!vpninfo->redirect_url) return -ENOMEM; } + if (!strcasecmp(buf, "WWW-Authenticate")) { + if (!strncasecmp(colon, "Negotiate", 9)) { + if (colon[9] == ' ') { + free(vpninfo->auth[AUTH_TYPE_GSSAPI].challenge); + vpninfo->auth[AUTH_TYPE_GSSAPI].challenge = strdup(&colon[10]); + } else { + vpninfo->spnego = 1; + } + } + } if (!strcasecmp(buf, "Content-Length")) { bodylen = atoi(colon); if (bodylen < 0) { @@ -794,6 +804,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 +869,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) @@ -892,6 +930,10 @@ int do_https_request(struct openconnect_info *vpninfo, const char *method, if (vpninfo->dump_http_traffic && buf->pos) dump_buf(vpninfo, '<', buf->data); + if (result == 401 && vpninfo->spnego > 0) { + result = 0; + goto out; + } if (result != 200 && vpninfo->redirect_url) { result = handle_redirect(vpninfo); if (result == 0) { @@ -1430,7 +1472,7 @@ struct auth_method { void (*cleanup)(struct openconnect_info *); } auth_methods[] = { #if defined(HAVE_GSSAPI) || defined(_WIN32) - { AUTH_TYPE_GSSAPI, "Negotiate", gssapi_authorization, cleanup_gssapi_auth }, + { AUTH_TYPE_GSSAPI, "Negotiate", gssapi_proxy_authorization, cleanup_gssapi_auth }, #endif { AUTH_TYPE_NTLM, "NTLM", ntlm_authorization, cleanup_ntlm_auth }, { AUTH_TYPE_DIGEST, "Digest", digest_authorization, NULL }, diff --git a/openconnect-internal.h b/openconnect-internal.h index 49383ef..21c3d0d 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -366,6 +366,7 @@ struct openconnect_info { int ntlm_helper_fd; #endif int authmethods_set; + int spnego; /* 0 inactive, 1 active, -1 disabled */ char *localname; char *hostname; @@ -938,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, @@ -951,7 +958,11 @@ int ntlm_authorization(struct openconnect_info *vpninfo, struct oc_text_buf *buf void cleanup_ntlm_auth(struct openconnect_info *vpninfo); /* gssapi.c */ -int gssapi_authorization(struct openconnect_info *vpninfo, struct oc_text_buf *buf); +#define GSSAPI_CONTINUE 2 +#define GSSAPI_COMPLETE 3 + +int gssapi_authorization(struct openconnect_info *vpninfo, struct oc_text_buf *hdrbuf, unsigned proxy); +int gssapi_proxy_authorization(struct openconnect_info *vpninfo, struct oc_text_buf *hdrbuf); void cleanup_gssapi_auth(struct openconnect_info *vpninfo); int socks_gssapi_auth(struct openconnect_info *vpninfo); -- 2.1.0