| /* ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #include "serf.h" |
| #include "serf_private.h" |
| #include "auth.h" |
| |
| #include <apr.h> |
| #include <apr_base64.h> |
| #include <apr_strings.h> |
| #include <apr_lib.h> |
| |
| /* These authentication schemes are in order of decreasing security, the topmost |
| scheme will be used first when the server supports it. |
| |
| Each set of handlers should support both server (401) and proxy (407) |
| authentication. |
| |
| Use lower case for the scheme names to enable case insensitive matching. |
| */ |
| static const serf__authn_scheme_t *serf_authn_schemes[] = { |
| #ifdef SERF_HAVE_SPNEGO |
| &serf__spnego_authn_scheme, |
| #ifdef WIN32 |
| &serf__ntlm_authn_scheme, |
| #endif /* #ifdef WIN32 */ |
| #endif /* SERF_HAVE_SPNEGO */ |
| &serf__digest_authn_scheme, |
| &serf__basic_authn_scheme, |
| /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */ |
| |
| /* sentinel */ |
| NULL |
| }; |
| |
| |
| /* Reads and discards all bytes in the response body. */ |
| static apr_status_t discard_body(serf_bucket_t *response) |
| { |
| apr_status_t status; |
| const char *data; |
| apr_size_t len; |
| |
| while (1) { |
| status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len); |
| |
| if (status) { |
| return status; |
| } |
| |
| /* feed me */ |
| } |
| } |
| |
| /** |
| * handle_auth_header is called for each header in the response. It filters |
| * out the Authenticate headers (WWW or Proxy depending on what's needed) and |
| * tries to find a matching scheme handler. |
| * |
| * Returns a non-0 value of a matching handler was found. |
| */ |
| static int handle_auth_headers(int code, |
| apr_hash_t *hdrs, |
| serf_request_t *request, |
| serf_bucket_t *response, |
| apr_pool_t *pool) |
| { |
| int scheme_idx; |
| serf_connection_t *conn = request->conn; |
| serf_context_t *ctx = conn->ctx; |
| apr_status_t status; |
| |
| status = SERF_ERROR_AUTHN_NOT_SUPPORTED; |
| |
| /* Find the matching authentication handler. |
| Note that we don't reuse the auth scheme stored in the context, |
| as that may have changed. (ex. fallback from ntlm to basic.) */ |
| for (scheme_idx = 0; serf_authn_schemes[scheme_idx]; ++scheme_idx) { |
| const char *auth_hdr; |
| serf__auth_handler_func_t handler; |
| serf__authn_info_t *authn_info; |
| const serf__authn_scheme_t *scheme = serf_authn_schemes[scheme_idx]; |
| |
| if (! (ctx->authn_types & scheme->type)) |
| continue; |
| |
| serf__log(LOGLVL_INFO, LOGCOMP_AUTHN, __FILE__, conn->config, |
| "Client supports: %s\n", scheme->name); |
| |
| auth_hdr = apr_hash_get(hdrs, scheme->key, APR_HASH_KEY_STRING); |
| |
| if (!auth_hdr) |
| continue; |
| |
| if (code == 401) { |
| authn_info = serf__get_authn_info_for_server(conn); |
| } else { |
| authn_info = &ctx->proxy_authn_info; |
| } |
| |
| if (authn_info->failed_authn_types & scheme->type) { |
| /* Skip this authn type since we already tried it before. */ |
| continue; |
| } |
| |
| /* Found a matching scheme */ |
| status = APR_SUCCESS; |
| |
| handler = scheme->handle_func; |
| |
| serf__log(LOGLVL_INFO, LOGCOMP_AUTHN, __FILE__, conn->config, |
| "... matched: %s\n", scheme->name); |
| |
| /* If this is the first time we use this scheme on this connection, |
| make sure to initialize the authentication handler first. */ |
| if (authn_info->scheme != scheme) { |
| status = scheme->init_conn_func(scheme, code, conn, |
| conn->pool); |
| if (!status) |
| authn_info->scheme = scheme; |
| else |
| authn_info->scheme = NULL; |
| } |
| |
| if (!status) { |
| const char *auth_attr = strchr(auth_hdr, ' '); |
| if (auth_attr) { |
| auth_attr++; |
| } |
| |
| status = handler(scheme, code, request, response, |
| auth_hdr, auth_attr, ctx->pool); |
| } |
| |
| if (status == APR_SUCCESS) |
| break; |
| |
| /* No success authenticating with this scheme, try the next. |
| If no more authn schemes are found the status of this scheme will be |
| returned. |
| */ |
| serf__log(LOGLVL_INFO, LOGCOMP_AUTHN, __FILE__, conn->config, |
| "%s authentication failed.\n", scheme->name); |
| |
| /* Clear per-request auth_baton when switching to next auth scheme. */ |
| request->auth_baton = NULL; |
| |
| /* Remember failed auth types to skip it in future. */ |
| authn_info->failed_authn_types |= scheme->type; |
| } |
| |
| return status; |
| } |
| |
| /** |
| * Baton passed to the store_header_in_dict callback function |
| */ |
| typedef struct auth_baton_t { |
| const char *header; |
| apr_pool_t *pool; |
| apr_hash_t *hdrs; |
| } auth_baton_t; |
| |
| static int store_header_in_dict(void *baton, |
| const char *key, |
| const char *header) |
| { |
| auth_baton_t *ab = baton; |
| const char *auth_attr; |
| char *auth_name, *c; |
| |
| /* We're only interested in xxxx-Authenticate headers. */ |
| if (strcasecmp(key, ab->header) != 0) |
| return 0; |
| |
| /* Extract the authentication scheme name. */ |
| auth_attr = strchr(header, ' '); |
| if (auth_attr) { |
| auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header); |
| } |
| else |
| auth_name = apr_pstrmemdup(ab->pool, header, strlen(header)); |
| |
| /* Convert scheme name to lower case to enable case insensitive matching. */ |
| for (c = auth_name; *c != '\0'; c++) |
| *c = (char)apr_tolower(*c); |
| |
| apr_hash_set(ab->hdrs, auth_name, APR_HASH_KEY_STRING, |
| apr_pstrdup(ab->pool, header)); |
| |
| return 0; |
| } |
| |
| /* Dispatch authentication handling. This function matches the possible |
| authentication mechanisms with those available. Server and proxy |
| authentication are evaluated separately. */ |
| static apr_status_t dispatch_auth(int code, |
| serf_request_t *request, |
| serf_bucket_t *response, |
| apr_pool_t *pool) |
| { |
| if (code == 401 || code == 407) { |
| serf_bucket_t *hdrs; |
| auth_baton_t ab = { 0 }; |
| |
| ab.hdrs = apr_hash_make(pool); |
| ab.pool = pool; |
| |
| if (code == 401) |
| ab.header = "WWW-Authenticate"; |
| else |
| ab.header = "Proxy-Authenticate"; |
| |
| hdrs = serf_bucket_response_get_headers(response); |
| |
| #ifdef SERF_LOGGING_ENABLED |
| if (serf__log_enabled(LOGLVL_WARNING, LOGCOMP_AUTHN, |
| request->conn->config)) { |
| const char *auth_hdr; |
| |
| /* ### headers_get() doesn't tell us whether to free this result |
| ### or not. but... meh. debug mode. */ |
| auth_hdr = serf_bucket_headers_get(hdrs, ab.header); |
| if (auth_hdr == NULL) { |
| serf__log(LOGLVL_WARNING, LOGCOMP_AUTHN, __FILE__, |
| request->conn->config, |
| "%s header missing in response!\n", ab.header); |
| } else { |
| serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, |
| request->conn->config, |
| "%s authz required. Response header(s): %s\n", |
| code == 401 ? "Server" : "Proxy", auth_hdr); |
| } |
| } |
| #endif /* SERF_LOGGING_ENABLED */ |
| |
| /* Store all WWW- or Proxy-Authenticate headers in a dictionary. |
| |
| Note: it is possible to have multiple Authentication: headers. We do |
| not want to combine them (per normal header combination rules) as that |
| would make it hard to parse. Instead, we want to individually parse |
| and handle each header in the response, looking for one that we can |
| work with. |
| */ |
| serf_bucket_headers_do(hdrs, |
| store_header_in_dict, |
| &ab); |
| if (apr_hash_count(ab.hdrs) == 0) |
| return SERF_ERROR_AUTHN_FAILED; |
| |
| /* Iterate over all authentication schemes, in order of decreasing |
| security. Try to find a authentication schema the server support. */ |
| return handle_auth_headers(code, ab.hdrs, |
| request, response, pool); |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Read the headers of the response and try the available handlers if |
| authentication or validation is needed. |
| *CONSUMED_RESPONSE will be 1 if authentication is involved (either a 401/407 |
| response or a response with an authn header), 0 otherwise. */ |
| apr_status_t serf__handle_auth_response(bool *consumed_response, |
| serf_request_t *request, |
| serf_bucket_t *response, |
| apr_pool_t *pool) |
| { |
| apr_status_t status; |
| serf_status_line sl; |
| |
| *consumed_response = false; |
| |
| /* TODO: the response bucket was created by the application, not at all |
| guaranteed that this is of type response_bucket!! */ |
| status = serf_bucket_response_status(response, &sl); |
| if (SERF_BUCKET_READ_ERROR(status)) { |
| return status; |
| } |
| if (!sl.version && (APR_STATUS_IS_EOF(status) || |
| APR_STATUS_IS_EAGAIN(status))) { |
| return status; |
| } |
| |
| status = serf_bucket_response_wait_for_headers(response); |
| if (status) { |
| if (!APR_STATUS_IS_EOF(status)) { |
| return status; |
| } |
| |
| /* If status is APR_EOF, there were no headers to read. |
| This can be ok in some situations, and it definitely |
| means there's no authentication requested now. */ |
| return APR_SUCCESS; |
| } |
| |
| if (sl.code == 401 || sl.code == 407) { |
| /* Authentication requested. */ |
| |
| /* Don't bother handling the authentication request if the response |
| wasn't received completely yet. Serf will call serf__handle_auth_response |
| again when more data is received. */ |
| |
| status = dispatch_auth(sl.code, request, response, pool); |
| if (status != APR_SUCCESS) { |
| return status; |
| } |
| |
| request->auth_done = true; |
| |
| /* Requeue the request with the necessary auth headers.*/ |
| status = serf_connection__request_requeue(request); |
| |
| if (status) |
| return status; |
| |
| *consumed_response = true; |
| |
| return APR_SUCCESS; |
| } else { |
| serf__validate_response_func_t validate_resp; |
| serf_connection_t *conn = request->conn; |
| serf_context_t *ctx = conn->ctx; |
| serf__authn_info_t *authn_info; |
| apr_status_t resp_status = APR_SUCCESS; |
| |
| |
| /* Validate the response server authn headers. */ |
| authn_info = serf__get_authn_info_for_server(conn); |
| if (authn_info->scheme) { |
| validate_resp = authn_info->scheme->validate_response_func; |
| resp_status = validate_resp(authn_info->scheme, HOST, sl.code, |
| conn, request, response, pool); |
| } |
| |
| /* Validate the response proxy authn headers. */ |
| authn_info = &ctx->proxy_authn_info; |
| if (!resp_status && authn_info->scheme) { |
| validate_resp = authn_info->scheme->validate_response_func; |
| resp_status = validate_resp(authn_info->scheme, PROXY, sl.code, |
| conn, request, response, pool); |
| } |
| |
| if (resp_status) { |
| /* If there was an error in the final step of the authentication, |
| consider the reponse body as invalid and discard it. */ |
| status = discard_body(response); |
| *consumed_response = true; |
| |
| if (!APR_STATUS_IS_EOF(status)) { |
| return status; |
| } |
| /* The whole body was discarded, now return our error. */ |
| return resp_status; |
| } |
| } |
| |
| request->auth_done = true; |
| |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * base64 encode the authentication data and build an authentication |
| * header in this format: |
| * [SCHEME] [BASE64 of auth DATA] |
| */ |
| void serf__encode_auth_header(const char **header, |
| const char *scheme, |
| const char *data, apr_size_t data_len, |
| apr_pool_t *pool) |
| { |
| apr_size_t encoded_len, scheme_len; |
| char *ptr; |
| |
| encoded_len = apr_base64_encode_len(data_len); |
| scheme_len = strlen(scheme); |
| |
| ptr = apr_palloc(pool, encoded_len + scheme_len + 1); |
| *header = ptr; |
| |
| apr_cpystrn(ptr, scheme, scheme_len + 1); |
| ptr += scheme_len; |
| *ptr++ = ' '; |
| |
| apr_base64_encode(ptr, data, data_len); |
| } |
| |
| const char *serf__construct_realm(peer_t peer, |
| serf_connection_t *conn, |
| const char *realm_name, |
| apr_pool_t *pool) |
| { |
| if (peer == HOST) { |
| return apr_psprintf(pool, "<%s://%s:%d> %s", |
| conn->host_info.scheme, |
| conn->host_info.hostname, |
| conn->host_info.port, |
| realm_name); |
| } else { |
| serf_context_t *ctx = conn->ctx; |
| |
| return apr_psprintf(pool, "<http://%s:%d> %s", |
| ctx->proxy_address->hostname, |
| ctx->proxy_address->port, |
| realm_name); |
| } |
| } |
| |
| serf__authn_info_t *serf__get_authn_info_for_server(serf_connection_t *conn) |
| { |
| serf_context_t *ctx = conn->ctx; |
| serf__authn_info_t *authn_info; |
| |
| authn_info = apr_hash_get(ctx->server_authn_info, conn->host_url, |
| APR_HASH_KEY_STRING); |
| |
| if (!authn_info) { |
| authn_info = apr_pcalloc(ctx->pool, sizeof(serf__authn_info_t)); |
| apr_hash_set(ctx->server_authn_info, |
| apr_pstrdup(ctx->pool, conn->host_url), |
| APR_HASH_KEY_STRING, authn_info); |
| } |
| |
| return authn_info; |
| } |
| |
| apr_status_t serf__auth_setup_connection(peer_t peer, |
| serf_connection_t *conn) |
| { |
| serf__authn_info_t *authn_info; |
| serf_context_t *ctx = conn->ctx; |
| apr_status_t status = APR_SUCCESS; |
| |
| if (peer == PROXY) { |
| authn_info = &ctx->proxy_authn_info; |
| if (authn_info->scheme) { |
| status = authn_info->scheme->init_conn_func(authn_info->scheme, |
| 407, conn, |
| conn->pool); |
| } |
| } |
| else { |
| authn_info = serf__get_authn_info_for_server(conn); |
| if (authn_info->scheme) { |
| status = authn_info->scheme->init_conn_func(authn_info->scheme, |
| 401, conn, |
| conn->pool); |
| } |
| } |
| |
| return status; |
| } |
| |
| apr_status_t serf__auth_setup_request(peer_t peer, |
| serf_request_t *request, |
| const char *method, |
| const char *uri, |
| serf_bucket_t *hdrs_bkt) |
| { |
| |
| if (peer == PROXY && request->conn->ctx->proxy_authn_info.scheme) { |
| serf__authn_info_t *authn_info = &request->conn->ctx->proxy_authn_info; |
| authn_info->scheme->setup_request_func(authn_info->scheme, |
| peer, 0, |
| request->conn, request, |
| method, uri, |
| hdrs_bkt); |
| } |
| else if (peer == HOST) |
| { |
| serf__authn_info_t *authn_info; |
| |
| authn_info = serf__get_authn_info_for_server(request->conn); |
| if (authn_info->scheme) { |
| authn_info->scheme->setup_request_func(authn_info->scheme, |
| HOST, 0, request->conn, |
| request, method, uri, |
| hdrs_bkt); |
| } |
| } |
| |
| return APR_SUCCESS; |
| } |