Blame auth/auth_spnego.c

Packit 3adb1e
/* ====================================================================
Packit 3adb1e
 *    Licensed to the Apache Software Foundation (ASF) under one
Packit 3adb1e
 *    or more contributor license agreements.  See the NOTICE file
Packit 3adb1e
 *    distributed with this work for additional information
Packit 3adb1e
 *    regarding copyright ownership.  The ASF licenses this file
Packit 3adb1e
 *    to you under the Apache License, Version 2.0 (the
Packit 3adb1e
 *    "License"); you may not use this file except in compliance
Packit 3adb1e
 *    with the License.  You may obtain a copy of the License at
Packit 3adb1e
 *
Packit 3adb1e
 *      http://www.apache.org/licenses/LICENSE-2.0
Packit 3adb1e
 *
Packit 3adb1e
 *    Unless required by applicable law or agreed to in writing,
Packit 3adb1e
 *    software distributed under the License is distributed on an
Packit 3adb1e
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
Packit 3adb1e
 *    KIND, either express or implied.  See the License for the
Packit 3adb1e
 *    specific language governing permissions and limitations
Packit 3adb1e
 *    under the License.
Packit 3adb1e
 * ====================================================================
Packit 3adb1e
 */
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
#include "auth_spnego.h"
Packit 3adb1e
Packit 3adb1e
#ifdef SERF_HAVE_SPNEGO
Packit 3adb1e
Packit 3adb1e
/** These functions implement SPNEGO-based Kerberos and NTLM authentication,
Packit 3adb1e
 *  using either GSS-API (RFC 2743) or SSPI on Windows.
Packit 3adb1e
 *  The HTTP message exchange is documented in RFC 4559.
Packit 3adb1e
 **/
Packit 3adb1e
Packit 3adb1e
#include <serf.h>
Packit 3adb1e
#include <serf_private.h>
Packit 3adb1e
#include <auth/auth.h>
Packit 3adb1e
Packit 3adb1e
#include <apr.h>
Packit 3adb1e
#include <apr_base64.h>
Packit 3adb1e
#include <apr_strings.h>
Packit 3adb1e
Packit 3adb1e
/** TODO:
Packit 3adb1e
 ** - send session key directly on new connections where we already know
Packit 3adb1e
 **   the server requires Kerberos authn.
Packit 3adb1e
 ** - Add a way for serf to give detailed error information back to the
Packit 3adb1e
 **   application.
Packit 3adb1e
 **/
Packit 3adb1e
Packit 3adb1e
/* Authentication over HTTP using Kerberos
Packit 3adb1e
 *
Packit 3adb1e
 * Kerberos involves three servers:
Packit 3adb1e
 * - Authentication Server (AS): verifies users during login
Packit 3adb1e
 * - Ticket-Granting Server (TGS): issues proof of identity tickets
Packit 3adb1e
 * - HTTP server (S)
Packit 3adb1e
 *
Packit 3adb1e
 * Steps:
Packit 3adb1e
 * 0. User logs in to the AS and receives a TGS ticket. On workstations
Packit 3adb1e
 * where the login program doesn't support Kerberos, the user can use
Packit 3adb1e
 * 'kinit'.
Packit 3adb1e
 *
Packit 3adb1e
 * 1. C  --> S:    GET
Packit 3adb1e
 *
Packit 3adb1e
 *    C <--  S:    401 Authentication Required
Packit 3adb1e
 *                 WWW-Authenticate: Negotiate
Packit 3adb1e
 *
Packit 3adb1e
 * -> app contacts the TGS to request a session key for the HTTP service
Packit 3adb1e
 *    @ target host. The returned session key is encrypted with the HTTP
Packit 3adb1e
 *    service's secret key, so we can safely send it to the server.
Packit 3adb1e
 *
Packit 3adb1e
 * 2. C  --> S:    GET
Packit 3adb1e
 *                 Authorization: Negotiate <Base64 encoded session key>
Packit 3adb1e
 *                 gss_api_ctx->state = gss_api_auth_in_progress;
Packit 3adb1e
 *
Packit 3adb1e
 *    C <--  S:    200 OK
Packit 3adb1e
 *                 WWW-Authenticate: Negotiate 
Packit 3adb1e
 *                                              authentication data>
Packit 3adb1e
 *
Packit 3adb1e
 * -> The server returned an (optional) key to proof itself to us. We check this
Packit 3adb1e
 *    key with the TGS again. If it checks out, we can return the response
Packit 3adb1e
 *    body to the application.
Packit 3adb1e
 *
Packit 3adb1e
 * Note: It's possible that the server returns 401 again in step 2, if the
Packit 3adb1e
 *       Kerberos context isn't complete yet. This means there is 3rd step
Packit 3adb1e
 *       where we'll send a request with an Authorization header to the 
Packit 3adb1e
 *       server. Some (simple) tests with mod_auth_kerb and MIT Kerberos 5 show
Packit 3adb1e
 *       this never happens.
Packit 3adb1e
 *
Packit 3adb1e
 * Depending on the type of HTTP server, this handshake is required for either
Packit 3adb1e
 * every new connection, or for every new request! For more info see the next
Packit 3adb1e
 * comment on authn_persistence_state_t.
Packit 3adb1e
 *
Packit 3adb1e
 * Note: Step 1 of the handshake will only happen on the first connection, once
Packit 3adb1e
 * we know the server requires Kerberos authentication, the initial requests
Packit 3adb1e
 * on the other connections will include a session key, so we start at
Packit 3adb1e
 * step 2 in the handshake.
Packit 3adb1e
 * ### TODO: Not implemented yet!
Packit 3adb1e
 */
