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


Reply via email to