1 /* Copyright 2009 Justin Erenkrantz and Greg Stein
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include "serf_private.h"
21 #include <apr_base64.h>
22 #include <apr_strings.h>
26 default_auth_response_handler(const serf__authn_scheme_t *scheme,
29 serf_connection_t *conn,
30 serf_request_t *request,
31 serf_bucket_t *response,
37 /* These authentication schemes are in order of decreasing security, the topmost
38 scheme will be used first when the server supports it.
40 Each set of handlers should support both server (401) and proxy (407)
43 Use lower case for the scheme names to enable case insensitive matching.
45 static const serf__authn_scheme_t serf_authn_schemes[] = {
46 #ifdef SERF_HAVE_SPNEGO
52 serf__init_spnego_connection,
53 serf__handle_spnego_auth,
54 serf__setup_request_spnego_auth,
55 serf__validate_response_spnego_auth,
63 serf__init_spnego_connection,
64 serf__handle_spnego_auth,
65 serf__setup_request_spnego_auth,
66 serf__validate_response_spnego_auth,
68 #endif /* #ifdef WIN32 */
69 #endif /* SERF_HAVE_SPNEGO */
75 serf__init_digest_connection,
76 serf__handle_digest_auth,
77 serf__setup_request_digest_auth,
78 serf__validate_response_digest_auth,
85 serf__init_basic_connection,
86 serf__handle_basic_auth,
87 serf__setup_request_basic_auth,
88 default_auth_response_handler,
90 /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */
97 /* Reads and discards all bytes in the response body. */
98 static apr_status_t discard_body(serf_bucket_t *response)
105 status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len);
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.
120 * Returns a non-0 value of a matching handler was found.
122 static int handle_auth_headers(int code,
125 serf_request_t *request,
126 serf_bucket_t *response,
129 const serf__authn_scheme_t *scheme;
130 serf_connection_t *conn = request->conn;
131 serf_context_t *ctx = conn->ctx;
134 status = SERF_ERROR_AUTHN_NOT_SUPPORTED;
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;
144 if (! (ctx->authn_types & scheme->type))
147 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
148 "Client supports: %s\n", scheme->name);
150 auth_hdr = apr_hash_get(hdrs, scheme->key, APR_HASH_KEY_STRING);
156 authn_info = serf__get_authn_info_for_server(conn);
158 authn_info = &ctx->proxy_authn_info;
161 if (authn_info->failed_authn_types & scheme->type) {
162 /* Skip this authn type since we already tried it before. */
166 /* Found a matching scheme */
167 status = APR_SUCCESS;
169 handler = scheme->handle_func;
171 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
172 "... matched: %s\n", scheme->name);
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
177 if (authn_info->scheme != scheme) {
178 status = scheme->init_ctx_func(code, ctx, ctx->pool);
180 status = scheme->init_conn_func(scheme, code, conn,
183 authn_info->scheme = scheme;
185 authn_info->scheme = NULL;
190 const char *auth_attr = strchr(auth_hdr, ' ');
195 status = handler(code, request, response,
196 auth_hdr, auth_attr, baton, ctx->pool);
199 if (status == APR_SUCCESS)
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
206 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
207 "%s authentication failed.\n", scheme->name);
209 /* Clear per-request auth_baton when switching to next auth scheme. */
210 request->auth_baton = NULL;
212 /* Remember failed auth types to skip in future. */
213 authn_info->failed_authn_types |= scheme->type;
220 * Baton passed to the store_header_in_dict callback function
228 static int store_header_in_dict(void *baton,
232 auth_baton_t *ab = baton;
233 const char *auth_attr;
236 /* We're only interested in xxxx-Authenticate headers. */
237 if (strcasecmp(key, ab->header) != 0)
240 /* Extract the authentication scheme name. */
241 auth_attr = strchr(header, ' ');
243 auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header);
246 auth_name = apr_pstrmemdup(ab->pool, header, strlen(header));
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);
252 apr_hash_set(ab->hdrs, auth_name, APR_HASH_KEY_STRING,
253 apr_pstrdup(ab->pool, header));
258 /* Dispatch authentication handling. This function matches the possible
259 authentication mechanisms with those available. Server and proxy
260 authentication are evaluated separately. */
261 static apr_status_t dispatch_auth(int code,
262 serf_request_t *request,
263 serf_bucket_t *response,
269 if (code == 401 || code == 407) {
270 auth_baton_t ab = { 0 };
271 const char *auth_hdr;
273 ab.hdrs = apr_hash_make(pool);
276 /* Before iterating over all authn headers, check if there are any. */
278 ab.header = "WWW-Authenticate";
280 ab.header = "Proxy-Authenticate";
282 hdrs = serf_bucket_response_get_headers(response);
283 auth_hdr = serf_bucket_headers_get(hdrs, ab.header);
286 return SERF_ERROR_AUTHN_FAILED;
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);
293 /* Store all WWW- or Proxy-Authenticate headers in a dictionary.
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
301 serf_bucket_headers_do(hdrs,
302 store_header_in_dict,
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);
314 /* Read the headers of the response and try the available
315 handlers if authentication or validation is needed. */
316 apr_status_t serf__handle_auth_response(int *consumed_response,
317 serf_request_t *request,
318 serf_bucket_t *response,
325 *consumed_response = 0;
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)) {
333 if (!sl.version && (APR_STATUS_IS_EOF(status) ||
334 APR_STATUS_IS_EAGAIN(status))) {
338 status = serf_bucket_response_wait_for_headers(response);
340 if (!APR_STATUS_IS_EOF(status)) {
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. */
350 if (sl.code == 401 || sl.code == 407) {
351 /* Authentication requested. */
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;
359 /* Discard all response body before processing authentication. */
360 if (!APR_STATUS_IS_EOF(status)) {
364 status = dispatch_auth(sl.code, request, response, baton, pool);
365 if (status != APR_SUCCESS) {
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,
374 request->setup_baton);
376 serf_connection_priority_request_create(request->conn,
378 request->setup_baton);
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;
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);
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);
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)) {
414 /* The whole body was discarded, now return our error. */
423 * base64 encode the authentication data and build an authentication
424 * header in this format:
425 * [SCHEME] [BASE64 of auth DATA]
427 void serf__encode_auth_header(const char **header,
429 const char *data, apr_size_t data_len,
432 apr_size_t encoded_len, scheme_len;
435 encoded_len = apr_base64_encode_len(data_len);
436 scheme_len = strlen(scheme);
438 ptr = apr_palloc(pool, encoded_len + scheme_len + 1);
441 apr_cpystrn(ptr, scheme, scheme_len + 1);
445 apr_base64_encode(ptr, data, data_len);
448 const char *serf__construct_realm(peer_t peer,
449 serf_connection_t *conn,
450 const char *realm_name,
454 return apr_psprintf(pool, "<%s://%s:%d> %s",
455 conn->host_info.scheme,
456 conn->host_info.hostname,
457 conn->host_info.port,
460 serf_context_t *ctx = conn->ctx;
462 return apr_psprintf(pool, "<http://%s:%d> %s",
463 ctx->proxy_address->hostname,
464 ctx->proxy_address->port,
469 serf__authn_info_t *serf__get_authn_info_for_server(serf_connection_t *conn)
471 serf_context_t *ctx = conn->ctx;
472 serf__authn_info_t *authn_info;
474 authn_info = apr_hash_get(ctx->server_authn_info, conn->host_url,
475 APR_HASH_KEY_STRING);
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);