Packit 3adb1e
Packit 3adb1e
/* Current state of the authentication of the current request. */
Packit 3adb1e
typedef enum {
Packit 3adb1e
    gss_api_auth_not_started,
Packit 3adb1e
    gss_api_auth_in_progress,
Packit 3adb1e
    gss_api_auth_completed,
Packit 3adb1e
} gss_api_auth_state;
Packit 3adb1e
Packit 3adb1e
/**
Packit 3adb1e
   authn_persistence_state_t: state that indicates if we are talking with a
Packit 3adb1e
   server that requires authentication only of the first request (stateful),
Packit 3adb1e
   or of each request (stateless).
Packit 3adb1e
 
Packit 3adb1e
   INIT: Begin state. Authenticating the first request on this connection.
Packit 3adb1e
   UNDECIDED: we haven't identified the server yet, assume STATEFUL for now.
Packit 3adb1e
     Pipeline mode disabled, requests are sent only after the response off the
Packit 3adb1e
     previous request arrived.
Packit 3adb1e
   STATELESS: we know the server requires authentication for each request.
Packit 3adb1e
     On all new requests add the Authorization header with an initial SPNEGO
Packit 3adb1e
     token (created per request).
Packit 3adb1e
     To keep things simple, keep the connection in one by one mode.
Packit 3adb1e
     (otherwise we'd have to keep a queue of gssapi context objects to match
Packit 3adb1e
      the Negotiate header of the response with the session initiated by the
Packit 3adb1e
      mathing request).
Packit 3adb1e
     This state is an final state.
Packit 3adb1e
   STATEFUL: alright, we have authenticated the connection and for the server
Packit 3adb1e
     that is enough. Don't add an Authorization header to new requests.
Packit 3adb1e
     Serf will switch to pipelined mode.
Packit 3adb1e
     This state is not a final state, although in practical scenario's it will
Packit 3adb1e
     be. When we receive a 40x response from the server switch to STATELESS
Packit 3adb1e
     mode.
Packit 3adb1e
Packit 3adb1e
   We start in state init for the first request until it is authenticated.
Packit 3adb1e
Packit 3adb1e
   The rest of the state machine starts with the arrival of the response to the
Packit 3adb1e
   second request, and then goes on with each response:
Packit 3adb1e
Packit 3adb1e
      --------
Packit 3adb1e
      | INIT |     C --> S:    GET request in response to 40x of the server
Packit 3adb1e
      --------                 add [Proxy]-Authorization header
Packit 3adb1e
          |
Packit 3adb1e
          |
Packit 3adb1e
    ------------
Packit 3adb1e
    | UNDECIDED|   C --> S:    GET request, assume stateful,
Packit 3adb1e
    ------------               no [Proxy]-Authorization header
Packit 3adb1e
          |
Packit 3adb1e
          |
Packit 3adb1e
          |------------------------------------------------
Packit 3adb1e
          |                                               |
Packit 3adb1e
          | C <-- S: 40x Authentication                   | C <-- S: 200 OK
Packit 3adb1e
          |          Required                             |
Packit 3adb1e
          |                                               |
Packit 3adb1e
          v                                               v
Packit 3adb1e
      -------------                               ------------
Packit 3adb1e
    ->| STATELESS |<------------------------------| STATEFUL |<--
Packit 3adb1e
    | -------------       C <-- S: 40x            ------------  |
Packit 3adb1e
  * |    |                Authentication                  |     | 200 OK
Packit 3adb1e
    |    /                Required                        |     |
Packit 3adb1e
    -----                                                 -----/
Packit 3adb1e
Packit 3adb1e
 **/
