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;
}