1/* Copyright 2009 Justin Erenkrantz and Greg Stein 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16#include "serf.h" 17#include "serf_private.h" 18#include "auth.h" 19 20#include <apr.h> 21#include <apr_base64.h> 22#include <apr_strings.h> 23#include <apr_lib.h> 24 25static apr_status_t 26default_auth_response_handler(const serf__authn_scheme_t *scheme, 27 peer_t peer, 28 int code, 29 serf_connection_t *conn, 30 serf_request_t *request, 31 serf_bucket_t *response, 32 apr_pool_t *pool) 33{ 34 return APR_SUCCESS; 35} 36 37/* These authentication schemes are in order of decreasing security, the topmost 38 scheme will be used first when the server supports it. 39 40 Each set of handlers should support both server (401) and proxy (407) 41 authentication. 42 43 Use lower case for the scheme names to enable case insensitive matching. 44 */ 45static const serf__authn_scheme_t serf_authn_schemes[] = { 46#ifdef SERF_HAVE_SPNEGO 47 { 48 "Negotiate", 49 "negotiate", 50 SERF_AUTHN_NEGOTIATE, 51 serf__init_spnego, 52 serf__init_spnego_connection, 53 serf__handle_spnego_auth, 54 serf__setup_request_spnego_auth, 55 serf__validate_response_spnego_auth, 56 }, 57#ifdef WIN32 58 { 59 "NTLM", 60 "ntlm", 61 SERF_AUTHN_NTLM, 62 serf__init_spnego, 63 serf__init_spnego_connection, 64 serf__handle_spnego_auth, 65 serf__setup_request_spnego_auth, 66 serf__validate_response_spnego_auth, 67 }, 68#endif /* #ifdef WIN32 */ 69#endif /* SERF_HAVE_SPNEGO */ 70 { 71 "Digest", 72 "digest", 73 SERF_AUTHN_DIGEST, 74 serf__init_digest, 75 serf__init_digest_connection, 76 serf__handle_digest_auth, 77 serf__setup_request_digest_auth, 78 serf__validate_response_digest_auth, 79 }, 80 { 81 "Basic", 82 "basic", 83 SERF_AUTHN_BASIC, 84 serf__init_basic, 85 serf__init_basic_connection, 86 serf__handle_basic_auth, 87 serf__setup_request_basic_auth, 88 default_auth_response_handler, 89 }, 90 /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */ 91 92 /* sentinel */ 93 { 0 } 94}; 95 96 97/* Reads and discards all bytes in the response body. */ 98static apr_status_t discard_body(serf_bucket_t *response) 99{ 100 apr_status_t status; 101 const char *data; 102 apr_size_t len; 103 104 while (1) { 105 status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len); 106 107 if (status) { 108 return status; 109 } 110 111 /* feed me */ 112 } 113} 114 115/** 116 * handle_auth_header is called for each header in the response. It filters 117 * out the Authenticate headers (WWW or Proxy depending on what's needed) and 118 * tries to find a matching scheme handler. 119 * 120 * Returns a non-0 value of a matching handler was found. 121 */ 122static int handle_auth_headers(int code, 123 void *baton, 124 apr_hash_t *hdrs, 125 serf_request_t *request, 126 serf_bucket_t *response, 127 apr_pool_t *pool) 128{ 129 const serf__authn_scheme_t *scheme; 130 serf_connection_t *conn = request->conn; 131 serf_context_t *ctx = conn->ctx; 132 apr_status_t status; 133 134 status = SERF_ERROR_AUTHN_NOT_SUPPORTED; 135 136 /* Find the matching authentication handler. 137 Note that we don't reuse the auth scheme stored in the context, 138 as that may have changed. (ex. fallback from ntlm to basic.) */ 139 for (scheme = serf_authn_schemes; scheme->name != 0; ++scheme) { 140 const char *auth_hdr; 141 serf__auth_handler_func_t handler; 142 serf__authn_info_t *authn_info; 143 144 if (! (ctx->authn_types & scheme->type)) 145 continue; 146 147 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt, 148 "Client supports: %s\n", scheme->name); 149 150 auth_hdr = apr_hash_get(hdrs, scheme->key, APR_HASH_KEY_STRING); 151 152 if (!auth_hdr) 153 continue; 154 155 if (code == 401) { 156 authn_info = serf__get_authn_info_for_server(conn); 157 } else { 158 authn_info = &ctx->proxy_authn_info; 159 } 160 161 if (authn_info->failed_authn_types & scheme->type) { 162 /* Skip this authn type since we already tried it before. */ 163 continue; 164 } 165 166 /* Found a matching scheme */ 167 status = APR_SUCCESS; 168 169 handler = scheme->handle_func; 170 171 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt, 172 "... matched: %s\n", scheme->name); 173 174 /* If this is the first time we use this scheme on this context and/or 175 this connection, make sure to initialize the authentication handler 176 first. */ 177 if (authn_info->scheme != scheme) { 178 status = scheme->init_ctx_func(code, ctx, ctx->pool); 179 if (!status) { 180 status = scheme->init_conn_func(scheme, code, conn, 181 conn->pool); 182 if (!status) 183 authn_info->scheme = scheme; 184 else 185 authn_info->scheme = NULL; 186 } 187 } 188 189 if (!status) { 190 const char *auth_attr = strchr(auth_hdr, ' '); 191 if (auth_attr) { 192 auth_attr++; 193 } 194 195 status = handler(code, request, response, 196 auth_hdr, auth_attr, baton, ctx->pool); 197 } 198 199 if (status == APR_SUCCESS) 200 break; 201 202 /* No success authenticating with this scheme, try the next. 203 If no more authn schemes are found the status of this scheme will be 204 returned. 205 */ 206 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt, 207 "%s authentication failed.\n", scheme->name); 208 209 /* Clear per-request auth_baton when switching to next auth scheme. */ 210 request->auth_baton = NULL; 211 212 /* Remember failed auth types to skip in future. */ 213 authn_info->failed_authn_types |= scheme->type; 214 } 215 216 return status; 217} 218 219/** 220 * Baton passed to the store_header_in_dict callback function 221 */ 222typedef struct { 223 const char *header; 224 apr_pool_t *pool; 225 apr_hash_t *hdrs; 226} auth_baton_t; 227 228static int store_header_in_dict(void *baton, 229 const char *key, 230 const char *header) 231{ 232 auth_baton_t *ab = baton; 233 const char *auth_attr; 234 char *auth_name, *c; 235 236 /* We're only interested in xxxx-Authenticate headers. */ 237 if (strcasecmp(key, ab->header) != 0) 238 return 0; 239 240 /* Extract the authentication scheme name. */ 241 auth_attr = strchr(header, ' '); 242 if (auth_attr) { 243 auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header); 244 } 245 else 246 auth_name = apr_pstrmemdup(ab->pool, header, strlen(header)); 247 248 /* Convert scheme name to lower case to enable case insensitive matching. */ 249 for (c = auth_name; *c != '\0'; c++) 250 *c = (char)apr_tolower(*c); 251 252 apr_hash_set(ab->hdrs, auth_name, APR_HASH_KEY_STRING, 253 apr_pstrdup(ab->pool, header)); 254 255 return 0; 256} 257 258/* Dispatch authentication handling. This function matches the possible 259 authentication mechanisms with those available. Server and proxy 260 authentication are evaluated separately. */ 261static apr_status_t dispatch_auth(int code, 262 serf_request_t *request, 263 serf_bucket_t *response, 264 void *baton, 265 apr_pool_t *pool) 266{ 267 serf_bucket_t *hdrs; 268 269 if (code == 401 || code == 407) { 270 auth_baton_t ab = { 0 }; 271 const char *auth_hdr; 272 273 ab.hdrs = apr_hash_make(pool); 274 ab.pool = pool; 275 276 /* Before iterating over all authn headers, check if there are any. */ 277 if (code == 401) 278 ab.header = "WWW-Authenticate"; 279 else 280 ab.header = "Proxy-Authenticate"; 281 282 hdrs = serf_bucket_response_get_headers(response); 283 auth_hdr = serf_bucket_headers_get(hdrs, ab.header); 284 285 if (!auth_hdr) { 286 return SERF_ERROR_AUTHN_FAILED; 287 } 288 serf__log_skt(AUTH_VERBOSE, __FILE__, request->conn->skt, 289 "%s authz required. Response header(s): %s\n", 290 code == 401 ? "Server" : "Proxy", auth_hdr); 291 292 293 /* Store all WWW- or Proxy-Authenticate headers in a dictionary. 294 295 Note: it is possible to have multiple Authentication: headers. We do 296 not want to combine them (per normal header combination rules) as that 297 would make it hard to parse. Instead, we want to individually parse 298 and handle each header in the response, looking for one that we can 299 work with. 300 */ 301 serf_bucket_headers_do(hdrs, 302 store_header_in_dict, 303 &ab); 304 305 /* Iterate over all authentication schemes, in order of decreasing 306 security. Try to find a authentication schema the server support. */ 307 return handle_auth_headers(code, baton, ab.hdrs, 308 request, response, pool); 309 } 310 311 return APR_SUCCESS; 312} 313 314/* Read the headers of the response and try the available 315 handlers if authentication or validation is needed. */ 316apr_status_t serf__handle_auth_response(int *consumed_response, 317 serf_request_t *request, 318 serf_bucket_t *response, 319 void *baton, 320 apr_pool_t *pool) 321{ 322 apr_status_t status; 323 serf_status_line sl; 324 325 *consumed_response = 0; 326 327 /* TODO: the response bucket was created by the application, not at all 328 guaranteed that this is of type response_bucket!! */ 329 status = serf_bucket_response_status(response, &sl); 330 if (SERF_BUCKET_READ_ERROR(status)) { 331 return status; 332 } 333 if (!sl.version && (APR_STATUS_IS_EOF(status) || 334 APR_STATUS_IS_EAGAIN(status))) { 335 return status; 336 } 337 338 status = serf_bucket_response_wait_for_headers(response); 339 if (status) { 340 if (!APR_STATUS_IS_EOF(status)) { 341 return status; 342 } 343 344 /* If status is APR_EOF, there were no headers to read. 345 This can be ok in some situations, and it definitely 346 means there's no authentication requested now. */ 347 return APR_SUCCESS; 348 } 349 350 if (sl.code == 401 || sl.code == 407) { 351 /* Authentication requested. */ 352 353 /* Don't bother handling the authentication request if the response 354 wasn't received completely yet. Serf will call serf__handle_auth_response 355 again when more data is received. */ 356 status = discard_body(response); 357 *consumed_response = 1; 358 359 /* Discard all response body before processing authentication. */ 360 if (!APR_STATUS_IS_EOF(status)) { 361 return status; 362 } 363 364 status = dispatch_auth(sl.code, request, response, baton, pool); 365 if (status != APR_SUCCESS) { 366 return status; 367 } 368 369 /* Requeue the request with the necessary auth headers. */ 370 /* ### Application doesn't know about this request! */ 371 if (request->ssltunnel) { 372 serf__ssltunnel_request_create(request->conn, 373 request->setup, 374 request->setup_baton); 375 } else { 376 serf_connection_priority_request_create(request->conn, 377 request->setup, 378 request->setup_baton); 379 } 380 381 return APR_EOF; 382 } else { 383 serf__validate_response_func_t validate_resp; 384 serf_connection_t *conn = request->conn; 385 serf_context_t *ctx = conn->ctx; 386 serf__authn_info_t *authn_info; 387 apr_status_t resp_status = APR_SUCCESS; 388 389 390 /* Validate the response server authn headers. */ 391 authn_info = serf__get_authn_info_for_server(conn); 392 if (authn_info->scheme) { 393 validate_resp = authn_info->scheme->validate_response_func; 394 resp_status = validate_resp(authn_info->scheme, HOST, sl.code, 395 conn, request, response, pool); 396 } 397 398 /* Validate the response proxy authn headers. */ 399 authn_info = &ctx->proxy_authn_info; 400 if (!resp_status && authn_info->scheme) { 401 validate_resp = authn_info->scheme->validate_response_func; 402 resp_status = validate_resp(authn_info->scheme, PROXY, sl.code, 403 conn, request, response, pool); 404 } 405 406 if (resp_status) { 407 /* If there was an error in the final step of the authentication, 408 consider the reponse body as invalid and discard it. */ 409 status = discard_body(response); 410 *consumed_response = 1; 411 if (!APR_STATUS_IS_EOF(status)) { 412 return status; 413 } 414 /* The whole body was discarded, now return our error. */ 415 return resp_status; 416 } 417 } 418 419 return APR_SUCCESS; 420} 421 422/** 423 * base64 encode the authentication data and build an authentication 424 * header in this format: 425 * [SCHEME] [BASE64 of auth DATA] 426 */ 427void serf__encode_auth_header(const char **header, 428 const char *scheme, 429 const char *data, apr_size_t data_len, 430 apr_pool_t *pool) 431{ 432 apr_size_t encoded_len, scheme_len; 433 char *ptr; 434 435 encoded_len = apr_base64_encode_len(data_len); 436 scheme_len = strlen(scheme); 437 438 ptr = apr_palloc(pool, encoded_len + scheme_len + 1); 439 *header = ptr; 440 441 apr_cpystrn(ptr, scheme, scheme_len + 1); 442 ptr += scheme_len; 443 *ptr++ = ' '; 444 445 apr_base64_encode(ptr, data, data_len); 446} 447 448const char *serf__construct_realm(peer_t peer, 449 serf_connection_t *conn, 450 const char *realm_name, 451 apr_pool_t *pool) 452{ 453 if (peer == HOST) { 454 return apr_psprintf(pool, "<%s://%s:%d> %s", 455 conn->host_info.scheme, 456 conn->host_info.hostname, 457 conn->host_info.port, 458 realm_name); 459 } else { 460 serf_context_t *ctx = conn->ctx; 461 462 return apr_psprintf(pool, "<http://%s:%d> %s", 463 ctx->proxy_address->hostname, 464 ctx->proxy_address->port, 465 realm_name); 466 } 467} 468 469serf__authn_info_t *serf__get_authn_info_for_server(serf_connection_t *conn) 470{ 471 serf_context_t *ctx = conn->ctx; 472 serf__authn_info_t *authn_info; 473 474 authn_info = apr_hash_get(ctx->server_authn_info, conn->host_url, 475 APR_HASH_KEY_STRING); 476 477 if (!authn_info) { 478 authn_info = apr_pcalloc(ctx->pool, sizeof(serf__authn_info_t)); 479 apr_hash_set(ctx->server_authn_info, 480 apr_pstrdup(ctx->pool, conn->host_url), 481 APR_HASH_KEY_STRING, authn_info); 482 } 483 484 return authn_info; 485} 486