aboutsummaryrefslogtreecommitdiffstats
path: root/auth/auth_spnego.c
diff options
context:
space:
mode:
Diffstat (limited to 'auth/auth_spnego.c')
-rw-r--r--auth/auth_spnego.c161
1 files changed, 131 insertions, 30 deletions
diff --git a/auth/auth_spnego.c b/auth/auth_spnego.c
index 4d22ec1c2508..c0ad27e362da 100644
--- a/auth/auth_spnego.c
+++ b/auth/auth_spnego.c
@@ -181,7 +181,8 @@ typedef struct
claim to be. The session key can only be used with the HTTP service
on the target host. */
static apr_status_t
-gss_api_get_credentials(char *token, apr_size_t token_len,
+gss_api_get_credentials(serf_connection_t *conn,
+ char *token, apr_size_t token_len,
const char *hostname,
const char **buf, apr_size_t *buf_len,
gss_authn_info_t *gss_info)
@@ -202,6 +203,7 @@ gss_api_get_credentials(char *token, apr_size_t token_len,
/* Establish a security context to the server. */
status = serf__spnego_init_sec_context(
+ conn,
gss_info->gss_ctx,
KRB_HTTP_SERVICE, hostname,
&input_buf,
@@ -212,7 +214,11 @@ gss_api_get_credentials(char *token, apr_size_t token_len,
switch(status) {
case APR_SUCCESS:
- gss_info->state = gss_api_auth_completed;
+ if (output_buf.length == 0) {
+ gss_info->state = gss_api_auth_completed;
+ } else {
+ gss_info->state = gss_api_auth_in_progress;
+ }
break;
case APR_EAGAIN:
gss_info->state = gss_api_auth_in_progress;
@@ -242,6 +248,7 @@ do_auth(peer_t peer,
int code,
gss_authn_info_t *gss_info,
serf_connection_t *conn,
+ serf_request_t *request,
const char *auth_hdr,
apr_pool_t *pool)
{
@@ -306,6 +313,14 @@ do_auth(peer_t peer,
break;
}
+ if (request->auth_baton && !token) {
+ /* We provided token with this request, but server responded with empty
+ authentication header. This means server rejected our credentials.
+ XXX: Probably we need separate error code for this case like
+ SERF_ERROR_AUTHN_CREDS_REJECTED? */
+ return SERF_ERROR_AUTHN_FAILED;
+ }
+
/* If the server didn't provide us with a token, start with a new initial
step in the SPNEGO authentication. */
if (!token) {
@@ -314,14 +329,16 @@ do_auth(peer_t peer,
}
if (peer == HOST) {
- status = gss_api_get_credentials(token, token_len,
+ status = gss_api_get_credentials(conn,
+ token, token_len,
conn->host_info.hostname,
&tmp, &tmp_len,
gss_info);
} else {
char *proxy_host;
apr_getnameinfo(&proxy_host, conn->ctx->proxy_address, 0);
- status = gss_api_get_credentials(token, token_len, proxy_host,
+ status = gss_api_get_credentials(conn,
+ token, token_len, proxy_host,
&tmp, &tmp_len,
gss_info);
}
@@ -357,24 +374,32 @@ serf__init_spnego_connection(const serf__authn_scheme_t *scheme,
serf_connection_t *conn,
apr_pool_t *pool)
{
- gss_authn_info_t *gss_info;
- apr_status_t status;
-
- gss_info = apr_pcalloc(conn->pool, sizeof(*gss_info));
- gss_info->pool = conn->pool;
- gss_info->state = gss_api_auth_not_started;
- gss_info->pstate = pstate_init;
- status = serf__spnego_create_sec_context(&gss_info->gss_ctx, scheme,
- gss_info->pool, pool);
-
- if (status) {
- return status;
- }
+ serf_context_t *ctx = conn->ctx;
+ serf__authn_info_t *authn_info;
+ gss_authn_info_t *gss_info = NULL;
+ /* For proxy authentication, reuse the gss context for all connections.
+ For server authentication, create a new gss context per connection. */
if (code == 401) {
- conn->authn_baton = gss_info;
+ authn_info = &conn->authn_info;
} else {
- conn->proxy_authn_baton = gss_info;
+ authn_info = &ctx->proxy_authn_info;
+ }
+ gss_info = authn_info->baton;
+
+ if (!gss_info) {
+ apr_status_t status;
+
+ gss_info = apr_pcalloc(conn->pool, sizeof(*gss_info));
+ gss_info->pool = conn->pool;
+ gss_info->state = gss_api_auth_not_started;
+ gss_info->pstate = pstate_init;
+ status = serf__spnego_create_sec_context(&gss_info->gss_ctx, scheme,
+ gss_info->pool, pool);
+ if (status) {
+ return status;
+ }
+ authn_info->baton = gss_info;
}
/* Make serf send the initial requests one by one */
@@ -397,13 +422,15 @@ serf__handle_spnego_auth(int code,
apr_pool_t *pool)
{
serf_connection_t *conn = request->conn;
- gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
- conn->proxy_authn_baton;
+ serf_context_t *ctx = conn->ctx;
+ gss_authn_info_t *gss_info = (code == 401) ? conn->authn_info.baton :
+ ctx->proxy_authn_info.baton;
return do_auth(code == 401 ? HOST : PROXY,
code,
gss_info,
request->conn,
+ request,
auth_hdr,
pool);
}
@@ -418,8 +445,9 @@ serf__setup_request_spnego_auth(peer_t peer,
const char *uri,
serf_bucket_t *hdrs_bkt)
{
- gss_authn_info_t *gss_info = (peer == HOST) ? conn->authn_baton :
- conn->proxy_authn_baton;
+ serf_context_t *ctx = conn->ctx;
+ gss_authn_info_t *gss_info = (peer == HOST) ? conn->authn_info.baton :
+ ctx->proxy_authn_info.baton;
/* If we have an ongoing authentication handshake, the handler of the
previous response will have created the authn headers for this request
@@ -431,6 +459,10 @@ serf__setup_request_spnego_auth(peer_t peer,
serf_bucket_headers_setn(hdrs_bkt, gss_info->header,
gss_info->value);
+ /* Remember that we're using this request for authentication
+ handshake. */
+ request->auth_baton = (void*) TRUE;
+
/* We should send each token only once. */
gss_info->header = NULL;
gss_info->value = NULL;
@@ -469,6 +501,7 @@ serf__setup_request_spnego_auth(peer_t peer,
code,
gss_info,
conn,
+ request,
0l, /* no response authn header */
conn->pool);
if (status)
@@ -476,6 +509,11 @@ serf__setup_request_spnego_auth(peer_t peer,
serf_bucket_headers_setn(hdrs_bkt, gss_info->header,
gss_info->value);
+
+ /* Remember that we're using this request for authentication
+ handshake. */
+ request->auth_baton = (void*) TRUE;
+
/* We should send each token only once. */
gss_info->header = NULL;
gss_info->value = NULL;
@@ -486,19 +524,70 @@ serf__setup_request_spnego_auth(peer_t peer,
return APR_SUCCESS;
}
+/**
+ * Baton passed to the get_auth_header callback function.
+ */
+typedef struct {
+ const char *hdr_name;
+ const char *auth_name;
+ const char *hdr_value;
+ apr_pool_t *pool;
+} get_auth_header_baton_t;
+
+static int
+get_auth_header_cb(void *baton,
+ const char *key,
+ const char *header)
+{
+ get_auth_header_baton_t *b = baton;
+
+ /* We're only interested in xxxx-Authenticate headers. */
+ if (strcasecmp(key, b->hdr_name) != 0)
+ return 0;
+
+ /* Check if header value starts with interesting auth name. */
+ if (strncmp(header, b->auth_name, strlen(b->auth_name)) == 0) {
+ /* Save interesting header value and stop iteration. */
+ b->hdr_value = apr_pstrdup(b->pool, header);
+ return 1;
+ }
+
+ return 0;
+}
+
+static const char *
+get_auth_header(serf_bucket_t *hdrs,
+ const char *hdr_name,
+ const char *auth_name,
+ apr_pool_t *pool)
+{
+ get_auth_header_baton_t b;
+
+ b.auth_name = hdr_name;
+ b.hdr_name = auth_name;
+ b.hdr_value = NULL;
+ b.pool = pool;
+
+ serf_bucket_headers_do(hdrs, get_auth_header_cb, &b);
+
+ return b.hdr_value;
+}
+
/* Function is called when 2xx responses are received. Normally we don't
* have to do anything, except for the first response after the
* authentication handshake. This specific response includes authentication
* data which should be validated by the client (mutual authentication).
*/
apr_status_t
-serf__validate_response_spnego_auth(peer_t peer,
+serf__validate_response_spnego_auth(const serf__authn_scheme_t *scheme,
+ peer_t peer,
int code,
serf_connection_t *conn,
serf_request_t *request,
serf_bucket_t *response,
apr_pool_t *pool)
{
+ serf_context_t *ctx = conn->ctx;
gss_authn_info_t *gss_info;
const char *auth_hdr_name;
@@ -511,10 +600,10 @@ serf__validate_response_spnego_auth(peer_t peer,
"Validate Negotiate response header.\n");
if (peer == HOST) {
- gss_info = conn->authn_baton;
+ gss_info = conn->authn_info.baton;
auth_hdr_name = "WWW-Authenticate";
} else {
- gss_info = conn->proxy_authn_baton;
+ gss_info = ctx->proxy_authn_info.baton;
auth_hdr_name = "Proxy-Authenticate";
}
@@ -524,11 +613,23 @@ serf__validate_response_spnego_auth(peer_t peer,
apr_status_t status;
hdrs = serf_bucket_response_get_headers(response);
- auth_hdr_val = serf_bucket_headers_get(hdrs, auth_hdr_name);
+ auth_hdr_val = get_auth_header(hdrs, auth_hdr_name, scheme->name,
+ pool);
+
+ if (auth_hdr_val) {
+ status = do_auth(peer, code, gss_info, conn, request, auth_hdr_val,
+ pool);
+ if (status) {
+ return status;
+ }
+ } else {
+ /* No Authenticate headers, nothing to validate: authentication
+ completed.*/
+ gss_info->state = gss_api_auth_completed;
- status = do_auth(peer, code, gss_info, conn, auth_hdr_val, pool);
- if (status)
- return status;
+ serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
+ "SPNEGO handshake completed.\n");
+ }
}
if (gss_info->state == gss_api_auth_completed) {