1 /* ====================================================================
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
18 * ====================================================================
22 #include "serf_private.h"
26 #include <apr_base64.h>
27 #include <apr_strings.h>
31 default_auth_response_handler(const serf__authn_scheme_t *scheme,
34 serf_connection_t *conn,
35 serf_request_t *request,
36 serf_bucket_t *response,
42 /* These authentication schemes are in order of decreasing security, the topmost
43 scheme will be used first when the server supports it.
45 Each set of handlers should support both server (401) and proxy (407)
48 Use lower case for the scheme names to enable case insensitive matching.
50 static const serf__authn_scheme_t serf_authn_schemes[] = {
51 #ifdef SERF_HAVE_SPNEGO
57 serf__init_spnego_connection,
58 serf__handle_spnego_auth,
59 serf__setup_request_spnego_auth,
60 serf__validate_response_spnego_auth,
68 serf__init_spnego_connection,
69 serf__handle_spnego_auth,
70 serf__setup_request_spnego_auth,
71 serf__validate_response_spnego_auth,
73 #endif /* #ifdef WIN32 */
74 #endif /* SERF_HAVE_SPNEGO */
80 serf__init_digest_connection,
81 serf__handle_digest_auth,
82 serf__setup_request_digest_auth,
83 serf__validate_response_digest_auth,
90 serf__init_basic_connection,
91 serf__handle_basic_auth,
92 serf__setup_request_basic_auth,
93 default_auth_response_handler,
95 /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */
102 /* Reads and discards all bytes in the response body. */
103 static apr_status_t discard_body(serf_bucket_t *response)
110 status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len);
121 * handle_auth_header is called for each header in the response. It filters
122 * out the Authenticate headers (WWW or Proxy depending on what's needed) and
123 * tries to find a matching scheme handler.
125 * Returns a non-0 value of a matching handler was found.
127 static int handle_auth_headers(int code,
130 serf_request_t *request,
131 serf_bucket_t *response,
134 const serf__authn_scheme_t *scheme;
135 serf_connection_t *conn = request->conn;
136 serf_context_t *ctx = conn->ctx;
139 status = SERF_ERROR_AUTHN_NOT_SUPPORTED;
141 /* Find the matching authentication handler.
142 Note that we don't reuse the auth scheme stored in the context,
143 as that may have changed. (ex. fallback from ntlm to basic.) */
144 for (scheme = serf_authn_schemes; scheme->name != 0; ++scheme) {
145 const char *auth_hdr;
146 serf__auth_handler_func_t handler;
147 serf__authn_info_t *authn_info;
149 if (! (ctx->authn_types & scheme->type))
152 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
153 "Client supports: %s\n", scheme->name);
155 auth_hdr = apr_hash_get(hdrs, scheme->key, APR_HASH_KEY_STRING);
161 authn_info = serf__get_authn_info_for_server(conn);
163 authn_info = &ctx->proxy_authn_info;
166 if (authn_info->failed_authn_types & scheme->type) {
167 /* Skip this authn type since we already tried it before. */
171 /* Found a matching scheme */
172 status = APR_SUCCESS;
174 handler = scheme->handle_func;
176 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
177 "... matched: %s\n", scheme->name);
179 /* If this is the first time we use this scheme on this context and/or
180 this connection, make sure to initialize the authentication handler
182 if (authn_info->scheme != scheme) {
183 status = scheme->init_ctx_func(code, ctx, ctx->pool);
185 status = scheme->init_conn_func(scheme, code, conn,
188 authn_info->scheme = scheme;
190 authn_info->scheme = NULL;
195 const char *auth_attr = strchr(auth_hdr, ' ');
200 status = handler(code, request, response,
201 auth_hdr, auth_attr, baton, ctx->pool);
204 if (status == APR_SUCCESS)
207 /* No success authenticating with this scheme, try the next.
208 If no more authn schemes are found the status of this scheme will be
211 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
212 "%s authentication failed.\n", scheme->name);
214 /* Clear per-request auth_baton when switching to next auth scheme. */
215 request->auth_baton = NULL;
217 /* Remember failed auth types to skip in future. */
218 authn_info->failed_authn_types |= scheme->type;
225 * Baton passed to the store_header_in_dict callback function
233 static int store_header_in_dict(void *baton,
237 auth_baton_t *ab = baton;
238 const char *auth_attr;
241 /* We're only interested in xxxx-Authenticate headers. */
242 if (strcasecmp(key, ab->header) != 0)
245 /* Extract the authentication scheme name. */
246 auth_attr = strchr(header, ' ');
248 auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header);
251 auth_name = apr_pstrmemdup(ab->pool, header, strlen(header));
253 /* Convert scheme name to lower case to enable case insensitive matching. */
254 for (c = auth_name; *c != '\0'; c++)
255 *c = (char)apr_tolower(*c);
257 apr_hash_set(ab->hdrs, auth_name, APR_HASH_KEY_STRING,
258 apr_pstrdup(ab->pool, header));
263 /* Dispatch authentication handling. This function matches the possible
264 authentication mechanisms with those available. Server and proxy
265 authentication are evaluated separately. */
266 static apr_status_t dispatch_auth(int code,
267 serf_request_t *request,
268 serf_bucket_t *response,
274 if (code == 401 || code == 407) {
275 auth_baton_t ab = { 0 };
276 const char *auth_hdr;
278 ab.hdrs = apr_hash_make(pool);
281 /* Before iterating over all authn headers, check if there are any. */
283 ab.header = "WWW-Authenticate";
285 ab.header = "Proxy-Authenticate";
287 hdrs = serf_bucket_response_get_headers(response);
288 auth_hdr = serf_bucket_headers_get(hdrs, ab.header);
291 return SERF_ERROR_AUTHN_FAILED;
293 serf__log_skt(AUTH_VERBOSE, __FILE__, request->conn->skt,
294 "%s authz required. Response header(s): %s\n",
295 code == 401 ? "Server" : "Proxy", auth_hdr);
298 /* Store all WWW- or Proxy-Authenticate headers in a dictionary.
300 Note: it is possible to have multiple Authentication: headers. We do
301 not want to combine them (per normal header combination rules) as that
302 would make it hard to parse. Instead, we want to individually parse
303 and handle each header in the response, looking for one that we can
306 serf_bucket_headers_do(hdrs,
307 store_header_in_dict,
310 /* Iterate over all authentication schemes, in order of decreasing
311 security. Try to find a authentication schema the server support. */
312 return handle_auth_headers(code, baton, ab.hdrs,
313 request, response, pool);
319 /* Read the headers of the response and try the available
320 handlers if authentication or validation is needed. */
321 apr_status_t serf__handle_auth_response(int *consumed_response,
322 serf_request_t *request,
323 serf_bucket_t *response,
330 *consumed_response = 0;
332 /* TODO: the response bucket was created by the application, not at all
333 guaranteed that this is of type response_bucket!! */
334 status = serf_bucket_response_status(response, &sl);
335 if (SERF_BUCKET_READ_ERROR(status)) {
338 if (!sl.version && (APR_STATUS_IS_EOF(status) ||
339 APR_STATUS_IS_EAGAIN(status))) {
343 status = serf_bucket_response_wait_for_headers(response);
345 if (!APR_STATUS_IS_EOF(status)) {
349 /* If status is APR_EOF, there were no headers to read.
350 This can be ok in some situations, and it definitely
351 means there's no authentication requested now. */
355 if (sl.code == 401 || sl.code == 407) {
356 /* Authentication requested. */
358 /* Don't bother handling the authentication request if the response
359 wasn't received completely yet. Serf will call serf__handle_auth_response
360 again when more data is received. */
361 status = discard_body(response);
362 *consumed_response = 1;
364 /* Discard all response body before processing authentication. */
365 if (!APR_STATUS_IS_EOF(status)) {
369 status = dispatch_auth(sl.code, request, response, baton, pool);
370 if (status != APR_SUCCESS) {
374 /* Requeue the request with the necessary auth headers. */
375 /* ### Application doesn't know about this request! */
376 if (request->ssltunnel) {
377 serf__ssltunnel_request_create(request->conn,
379 request->setup_baton);
381 serf_connection_priority_request_create(request->conn,
383 request->setup_baton);
388 serf__validate_response_func_t validate_resp;
389 serf_connection_t *conn = request->conn;
390 serf_context_t *ctx = conn->ctx;
391 serf__authn_info_t *authn_info;
392 apr_status_t resp_status = APR_SUCCESS;
395 /* Validate the response server authn headers. */
396 authn_info = serf__get_authn_info_for_server(conn);
397 if (authn_info->scheme) {
398 validate_resp = authn_info->scheme->validate_response_func;
399 resp_status = validate_resp(authn_info->scheme, HOST, sl.code,
400 conn, request, response, pool);
403 /* Validate the response proxy authn headers. */
404 authn_info = &ctx->proxy_authn_info;
405 if (!resp_status && authn_info->scheme) {
406 validate_resp = authn_info->scheme->validate_response_func;
407 resp_status = validate_resp(authn_info->scheme, PROXY, sl.code,
408 conn, request, response, pool);
412 /* If there was an error in the final step of the authentication,
413 consider the reponse body as invalid and discard it. */
414 status = discard_body(response);
415 *consumed_response = 1;
416 if (!APR_STATUS_IS_EOF(status)) {
419 /* The whole body was discarded, now return our error. */
428 * base64 encode the authentication data and build an authentication
429 * header in this format:
430 * [SCHEME] [BASE64 of auth DATA]
432 void serf__encode_auth_header(const char **header,
434 const char *data, apr_size_t data_len,
437 apr_size_t encoded_len, scheme_len;
440 encoded_len = apr_base64_encode_len(data_len);
441 scheme_len = strlen(scheme);
443 ptr = apr_palloc(pool, encoded_len + scheme_len + 1);
446 apr_cpystrn(ptr, scheme, scheme_len + 1);
450 apr_base64_encode(ptr, data, data_len);
453 const char *serf__construct_realm(peer_t peer,
454 serf_connection_t *conn,
455 const char *realm_name,
459 return apr_psprintf(pool, "<%s://%s:%d> %s",
460 conn->host_info.scheme,
461 conn->host_info.hostname,
462 conn->host_info.port,
465 serf_context_t *ctx = conn->ctx;
467 return apr_psprintf(pool, "<http://%s:%d> %s",
468 ctx->proxy_address->hostname,
469 ctx->proxy_address->port,
474 serf__authn_info_t *serf__get_authn_info_for_server(serf_connection_t *conn)
476 serf_context_t *ctx = conn->ctx;
477 serf__authn_info_t *authn_info;
479 authn_info = apr_hash_get(ctx->server_authn_info, conn->host_url,
480 APR_HASH_KEY_STRING);
483 authn_info = apr_pcalloc(ctx->pool, sizeof(serf__authn_info_t));
484 apr_hash_set(ctx->server_authn_info,
485 apr_pstrdup(ctx->pool, conn->host_url),
486 APR_HASH_KEY_STRING, authn_info);