| /* ==================================================================== |
| * 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 <limits.h> |
| |
| #include <apr.h> |
| #include <apr_base64.h> |
| #include <apr_errno.h> |
| #include <apr_strings.h> |
| #include <apr_lib.h> |
| #if APR_HAS_THREADS |
| # include <apr_thread_mutex.h> |
| #endif |
| |
| #include "serf.h" |
| #include "serf_private.h" |
| #include "auth.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. |
| |
| The size of the array is the number of bits in serf__authn_scheme_t::type, |
| plus one slot for the NULL sentinel. |
| */ |
| #define AUTHN_SCHEMES_SIZE (sizeof(unsigned int) * CHAR_BIT) |
| static const serf__authn_scheme_t *serf_authn_schemes[AUTHN_SCHEMES_SIZE + 1] = { |
| #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 |
| |
| /* The rest of the array will be automagically zero-initialized. */ |
| }; |
| |
| #if APR_HAS_THREADS |
| /* Guard access to serf_authn_schemes and related global data. */ |
| static apr_thread_mutex_t *authn_schemes_guard; |
| static apr_pool_t *authn_schemes_guard_pool; |
| static apr_status_t init_authn_schemes_guard(serf_config_t *config); |
| #endif |
| |
| static apr_status_t lock_authn_schemes(serf_config_t *config) |
| { |
| #if APR_HAS_THREADS |
| apr_status_t status = init_authn_schemes_guard(config); |
| if (status == APR_SUCCESS) { |
| status = apr_thread_mutex_lock(authn_schemes_guard); |
| if (status) { |
| char buffer[256]; |
| serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, config, |
| "Lock authn schemes: %s\n", |
| apr_strerror(status, buffer, sizeof(buffer))); |
| } |
| } |
| return status; |
| #else |
| return APR_SUCCESS; |
| #endif |
| } |
| |
| static apr_status_t unlock_authn_schemes(serf_config_t *config) |
| { |
| #if APR_HAS_THREADS |
| apr_status_t status = init_authn_schemes_guard(config); |
| if (status == APR_SUCCESS) { |
| status = apr_thread_mutex_unlock(authn_schemes_guard); |
| if (status) { |
| char buffer[256]; |
| serf__log(LOGLVL_ERROR, LOGCOMP_AUTHN, __FILE__, config, |
| "Unlock authn schemes: %s\n", |
| apr_strerror(status, buffer, sizeof(buffer))); |
| } |
| } |
| return status; |
| #else |
| return APR_SUCCESS; |
| #endif |
| } |
| |
| |
| /* 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, lock_status; |
| |
| lock_status = lock_authn_schemes(conn->config); |
| if (lock_status) |
| return lock_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 == SERF_AUTHN_CODE_HOST) { |
| 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; |
| } |
| |
| lock_status = unlock_authn_schemes(conn->config); |
| if (lock_status) |
| return lock_status; |
| |
| 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; |
| apr_size_t auth_attr_len; |
| char *auth_name; |
| |
| /* We're only interested in xxxx-Authenticate headers. */ |
| if (strcasecmp(key, ab->header) != 0) |
| return 0; |
| |
| /* Extract the authentication scheme name. */ |
| auth_attr_len = strcspn(header, " "); |
| auth_name = apr_pstrmemdup(ab->pool, header, auth_attr_len); |
| |
| /* Convert scheme name to lower case to enable case insensitive matching. */ |
| serf__tolower_inplace(auth_name, auth_attr_len); |
| 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 == SERF_AUTHN_CODE_HOST || code == SERF_AUTHN_CODE_PROXY) { |
| serf_bucket_t *hdrs; |
| auth_baton_t ab = { 0 }; |
| |
| ab.hdrs = apr_hash_make(pool); |
| ab.pool = pool; |
| |
| if (code == SERF_AUTHN_CODE_HOST) |
| 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 == SERF_AUTHN_CODE_HOST ? "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 == SERF_AUTHN_CODE_HOST || sl.code == SERF_AUTHN_CODE_PROXY) { |
| /* 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 response 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; |
| int int_data_len; |
| char *ptr; |
| |
| |
| SERF__POSITIVE_TO_INT(int_data_len, apr_size_t, data_len); |
| |
| encoded_len = apr_base64_encode_len(int_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, int_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, |
| SERF_AUTHN_CODE_PROXY, |
| 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, |
| SERF_AUTHN_CODE_HOST, |
| 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; |
| } |
| |
| /* User-defined authentication providers. */ |
| |
| /* The type range for user-defined schemes: */ |
| #if UINT_MAX >= 0xFFFFFFFFFFFFFFFF /* Integers are at least 64 bits wide. */ |
| #define SERF__AUTHN_USER_FIRST 0x80000000000u /* 43 built-in + 21 user. */ |
| #elif UINT_MAX >= 0xFFFFFFFF /* Integers are at least 32 bits wide. */ |
| #define SERF__AUTHN_USER_FIRST 0x200000u /* 21 built-in + 11 user. */ |
| #else /* Integers are at least 16 bits wide. */ |
| #define SERF__AUTHN_USER_FIRST 0x800u /* 11 built-in + 5 user. */ |
| #endif |
| |
| /* The magic number in the scheme struct. serfauthnschemes */ |
| const apr_uint64_t serf__authn_user__magic = 0x5e6fa02895c8e3e5; |
| |
| /* The available user-defined scheme types. This is a bit mask based on the |
| first scheme, later modified to account for any overflow from the built-in |
| schemes list (not likely, but safey). Should be const, but it's modified |
| during one-time initialization. |
| |
| Access is controlled by authn_schemes_guard. */ |
| static unsigned int user_authn_type_mask = ~(SERF__AUTHN_USER_FIRST - 1u); |
| |
| /* Access to the above from other modules, made const. */ |
| const unsigned int *const serf__authn_user__type_mask = &user_authn_type_mask; |
| |
| /* The currently registered user-defined scheme types. |
| |
| Access is controlled by authn_schemes_guard. */ |
| static unsigned int user_authn_registered = 0; |
| |
| /* Find the next available bit for a user-defined authentication |
| scheme. Computes an available bit, using user_authn_type_mask |
| and user_authn_registered. Returns 0 if there's no more room for |
| user-defined schemes. |
| |
| The authn_schemes_guard mutex must be locked. */ |
| static unsigned int find_next_user_scheme_type(void) |
| { |
| const unsigned int avail = user_authn_type_mask & ~user_authn_registered; |
| |
| /* For the source of this horrible hack, see: |
| https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan |
| |
| return avail & ~(avail & (avail - 1)); |
| |
| Along comes clang and optimizes the above to just two instructions... */ |
| return avail & -avail; |
| } |
| |
| /* Pool cleanup handler for user-defined schemes. */ |
| static apr_status_t cleanup_user_scheme(void* data) |
| { |
| const serf__authn_scheme_t *const authn_scheme = data; |
| const serf__authn_scheme_t *slot = NULL; |
| int index; |
| |
| apr_status_t lock_status = lock_authn_schemes(NULL); |
| if (lock_status) |
| return lock_status; |
| |
| /* Find the scheme in the table. */ |
| for (index = 0; index < AUTHN_SCHEMES_SIZE; ++index) { |
| slot = serf_authn_schemes[index]; |
| if (slot == NULL || slot == authn_scheme) |
| break; |
| } |
| |
| if (slot != NULL) { |
| /* Remove the scheme type from the registered mask. */ |
| user_authn_registered &= ~slot->type; |
| |
| /* Remove the scheme from the table, moving the other |
| schemes back over its position. */ |
| for (; slot != NULL && index < AUTHN_SCHEMES_SIZE; ++index) |
| serf_authn_schemes[index] = slot = serf_authn_schemes[index + 1]; |
| } |
| |
| lock_status = unlock_authn_schemes(NULL); |
| if (lock_status) |
| return lock_status; |
| return APR_SUCCESS; |
| } |
| |
| apr_status_t serf_authn_register_scheme( |
| int *type, |
| serf_context_t *ctx, |
| const char *name, void *baton, int flags, |
| serf_authn_init_conn_func_t init_conn, |
| serf_authn_get_realm_func_t get_realm, |
| serf_authn_handle_func_t handle, |
| serf_authn_setup_request_func_t setup_request, |
| serf_authn_validate_response_func_t validate_response, |
| apr_pool_t *result_pool) |
| { |
| serf_config_t *const config = ctx->config; |
| serf__authn_scheme_t *authn_scheme; |
| apr_status_t lock_status; |
| apr_status_t status; |
| unsigned int scheme_type; |
| const char *key; |
| int index; |
| |
| serf__log(LOGLVL_INFO, LOGCOMP_AUTHN, __FILE__, config, |
| "Registering user-defined scheme %s", name); |
| |
| *type = SERF_AUTHN_NONE; |
| authn_scheme = apr_palloc(result_pool, sizeof(*authn_scheme)); |
| |
| /* Generate a lower-case key for the scheme. */ |
| key = serf__tolower(name, result_pool); |
| |
| authn_scheme->name = apr_pstrdup(result_pool, name); |
| authn_scheme->key = key; |
| /* user_scheme->type = ?; Will be updated later, under lock. */ |
| authn_scheme->init_conn_func = serf__authn_user__init_conn; |
| authn_scheme->handle_func = serf__authn_user__handle; |
| authn_scheme->setup_request_func = serf__authn_user__setup_request; |
| authn_scheme->validate_response_func = serf__authn_user__validate_response; |
| |
| /* User-defined scheme data. */ |
| authn_scheme->user_magic = serf__authn_user__magic; |
| authn_scheme->user_pool = result_pool; |
| authn_scheme->user_flags = flags; |
| authn_scheme->user_baton = baton; |
| authn_scheme->user_init_conn_func = init_conn; |
| authn_scheme->user_get_realm_func = get_realm; |
| authn_scheme->user_handle_func = handle; |
| authn_scheme->user_setup_request_func = setup_request; |
| authn_scheme->user_validate_response_func = validate_response; |
| |
| lock_status = lock_authn_schemes(config); |
| if (lock_status) { |
| serf__log_nopref(LOGLVL_INFO, LOGCOMP_AUTHN, config, |
| ", lock failed %d\n", lock_status); |
| return lock_status; |
| } |
| |
| scheme_type = find_next_user_scheme_type(); |
| if (!scheme_type) { |
| status = APR_ENOSPC; |
| goto cleanup; |
| } |
| |
| status = APR_SUCCESS; |
| |
| /* Scan the array for a free slot and also check that this |
| scheme type hasn't been used yet. */ |
| for (index = 0; index < AUTHN_SCHEMES_SIZE; ++index) |
| { |
| const serf__authn_scheme_t *const slot = serf_authn_schemes[index]; |
| if (slot == NULL) |
| break; |
| |
| if (slot->type & scheme_type || 0 == strcmp(slot->key, key)) { |
| /* We somehow managed to register the same thing twice. */ |
| status = APR_EEXIST; |
| goto cleanup; |
| } |
| } |
| if (index >= AUTHN_SCHEMES_SIZE) { |
| /* No more space in the table. Shouldn't be possible; if we got this |
| far, we have a valid scheme type, and as long as we have a scheme |
| type, there should be space for the scheme in the table. */ |
| status = APR_ENOSPC; |
| goto cleanup; |
| } |
| |
| /* Insert into the slot, and add the sentinel. */ |
| authn_scheme->type = scheme_type; |
| serf_authn_schemes[index] = authn_scheme; |
| serf_authn_schemes[index + 1] = NULL; |
| apr_pool_cleanup_register(authn_scheme->user_pool, authn_scheme, |
| cleanup_user_scheme, apr_pool_cleanup_null); |
| *type = scheme_type; |
| |
| /* Add the scheme type to the registered mask. */ |
| user_authn_registered |= scheme_type; |
| |
| cleanup: |
| lock_status = unlock_authn_schemes(config); |
| if (lock_status) { |
| serf__log_nopref(LOGLVL_INFO, LOGCOMP_AUTHN, config, |
| ", unlock failed %d, status %d\n", |
| lock_status, status); |
| return lock_status; |
| } |
| |
| serf__log_nopref(LOGLVL_INFO, LOGCOMP_AUTHN, config, |
| ", status %d\n", status); |
| return status; |
| } |
| |
| apr_status_t serf_authn_unregister_scheme(serf_context_t *ctx, |
| int type, |
| const char *name, |
| apr_pool_t *scratch_pool) |
| { |
| serf_config_t *const config = ctx->config; |
| const serf__authn_scheme_t *authn_scheme = NULL; |
| const unsigned int scheme_type = type; |
| apr_status_t lock_status; |
| apr_status_t status; |
| const char *key; |
| int index; |
| |
| serf__log(LOGLVL_INFO, LOGCOMP_AUTHN, __FILE__, config, |
| "Unregistering user-defined scheme %s", name); |
| |
| /* Generate a lower-case key for the scheme. */ |
| key = serf__tolower(name, scratch_pool); |
| |
| lock_status = lock_authn_schemes(config); |
| if (lock_status) { |
| serf__log_nopref(LOGLVL_INFO, LOGCOMP_AUTHN, config, |
| ", lock failed %d\n", lock_status); |
| return lock_status; |
| } |
| |
| status = APR_SUCCESS; |
| |
| /* Look for the scheme in the table. */ |
| for (index = 0; index < AUTHN_SCHEMES_SIZE; ++index) |
| { |
| authn_scheme = serf_authn_schemes[index]; |
| if (authn_scheme == NULL) { |
| status = APR_ENOENT; |
| goto cleanup; |
| } |
| |
| if (authn_scheme->type == scheme_type |
| && 0 == strcmp(authn_scheme->key, key)) |
| break; |
| } |
| if (index >= AUTHN_SCHEMES_SIZE) { |
| /* The scheme wasn't registered */ |
| status = APR_ENOENT; |
| goto cleanup; |
| } |
| |
| /* Move all the following schemes back. This is a memmove, but |
| it doesn't make much sense to use that since we don't knkow |
| how many schemes are left after this one. */ |
| for (; index < AUTHN_SCHEMES_SIZE; ++index) |
| { |
| serf_authn_schemes[index] = serf_authn_schemes[index + 1]; |
| if (serf_authn_schemes[index] == NULL) |
| break; |
| } |
| |
| apr_pool_cleanup_kill(authn_scheme->user_pool, authn_scheme, |
| cleanup_user_scheme); |
| |
| /* Remove the scheme type from the registered mask. */ |
| user_authn_registered &= ~scheme_type; |
| |
| cleanup: |
| lock_status = unlock_authn_schemes(config); |
| if (lock_status) { |
| serf__log_nopref(LOGLVL_INFO, LOGCOMP_AUTHN, config, |
| ", unlock failed %d, status %d\n", |
| lock_status, status); |
| return lock_status; |
| } |
| |
| serf__log_nopref(LOGLVL_INFO, LOGCOMP_AUTHN, config, |
| ", status %d\n", status); |
| return status; |
| } |
| |
| |
| #if APR_HAS_THREADS |
| /* Unfortunately APR does not provide a statically-initialized mutex type, so we |
| use a simple spinlock to make sure that authn_schemes_guard is initialized |
| exactly once. This includes creating a detached global pool where the mutex |
| will be allocated ... |
| |
| ... yuck. */ |
| static apr_status_t do_init_authn_schemes_guard(void *baton) |
| { |
| serf_config_t *const config = baton; |
| unsigned int builtin_types; |
| apr_allocator_t *allocator; |
| apr_status_t status; |
| int index; |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_AUTHN, __FILE__, config, |
| "Initializing authn schemes mutex"); |
| |
| /* Create a self-contained root pool for the mutex. */ |
| status = apr_allocator_create(&allocator); |
| if (status || !allocator) { |
| status = status ? status : APR_ENOMEM; |
| goto finish; |
| } |
| |
| status = apr_pool_create_ex(&authn_schemes_guard_pool, |
| NULL, NULL, allocator); |
| if (status || !authn_schemes_guard_pool) { |
| status = status ? status : APR_ENOMEM; |
| goto finish; |
| } |
| #if APR_POOL_DEBUG |
| apr_pool_tag(authn_schemes_guard_pool, "serf-authn-guard"); |
| #endif |
| |
| status = apr_thread_mutex_create(&authn_schemes_guard, |
| APR_THREAD_MUTEX_DEFAULT, |
| authn_schemes_guard_pool); |
| if (status || !authn_schemes_guard) { |
| status = status ? status : APR_ENOMEM; |
| goto finish; |
| } |
| |
| /* Adjust the mask of available user-defined schemes. */ |
| builtin_types = 0; |
| for (index = 0; serf_authn_schemes[index]; ++index) |
| builtin_types |= serf_authn_schemes[index]->type; |
| user_authn_type_mask &= ~builtin_types; |
| |
| finish: |
| serf__log_nopref(LOGLVL_DEBUG, LOGCOMP_AUTHN, config, |
| ", status %d\n", status); |
| return status; |
| } |
| |
| static apr_status_t init_authn_schemes_guard(serf_config_t *config) |
| { |
| SERF__DECLARE_STATIC_INIT_ONCE_CONTEXT(init_ctx); |
| return serf__init_once(&init_ctx, do_init_authn_schemes_guard, config); |
| } |
| #endif /* APR_HAS_THREADS */ |