Packit 3adb1e
typedef enum {
Packit 3adb1e
    pstate_init,
Packit 3adb1e
    pstate_undecided,
Packit 3adb1e
    pstate_stateless,
Packit 3adb1e
    pstate_stateful,
Packit 3adb1e
} authn_persistence_state_t;
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
/* HTTP Service name, used to get the session key.  */
Packit 3adb1e
#define KRB_HTTP_SERVICE "HTTP"
Packit 3adb1e
Packit 3adb1e
/* Stores the context information related to Kerberos authentication. */
Packit 3adb1e
typedef struct
Packit 3adb1e
{
Packit 3adb1e
    apr_pool_t *pool;
Packit 3adb1e
Packit 3adb1e
    /* GSSAPI context */
Packit 3adb1e
    serf__spnego_context_t *gss_ctx;
Packit 3adb1e
Packit 3adb1e
    /* Current state of the authentication cycle. */
Packit 3adb1e
    gss_api_auth_state state;
Packit 3adb1e
Packit 3adb1e
    /* Current persistence state. */
Packit 3adb1e
    authn_persistence_state_t pstate;
Packit 3adb1e
Packit 3adb1e
    const char *header;
Packit 3adb1e
    const char *value;
Packit 3adb1e
} gss_authn_info_t;
Packit 3adb1e
Packit 3adb1e
/* On the initial 401 response of the server, request a session key from
Packit 3adb1e
   the Kerberos KDC to pass to the server, proving that we are who we
Packit 3adb1e
   claim to be. The session key can only be used with the HTTP service
Packit 3adb1e
   on the target host. */
Packit 3adb1e
static apr_status_t
Packit 3adb1e
gss_api_get_credentials(serf_connection_t *conn,
Packit 3adb1e
                        char *token, apr_size_t token_len,
Packit 3adb1e
                        const char *hostname,
Packit 3adb1e
                        const char **buf, apr_size_t *buf_len,
Packit 3adb1e
                        gss_authn_info_t *gss_info)
