Author: brane Date: Wed Jun 4 15:11:34 2025 New Revision: 1926120 URL: http://svn.apache.org/viewvc?rev=1926120&view=rev Log: On the user-defined-authn branch: Learn to unregister user-defined authentication types, and add tests.
* serf.h (serf_authn_register_scheme): Add a scheme type output parameter. (serf_authn_unregister_scheme): New prototype, currently not published. * auth/auth.h (SERF__AUTHN_USER_FIRST, SERF__AUTHN_USER_LAST): Remove. (serf__authn__unregister_scheme): Declare private prototype instead of the serf_authn_unregister_scheme in serf.h, to make the implementation accessible for testing. (serf__authn_user__type_mask): Declare extern. * auth/auth.c (SERF__AUTHN_USER_FIRST): Define here for different int sizes. (user_authn_scheme_type): Remove. (user_authn_type_mask, user_authn_registered): New global variables. (serf__authn_user__type_mask): Const-ified access to user_authn_type_mask. (find_next_user_scheme_type): New. Bit twiddling. (serf_authn_register_scheme): Add the new 'type' argument. Call find_next_user_scheme_type() to select a type bitmask for the new scheme. (serf__authn__unregister_scheme/serf_authn_unregister_scheme): Implement user-defined scheme unregistration. (init_authn_schemes_guard): On first init, modify user_authn_type_mask so that it doesn't conflict with built-in scheme types. * auth/auth_user_defined.c (safe_cast_scheme): Use serf__authn_user__type_mask to filter by type. * test/test_auth.c (serf_authn_unregister_scheme): Temporary, local implementation of the disabled public function. (test_authn_register_one, test_authn_register_two, test_authn_register_twice, test_authn_unregister_unknown): New test cases. (test_auth): Add the new testcases to the test suite. Modified: serf/branches/user-defined-authn/auth/auth.c serf/branches/user-defined-authn/auth/auth.h serf/branches/user-defined-authn/auth/auth_user_defined.c serf/branches/user-defined-authn/serf.h serf/branches/user-defined-authn/test/test_auth.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=1926120&r1=1926119&r2=1926120&view=diff ============================================================================== --- serf/branches/user-defined-authn/auth/auth.c (original) +++ serf/branches/user-defined-authn/auth/auth.c Wed Jun 4 15:11:34 2025 @@ -66,7 +66,7 @@ static const serf__authn_scheme_t *serf_ }; #if APR_HAS_THREADS -/* Guard access to serf_authn_schemes and user_authn_scheme_type. */ +/* 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(); @@ -575,38 +575,76 @@ apr_status_t serf__auth_setup_request(pe /* 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 next scheme type for user-defined schemes. */ -static unsigned int user_authn_scheme_type = SERF__AUTHN_USER_FIRST; +/* The available user-defined scheme types. This is a bit mask based on the + first scheme, later modified to account for any overlfow 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)); +} apr_status_t serf_authn_register_scheme(const char *name, void *baton, - apr_pool_t *pool) + apr_pool_t *result_pool, + int *type) { serf__user_authn_scheme_t *user_scheme; apr_status_t lock_status; apr_status_t status; - char *key, *cp; + unsigned int scheme_type; + const char *key; + char *cp; int index; - if (0 == user_authn_scheme_type) - return APR_ENOSPC; - - user_scheme = apr_palloc(pool, sizeof(*user_scheme)); + *type = SERF_AUTHN_NONE; + user_scheme = apr_palloc(result_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); + key = cp = apr_pstrdup(result_pool, name); while (*cp) { *cp = apr_tolower(*cp); ++cp; } - user_scheme->authn_scheme.name = apr_pstrdup(pool, name); + user_scheme->authn_scheme.name = apr_pstrdup(result_pool, name); user_scheme->authn_scheme.key = key; - user_scheme->authn_scheme.type = user_authn_scheme_type; + /* user_scheme->authn_scheme.type = ?; Will be updated later, under lock. */ 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; @@ -616,18 +654,23 @@ apr_status_t serf_authn_register_scheme( if (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 - 1; ++index) { - const serf__authn_scheme_t **const slot = &serf_authn_schemes[index]; - if (*slot == NULL) + 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)) { + if (slot->type & scheme_type || 0 == strcmp(slot->key, key)) { /* We somehow managed to register the same thing twice. */ status = APR_EEXIST; goto cleanup; @@ -640,11 +683,80 @@ apr_status_t serf_authn_register_scheme( } /* Insert into the slot, and add the sentinel. */ + user_scheme->authn_scheme.type = scheme_type; serf_authn_schemes[index] = &user_scheme->authn_scheme; serf_authn_schemes[index + 1] = NULL; + *type = scheme_type; + + /* Add the scheme type to the registered mask. */ + user_authn_registered |= scheme_type; + + cleanup: + lock_status = unlock_autn_schemes(NULL /* TODO: whence cometh config? */); + if (lock_status) + return lock_status; + return status; +} + +#ifdef SERF__AUTHN__HAVE_UNREGISTER +apr_status_t serf_authn_unregister_scheme(int type, + const char *name, + apr_pool_t *scratch_pool) +#else +apr_status_t serf__authn__unregister_scheme(int type, + const char *name, + apr_pool_t *scratch_pool) +{ + const unsigned int scheme_type = type; + apr_status_t lock_status; + apr_status_t status; + const char *key; + char *cp; + int index; + + /* Generate a lower-case key for the scheme. */ + key = cp = apr_pstrdup(scratch_pool, name); + while (*cp) { + *cp = apr_tolower(*cp); + ++cp; + } + + lock_status = lock_autn_schemes(NULL /* TODO: whence cometh config? */); + if (lock_status) + return lock_status; + + status = APR_SUCCESS; + + /* Look for the scheme in the table. */ + for (index = 0; index < AUTHN_SCHEMES_SIZE - 1; ++index) + { + const serf__authn_scheme_t *const slot = serf_authn_schemes[index]; + if (slot == NULL) { + status = APR_ENOENT; + goto cleanup; + } - /* Prepare the next scheme type. Will become zero on overflow. */ - user_authn_scheme_type <<= 1; + if (slot->type == scheme_type && 0 == strcmp(slot->key, key)) + break; + } + if (index >= AUTHN_SCHEMES_SIZE - 1) { + /* 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 - 1; ++index) + { + serf_authn_schemes[index] = serf_authn_schemes[index + 1]; + if (serf_authn_schemes[index] == NULL) + break; + } + + /* Remove the scheme type from the registered mask. */ + user_authn_registered &= ~scheme_type; cleanup: lock_status = unlock_autn_schemes(NULL /* TODO: whence cometh config? */); @@ -652,6 +764,7 @@ apr_status_t serf_authn_register_scheme( return lock_status; return status; } +#endif /* SERF__AUTHN__HAVE_UNREGISTER */ #if APR_HAS_THREADS @@ -671,6 +784,8 @@ static apr_status_t init_authn_schemes_g static apr_status_t init_failed_status = APR_EGENERAL; + int scheme_idx; + unsigned int builtin_types; apr_allocator_t *allocator; apr_status_t status; apr_uint32_t current_state = apr_atomic_cas32(&global_state, @@ -721,6 +836,13 @@ static apr_status_t init_authn_schemes_g if (status || !authn_schemes_guard) goto error_return; + /* Adjust the mask of available user-defined schemes. */ + builtin_types = 0; + for (scheme_idx = 0; serf_authn_schemes[scheme_idx]; ++scheme_idx) + builtin_types |= serf_authn_schemes[scheme_idx]->type; + user_authn_type_mask &= ~builtin_types; + + /* Release the spinlock. */ apr_atomic_cas32(&global_state, initialized, init_starting); return APR_SUCCESS; Modified: serf/branches/user-defined-authn/auth/auth.h URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/auth/auth.h?rev=1926120&r1=1926119&r2=1926120&view=diff ============================================================================== --- serf/branches/user-defined-authn/auth/auth.h (original) +++ serf/branches/user-defined-authn/auth/auth.h Wed Jun 4 15:11:34 2025 @@ -27,10 +27,6 @@ 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 @@ -161,6 +157,12 @@ struct serf__user_authn_scheme_t { void *baton; }; +#ifndef SERF__AUTHN__HAVE_UNREGISTER +/* Declare the prototype for the internal unregister implementation */ +apr_status_t serf__authn__unregister_scheme(int type, + const char *name, + apr_pool_t *scratch_pool); +#endif apr_status_t serf__authn_user__init_conn(const serf__authn_scheme_t *scheme, @@ -197,6 +199,7 @@ serf__authn_user__validate_response(cons apr_pool_t *pool); extern const apr_uint64_t serf__authn_user__magic; +extern const unsigned int *const serf__authn_user__type_mask; #ifdef __cplusplus } Modified: 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=1926120&r1=1926119&r2=1926120&view=diff ============================================================================== --- serf/branches/user-defined-authn/auth/auth_user_defined.c (original) +++ serf/branches/user-defined-authn/auth/auth_user_defined.c Wed Jun 4 15:11:34 2025 @@ -29,7 +29,7 @@ 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 + if (scheme->type & *serf__authn_user__type_mask && user_scheme->magic == serf__authn_user__magic) return user_scheme; return NULL; Modified: serf/branches/user-defined-authn/serf.h URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/serf.h?rev=1926120&r1=1926119&r2=1926120&view=diff ============================================================================== --- serf/branches/user-defined-authn/serf.h (original) +++ serf/branches/user-defined-authn/serf.h Wed Jun 4 15:11:34 2025 @@ -960,12 +960,17 @@ serf_bucket_t *serf_request_bucket_reque /** * Register an autehtication scheme. * + * The number returned in @a type can be used as a bit mask in + * serf_config_authn_types(). If an error occurs during registration, + * @a type will be set to @c SERF_AUTHN_NONE. + * * 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. + * Internal structures related to this provider will be allocated from + * @a result_pool, so take care that it lives as long as the autehtication + * scheme is registered. * * @see https://www.rfc-editor.org/rfc/rfc9110#section-11.1 * @since New in 1.4 @@ -973,7 +978,31 @@ serf_bucket_t *serf_request_bucket_reque apr_status_t serf_authn_register_scheme(const char *name, void *baton, - apr_pool_t *pool); + apr_pool_t *result_pool, + int *type); + +#ifdef SERF__AUTHN__HAVE_UNREGISTER +/* FIXME: Think some more about whether unregistering schemes makes sense. */ +/** + * Unregister an uthentication scheme. + * + * Removes the scheme, identified by @a type that was returned from and + * @a name that was supplied to serf_authn_register_scheme(), from the + * list of supported authentication schemes. Uses @a scratch_pool for + * temporary allocations; this pool can be destroyed afterthe function + * returns. + * + * Unregistering a scheme should be avoided while requests that might + * use the scheme are in flight. + * + * So in short, don't use this function at all...? + * + * @since New in 1.4 + */ +apr_status_t serf_authn_unregister_scheme(int type, + const char *name, + apr_pool_t *scratch_pool); +#endif /* SERF__AUTHN__HAVE_UNREGISTER */ /** @} */ Modified: serf/branches/user-defined-authn/test/test_auth.c URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/test/test_auth.c?rev=1926120&r1=1926119&r2=1926120&view=diff ============================================================================== --- serf/branches/user-defined-authn/test/test_auth.c (original) +++ serf/branches/user-defined-authn/test/test_auth.c Wed Jun 4 15:11:34 2025 @@ -511,6 +511,92 @@ static void test_auth_on_HEAD(CuTest *tc CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); } + +#ifndef SERF__AUTHN__HAVE_UNREGISTER +/* FIXME: Temporary implementation of unregister function. */ +apr_status_t serf__authn__unregister_scheme(int type, + const char *name, + apr_pool_t *scratch_pool); +static apr_status_t serf_authn_unregister_scheme(int type, const char *name, + apr_pool_t *pool) +{ + return serf__authn__unregister_scheme(type, name, pool); +} +#endif + +static void test_authn_register_one(CuTest *tc) +{ + test_baton_t *tb = tc->testBaton; + void *const baton = (void *)0xdeadbeef; + apr_status_t status; + int type; + + /* Register an authentication scheme */ + status = serf_authn_register_scheme("Fizzle", baton, tb->pool, &type); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertTrue(tc, type != SERF_AUTHN_NONE); + + /* Unregister the scheme */ + status =serf_authn_unregister_scheme(type, "fiZzlE", tb->pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); +} + +static void test_authn_register_two(CuTest *tc) +{ + test_baton_t *tb = tc->testBaton; + void *const baton1 = (void *)0xdeadbeef; + void *const baton2 = (void *)0xbadf00d; + apr_status_t status; + int type1, type2; + + /* Register the schemes */ + status = serf_authn_register_scheme("Tweedledee", baton1, tb->pool, &type1); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertTrue(tc, type1 != SERF_AUTHN_NONE); + + status = serf_authn_register_scheme("Tweedledum", baton2, tb->pool, &type2); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertTrue(tc, type2 != SERF_AUTHN_NONE); + CuAssertTrue(tc, type2 != type1); + + /* Unregister the schemes */ + status =serf_authn_unregister_scheme(type1, "tweedleDee", tb->pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + status =serf_authn_unregister_scheme(type2, "tweedleDum", tb->pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); +} + +static void test_authn_register_twice(CuTest *tc) +{ + test_baton_t *tb = tc->testBaton; + void *const baton = (void *)0xdeadbeef; + apr_status_t status; + int type, epyt; + + /* Register an authentication scheme */ + status = serf_authn_register_scheme("Tweens", baton, tb->pool, &type); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertTrue(tc, type != SERF_AUTHN_NONE); + + status = serf_authn_register_scheme("Tweens", baton, tb->pool, &epyt); + CuAssertIntEquals(tc, APR_EEXIST, status); + CuAssertTrue(tc, epyt == SERF_AUTHN_NONE); + + /* Unregister the scheme */ + status =serf_authn_unregister_scheme(type, "Tweens", tb->pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); +} + +static void test_authn_unregister_unknown(CuTest *tc) +{ + test_baton_t *tb = tc->testBaton; + apr_status_t status; + + /* Unregister the scheme */ + status = serf_authn_unregister_scheme(1 << 15, "Neverland", tb->pool); + CuAssertIntEquals(tc, APR_ENOENT, status); +} + /*****************************************************************************/ CuSuite *test_auth(void) { @@ -527,6 +613,9 @@ CuSuite *test_auth(void) SUITE_ADD_TEST(suite, test_basic_switch_realms); SUITE_ADD_TEST(suite, test_digest_switch_realms); SUITE_ADD_TEST(suite, test_auth_on_HEAD); - + SUITE_ADD_TEST(suite, test_authn_register_one); + SUITE_ADD_TEST(suite, test_authn_register_two); + SUITE_ADD_TEST(suite, test_authn_register_twice); + SUITE_ADD_TEST(suite, test_authn_unregister_unknown); return suite; }