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