Packit 3adb1e
{
Packit 3adb1e
    serf__spnego_buffer_t input_buf;
Packit 3adb1e
    serf__spnego_buffer_t output_buf;
Packit 3adb1e
    apr_status_t status = APR_SUCCESS;
Packit 3adb1e
Packit 3adb1e
    /* If the server sent us a token, pass it to gss_init_sec_token for
Packit 3adb1e
       validation. */
Packit 3adb1e
    if (token) {
Packit 3adb1e
        input_buf.value = token;
Packit 3adb1e
        input_buf.length = token_len;
Packit 3adb1e
    } else {
Packit 3adb1e
        input_buf.value = 0;
Packit 3adb1e
        input_buf.length = 0;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    /* Establish a security context to the server. */
Packit 3adb1e
    status = serf__spnego_init_sec_context(
Packit 3adb1e
         conn,
Packit 3adb1e
         gss_info->gss_ctx,
Packit 3adb1e
         KRB_HTTP_SERVICE, hostname,
Packit 3adb1e
         &input_buf,
Packit 3adb1e
         &output_buf,
Packit 3adb1e
         gss_info->pool,
Packit 3adb1e
         gss_info->pool
Packit 3adb1e
        );
Packit 3adb1e
Packit 3adb1e
    switch(status) {
Packit 3adb1e
    case APR_SUCCESS:
Packit 3adb1e
        if (output_buf.length == 0) {
Packit 3adb1e
            gss_info->state = gss_api_auth_completed;
Packit 3adb1e
        } else {
Packit 3adb1e
            gss_info->state = gss_api_auth_in_progress;
Packit 3adb1e
        }
Packit 3adb1e
        break;
Packit 3adb1e
    case APR_EAGAIN:
Packit 3adb1e
        gss_info->state = gss_api_auth_in_progress;
Packit 3adb1e
        status = APR_SUCCESS;
Packit 3adb1e
        break;
Packit 3adb1e
    default:
Packit 3adb1e
        return status;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    /* Return the session key to our caller. */
Packit 3adb1e
    *buf = output_buf.value;
Packit 3adb1e
    *buf_len = output_buf.length;
Packit 3adb1e
Packit 3adb1e
    return status;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
/* do_auth is invoked in two situations:
Packit 3adb1e
   - when a response from a server is received that contains an authn header
Packit 3adb1e
     (either from a 40x or 2xx response)
Packit 3adb1e
   - when a request is prepared on a connection with stateless authentication.
Packit 3adb1e
Packit 3adb1e
   Read the header sent by the server (if any), invoke the gssapi authn
Packit 3adb1e
   code and use the resulting Server Ticket on the next request to the
Packit 3adb1e
   server. */
Packit 3adb1e
static apr_status_t
Packit 3adb1e
do_auth(peer_t peer,
Packit 3adb1e
        int code,
Packit 3adb1e
        gss_authn_info_t *gss_info,
Packit 3adb1e
        serf_connection_t *conn,
Packit 3adb1e
        serf_request_t *request,
Packit 3adb1e
        const char *auth_hdr,
Packit 3adb1e
        apr_pool_t *pool)
Packit 3adb1e
{
Packit 3adb1e
    serf_context_t *ctx = conn->ctx;
Packit 3adb1e
    serf__authn_info_t *authn_info;
Packit 3adb1e
    const char *tmp = NULL;
Packit 3adb1e
    char *token = NULL;
Packit 3adb1e
    apr_size_t tmp_len = 0, token_len = 0;
Packit 3adb1e
    apr_status_t status;
Packit 3adb1e
Packit 3adb1e
    if (peer == HOST) {
Packit 3adb1e
        authn_info = serf__get_authn_info_for_server(conn);
Packit 3adb1e
    } else {
Packit 3adb1e
        authn_info = &ctx->proxy_authn_info;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    /* Is this a response from a host/proxy? auth_hdr should always be set. */
Packit 3adb1e
    if (code && auth_hdr) {
Packit 3adb1e
        const char *space = NULL;
Packit 3adb1e
        /* The server will return a token as attribute to the Negotiate key.
Packit 3adb1e
           Negotiate YGwGCSqGSIb3EgECAgIAb10wW6ADAgEFoQMCAQ+iTzBNoAMCARCiRgREa6
Packit 3adb1e
           mouMBAMFqKVdTGtfpZNXKzyw4Yo1paphJdIA3VOgncaoIlXxZLnkHiIHS2v65pVvrp
Packit 3adb1e
           bRIyjF8xve9HxpnNIucCY9c=
Packit 3adb1e
Packit 3adb1e
           Read this base64 value, decode it and validate it so we're sure the
Packit 3adb1e
           server is who we expect it to be. */
Packit 3adb1e
        space = strchr(auth_hdr, ' ');
Packit 3adb1e
Packit 3adb1e
        if (space) {
Packit 3adb1e
            token = apr_palloc(pool, apr_base64_decode_len(space + 1));
Packit 3adb1e
            token_len = apr_base64_decode(token, space + 1);
Packit 3adb1e
        }
Packit 3adb1e
    } else {
Packit 3adb1e
        /* This is a new request, not a retry in response to a 40x of the
Packit 3adb1e
           host/proxy. 
Packit 3adb1e
           Only add the Authorization header if we know the server requires
Packit 3adb1e
           per-request authentication (stateless). */
Packit 3adb1e
        if (gss_info->pstate != pstate_stateless)
Packit 3adb1e
            return APR_SUCCESS;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    switch(gss_info->pstate) {
Packit 3adb1e
        case pstate_init:
Packit 3adb1e
            /* Nothing to do here */
Packit 3adb1e
            break;
Packit 3adb1e
        case pstate_undecided: /* Fall through */
Packit 3adb1e
        case pstate_stateful:
Packit 3adb1e
            {
Packit 3adb1e
                /* Switch to stateless mode, from now on handle authentication
Packit 3adb1e
                   of each request with a new gss context. This is easiest to
Packit 3adb1e
                   manage when sending requests one by one. */
Packit 3adb1e
                serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
Packit 3adb1e
                              "Server requires per-request SPNEGO authn, "
Packit 3adb1e
                              "switching to stateless mode.\n");
Packit 3adb1e
Packit 3adb1e
                gss_info->pstate = pstate_stateless;
Packit 3adb1e
                serf_connection_set_max_outstanding_requests(conn, 1);
Packit 3adb1e
                break;
Packit 3adb1e
            }
Packit 3adb1e
        case pstate_stateless:
Packit 3adb1e
            /* Nothing to do here */
Packit 3adb1e
            break;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    if (request->auth_baton && !token) {
Packit 3adb1e
        /* We provided token with this request, but server responded with empty
Packit 3adb1e
           authentication header. This means server rejected our credentials.
Packit 3adb1e
           XXX: Probably we need separate error code for this case like
Packit 3adb1e
           SERF_ERROR_AUTHN_CREDS_REJECTED? */
Packit 3adb1e
        return SERF_ERROR_AUTHN_FAILED;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    /* If the server didn't provide us with a token, start with a new initial
Packit 3adb1e
       step in the SPNEGO authentication. */
Packit 3adb1e
    if (!token) {
Packit 3adb1e
        serf__spnego_reset_sec_context(gss_info->gss_ctx);
Packit 3adb1e
        gss_info->state = gss_api_auth_not_started;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    if (peer == HOST) {
Packit 3adb1e
        status = gss_api_get_credentials(conn,
Packit 3adb1e
                                         token, token_len,
Packit 3adb1e
                                         conn->host_info.hostname,
Packit 3adb1e
                                         &tmp, &tmp_len,
Packit 3adb1e
                                         gss_info);
Packit 3adb1e
    } else {
Packit 3adb1e
        char *proxy_host = conn->ctx->proxy_address->hostname;
Packit 3adb1e
        status = gss_api_get_credentials(conn,
Packit 3adb1e
                                         token, token_len, proxy_host,
Packit 3adb1e
                                         &tmp, &tmp_len,
Packit 3adb1e
                                         gss_info);
Packit 3adb1e
    }
Packit 3adb1e
    if (status)
Packit 3adb1e
        return status;
Packit 3adb1e
Packit 3adb1e
    /* On the next request, add an Authorization header. */
Packit 3adb1e
    if (tmp_len) {
Packit 3adb1e
        serf__encode_auth_header(&gss_info->value, authn_info->scheme->name,
Packit 3adb1e
                                 tmp,
Packit 3adb1e
                                 tmp_len,
Packit 3adb1e
                                 pool);
Packit 3adb1e
        gss_info->header = (peer == HOST) ?
Packit 3adb1e
            "Authorization" : "Proxy-Authorization";
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    return APR_SUCCESS;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
apr_status_t
Packit 3adb1e
serf__init_spnego(int code,
Packit 3adb1e
                  serf_context_t *ctx,
Packit 3adb1e
                  apr_pool_t *pool)
Packit 3adb1e
{
Packit 3adb1e
    return APR_SUCCESS;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
/* A new connection is created to a server that's known to use
Packit 3adb1e
   Kerberos. */
Packit 3adb1e
apr_status_t
Packit 3adb1e
serf__init_spnego_connection(const serf__authn_scheme_t *scheme,
Packit 3adb1e
                             int code,
Packit 3adb1e
                             serf_connection_t *conn,
Packit 3adb1e
                             apr_pool_t *pool)
Packit 3adb1e
{
Packit 3adb1e
    serf_context_t *ctx = conn->ctx;
Packit 3adb1e
    serf__authn_info_t *authn_info;
Packit 3adb1e
    gss_authn_info_t *gss_info = NULL;
Packit 3adb1e
Packit 3adb1e
    /* For proxy authentication, reuse the gss context for all connections. 
Packit 3adb1e
       For server authentication, create a new gss context per connection. */
Packit 3adb1e
    if (code == 401) {
Packit 3adb1e
        authn_info = &conn->authn_info;
Packit 3adb1e
    } else {
Packit 3adb1e
        authn_info = &ctx->proxy_authn_info;
Packit 3adb1e
    }
Packit 3adb1e
    gss_info = authn_info->baton;
Packit 3adb1e
Packit 3adb1e
    if (!gss_info) {
Packit 3adb1e
        apr_status_t status;
Packit 3adb1e
Packit 3adb1e
        gss_info = apr_pcalloc(conn->pool, sizeof(*gss_info));
Packit 3adb1e
        gss_info->pool = conn->pool;
Packit 3adb1e
        gss_info->state = gss_api_auth_not_started;
Packit 3adb1e
        gss_info->pstate = pstate_init;
Packit 3adb1e
        status = serf__spnego_create_sec_context(&gss_info->gss_ctx, scheme,
Packit 3adb1e
                                                 gss_info->pool, pool);
Packit 3adb1e
        if (status) {
Packit 3adb1e
            return status;
Packit 3adb1e
        }
Packit 3adb1e
        authn_info->baton = gss_info;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    /* Make serf send the initial requests one by one */
Packit 3adb1e
    serf_connection_set_max_outstanding_requests(conn, 1);
Packit 3adb1e
Packit 3adb1e
    serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
Packit 3adb1e
                  "Initialized Kerberos context for this connection.\n");
Packit 3adb1e
Packit 3adb1e
    return APR_SUCCESS;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
/* A 40x response was received, handle the authentication. */
Packit 3adb1e
apr_status_t
Packit 3adb1e
serf__handle_spnego_auth(int code,
Packit 3adb1e
                         serf_request_t *request,
Packit 3adb1e
                         serf_bucket_t *response,
Packit 3adb1e
                         const char *auth_hdr,
Packit 3adb1e
                         const char *auth_attr,
Packit 3adb1e
                         void *baton,
Packit 3adb1e
                         apr_pool_t *pool)
Packit 3adb1e
{
Packit 3adb1e
    serf_connection_t *conn = request->conn;
Packit 3adb1e
    serf_context_t *ctx = conn->ctx;
Packit 3adb1e
    gss_authn_info_t *gss_info = (code == 401) ? conn->authn_info.baton :
Packit 3adb1e
                                                 ctx->proxy_authn_info.baton;
Packit 3adb1e
Packit 3adb1e
    return do_auth(code == 401 ? HOST : PROXY,
Packit 3adb1e
                   code,
Packit 3adb1e
                   gss_info,
Packit 3adb1e
                   request->conn,
Packit 3adb1e
                   request,
Packit 3adb1e
                   auth_hdr,
Packit 3adb1e
                   pool);
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
/* Setup the authn headers on this request message. */
Packit 3adb1e
apr_status_t
Packit 3adb1e
serf__setup_request_spnego_auth(peer_t peer,
Packit 3adb1e
                                int code,
Packit 3adb1e
                                serf_connection_t *conn,
Packit 3adb1e
                                serf_request_t *request,
Packit 3adb1e
                                const char *method,
Packit 3adb1e
                                const char *uri,
Packit 3adb1e
                                serf_bucket_t *hdrs_bkt)
Packit 3adb1e
{
Packit 3adb1e
    serf_context_t *ctx = conn->ctx;
Packit 3adb1e
    gss_authn_info_t *gss_info = (peer == HOST) ? conn->authn_info.baton :
Packit 3adb1e
                                                  ctx->proxy_authn_info.baton;
Packit 3adb1e
Packit 3adb1e
    /* If we have an ongoing authentication handshake, the handler of the
Packit 3adb1e
       previous response will have created the authn headers for this request
Packit 3adb1e
       already. */
Packit 3adb1e
    if (gss_info && gss_info->header && gss_info->value) {
Packit 3adb1e
        serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
Packit 3adb1e
                      "Set Negotiate authn header on retried request.\n");
Packit 3adb1e
Packit 3adb1e
        serf_bucket_headers_setn(hdrs_bkt, gss_info->header,
Packit 3adb1e
                                 gss_info->value);
Packit 3adb1e
Packit 3adb1e
        /* Remember that we're using this request for authentication
Packit 3adb1e
           handshake. */
Packit 3adb1e
        request->auth_baton = (void*) TRUE;
Packit 3adb1e
Packit 3adb1e
        /* We should send each token only once. */
Packit 3adb1e
        gss_info->header = NULL;
Packit 3adb1e
        gss_info->value = NULL;
Packit 3adb1e
Packit 3adb1e
        return APR_SUCCESS;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    switch (gss_info->pstate) {
Packit 3adb1e
        case pstate_init:
Packit 3adb1e
            /* We shouldn't normally arrive here, do nothing. */
Packit 3adb1e
            break;
Packit 3adb1e
        case pstate_undecided: /* fall through */
Packit 3adb1e
            serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
Packit 3adb1e
                          "Assume for now that the server supports persistent "
Packit 3adb1e
                          "SPNEGO authentication.\n");
Packit 3adb1e
            /* Nothing to do here. */
Packit 3adb1e
            break;
Packit 3adb1e
        case pstate_stateful:
Packit 3adb1e
            serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
Packit 3adb1e
                          "SPNEGO on this connection is persistent, "
Packit 3adb1e
                          "don't set authn header on next request.\n");
Packit 3adb1e
            /* Nothing to do here. */
Packit 3adb1e
            break;
Packit 3adb1e
        case pstate_stateless:
Packit 3adb1e
            {
Packit 3adb1e
                apr_status_t status;
Packit 3adb1e
Packit 3adb1e
                /* Authentication on this connection is known to be stateless.
Packit 3adb1e
                   Add an initial Negotiate token for the server, to bypass the
Packit 3adb1e
                   40x response we know we'll otherwise receive.
Packit 3adb1e
                  (RFC 4559 section 4.2) */
Packit 3adb1e
                serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
Packit 3adb1e
                              "Add initial Negotiate header to request.\n");
Packit 3adb1e
Packit 3adb1e
                status = do_auth(peer,
Packit 3adb1e
                                 code,
Packit 3adb1e
                                 gss_info,
Packit 3adb1e
                                 conn,
Packit 3adb1e
                                 request,
Packit 3adb1e
                                 0l,    /* no response authn header */
Packit 3adb1e
                                 conn->pool);
Packit 3adb1e
                if (status)
Packit 3adb1e
                    return status;
Packit 3adb1e
Packit 3adb1e
                serf_bucket_headers_setn(hdrs_bkt, gss_info->header,
Packit 3adb1e
                                         gss_info->value);
Packit 3adb1e
Packit 3adb1e
                /* Remember that we're using this request for authentication
Packit 3adb1e
                   handshake. */
Packit 3adb1e
                request->auth_baton = (void*) TRUE;
Packit 3adb1e
Packit 3adb1e
                /* We should send each token only once. */
Packit 3adb1e
                gss_info->header = NULL;
Packit 3adb1e
                gss_info->value = NULL;
Packit 3adb1e
                break;
Packit 3adb1e
            }
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    return APR_SUCCESS;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
/**
Packit 3adb1e
 * Baton passed to the get_auth_header callback function.
Packit 3adb1e
 */
Packit 3adb1e
typedef struct {
Packit 3adb1e
    const char *hdr_name;
Packit 3adb1e
    const char *auth_name;
Packit 3adb1e
    const char *hdr_value;
Packit 3adb1e
    apr_pool_t *pool;
Packit 3adb1e
} get_auth_header_baton_t;
Packit 3adb1e
Packit 3adb1e
static int
Packit 3adb1e
get_auth_header_cb(void *baton,
Packit 3adb1e
                   const char *key,
Packit 3adb1e
                   const char *header)
Packit 3adb1e
{
Packit 3adb1e
    get_auth_header_baton_t *b = baton;
Packit 3adb1e
Packit 3adb1e
    /* We're only interested in xxxx-Authenticate headers. */
Packit 3adb1e
    if (strcasecmp(key, b->hdr_name) != 0)
Packit 3adb1e
        return 0;
Packit 3adb1e
Packit 3adb1e
    /* Check if header value starts with interesting auth name. */
Packit 3adb1e
    if (strncmp(header, b->auth_name, strlen(b->auth_name)) == 0) {
Packit 3adb1e
        /* Save interesting header value and stop iteration. */
Packit 3adb1e
        b->hdr_value = apr_pstrdup(b->pool,  header);
Packit 3adb1e
        return 1;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    return 0;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
static const char *
Packit 3adb1e
get_auth_header(serf_bucket_t *hdrs,
Packit 3adb1e
                const char *hdr_name,
Packit 3adb1e
                const char *auth_name,
Packit 3adb1e
                apr_pool_t *pool)
Packit 3adb1e
{
Packit 3adb1e
    get_auth_header_baton_t b;
Packit 3adb1e
Packit 3adb1e
    b.auth_name = hdr_name;
Packit 3adb1e
    b.hdr_name = auth_name;
Packit 3adb1e
    b.hdr_value = NULL;
Packit 3adb1e
    b.pool = pool;
Packit 3adb1e
Packit 3adb1e
    serf_bucket_headers_do(hdrs, get_auth_header_cb, &b);
Packit 3adb1e
Packit 3adb1e
    return b.hdr_value;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
/* Function is called when 2xx responses are received. Normally we don't
Packit 3adb1e
 * have to do anything, except for the first response after the
Packit 3adb1e
 * authentication handshake. This specific response includes authentication
Packit 3adb1e
 * data which should be validated by the client (mutual authentication).
Packit 3adb1e
 */
Packit 3adb1e
apr_status_t
Packit 3adb1e
serf__validate_response_spnego_auth(const serf__authn_scheme_t *scheme,
Packit 3adb1e
                                    peer_t peer,
Packit 3adb1e
                                    int code,
Packit 3adb1e
                                    serf_connection_t *conn,
Packit 3adb1e
                                    serf_request_t *request,
Packit 3adb1e
                                    serf_bucket_t *response,
Packit 3adb1e
                                    apr_pool_t *pool)
Packit 3adb1e
{
Packit 3adb1e
    serf_context_t *ctx = conn->ctx;
Packit 3adb1e
    gss_authn_info_t *gss_info;
Packit 3adb1e
    const char *auth_hdr_name;
Packit 3adb1e
Packit 3adb1e
    /* TODO: currently this function is only called when a response includes
Packit 3adb1e
       an Authenticate header. This header is optional. If the server does
Packit 3adb1e
       not provide this header on the first 2xx response, we will not promote
Packit 3adb1e
       the connection from undecided to stateful. This won't break anything,
Packit 3adb1e
       but means we stay in non-pipelining mode. */
Packit 3adb1e
    serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
Packit 3adb1e
                  "Validate Negotiate response header.\n");
Packit 3adb1e
Packit 3adb1e
    if (peer == HOST) {
Packit 3adb1e
        gss_info = conn->authn_info.baton;
Packit 3adb1e
        auth_hdr_name = "WWW-Authenticate";
Packit 3adb1e
    } else {
Packit 3adb1e
        gss_info = ctx->proxy_authn_info.baton;
Packit 3adb1e
        auth_hdr_name = "Proxy-Authenticate";
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    if (gss_info->state != gss_api_auth_completed) {
Packit 3adb1e
        serf_bucket_t *hdrs;
Packit 3adb1e
        const char *auth_hdr_val;
Packit 3adb1e
        apr_status_t status;
Packit 3adb1e
Packit 3adb1e
        hdrs = serf_bucket_response_get_headers(response);
Packit 3adb1e
        auth_hdr_val = get_auth_header(hdrs, auth_hdr_name, scheme->name,
Packit 3adb1e
                                       pool);
Packit 3adb1e
Packit 3adb1e
        if (auth_hdr_val) {
Packit 3adb1e
            status = do_auth(peer, code, gss_info, conn, request, auth_hdr_val,
Packit 3adb1e
                             pool);
Packit 3adb1e
            if (status) {
Packit 3adb1e
                return status;
Packit 3adb1e
            }
Packit 3adb1e
        } else {
Packit 3adb1e
            /* No Authenticate headers, nothing to validate: authentication
Packit 3adb1e
               completed.*/
Packit 3adb1e
            gss_info->state = gss_api_auth_completed;
Packit 3adb1e
Packit 3adb1e
            serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
Packit 3adb1e
                          "SPNEGO handshake completed.\n");
Packit 3adb1e
        }
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    if (gss_info->state == gss_api_auth_completed) {
Packit 3adb1e
        switch(gss_info->pstate) {
Packit 3adb1e
            case pstate_init:
Packit 3adb1e
                /* Authentication of the first request is done. */
Packit 3adb1e
                gss_info->pstate = pstate_undecided;
Packit 3adb1e
                break;
Packit 3adb1e
            case pstate_undecided:
Packit 3adb1e
                /* The server didn't request for authentication even though
Packit 3adb1e
                   we didn't add an Authorization header to previous
Packit 3adb1e
                   request. That means it supports persistent authentication. */
Packit 3adb1e
                gss_info->pstate = pstate_stateful;
Packit 3adb1e
                serf_connection_set_max_outstanding_requests(conn, 0);
Packit 3adb1e
                break;
Packit 3adb1e
            default:
Packit 3adb1e
                /* Nothing to do here. */
Packit 3adb1e
                break;
Packit 3adb1e
        }
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    return APR_SUCCESS;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
#endif /* SERF_HAVE_SPNEGO */