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(peer_t peer,
28 serf_connection_t *conn,
29 serf_request_t *request,
30 serf_bucket_t *response,
36 /* These authentication schemes are in order of decreasing security, the topmost
37 scheme will be used first when the server supports it.
39 Each set of handlers should support both server (401) and proxy (407)
42 Use lower case for the scheme names to enable case insensitive matching.
44 static const serf__authn_scheme_t serf_authn_schemes[] = {
45 #ifdef SERF_HAVE_SPNEGO
51 serf__init_spnego_connection,
52 serf__handle_spnego_auth,
53 serf__setup_request_spnego_auth,
54 serf__validate_response_spnego_auth,
62 serf__init_spnego_connection,
63 serf__handle_spnego_auth,
64 serf__setup_request_spnego_auth,
65 serf__validate_response_spnego_auth,
67 #endif /* #ifdef WIN32 */
68 #endif /* SERF_HAVE_SPNEGO */
74 serf__init_digest_connection,
75 serf__handle_digest_auth,
76 serf__setup_request_digest_auth,
77 serf__validate_response_digest_auth,
84 serf__init_basic_connection,
85 serf__handle_basic_auth,
86 serf__setup_request_basic_auth,
87 default_auth_response_handler,
89 /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */
96 /* Reads and discards all bytes in the response body. */
97 static apr_status_t discard_body(serf_bucket_t *response)
104 status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len);
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.
119 * Returns a non-0 value of a matching handler was found.
121 static int handle_auth_headers(int code,
124 serf_request_t *request,
125 serf_bucket_t *response,
128 const serf__authn_scheme_t *scheme;
129 serf_connection_t *conn = request->conn;
130 serf_context_t *ctx = conn->ctx;
133 status = SERF_ERROR_AUTHN_NOT_SUPPORTED;
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;
143 if (! (ctx->authn_types & scheme->type))
146 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
147 "Client supports: %s\n", scheme->name);
149 auth_hdr = apr_hash_get(hdrs, scheme->key, APR_HASH_KEY_STRING);
154 /* Found a matching scheme */
155 status = APR_SUCCESS;
157 handler = scheme->handle_func;
159 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
160 "... matched: %s\n", scheme->name);
163 authn_info = serf__get_authn_info_for_server(conn);
165 authn_info = &ctx->proxy_authn_info;
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
170 if (authn_info->scheme != scheme) {
171 status = scheme->init_ctx_func(code, ctx, ctx->pool);
173 status = scheme->init_conn_func(scheme, code, conn,
176 authn_info->scheme = scheme;
178 authn_info->scheme = NULL;
183 const char *auth_attr = strchr(auth_hdr, ' ');
188 status = handler(code, request, response,
189 auth_hdr, auth_attr, baton, ctx->pool);
192 if (status == APR_SUCCESS)
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
199 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
200 "%s authentication failed.\n", scheme->name);
207 * Baton passed to the store_header_in_dict callback function
215 static int store_header_in_dict(void *baton,
219 auth_baton_t *ab = baton;
220 const char *auth_attr;
223 /* We're only interested in xxxx-Authenticate headers. */
224 if (strcmp(key, ab->header) != 0)
227 /* Extract the authentication scheme name. */
228 auth_attr = strchr(header, ' ');
230 auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header);
233 auth_name = apr_pstrmemdup(ab->pool, header, strlen(header));
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);
239 apr_hash_set(ab->hdrs, auth_name, APR_HASH_KEY_STRING,
240 apr_pstrdup(ab->pool, header));
245 /* Dispatch authentication handling. This function matches the possible
246 authentication mechanisms with those available. Server and proxy
247 authentication are evaluated separately. */
248 static apr_status_t dispatch_auth(int code,
249 serf_request_t *request,
250 serf_bucket_t *response,
256 if (code == 401 || code == 407) {
257 auth_baton_t ab = { 0 };
258 const char *auth_hdr;
260 ab.hdrs = apr_hash_make(pool);
263 /* Before iterating over all authn headers, check if there are any. */
265 ab.header = "WWW-Authenticate";
267 ab.header = "Proxy-Authenticate";
269 hdrs = serf_bucket_response_get_headers(response);
270 auth_hdr = serf_bucket_headers_get(hdrs, ab.header);
273 return SERF_ERROR_AUTHN_FAILED;
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);
280 /* Store all WWW- or Proxy-Authenticate headers in a dictionary.
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
288 serf_bucket_headers_do(hdrs,
289 store_header_in_dict,
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);
301 /* Read the headers of the response and try the available
302 handlers if authentication or validation is needed. */
303 apr_status_t serf__handle_auth_response(int *consumed_response,
304 serf_request_t *request,
305 serf_bucket_t *response,
312 *consumed_response = 0;
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)) {
320 if (!sl.version && (APR_STATUS_IS_EOF(status) ||
321 APR_STATUS_IS_EAGAIN(status))) {
325 status = serf_bucket_response_wait_for_headers(response);
327 if (!APR_STATUS_IS_EOF(status)) {
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. */
337 if (sl.code == 401 || sl.code == 407) {
338 /* Authentication requested. */
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;
346 /* Discard all response body before processing authentication. */
347 if (!APR_STATUS_IS_EOF(status)) {
351 status = dispatch_auth(sl.code, request, response, baton, pool);
352 if (status != APR_SUCCESS) {
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,
361 request->setup_baton);
363 serf_connection_priority_request_create(request->conn,
365 request->setup_baton);
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;
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,
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,
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)) {
401 /* The whole body was discarded, now return our error. */
410 * base64 encode the authentication data and build an authentication
411 * header in this format:
412 * [SCHEME] [BASE64 of auth DATA]
414 void serf__encode_auth_header(const char **header,
416 const char *data, apr_size_t data_len,
419 apr_size_t encoded_len, scheme_len;
422 encoded_len = apr_base64_encode_len(data_len);
423 scheme_len = strlen(scheme);
425 ptr = apr_palloc(pool, encoded_len + scheme_len + 1);
428 apr_cpystrn(ptr, scheme, scheme_len + 1);
432 apr_base64_encode(ptr, data, data_len);
435 const char *serf__construct_realm(peer_t peer,
436 serf_connection_t *conn,
437 const char *realm_name,
441 return apr_psprintf(pool, "<%s://%s:%d> %s",
442 conn->host_info.scheme,
443 conn->host_info.hostname,
444 conn->host_info.port,
447 serf_context_t *ctx = conn->ctx;
449 return apr_psprintf(pool, "<http://%s:%d> %s",
450 ctx->proxy_address->hostname,
451 ctx->proxy_address->port,
456 serf__authn_info_t *serf__get_authn_info_for_server(serf_connection_t *conn)
458 serf_context_t *ctx = conn->ctx;
459 serf__authn_info_t *authn_info;
461 authn_info = apr_hash_get(ctx->server_authn_info, conn->host_url,
462 APR_HASH_KEY_STRING);
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);