auth.c revision 262339
162587Sitojun/* Copyright 2009 Justin Erenkrantz and Greg Stein
278064Sume *
362587Sitojun * Licensed under the Apache License, Version 2.0 (the "License");
452904Sshin * you may not use this file except in compliance with the License.
552904Sshin * You may obtain a copy of the License at
652904Sshin *
753541Sshin *     http://www.apache.org/licenses/LICENSE-2.0
852904Sshin *
952904Sshin * Unless required by applicable law or agreed to in writing, software
1052904Sshin * distributed under the License is distributed on an "AS IS" BASIS,
1152904Sshin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1252904Sshin * See the License for the specific language governing permissions and
1352904Sshin * limitations under the License.
1452904Sshin */
1552904Sshin
1652904Sshin#include "serf.h"
1752904Sshin#include "serf_private.h"
1852904Sshin#include "auth.h"
1953541Sshin
2052904Sshin#include <apr.h>
2152904Sshin#include <apr_base64.h>
2252904Sshin#include <apr_strings.h>
2352904Sshin#include <apr_lib.h>
2452904Sshin
2552904Sshinstatic apr_status_t
2652904Sshindefault_auth_response_handler(const serf__authn_scheme_t *scheme,
2752904Sshin                              peer_t peer,
2852904Sshin                              int code,
2952904Sshin                              serf_connection_t *conn,
3052904Sshin                              serf_request_t *request,
3152904Sshin                              serf_bucket_t *response,
3252904Sshin                              apr_pool_t *pool)
3352904Sshin{
3452904Sshin    return APR_SUCCESS;
3552904Sshin}
3652904Sshin
3752904Sshin/* These authentication schemes are in order of decreasing security, the topmost
3852904Sshin   scheme will be used first when the server supports it.
3952904Sshin
4052904Sshin   Each set of handlers should support both server (401) and proxy (407)
4152904Sshin   authentication.
4252904Sshin
4352904Sshin   Use lower case for the scheme names to enable case insensitive matching.
4452904Sshin */
4552904Sshinstatic const serf__authn_scheme_t serf_authn_schemes[] = {
4652904Sshin#ifdef SERF_HAVE_SPNEGO
4752904Sshin    {
4852904Sshin        "Negotiate",
4952904Sshin        "negotiate",
5052904Sshin        SERF_AUTHN_NEGOTIATE,
5152904Sshin        serf__init_spnego,
5252904Sshin        serf__init_spnego_connection,
5352904Sshin        serf__handle_spnego_auth,
5452904Sshin        serf__setup_request_spnego_auth,
5552904Sshin        serf__validate_response_spnego_auth,
5652904Sshin    },
5752904Sshin#ifdef WIN32
5852904Sshin    {
5952904Sshin        "NTLM",
6052904Sshin        "ntlm",
6152904Sshin        SERF_AUTHN_NTLM,
6252904Sshin        serf__init_spnego,
6352904Sshin        serf__init_spnego_connection,
6452904Sshin        serf__handle_spnego_auth,
6552904Sshin        serf__setup_request_spnego_auth,
6652904Sshin        serf__validate_response_spnego_auth,
6752904Sshin    },
6862587Sitojun#endif /* #ifdef WIN32 */
6978064Sume#endif /* SERF_HAVE_SPNEGO */
7057120Sshin    {
7157120Sshin        "Digest",
7262587Sitojun        "digest",
7362587Sitojun        SERF_AUTHN_DIGEST,
7462587Sitojun        serf__init_digest,
7552904Sshin        serf__init_digest_connection,
7652904Sshin        serf__handle_digest_auth,
7778064Sume        serf__setup_request_digest_auth,
7878064Sume        serf__validate_response_digest_auth,
7952904Sshin    },
8062587Sitojun    {
8178064Sume        "Basic",
8252904Sshin        "basic",
8352904Sshin        SERF_AUTHN_BASIC,
8452904Sshin        serf__init_basic,
8552904Sshin        serf__init_basic_connection,
8652904Sshin        serf__handle_basic_auth,
8752904Sshin        serf__setup_request_basic_auth,
8852904Sshin        default_auth_response_handler,
8952904Sshin    },
9052904Sshin    /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */
9152904Sshin
9262587Sitojun    /* sentinel */
9352904Sshin    { 0 }
9452904Sshin};
9552904Sshin
9652904Sshin
9752904Sshin/* Reads and discards all bytes in the response body. */
9852904Sshinstatic apr_status_t discard_body(serf_bucket_t *response)
9952904Sshin{
10052904Sshin    apr_status_t status;
10152904Sshin    const char *data;
10252904Sshin    apr_size_t len;
10352904Sshin
10452904Sshin    while (1) {
10552904Sshin        status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len);
10652904Sshin
10752904Sshin        if (status) {
10852904Sshin            return status;
10952904Sshin        }
11097181Smike
11152904Sshin        /* feed me */
11252904Sshin    }
11352904Sshin}
11452904Sshin
11552904Sshin/**
11697181Smike * handle_auth_header is called for each header in the response. It filters
11752904Sshin * out the Authenticate headers (WWW or Proxy depending on what's needed) and
11852904Sshin * tries to find a matching scheme handler.
11952904Sshin *
12052904Sshin * Returns a non-0 value of a matching handler was found.
12152904Sshin */
12252904Sshinstatic int handle_auth_headers(int code,
12397181Smike                               void *baton,
12497181Smike                               apr_hash_t *hdrs,
12597181Smike                               serf_request_t *request,
12652904Sshin                               serf_bucket_t *response,
12752904Sshin                               apr_pool_t *pool)
12852904Sshin{
12962587Sitojun    const serf__authn_scheme_t *scheme;
13095023Ssuz    serf_connection_t *conn = request->conn;
13162587Sitojun    serf_context_t *ctx = conn->ctx;
13262587Sitojun    apr_status_t status;
13362587Sitojun
13452904Sshin    status = SERF_ERROR_AUTHN_NOT_SUPPORTED;
13552904Sshin
13662587Sitojun    /* Find the matching authentication handler.
13752904Sshin       Note that we don't reuse the auth scheme stored in the context,
13852904Sshin       as that may have changed. (ex. fallback from ntlm to basic.) */
13997181Smike    for (scheme = serf_authn_schemes; scheme->name != 0; ++scheme) {
14097181Smike        const char *auth_hdr;
14197181Smike        serf__auth_handler_func_t handler;
14297181Smike        serf__authn_info_t *authn_info;
14352904Sshin
14452904Sshin        if (! (ctx->authn_types & scheme->type))
14597181Smike            continue;
14662587Sitojun
14752904Sshin        serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
14897181Smike                      "Client supports: %s\n", scheme->name);
14952904Sshin
15097181Smike        auth_hdr = apr_hash_get(hdrs, scheme->key, APR_HASH_KEY_STRING);
15197181Smike
15297181Smike        if (!auth_hdr)
15397181Smike            continue;
15462587Sitojun
15597181Smike        if (code == 401) {
15652904Sshin            authn_info = serf__get_authn_info_for_server(conn);
15752904Sshin        } else {
15852904Sshin            authn_info = &ctx->proxy_authn_info;
15952904Sshin        }
16052904Sshin
16195023Ssuz        if (authn_info->failed_authn_types & scheme->type) {
16262587Sitojun            /* Skip this authn type since we already tried it before. */
16362587Sitojun            continue;
16452904Sshin        }
16562587Sitojun
16652904Sshin        /* Found a matching scheme */
16762587Sitojun        status = APR_SUCCESS;
16852904Sshin
16962587Sitojun        handler = scheme->handle_func;
17052904Sshin
17152904Sshin        serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
17252904Sshin                      "... matched: %s\n", scheme->name);
17352904Sshin
17478064Sume        /* If this is the first time we use this scheme on this context and/or
17578064Sume           this connection, make sure to initialize the authentication handler
17652904Sshin           first. */
17752904Sshin        if (authn_info->scheme != scheme) {
17852904Sshin            status = scheme->init_ctx_func(code, ctx, ctx->pool);
17952904Sshin            if (!status) {
18052904Sshin                status = scheme->init_conn_func(scheme, code, conn,
18152904Sshin                                                conn->pool);
18252904Sshin                if (!status)
18352904Sshin                    authn_info->scheme = scheme;
18452904Sshin                else
18552904Sshin                    authn_info->scheme = NULL;
18695023Ssuz            }
18797181Smike        }
18862587Sitojun
18962587Sitojun        if (!status) {
19062587Sitojun            const char *auth_attr = strchr(auth_hdr, ' ');
19162587Sitojun            if (auth_attr) {
19262587Sitojun                auth_attr++;
19362587Sitojun            }
19462587Sitojun
19562587Sitojun            status = handler(code, request, response,
19697181Smike                             auth_hdr, auth_attr, baton, ctx->pool);
19762587Sitojun        }
19862587Sitojun
19962587Sitojun        if (status == APR_SUCCESS)
20062587Sitojun            break;
20162587Sitojun
20262587Sitojun        /* No success authenticating with this scheme, try the next.
20362587Sitojun           If no more authn schemes are found the status of this scheme will be
20462587Sitojun           returned.
20552904Sshin        */
20652904Sshin        serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
20752904Sshin                      "%s authentication failed.\n", scheme->name);
20852904Sshin
20952904Sshin        /* Clear per-request auth_baton when switching to next auth scheme. */
21052904Sshin        request->auth_baton = NULL;
21197181Smike
21262587Sitojun        /* Remember failed auth types to skip in future. */
21352904Sshin        authn_info->failed_authn_types |= scheme->type;
21452904Sshin    }
21562587Sitojun
21652904Sshin    return status;
21752904Sshin}
21862587Sitojun
21952904Sshin/**
22052904Sshin * Baton passed to the store_header_in_dict callback function
22162587Sitojun */
22252904Sshintypedef struct {
22352904Sshin    const char *header;
22462587Sitojun    apr_pool_t *pool;
22552904Sshin    apr_hash_t *hdrs;
22652904Sshin} auth_baton_t;
22797181Smike
22852904Sshinstatic int store_header_in_dict(void *baton,
22952904Sshin                                const char *key,
23052904Sshin                                const char *header)
23197181Smike{
23252904Sshin    auth_baton_t *ab = baton;
23352904Sshin    const char *auth_attr;
23452904Sshin    char *auth_name, *c;
23597181Smike
23652904Sshin    /* We're only interested in xxxx-Authenticate headers. */
23752904Sshin    if (strcasecmp(key, ab->header) != 0)
23852904Sshin        return 0;
23953877Sitojun
24053877Sitojun    /* Extract the authentication scheme name.  */
24153877Sitojun    auth_attr = strchr(header, ' ');
24252904Sshin    if (auth_attr) {
24353877Sitojun        auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header);
24462587Sitojun    }
24562587Sitojun    else
24653877Sitojun        auth_name = apr_pstrmemdup(ab->pool, header, strlen(header));
24797181Smike
24862587Sitojun    /* Convert scheme name to lower case to enable case insensitive matching. */
24962587Sitojun    for (c = auth_name; *c != '\0'; c++)
25053877Sitojun        *c = (char)apr_tolower(*c);
25197181Smike
25252904Sshin    apr_hash_set(ab->hdrs, auth_name, APR_HASH_KEY_STRING,
25378064Sume                 apr_pstrdup(ab->pool, header));
25478064Sume
25578064Sume    return 0;
25678064Sume}
25778064Sume
25878064Sume/* Dispatch authentication handling. This function matches the possible
25978064Sume   authentication mechanisms with those available. Server and proxy
26078064Sume   authentication are evaluated separately. */
26152904Sshinstatic apr_status_t dispatch_auth(int code,
26252904Sshin                                  serf_request_t *request,
26352904Sshin                                  serf_bucket_t *response,
26462587Sitojun                                  void *baton,
26578064Sume                                  apr_pool_t *pool)
26678064Sume{
26778064Sume    serf_bucket_t *hdrs;
26878064Sume
26952904Sshin    if (code == 401 || code == 407) {
27052904Sshin        auth_baton_t ab = { 0 };
27152904Sshin        const char *auth_hdr;
27252904Sshin
27362587Sitojun        ab.hdrs = apr_hash_make(pool);
27478064Sume        ab.pool = pool;
27578064Sume
27678064Sume        /* Before iterating over all authn headers, check if there are any. */
27778064Sume        if (code == 401)
27852904Sshin            ab.header = "WWW-Authenticate";
27952904Sshin        else
28052904Sshin            ab.header = "Proxy-Authenticate";
28152904Sshin
28262587Sitojun        hdrs = serf_bucket_response_get_headers(response);
28378064Sume        auth_hdr = serf_bucket_headers_get(hdrs, ab.header);
28478064Sume
28578064Sume        if (!auth_hdr) {
28678064Sume            return SERF_ERROR_AUTHN_FAILED;
28778064Sume        }
28852904Sshin        serf__log_skt(AUTH_VERBOSE, __FILE__, request->conn->skt,
28952904Sshin                      "%s authz required. Response header(s): %s\n",
29052904Sshin                      code == 401 ? "Server" : "Proxy", auth_hdr);
29152904Sshin
29262587Sitojun
29378064Sume        /* Store all WWW- or Proxy-Authenticate headers in a dictionary.
29478064Sume
29578064Sume           Note: it is possible to have multiple Authentication: headers. We do
29652904Sshin           not want to combine them (per normal header combination rules) as that
29752904Sshin           would make it hard to parse. Instead, we want to individually parse
29852904Sshin           and handle each header in the response, looking for one that we can
29952904Sshin           work with.
30052904Sshin        */
30195023Ssuz        serf_bucket_headers_do(hdrs,
30262587Sitojun                               store_header_in_dict,
30362587Sitojun                               &ab);
30462587Sitojun
30562587Sitojun        /* Iterate over all authentication schemes, in order of decreasing
30662587Sitojun           security. Try to find a authentication schema the server support. */
30752904Sshin        return handle_auth_headers(code, baton, ab.hdrs,
30862587Sitojun                                   request, response, pool);
30962587Sitojun    }
31062587Sitojun
31162587Sitojun    return APR_SUCCESS;
31262587Sitojun}
31352904Sshin
31452904Sshin/* Read the headers of the response and try the available
31552904Sshin   handlers if authentication or validation is needed. */
31652904Sshinapr_status_t serf__handle_auth_response(int *consumed_response,
31752904Sshin                                        serf_request_t *request,
31852904Sshin                                        serf_bucket_t *response,
31962587Sitojun                                        void *baton,
32052904Sshin                                        apr_pool_t *pool)
32162587Sitojun{
32252904Sshin    apr_status_t status;
32352904Sshin    serf_status_line sl;
32452904Sshin
32552904Sshin    *consumed_response = 0;
32652904Sshin
32752904Sshin    /* TODO: the response bucket was created by the application, not at all
32852904Sshin       guaranteed that this is of type response_bucket!! */
32995023Ssuz    status = serf_bucket_response_status(response, &sl);
33062587Sitojun    if (SERF_BUCKET_READ_ERROR(status)) {
33152904Sshin        return status;
33262587Sitojun    }
33352904Sshin    if (!sl.version && (APR_STATUS_IS_EOF(status) ||
33452904Sshin                        APR_STATUS_IS_EAGAIN(status))) {
33552904Sshin        return status;
33652904Sshin    }
33752904Sshin
33895023Ssuz    status = serf_bucket_response_wait_for_headers(response);
33962587Sitojun    if (status) {
34052904Sshin        if (!APR_STATUS_IS_EOF(status)) {
34152904Sshin            return status;
34262587Sitojun        }
34352904Sshin
34452904Sshin        /* If status is APR_EOF, there were no headers to read.
34562587Sitojun           This can be ok in some situations, and it definitely
34652904Sshin           means there's no authentication requested now. */
34752904Sshin        return APR_SUCCESS;
34862587Sitojun    }
34952904Sshin
35052904Sshin    if (sl.code == 401 || sl.code == 407) {
35162587Sitojun        /* Authentication requested. */
35252904Sshin
35352904Sshin        /* Don't bother handling the authentication request if the response
35452904Sshin           wasn't received completely yet. Serf will call serf__handle_auth_response
35562587Sitojun           again when more data is received. */
35652904Sshin        status = discard_body(response);
35752904Sshin        *consumed_response = 1;
35862587Sitojun
35952904Sshin        /* Discard all response body before processing authentication. */
36052904Sshin        if (!APR_STATUS_IS_EOF(status)) {
36162587Sitojun            return status;
36252904Sshin        }
36352904Sshin
36462587Sitojun        status = dispatch_auth(sl.code, request, response, baton, pool);
36552904Sshin        if (status != APR_SUCCESS) {
36652904Sshin            return status;
36762587Sitojun        }
36852904Sshin
36952904Sshin        /* Requeue the request with the necessary auth headers. */
37052904Sshin        /* ### Application doesn't know about this request! */
37152904Sshin        if (request->ssltunnel) {
37295023Ssuz            serf__ssltunnel_request_create(request->conn,
37378064Sume                                           request->setup,
37452904Sshin                                           request->setup_baton);
37552904Sshin        } else {
37662587Sitojun            serf_connection_priority_request_create(request->conn,
37752904Sshin                                                    request->setup,
37852904Sshin                                                    request->setup_baton);
37952904Sshin        }
38078064Sume
38178064Sume        return APR_EOF;
38278064Sume    } else {
38378064Sume        serf__validate_response_func_t validate_resp;
38478064Sume        serf_connection_t *conn = request->conn;
38578064Sume        serf_context_t *ctx = conn->ctx;
38678064Sume        serf__authn_info_t *authn_info;
38778064Sume        apr_status_t resp_status = APR_SUCCESS;
38852904Sshin
38952904Sshin
39052904Sshin        /* Validate the response server authn headers. */
39197181Smike        authn_info = serf__get_authn_info_for_server(conn);
39252904Sshin        if (authn_info->scheme) {
39352904Sshin            validate_resp = authn_info->scheme->validate_response_func;
39452904Sshin            resp_status = validate_resp(authn_info->scheme, HOST, sl.code,
39552904Sshin                                        conn, request, response, pool);
39652904Sshin        }
39752904Sshin
39852904Sshin        /* Validate the response proxy authn headers. */
39952904Sshin        authn_info = &ctx->proxy_authn_info;
40052904Sshin        if (!resp_status && authn_info->scheme) {
40152904Sshin            validate_resp = authn_info->scheme->validate_response_func;
40297181Smike            resp_status = validate_resp(authn_info->scheme, PROXY, sl.code,
40397181Smike                                        conn, request, response, pool);
40497181Smike        }
40597181Smike
40697181Smike        if (resp_status) {
40797181Smike            /* If there was an error in the final step of the authentication,
40897181Smike               consider the reponse body as invalid and discard it. */
40997181Smike            status = discard_body(response);
41097181Smike            *consumed_response = 1;
41152904Sshin            if (!APR_STATUS_IS_EOF(status)) {
41262587Sitojun                return status;
41362587Sitojun            }
41462587Sitojun            /* The whole body was discarded, now return our error. */
41562587Sitojun            return resp_status;
41662587Sitojun        }
41762587Sitojun    }
41862587Sitojun
41962587Sitojun    return APR_SUCCESS;
42062587Sitojun}
42162587Sitojun
42278064Sume/**
42378064Sume * base64 encode the authentication data and build an authentication
42462587Sitojun * header in this format:
42562587Sitojun * [SCHEME] [BASE64 of auth DATA]
42662587Sitojun */
42762587Sitojunvoid serf__encode_auth_header(const char **header,
42862587Sitojun                              const char *scheme,
42962587Sitojun                              const char *data, apr_size_t data_len,
43078064Sume                              apr_pool_t *pool)
43162587Sitojun{
43278064Sume    apr_size_t encoded_len, scheme_len;
43378064Sume    char *ptr;
43478064Sume
43552904Sshin    encoded_len = apr_base64_encode_len(data_len);
43695023Ssuz    scheme_len = strlen(scheme);
43762587Sitojun
43878064Sume    ptr = apr_palloc(pool, encoded_len + scheme_len + 1);
43962587Sitojun    *header = ptr;
44052904Sshin
44195023Ssuz    apr_cpystrn(ptr, scheme, scheme_len + 1);
44262587Sitojun    ptr += scheme_len;
44362587Sitojun    *ptr++ = ' ';
44462587Sitojun
44562587Sitojun    apr_base64_encode(ptr, data, data_len);
44662587Sitojun}
44778064Sume
44878064Sumeconst char *serf__construct_realm(peer_t peer,
44965124Sitojun                                  serf_connection_t *conn,
45052904Sshin                                  const char *realm_name,
45162587Sitojun                                  apr_pool_t *pool)
45262587Sitojun{
45362587Sitojun    if (peer == HOST) {
45452904Sshin        return apr_psprintf(pool, "<%s://%s:%d> %s",
45552904Sshin                            conn->host_info.scheme,
45652904Sshin                            conn->host_info.hostname,
45752904Sshin                            conn->host_info.port,
45895023Ssuz                            realm_name);
45995023Ssuz    } else {
46052904Sshin        serf_context_t *ctx = conn->ctx;
46152904Sshin
46252904Sshin        return apr_psprintf(pool, "<http://%s:%d> %s",
46352904Sshin                            ctx->proxy_address->hostname,
46452904Sshin                            ctx->proxy_address->port,
46562587Sitojun                            realm_name);
46662587Sitojun    }
46752904Sshin}
46852904Sshin
46952904Sshinserf__authn_info_t *serf__get_authn_info_for_server(serf_connection_t *conn)
47052904Sshin{
47152904Sshin    serf_context_t *ctx = conn->ctx;
47252904Sshin    serf__authn_info_t *authn_info;
47362587Sitojun
47462587Sitojun    authn_info = apr_hash_get(ctx->server_authn_info, conn->host_url,
47552904Sshin                              APR_HASH_KEY_STRING);
47652904Sshin
47752904Sshin    if (!authn_info) {
47852904Sshin        authn_info = apr_pcalloc(ctx->pool, sizeof(serf__authn_info_t));
47952904Sshin        apr_hash_set(ctx->server_authn_info,
48052904Sshin                     apr_pstrdup(ctx->pool, conn->host_url),
48152904Sshin                     APR_HASH_KEY_STRING, authn_info);
48252904Sshin    }
48352904Sshin
48452904Sshin    return authn_info;
48552904Sshin}
48652904Sshin