Author: brane Date: Sat May 31 21:22:00 2025 New Revision: 1926022 URL: http://svn.apache.org/viewvc?rev=1926022&view=rev Log: On the user-defined-authn branch: Prepare for registering new authentication schemes at runtime.
* CMakeLists.txt (SOURCES): Add auth/auth_user_defined.c. * serf.h (serf_incoming_response_create, serf_config_authn_types): Tweak docstirng. (SERF_AUTHN_NONE et al.): Move to new section, "serf authorization". (SERF_AUTHN_ALL): Widen the value to a whole int's worth. (serf_authn_register_scheme): Declare prototype (work in progress). * auth/auth.h (SERF__AUTHN_USER_FIRST, SERF__AUTHN_USER_LAST): Define limits for the types of user-defined authentication schemes. (serf__authn_scheme_t::type): Change to an unsigned int. (serf__user_authn_scheme_t): New type, descriptor for user-defined authentication shcemes. (serf__authn_user__init_conn, serf__authn_user__handler, serf__authn_user__setup_request, serf__authn_user__validate_response): Authn handlers for same. (serf__authn_user__magic): A magic identifier for serf__user_authn_scheme_t. * auth/auth.c (serf_authn_schemes): Make the table large enough to handle as many authentication schemes as we can support, half of them reserved for user-defined shcemes. (authn_schemes_guard, authn_schemes_guard_pool, init_authn_schemes_guard, lock_autn_schemes, unlock_autn_schemes): Mutex and helpers for serializing access to serf_authn_schemes if APR threads are available. (handle_auth_headers): Lock/unlock serf_authn_schemes. (serf__authn_user__magic): Define here. (user_authn_scheme_type): Tracks the available user-defined scheme types. Access is protected by authn_schemes_guard. (serf_authn_register_scheme): Implement here (work in progress). * auth/auth_user_defined.c: New file. Private implementation wrapper for user-defined authentication schemes. Added: serf/branches/user-defined-authn/auth/auth_user_defined.c (with props) Modified: serf/branches/user-defined-authn/CMakeLists.txt serf/branches/user-defined-authn/auth/auth.c serf/branches/user-defined-authn/auth/auth.h serf/branches/user-defined-authn/serf.h Modified: serf/branches/user-defined-authn/CMakeLists.txt URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/CMakeLists.txt?rev=1926022&r1=1926021&r2=1926022&view=diff ============================================================================== --- serf/branches/user-defined-authn/CMakeLists.txt (original) +++ serf/branches/user-defined-authn/CMakeLists.txt Sat May 31 21:22:00 2025 @@ -151,6 +151,7 @@ list(APPEND SOURCES "auth/auth_spnego.c" "auth/auth_spnego_gss.c" "auth/auth_spnego_sspi.c" + "auth/auth_user_defined.c" "buckets/aggregate_buckets.c" "buckets/allocator.c" "buckets/barrier_buckets.c" Modified: serf/branches/user-defined-authn/auth/auth.c URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/auth/auth.c?rev=1926022&r1=1926021&r2=1926022&view=diff ============================================================================== --- serf/branches/user-defined-authn/auth/auth.c (original) +++ serf/branches/user-defined-authn/auth/auth.c Sat May 31 21:22:00 2025 @@ -24,8 +24,17 @@ #include <apr.h> #include <apr_base64.h> +#include <apr_errno.h> #include <apr_strings.h> #include <apr_lib.h> +#if APR_HAS_THREADS +# include <stdlib.h> +# include <apr_atomic.h> +# include <apr_time.h> +# include <apr_thread_mutex.h> +#endif + +#include <limits.h> /* These authentication schemes are in order of decreasing security, the topmost scheme will be used first when the server supports it. @@ -34,8 +43,12 @@ 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. */ -static const serf__authn_scheme_t *serf_authn_schemes[] = { +#define AUTHN_SCHEMES_SIZE (sizeof(unsigned int) * CHAR_BIT + 1) +static const serf__authn_scheme_t *serf_authn_schemes[AUTHN_SCHEMES_SIZE] = { #ifdef SERF_HAVE_SPNEGO &serf__spnego_authn_scheme, #ifdef WIN32 @@ -48,8 +61,55 @@ static const serf__authn_scheme_t *serf_ /* sentinel */ NULL + + /* The rest of the array will be automagically zero-initialized. */ }; +#if APR_HAS_THREADS +/* Guard access to serf_authn_schemes and user_authn_scheme_type. */ +static apr_thread_mutex_t *authn_schemes_guard; +static apr_pool_t *authn_schemes_guard_pool; +static apr_status_t init_authn_schemes_guard(); +#endif + +static apr_status_t lock_autn_schemes(serf_config_t *config) +{ +#if APR_HAS_THREADS + apr_status_t status = init_authn_schemes_guard(); + 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_autn_schemes(serf_config_t *config) +{ +#if APR_HAS_THREADS + apr_status_t status = init_authn_schemes_guard(); + 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) @@ -85,7 +145,11 @@ static int handle_auth_headers(int code, int scheme_idx; serf_connection_t *conn = request->conn; serf_context_t *ctx = conn->ctx; - apr_status_t status; + apr_status_t status, lock_status; + + lock_status = lock_autn_schemes(conn->config); + if (lock_status) + return lock_status; status = SERF_ERROR_AUTHN_NOT_SUPPORTED; @@ -166,6 +230,10 @@ static int handle_auth_headers(int code, authn_info->failed_authn_types |= scheme->type; } + lock_status = unlock_autn_schemes(conn->config); + if (lock_status) + return lock_status; + return status; } @@ -504,3 +572,164 @@ apr_status_t serf__auth_setup_request(pe return APR_SUCCESS; } + +/* User-defined authentication providers. */ + +/* The magic number in the scheme struct. serfauthnschemes */ +const apr_uint64_t serf__authn_user__magic = 0x5e6fa02895c8e3e5; + +/* The next scheme type for user-defined schemes. */ +static unsigned int user_authn_scheme_type = SERF__AUTHN_USER_FIRST; + +apr_status_t serf_authn_register_scheme(const char *name, + void *baton, + apr_pool_t *pool) +{ + serf__user_authn_scheme_t *user_scheme; + apr_status_t lock_status; + apr_status_t status; + char *key, *cp; + int index; + + if (0 == user_authn_scheme_type) + return APR_ENOSPC; + + user_scheme = apr_palloc(pool, sizeof(*user_scheme)); + user_scheme->magic = serf__authn_user__magic; + user_scheme->baton = baton; + + /* Generate a lower-case key for the scheme. */ + cp = key = apr_pstrdup(pool, name); + while (*cp) { + *cp = apr_tolower(*cp); + ++cp; + } + user_scheme->authn_scheme.name = apr_pstrdup(pool, name); + user_scheme->authn_scheme.key = key; + user_scheme->authn_scheme.type = user_authn_scheme_type; + user_scheme->authn_scheme.init_conn_func = serf__authn_user__init_conn; + user_scheme->authn_scheme.handle_func = serf__authn_user__handler; + user_scheme->authn_scheme.setup_request_func = serf__authn_user__setup_request; + user_scheme->authn_scheme.validate_response_func = serf__authn_user__validate_response; + + lock_status = lock_autn_schemes(NULL /* TODO: whence cometh config? */); + if (lock_status) + return lock_status; + + 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 - 1; ++index) + { + const serf__authn_scheme_t **const slot = &serf_authn_schemes[index]; + if (*slot == NULL) + break; + + if ((*slot)->type & user_authn_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 - 1) { + /* No more space in the table. Not very likely. */ + status = APR_ENOSPC; + goto cleanup; + } + + /* Insert into the slot, and add the sentinel. */ + serf_authn_schemes[index] = &user_scheme->authn_scheme; + serf_authn_schemes[index + 1] = NULL; + + /* Prepare the next scheme type. Will become zero on overflow. */ + user_authn_scheme_type <<= 1; + + cleanup: + lock_status = unlock_autn_schemes(NULL /* TODO: whence cometh config? */); + if (lock_status) + return lock_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 + exaclty once. This includes creating a detached global pool where the mutex + will be allocated ... + + ... yuck. */ +static apr_status_t init_authn_schemes_guard() +{ + static volatile apr_uint32_t global_state = 0; /* uninitialized */ + static const apr_uint32_t uninitialized = 0; + static const apr_uint32_t init_starting = 1; + static const apr_uint32_t init_failed = 2; + static const apr_uint32_t initialized = 3; + + static apr_status_t init_failed_status = APR_EGENERAL; + + apr_allocator_t *allocator; + apr_status_t status; + apr_uint32_t current_state = apr_atomic_cas32(&global_state, + init_starting, + uninitialized); + for (;;) + { + if (current_state == initialized) + return APR_SUCCESS; + + if (current_state == uninitialized) + /* We're the single initializer, run the init code. */ + break; + + if (current_state == init_starting) + { + /* Spin while the initializer is working. */ + apr_sleep(APR_USEC_PER_SEC / 100); + current_state = apr_atomic_cas32(&global_state, + uninitialized, + uninitialized); + continue; + } + + if (current_state == init_failed) + return init_failed_status; + + /* Not reached, can't happen. */ + return APR_EGENERAL; /* FIXME: Just abort()? */ + } + + /* Create a self-contained root pool for the mutex. */ + status = apr_allocator_create(&allocator); + if (status || !allocator) + goto error_return; + + status = apr_pool_create_ex(&authn_schemes_guard_pool, + NULL, NULL, allocator); + if (status || !authn_schemes_guard_pool) + goto error_return; +#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) + goto error_return; + + apr_atomic_cas32(&global_state, initialized, init_starting); + return APR_SUCCESS; + + error_return: + /* We only reach here if something went wrong during initialization. */ + if (status == APR_SUCCESS) /* Not likely, but don't return "OK". */ + status = APR_ENOMEM; /* Probable failures are allocations. */ + init_failed_status = status; + apr_atomic_cas32(&global_state, init_failed, init_starting); + return status; +} +#endif /* APR_HAS_THREADS */ Modified: serf/branches/user-defined-authn/auth/auth.h URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/auth/auth.h?rev=1926022&r1=1926021&r2=1926022&view=diff ============================================================================== --- serf/branches/user-defined-authn/auth/auth.h (original) +++ serf/branches/user-defined-authn/auth/auth.h Sat May 31 21:22:00 2025 @@ -27,6 +27,10 @@ extern "C" { #endif +/* User-defined authentication types */ +#define SERF__AUTHN_USER_FIRST 0x10000u /* Won't work with 16-bit ints... */ +#define SERF__AUTHN_USER_LAST ~(~0u >> 1u) + /** * For each authentication scheme we need a handler function of type * serf__auth_handler_func_t. This function will be called when an @@ -95,7 +99,7 @@ struct serf__authn_scheme_t { const char *key; /* Internal code used for this authn type. */ - int type; + unsigned int type; /* The connection initialization function if any; otherwise, NULL */ serf__init_conn_func_t init_conn_func; @@ -139,6 +143,61 @@ extern const serf__authn_scheme_t serf__ #endif /* SERF_HAVE_SPNEGO */ +/** User-defined authentication scheme handlers */ + +/* This struct extends serf__authn_scheme_t with info needed for + the user-defined scheme implementation. It's essentially a subclass; + per C semantics, the address of the struct is also the address of + its first member, so we can safely put a pointer to this struct + into serf_authn_schemes. */ +typedef struct serf__user_authn_scheme_t serf__user_authn_scheme_t; +struct serf__user_authn_scheme_t { + serf__authn_scheme_t authn_scheme; + + /* The magic number that helps identify this struct. */ + apr_uint64_t magic; + + /* The baton used by the callbacks. */ + void *baton; +}; + + +apr_status_t +serf__authn_user__init_conn(const serf__authn_scheme_t *scheme, + int code, + serf_connection_t *conn, + apr_pool_t *pool); + +apr_status_t +serf__authn_user__handler(const serf__authn_scheme_t *scheme, + int code, + serf_request_t *request, + serf_bucket_t *response, + const char *auth_hdr, + const char *auth_attr, + apr_pool_t *pool); + +apr_status_t +serf__authn_user__setup_request(const serf__authn_scheme_t *scheme, + peer_t peer, + int code, + serf_connection_t *conn, + serf_request_t *request, + const char *method, + const char *uri, + serf_bucket_t *hdrs_bkt); + +apr_status_t +serf__authn_user__validate_response(const serf__authn_scheme_t *scheme, + peer_t peer, + int code, + serf_connection_t *conn, + serf_request_t *request, + serf_bucket_t *response, + apr_pool_t *pool); + +extern const apr_uint64_t serf__authn_user__magic; + #ifdef __cplusplus } #endif Added: serf/branches/user-defined-authn/auth/auth_user_defined.c URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/auth/auth_user_defined.c?rev=1926022&view=auto ============================================================================== --- serf/branches/user-defined-authn/auth/auth_user_defined.c (added) +++ serf/branches/user-defined-authn/auth/auth_user_defined.c Sat May 31 21:22:00 2025 @@ -0,0 +1,94 @@ +/* ==================================================================== + * 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 "apr_errno.h" +#include "auth.h" + + +static const serf__user_authn_scheme_t * +safe_cast_scheme(const serf__authn_scheme_t *scheme) +{ + const serf__user_authn_scheme_t *const user_scheme = (const void *)scheme; + if (scheme->type >= SERF__AUTHN_USER_FIRST + && user_scheme->magic == serf__authn_user__magic) + return user_scheme; + return NULL; +} + +apr_status_t +serf__authn_user__init_conn(const serf__authn_scheme_t *scheme, + int code, + serf_connection_t *conn, + apr_pool_t *pool) +{ + if (!safe_cast_scheme(scheme)) + return APR_EINVAL; + + return APR_ENOTIMPL; +} + +apr_status_t +serf__authn_user__handler(const serf__authn_scheme_t *scheme, + int code, + serf_request_t *request, + serf_bucket_t *response, + const char *auth_hdr, + const char *auth_attr, + apr_pool_t *pool) +{ + if (!safe_cast_scheme(scheme)) + return APR_EINVAL; + + return APR_ENOTIMPL; +} + +apr_status_t +serf__authn_user__setup_request(const serf__authn_scheme_t *scheme, + peer_t peer, + int code, + serf_connection_t *conn, + serf_request_t *request, + const char *method, + const char *uri, + serf_bucket_t *hdrs_bkt) +{ + if (!safe_cast_scheme(scheme)) + return APR_EINVAL; + + return APR_ENOTIMPL; +} + +apr_status_t +serf__authn_user__validate_response(const serf__authn_scheme_t *scheme, + peer_t peer, + int code, + serf_connection_t *conn, + serf_request_t *request, + serf_bucket_t *response, + apr_pool_t *pool) +{ + if (!safe_cast_scheme(scheme)) + return APR_EINVAL; + + return APR_ENOTIMPL; +} Propchange: serf/branches/user-defined-authn/auth/auth_user_defined.c ------------------------------------------------------------------------------ svn:eol-style = native Modified: serf/branches/user-defined-authn/serf.h URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/serf.h?rev=1926022&r1=1926021&r2=1926022&view=diff ============================================================================== --- serf/branches/user-defined-authn/serf.h (original) +++ serf/branches/user-defined-authn/serf.h Sat May 31 21:22:00 2025 @@ -631,7 +631,8 @@ apr_status_t serf_incoming_create2( void *req_setup_baton, apr_pool_t *client_pool); -/* Allows creating a response before the request is completely +/** + * Allows creating a response before the request is completely * read. Will call the response create function if it hasn't * been called yet. * @@ -893,16 +894,10 @@ void serf_config_proxy( serf_context_t *ctx, apr_sockaddr_t *address); -/* Supported authentication types. */ -#define SERF_AUTHN_NONE 0x00 -#define SERF_AUTHN_BASIC 0x01 -#define SERF_AUTHN_DIGEST 0x02 -#define SERF_AUTHN_NTLM 0x04 -#define SERF_AUTHN_NEGOTIATE 0x08 -#define SERF_AUTHN_ALL 0xFF - /** * Define the authentication handlers that serf will try on incoming requests. + * + * @see @c SERF_AUTHN_ALL etc. */ void serf_config_authn_types( serf_context_t *ctx, @@ -948,6 +943,39 @@ serf_bucket_t *serf_request_bucket_reque /** @} */ +/** + * @defgroup serf authentication + * @ingroup serf + * @{ + */ + +/* Supported authentication types. */ +#define SERF_AUTHN_NONE 0x00 /**< Authentication type: None */ +#define SERF_AUTHN_BASIC 0x01 /**< Authentication type: Basic */ +#define SERF_AUTHN_DIGEST 0x02 /**< Authentication type: Digest */ +#define SERF_AUTHN_NTLM 0x04 /**< Authentication type: NTLM */ +#define SERF_AUTHN_NEGOTIATE 0x08 /**< Authentication type: Negotiate */ +#define SERF_AUTHN_ALL ~0x00 /**< All authentication types */ + +/** + * Register an autehtication scheme. + * + * The @a name is the name of the authentication scheme as it appears in the + * authorization headers. It must be a valid token as defined in RFC-9110 + * (see reference, below). + * + * Internal structures related to this provider will be allocated from @a pool, + * take care that its lifetime is long enough. + * + * @see https://www.rfc-editor.org/rfc/rfc9110#section-11.1 + * @since New in 1.4 + */ + +apr_status_t serf_authn_register_scheme(const char *name, + void *baton, + apr_pool_t *pool); + +/** @} */ /** * @defgroup serf buckets