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