From 569042204e66ca96869ab49e1ff255ca494ffa25 Mon Sep 17 00:00:00 2001 From: Nikos Mavrogiannopoulos Date: Fri, 13 Feb 2015 18:50:29 +0100 Subject: [PATCH 1/2] Added support for SPNEGO in the CSTP channel Signed-off-by: Nikos Mavrogiannopoulos --- auth.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ cstp.c | 2 ++ gssapi.c | 54 +++++++++++++++++++++++++---------------- http.c | 44 +++++++++++++++++++++++++++++++++- library.c | 1 + openconnect-internal.h | 14 ++++++++++- 6 files changed, 157 insertions(+), 23 deletions(-) diff --git a/auth.c b/auth.c index f055c7a..3ccfab2 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->https_auth.state = AUTH_AVAILABLE; + do { + reqbuf = buf_alloc(); + + result = gssapi_authorization(vpninfo, &vpninfo->https_auth, 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->https_auth.state == GSSAPI_COMPLETE) { + result = 0; + break; + } else if (vpninfo->https_auth.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,20 @@ 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 */ + openconnect_close_https(vpninfo, 0); + 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 +1450,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..19d54e5 100644 --- a/cstp.c +++ b/cstp.c @@ -1145,6 +1145,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..2372d34 100644 --- a/gssapi.c +++ b/gssapi.c @@ -50,17 +50,17 @@ static void print_gss_err(struct openconnect_info *vpninfo, const char *where, static const char spnego_OID[] = "\x2b\x06\x01\x05\x05\x02"; static const gss_OID_desc gss_mech_spnego = { - 6, + sizeof(spnego_OID)-1, (void *)&spnego_OID }; -static int gssapi_setup(struct openconnect_info *vpninfo, const char *service) +static int gssapi_setup(struct openconnect_info *vpninfo, const char *service, const char *server) { OM_uint32 major, minor; gss_buffer_desc token = GSS_C_EMPTY_BUFFER; char *name; - if (asprintf(&name, "%s@%s", service, vpninfo->proxy) == -1) + if (asprintf(&name, "%s@%s", service, server) == -1) return -ENOMEM; token.length = strlen(name); token.value = name; @@ -77,28 +77,37 @@ 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 proxy_auth_state *auth_state, + struct oc_text_buf *hdrbuf, unsigned proxy) { OM_uint32 major, minor; gss_buffer_desc in = GSS_C_EMPTY_BUFFER; gss_buffer_desc out = GSS_C_EMPTY_BUFFER; gss_OID mech = GSS_C_NO_OID; + int ret; - if (vpninfo->auth[AUTH_TYPE_GSSAPI].state == AUTH_AVAILABLE && gssapi_setup(vpninfo, "HTTP")) { - vpninfo->auth[AUTH_TYPE_GSSAPI].state = AUTH_FAILED; - return -EIO; + if (!auth_state->challenge) + vpn_progress(vpninfo, PRG_INFO, + _("Attempting GSSAPI authentication\n")); + + if (auth_state->state == AUTH_AVAILABLE) { + if (proxy) + ret = gssapi_setup(vpninfo, "HTTP", vpninfo->proxy); + else + ret = gssapi_setup(vpninfo, "HTTP", vpninfo->hostname); + if (ret) { + auth_state->state = AUTH_FAILED; + return -EIO; + } } - if (vpninfo->auth[AUTH_TYPE_GSSAPI].challenge && *vpninfo->auth[AUTH_TYPE_GSSAPI].challenge) { + if (auth_state->challenge && *auth_state->challenge) { int len = -EINVAL; - in.value = openconnect_base64_decode(&len, vpninfo->auth[AUTH_TYPE_GSSAPI].challenge); + in.value = openconnect_base64_decode(&len, auth_state->challenge); if (!in.value) return len; in.length = len; - } else if (vpninfo->auth[AUTH_TYPE_GSSAPI].state > AUTH_AVAILABLE) { + } else if (auth_state->state > AUTH_AVAILABLE) { /* This indicates failure. We were trying, but got an empty 'Proxy-Authorization: Negotiate' header back from the server implying that we should start again... */ @@ -113,31 +122,34 @@ int gssapi_authorization(struct openconnect_info *vpninfo, struct oc_text_buf *h free(in.value); if (major == GSS_S_COMPLETE) - vpninfo->auth[AUTH_TYPE_GSSAPI].state = GSSAPI_COMPLETE; + auth_state->state = GSSAPI_COMPLETE; else if (major == GSS_S_CONTINUE_NEEDED) - vpninfo->auth[AUTH_TYPE_GSSAPI].state = GSSAPI_CONTINUE; + auth_state->state = GSSAPI_CONTINUE; else { vpn_progress(vpninfo, PRG_ERR, _("Error generating GSSAPI response:\n")); print_gss_err(vpninfo, "gss_init_sec_context()", mech, major, minor); fail_gssapi: - vpninfo->auth[AUTH_TYPE_GSSAPI].state = AUTH_FAILED; + auth_state->state = AUTH_FAILED; cleanup_gssapi_auth(vpninfo); /* If we were *trying*, then -EAGAIN. Else -ENOENT to let another 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")); return 0; } +int gssapi_proxy_authorization(struct openconnect_info *vpninfo, struct oc_text_buf *hdrbuf) +{ + return gssapi_authorization(vpninfo, &vpninfo->auth[AUTH_TYPE_GSSAPI], hdrbuf, 1); +} + void cleanup_gssapi_auth(struct openconnect_info *vpninfo) { OM_uint32 minor; @@ -163,7 +175,7 @@ int socks_gssapi_auth(struct openconnect_info *vpninfo) int i; int ret = -EIO; - if (gssapi_setup(vpninfo, "rcmd")) + if (gssapi_setup(vpninfo, "rcmd", vpninfo->proxy)) return -EIO; pktbuf = malloc(65538); diff --git a/http.c b/http.c index 8a459d9..bf1e3fb 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->https_auth.challenge); + vpninfo->https_auth.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/library.c b/library.c index fe25611..a4ea509 100644 --- a/library.c +++ b/library.c @@ -313,6 +313,7 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo) free(vpninfo->localname); free(vpninfo->useragent); free(vpninfo->authgroup); + free(vpninfo->https_auth.challenge); #ifdef HAVE_LIBSTOKEN if (vpninfo->stoken_pin) free(vpninfo->stoken_pin); diff --git a/openconnect-internal.h b/openconnect-internal.h index b637a9e..9f7c310 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -352,6 +352,7 @@ struct openconnect_info { char *proxy_pass; int proxy_close_during_auth; struct proxy_auth_state auth[MAX_AUTH_TYPES]; + struct proxy_auth_state https_auth; #ifdef HAVE_GSSAPI gss_name_t gss_target_name; gss_ctx_id_t gss_context; @@ -366,6 +367,7 @@ struct openconnect_info { int ntlm_helper_fd; #endif int authmethods_set; + int spnego; /* 0 inactive, 1 active, -1 disabled */ char *localname; char *hostname; @@ -937,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, @@ -950,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 proxy_auth_state *, 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