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


Reply via email to