1362181Sdim/* ==================================================================== 2362181Sdim * Licensed to the Apache Software Foundation (ASF) under one 3362181Sdim * or more contributor license agreements. See the NOTICE file 4362181Sdim * distributed with this work for additional information 5362181Sdim * regarding copyright ownership. The ASF licenses this file 6362181Sdim * to you under the Apache License, Version 2.0 (the 7362181Sdim * "License"); you may not use this file except in compliance 8362181Sdim * with the License. You may obtain a copy of the License at 9251877Speter * 10362181Sdim * http://www.apache.org/licenses/LICENSE-2.0 11251877Speter * 12362181Sdim * Unless required by applicable law or agreed to in writing, 13362181Sdim * software distributed under the License is distributed on an 14362181Sdim * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15362181Sdim * KIND, either express or implied. See the License for the 16362181Sdim * specific language governing permissions and limitations 17362181Sdim * under the License. 18362181Sdim * ==================================================================== 19251877Speter */ 20251877Speter 21251877Speter#include "serf.h" 22251877Speter#include "serf_private.h" 23251877Speter#include "auth.h" 24251877Speter 25251877Speter#include <apr.h> 26251877Speter#include <apr_base64.h> 27251877Speter#include <apr_strings.h> 28253895Speter#include <apr_lib.h> 29251877Speter 30251877Speterstatic apr_status_t 31262324Speterdefault_auth_response_handler(const serf__authn_scheme_t *scheme, 32262324Speter peer_t peer, 33251877Speter int code, 34251877Speter serf_connection_t *conn, 35251877Speter serf_request_t *request, 36251877Speter serf_bucket_t *response, 37251877Speter apr_pool_t *pool) 38251877Speter{ 39251877Speter return APR_SUCCESS; 40251877Speter} 41251877Speter 42253895Speter/* These authentication schemes are in order of decreasing security, the topmost 43253895Speter scheme will be used first when the server supports it. 44253895Speter 45253895Speter Each set of handlers should support both server (401) and proxy (407) 46253895Speter authentication. 47253895Speter 48253895Speter Use lower case for the scheme names to enable case insensitive matching. 49253895Speter */ 50251877Speterstatic const serf__authn_scheme_t serf_authn_schemes[] = { 51253895Speter#ifdef SERF_HAVE_SPNEGO 52251877Speter { 53253895Speter "Negotiate", 54253895Speter "negotiate", 55253895Speter SERF_AUTHN_NEGOTIATE, 56253895Speter serf__init_spnego, 57253895Speter serf__init_spnego_connection, 58253895Speter serf__handle_spnego_auth, 59253895Speter serf__setup_request_spnego_auth, 60253895Speter serf__validate_response_spnego_auth, 61251877Speter }, 62253895Speter#ifdef WIN32 63251877Speter { 64253895Speter "NTLM", 65253895Speter "ntlm", 66253895Speter SERF_AUTHN_NTLM, 67253895Speter serf__init_spnego, 68253895Speter serf__init_spnego_connection, 69253895Speter serf__handle_spnego_auth, 70253895Speter serf__setup_request_spnego_auth, 71253895Speter serf__validate_response_spnego_auth, 72251877Speter }, 73253895Speter#endif /* #ifdef WIN32 */ 74253895Speter#endif /* SERF_HAVE_SPNEGO */ 75251877Speter { 76251877Speter "Digest", 77253895Speter "digest", 78251877Speter SERF_AUTHN_DIGEST, 79251877Speter serf__init_digest, 80251877Speter serf__init_digest_connection, 81251877Speter serf__handle_digest_auth, 82251877Speter serf__setup_request_digest_auth, 83251877Speter serf__validate_response_digest_auth, 84251877Speter }, 85251877Speter { 86253895Speter "Basic", 87253895Speter "basic", 88253895Speter SERF_AUTHN_BASIC, 89253895Speter serf__init_basic, 90253895Speter serf__init_basic_connection, 91253895Speter serf__handle_basic_auth, 92253895Speter serf__setup_request_basic_auth, 93253895Speter default_auth_response_handler, 94251877Speter }, 95251877Speter /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */ 96251877Speter 97251877Speter /* sentinel */ 98251877Speter { 0 } 99251877Speter}; 100251877Speter 101251877Speter 102251877Speter/* Reads and discards all bytes in the response body. */ 103251877Speterstatic apr_status_t discard_body(serf_bucket_t *response) 104251877Speter{ 105251877Speter apr_status_t status; 106251877Speter const char *data; 107251877Speter apr_size_t len; 108251877Speter 109251877Speter while (1) { 110251877Speter status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len); 111251877Speter 112251877Speter if (status) { 113251877Speter return status; 114251877Speter } 115251877Speter 116251877Speter /* feed me */ 117251877Speter } 118251877Speter} 119251877Speter 120251877Speter/** 121251877Speter * handle_auth_header is called for each header in the response. It filters 122251877Speter * out the Authenticate headers (WWW or Proxy depending on what's needed) and 123251877Speter * tries to find a matching scheme handler. 124251877Speter * 125251877Speter * Returns a non-0 value of a matching handler was found. 126251877Speter */ 127253895Speterstatic int handle_auth_headers(int code, 128253895Speter void *baton, 129253895Speter apr_hash_t *hdrs, 130253895Speter serf_request_t *request, 131253895Speter serf_bucket_t *response, 132253895Speter apr_pool_t *pool) 133251877Speter{ 134253895Speter const serf__authn_scheme_t *scheme; 135253895Speter serf_connection_t *conn = request->conn; 136251877Speter serf_context_t *ctx = conn->ctx; 137253895Speter apr_status_t status; 138251877Speter 139253895Speter status = SERF_ERROR_AUTHN_NOT_SUPPORTED; 140251877Speter 141251877Speter /* Find the matching authentication handler. 142251877Speter Note that we don't reuse the auth scheme stored in the context, 143251877Speter as that may have changed. (ex. fallback from ntlm to basic.) */ 144253895Speter for (scheme = serf_authn_schemes; scheme->name != 0; ++scheme) { 145253895Speter const char *auth_hdr; 146253895Speter serf__auth_handler_func_t handler; 147253895Speter serf__authn_info_t *authn_info; 148253895Speter 149253895Speter if (! (ctx->authn_types & scheme->type)) 150251877Speter continue; 151251877Speter 152251877Speter serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt, 153251877Speter "Client supports: %s\n", scheme->name); 154251877Speter 155253895Speter auth_hdr = apr_hash_get(hdrs, scheme->key, APR_HASH_KEY_STRING); 156251877Speter 157253895Speter if (!auth_hdr) 158253895Speter continue; 159251877Speter 160262324Speter if (code == 401) { 161262324Speter authn_info = serf__get_authn_info_for_server(conn); 162262324Speter } else { 163262324Speter authn_info = &ctx->proxy_authn_info; 164262324Speter } 165262324Speter 166262324Speter if (authn_info->failed_authn_types & scheme->type) { 167262324Speter /* Skip this authn type since we already tried it before. */ 168262324Speter continue; 169262324Speter } 170262324Speter 171253895Speter /* Found a matching scheme */ 172253895Speter status = APR_SUCCESS; 173251877Speter 174253895Speter handler = scheme->handle_func; 175253895Speter 176253895Speter serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt, 177253895Speter "... matched: %s\n", scheme->name); 178253895Speter 179253895Speter /* If this is the first time we use this scheme on this context and/or 180253895Speter this connection, make sure to initialize the authentication handler 181253895Speter first. */ 182253895Speter if (authn_info->scheme != scheme) { 183253895Speter status = scheme->init_ctx_func(code, ctx, ctx->pool); 184251877Speter if (!status) { 185253895Speter status = scheme->init_conn_func(scheme, code, conn, 186253895Speter conn->pool); 187253895Speter if (!status) 188253895Speter authn_info->scheme = scheme; 189253895Speter else 190253895Speter authn_info->scheme = NULL; 191251877Speter } 192253895Speter } 193251877Speter 194253895Speter if (!status) { 195253895Speter const char *auth_attr = strchr(auth_hdr, ' '); 196253895Speter if (auth_attr) { 197253895Speter auth_attr++; 198251877Speter } 199251877Speter 200253895Speter status = handler(code, request, response, 201253895Speter auth_hdr, auth_attr, baton, ctx->pool); 202253895Speter } 203253895Speter 204253895Speter if (status == APR_SUCCESS) 205251877Speter break; 206253895Speter 207253895Speter /* No success authenticating with this scheme, try the next. 208253895Speter If no more authn schemes are found the status of this scheme will be 209253895Speter returned. 210253895Speter */ 211253895Speter serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt, 212253895Speter "%s authentication failed.\n", scheme->name); 213262324Speter 214262324Speter /* Clear per-request auth_baton when switching to next auth scheme. */ 215262324Speter request->auth_baton = NULL; 216262324Speter 217262324Speter /* Remember failed auth types to skip in future. */ 218262324Speter authn_info->failed_authn_types |= scheme->type; 219251877Speter } 220251877Speter 221253895Speter return status; 222251877Speter} 223251877Speter 224253895Speter/** 225253895Speter * Baton passed to the store_header_in_dict callback function 226253895Speter */ 227253895Spetertypedef struct { 228253895Speter const char *header; 229253895Speter apr_pool_t *pool; 230253895Speter apr_hash_t *hdrs; 231253895Speter} auth_baton_t; 232253895Speter 233253895Speterstatic int store_header_in_dict(void *baton, 234253895Speter const char *key, 235253895Speter const char *header) 236253895Speter{ 237253895Speter auth_baton_t *ab = baton; 238253895Speter const char *auth_attr; 239253895Speter char *auth_name, *c; 240253895Speter 241253895Speter /* We're only interested in xxxx-Authenticate headers. */ 242262324Speter if (strcasecmp(key, ab->header) != 0) 243253895Speter return 0; 244253895Speter 245253895Speter /* Extract the authentication scheme name. */ 246253895Speter auth_attr = strchr(header, ' '); 247253895Speter if (auth_attr) { 248253895Speter auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header); 249253895Speter } 250253895Speter else 251253895Speter auth_name = apr_pstrmemdup(ab->pool, header, strlen(header)); 252253895Speter 253253895Speter /* Convert scheme name to lower case to enable case insensitive matching. */ 254253895Speter for (c = auth_name; *c != '\0'; c++) 255253895Speter *c = (char)apr_tolower(*c); 256253895Speter 257253895Speter apr_hash_set(ab->hdrs, auth_name, APR_HASH_KEY_STRING, 258253895Speter apr_pstrdup(ab->pool, header)); 259253895Speter 260253895Speter return 0; 261253895Speter} 262253895Speter 263251877Speter/* Dispatch authentication handling. This function matches the possible 264251877Speter authentication mechanisms with those available. Server and proxy 265251877Speter authentication are evaluated separately. */ 266251877Speterstatic apr_status_t dispatch_auth(int code, 267251877Speter serf_request_t *request, 268251877Speter serf_bucket_t *response, 269251877Speter void *baton, 270251877Speter apr_pool_t *pool) 271251877Speter{ 272251877Speter serf_bucket_t *hdrs; 273251877Speter 274251877Speter if (code == 401 || code == 407) { 275251877Speter auth_baton_t ab = { 0 }; 276251877Speter const char *auth_hdr; 277251877Speter 278253895Speter ab.hdrs = apr_hash_make(pool); 279251877Speter ab.pool = pool; 280251877Speter 281251877Speter /* Before iterating over all authn headers, check if there are any. */ 282251877Speter if (code == 401) 283251877Speter ab.header = "WWW-Authenticate"; 284251877Speter else 285251877Speter ab.header = "Proxy-Authenticate"; 286251877Speter 287251877Speter hdrs = serf_bucket_response_get_headers(response); 288251877Speter auth_hdr = serf_bucket_headers_get(hdrs, ab.header); 289251877Speter 290251877Speter if (!auth_hdr) { 291251877Speter return SERF_ERROR_AUTHN_FAILED; 292251877Speter } 293251877Speter serf__log_skt(AUTH_VERBOSE, __FILE__, request->conn->skt, 294251877Speter "%s authz required. Response header(s): %s\n", 295251877Speter code == 401 ? "Server" : "Proxy", auth_hdr); 296251877Speter 297251877Speter 298253895Speter /* Store all WWW- or Proxy-Authenticate headers in a dictionary. 299253895Speter 300251877Speter Note: it is possible to have multiple Authentication: headers. We do 301251877Speter not want to combine them (per normal header combination rules) as that 302251877Speter would make it hard to parse. Instead, we want to individually parse 303251877Speter and handle each header in the response, looking for one that we can 304251877Speter work with. 305251877Speter */ 306251877Speter serf_bucket_headers_do(hdrs, 307253895Speter store_header_in_dict, 308251877Speter &ab); 309251877Speter 310253895Speter /* Iterate over all authentication schemes, in order of decreasing 311253895Speter security. Try to find a authentication schema the server support. */ 312253895Speter return handle_auth_headers(code, baton, ab.hdrs, 313253895Speter request, response, pool); 314251877Speter } 315251877Speter 316251877Speter return APR_SUCCESS; 317251877Speter} 318251877Speter 319251877Speter/* Read the headers of the response and try the available 320251877Speter handlers if authentication or validation is needed. */ 321251877Speterapr_status_t serf__handle_auth_response(int *consumed_response, 322251877Speter serf_request_t *request, 323251877Speter serf_bucket_t *response, 324251877Speter void *baton, 325251877Speter apr_pool_t *pool) 326251877Speter{ 327251877Speter apr_status_t status; 328251877Speter serf_status_line sl; 329251877Speter 330251877Speter *consumed_response = 0; 331251877Speter 332251877Speter /* TODO: the response bucket was created by the application, not at all 333251877Speter guaranteed that this is of type response_bucket!! */ 334251877Speter status = serf_bucket_response_status(response, &sl); 335251877Speter if (SERF_BUCKET_READ_ERROR(status)) { 336251877Speter return status; 337251877Speter } 338251877Speter if (!sl.version && (APR_STATUS_IS_EOF(status) || 339251877Speter APR_STATUS_IS_EAGAIN(status))) { 340251877Speter return status; 341251877Speter } 342251877Speter 343251877Speter status = serf_bucket_response_wait_for_headers(response); 344251877Speter if (status) { 345251877Speter if (!APR_STATUS_IS_EOF(status)) { 346251877Speter return status; 347251877Speter } 348251877Speter 349251877Speter /* If status is APR_EOF, there were no headers to read. 350251877Speter This can be ok in some situations, and it definitely 351251877Speter means there's no authentication requested now. */ 352251877Speter return APR_SUCCESS; 353251877Speter } 354251877Speter 355251877Speter if (sl.code == 401 || sl.code == 407) { 356251877Speter /* Authentication requested. */ 357251877Speter 358251877Speter /* Don't bother handling the authentication request if the response 359251877Speter wasn't received completely yet. Serf will call serf__handle_auth_response 360251877Speter again when more data is received. */ 361251877Speter status = discard_body(response); 362251877Speter *consumed_response = 1; 363251877Speter 364251877Speter /* Discard all response body before processing authentication. */ 365251877Speter if (!APR_STATUS_IS_EOF(status)) { 366251877Speter return status; 367251877Speter } 368251877Speter 369251877Speter status = dispatch_auth(sl.code, request, response, baton, pool); 370251877Speter if (status != APR_SUCCESS) { 371251877Speter return status; 372251877Speter } 373251877Speter 374251877Speter /* Requeue the request with the necessary auth headers. */ 375251877Speter /* ### Application doesn't know about this request! */ 376253895Speter if (request->ssltunnel) { 377253895Speter serf__ssltunnel_request_create(request->conn, 378253895Speter request->setup, 379253895Speter request->setup_baton); 380253895Speter } else { 381253895Speter serf_connection_priority_request_create(request->conn, 382253895Speter request->setup, 383253895Speter request->setup_baton); 384253895Speter } 385251877Speter 386251877Speter return APR_EOF; 387251877Speter } else { 388251877Speter serf__validate_response_func_t validate_resp; 389251877Speter serf_connection_t *conn = request->conn; 390251877Speter serf_context_t *ctx = conn->ctx; 391253895Speter serf__authn_info_t *authn_info; 392251877Speter apr_status_t resp_status = APR_SUCCESS; 393253895Speter 394253895Speter 395253895Speter /* Validate the response server authn headers. */ 396253895Speter authn_info = serf__get_authn_info_for_server(conn); 397253895Speter if (authn_info->scheme) { 398253895Speter validate_resp = authn_info->scheme->validate_response_func; 399262324Speter resp_status = validate_resp(authn_info->scheme, HOST, sl.code, 400262324Speter conn, request, response, pool); 401251877Speter } 402253895Speter 403253895Speter /* Validate the response proxy authn headers. */ 404253895Speter authn_info = &ctx->proxy_authn_info; 405253895Speter if (!resp_status && authn_info->scheme) { 406253895Speter validate_resp = authn_info->scheme->validate_response_func; 407262324Speter resp_status = validate_resp(authn_info->scheme, PROXY, sl.code, 408262324Speter conn, request, response, pool); 409251877Speter } 410253895Speter 411251877Speter if (resp_status) { 412251877Speter /* If there was an error in the final step of the authentication, 413251877Speter consider the reponse body as invalid and discard it. */ 414251877Speter status = discard_body(response); 415251877Speter *consumed_response = 1; 416251877Speter if (!APR_STATUS_IS_EOF(status)) { 417251877Speter return status; 418251877Speter } 419251877Speter /* The whole body was discarded, now return our error. */ 420251877Speter return resp_status; 421251877Speter } 422251877Speter } 423251877Speter 424251877Speter return APR_SUCCESS; 425251877Speter} 426251877Speter 427251877Speter/** 428251877Speter * base64 encode the authentication data and build an authentication 429251877Speter * header in this format: 430251877Speter * [SCHEME] [BASE64 of auth DATA] 431251877Speter */ 432251877Spetervoid serf__encode_auth_header(const char **header, 433251877Speter const char *scheme, 434251877Speter const char *data, apr_size_t data_len, 435251877Speter apr_pool_t *pool) 436251877Speter{ 437251877Speter apr_size_t encoded_len, scheme_len; 438251877Speter char *ptr; 439251877Speter 440251877Speter encoded_len = apr_base64_encode_len(data_len); 441251877Speter scheme_len = strlen(scheme); 442251877Speter 443251877Speter ptr = apr_palloc(pool, encoded_len + scheme_len + 1); 444251877Speter *header = ptr; 445251877Speter 446251877Speter apr_cpystrn(ptr, scheme, scheme_len + 1); 447251877Speter ptr += scheme_len; 448251877Speter *ptr++ = ' '; 449251877Speter 450251877Speter apr_base64_encode(ptr, data, data_len); 451251877Speter} 452253895Speter 453253895Speterconst char *serf__construct_realm(peer_t peer, 454253895Speter serf_connection_t *conn, 455253895Speter const char *realm_name, 456253895Speter apr_pool_t *pool) 457253895Speter{ 458253895Speter if (peer == HOST) { 459253895Speter return apr_psprintf(pool, "<%s://%s:%d> %s", 460253895Speter conn->host_info.scheme, 461253895Speter conn->host_info.hostname, 462253895Speter conn->host_info.port, 463253895Speter realm_name); 464253895Speter } else { 465253895Speter serf_context_t *ctx = conn->ctx; 466253895Speter 467253895Speter return apr_psprintf(pool, "<http://%s:%d> %s", 468253895Speter ctx->proxy_address->hostname, 469253895Speter ctx->proxy_address->port, 470253895Speter realm_name); 471253895Speter } 472253895Speter} 473253895Speter 474253895Speterserf__authn_info_t *serf__get_authn_info_for_server(serf_connection_t *conn) 475253895Speter{ 476253895Speter serf_context_t *ctx = conn->ctx; 477253895Speter serf__authn_info_t *authn_info; 478253895Speter 479253895Speter authn_info = apr_hash_get(ctx->server_authn_info, conn->host_url, 480253895Speter APR_HASH_KEY_STRING); 481253895Speter 482253895Speter if (!authn_info) { 483253895Speter authn_info = apr_pcalloc(ctx->pool, sizeof(serf__authn_info_t)); 484253895Speter apr_hash_set(ctx->server_authn_info, 485253895Speter apr_pstrdup(ctx->pool, conn->host_url), 486253895Speter APR_HASH_KEY_STRING, authn_info); 487253895Speter } 488253895Speter 489253895Speter return authn_info; 490253895Speter} 491