Skip to content

Commit

Permalink
callback.c: Add singularly provided callback interface.
Browse files Browse the repository at this point in the history
Refactor singularly provided callbacks in various modules
and the core into an abstract interface for the registration,
unregistration, and execution of callbacks that may only be
provided by one module, to simplify callback-related code.

This interface supports both function pointers and struct pointers,
provided by a BBS module.
  • Loading branch information
InterLinked1 committed Jan 27, 2024
1 parent 22140cf commit 90e4bff
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 222 deletions.
127 changes: 30 additions & 97 deletions bbs/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "include/crypt.h"
#include "include/startup.h"
#include "include/cli.h"
#include "include/callback.h"

/*! \note Even though multiple auth providers are technically allowed, in general only 1 should be registered.
* The original thinking behind allowing multiple is to allow alternates for authentication
Expand All @@ -54,116 +55,56 @@ struct auth_provider {

static RWLIST_HEAD_STATIC(providers, auth_provider);

static int (*registerprovider)(struct bbs_node *node) = NULL;
static void *registermod = NULL;
/* Unlike auth providers, there is only 1 user registration handler */
BBS_SINGULAR_CALLBACK_DECLARE(registerprovider, int, struct bbs_node *node);

static int (*pwresethandler)(const char *username, const char *password) = NULL;
static void *pwresetmod = NULL;
/* Only one password reset handler */
BBS_SINGULAR_CALLBACK_DECLARE(pwresethandler, int, const char *username, const char *password);

static struct bbs_user* (*userinfohandler)(const char *username) = NULL;
static void *userinfomod = NULL;
/* Only one user info handler */
BBS_SINGULAR_CALLBACK_DECLARE(userinfohandler, struct bbs_user *, const char *username);

static struct bbs_user** (*userlisthandler)(void) = NULL;
static void *userlistmod = NULL;
/* Only one user list handler */
BBS_SINGULAR_CALLBACK_DECLARE(userlisthandler, struct bbs_user**, void);

int __bbs_register_user_registration_provider(int (*regprovider)(struct bbs_node *node), void *mod)
{
/* Unlike auth providers, there is only 1 user registration handler */
if (registerprovider) {
bbs_error("A user registration provider is already registered.\n");
return -1;
}

registerprovider = regprovider;
registermod = mod;
return 0;
return bbs_singular_callback_register(&registerprovider, regprovider, mod);
}

int bbs_unregister_user_registration_provider(int (*regprovider)(struct bbs_node *node))
{
if (regprovider != registerprovider) {
bbs_error("User registration provider does not match registered provider\n");
return -1;
}

registerprovider = NULL;
registermod = NULL;
return 0;
return bbs_singular_callback_unregister(&registerprovider, regprovider);
}

int __bbs_register_password_reset_handler(int (*handler)(const char *username, const char *password), void *mod)
{
/* Only one password reset handler */
if (pwresethandler) {
bbs_error("A password reset handler is already registered.\n");
return -1;
}

pwresethandler = handler;
pwresetmod = mod;
return 0;
return bbs_singular_callback_register(&pwresethandler, handler, mod);
}

int bbs_unregister_password_reset_handler(int (*handler)(const char *username, const char *password))
{
if (handler != pwresethandler) {
bbs_error("Password reset handler does not match registered handler\n");
return -1;
}

pwresethandler = NULL;
pwresetmod = NULL;
return 0;
return bbs_singular_callback_unregister(&pwresethandler, handler);
}

int __bbs_register_user_info_handler(struct bbs_user* (*handler)(const char *username), void *mod)
{
/* Only one user info handler */
if (userinfohandler) {
bbs_error("A user info handler is already registered.\n");
return -1;
}

userinfohandler = handler;
userinfomod = mod;
return 0;
return bbs_singular_callback_register(&userinfohandler, handler, mod);
}

int bbs_unregister_user_info_handler(struct bbs_user* (*handler)(const char *username))
{
if (handler != userinfohandler) {
bbs_error("User info handler does not match registered handler\n");
return -1;
}

userinfohandler = NULL;
userinfomod = NULL;
return 0;
return bbs_singular_callback_unregister(&userinfohandler, handler);
}

int __bbs_register_user_list_handler(struct bbs_user** (*handler)(void), void *mod)
{
/* Only one user list handler */
if (userlisthandler) {
bbs_error("A user list handler is already registered.\n");
return -1;
}

userlisthandler = handler;
userlistmod = mod;
return 0;
return bbs_singular_callback_register(&userlisthandler, handler, mod);
}

int bbs_unregister_user_list_handler(struct bbs_user** (*handler)(void))
{
if (handler != userlisthandler) {
bbs_error("User list handler does not match registered handler\n");
return -1;
}

userlisthandler = NULL;
userlistmod = NULL;
return 0;
return bbs_singular_callback_unregister(&userlisthandler, handler);
}

int __bbs_register_auth_provider(const char *name, int (*provider)(AUTH_PROVIDER_PARAMS), void *mod)
Expand Down Expand Up @@ -219,7 +160,7 @@ int bbs_num_auth_providers(void)
}
RWLIST_UNLOCK(&providers);

if (!registerprovider) {
if (!bbs_singular_callback_registered(&registerprovider)) {
/* This is kind of kludgy way to check this to warn,
* but that way we don't need to expose this separately to bbs.c,
* since it calls this function to ensure we have auth providers already.
Expand Down Expand Up @@ -704,16 +645,14 @@ int bbs_user_register(struct bbs_node *node)
{
int res;

if (!registerprovider) {
if (bbs_singular_callback_execute_pre(&registerprovider)) {
bbs_error("No user registration provider is currently registered, registration rejected\n");
return -1;
}

node->menu = "Register";
bbs_assert_exists(registermod);
bbs_module_ref(registermod, 2);
res = registerprovider(node);
bbs_module_unref(registermod, 2);
res = BBS_SINGULAR_CALLBACK_EXECUTE(registerprovider)(node);
bbs_singular_callback_execute_post(&registerprovider);
node->menu = NULL;

if (bbs_user_is_registered(node->user)) { /* we might have returned -1 after registration succeeded on timeout */
Expand All @@ -732,15 +671,13 @@ int bbs_user_reset_password(const char *username, const char *password)
{
int res;

if (!pwresethandler) {
if (bbs_singular_callback_execute_pre(&pwresethandler)) {
bbs_error("No password reset handler is currently registered\n");
return -1;
}

bbs_assert_exists(pwresetmod);
bbs_module_ref(pwresetmod, 3);
res = pwresethandler(username, password);
bbs_module_unref(pwresetmod, 3);
res = BBS_SINGULAR_CALLBACK_EXECUTE(pwresethandler)(username, password);
bbs_singular_callback_execute_post(&pwresethandler);

if (!res) {
struct bbs_event event;
Expand All @@ -759,15 +696,13 @@ struct bbs_user *bbs_user_info_by_username(const char *username)
{
struct bbs_user *user = NULL;

if (!userinfohandler) {
if (bbs_singular_callback_execute_pre(&userinfohandler)) {
bbs_error("No user info handler is currently registered\n");
return NULL;
}

bbs_assert_exists(userinfomod);
bbs_module_ref(userinfomod, 4);
user = userinfohandler(username);
bbs_module_unref(userinfomod, 4);
user = BBS_SINGULAR_CALLBACK_EXECUTE(userinfohandler)(username);
bbs_singular_callback_execute_post(&userinfohandler);

return user;
}
Expand All @@ -776,15 +711,13 @@ struct bbs_user **bbs_user_list(void)
{
struct bbs_user **userlist = NULL;

if (!userlisthandler) {
if (bbs_singular_callback_execute_pre(&userlisthandler)) {
bbs_error("No user list handler is currently registered\n");
return NULL;
}

bbs_assert_exists(userlistmod);
bbs_module_ref(userlistmod, 5);
userlist = userlisthandler();
bbs_module_unref(userlistmod, 5);
userlist = BBS_SINGULAR_CALLBACK_EXECUTE(userlisthandler)();
bbs_singular_callback_execute_post(&userlisthandler);

return userlist;
}
Expand Down
135 changes: 135 additions & 0 deletions bbs/callback.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* LBBS -- The Lightweight Bulletin Board System
*
* Copyright (C) 2024, Naveen Albert
*
* Naveen Albert <[email protected]>
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/

/*! \file
*
* \brief Module Callbacks
*
* \author Naveen Albert <[email protected]>
*/

#include "include/bbs.h"

#include "include/callback.h"
#include "include/module.h"

static inline void set_function_pointer(struct bbs_singular_callback *scb, void *ptr)
{
/* We can't directly assign to *scb->func_pointer_ptr, since it's a void pointer,
* and we can't dereference void pointers in C.
* Therefore, cast to some arbitrary but concrete type, so we're allowed
* to make the assignment.
* This works, since all pointers are the same size. */
int **ptraddress = scb->func_pointer_ptr;
*ptraddress = (int*) ptr;
}

static inline int function_pointer_exists(struct bbs_singular_callback *scb)
{
int **ptraddress = scb->func_pointer_ptr;
return *ptraddress ? 1 : 0;
}

/*! \brief Dereference a void pointer. Yeah, you read that right. */
static inline void *function_pointer_value(struct bbs_singular_callback *scb)
{
int **ptraddress = scb->func_pointer_ptr;
return (void*) *ptraddress;
}

int bbs_singular_callback_destroy(struct bbs_singular_callback *scb)
{
pthread_rwlock_destroy(&scb->lock);
return 0;
}

int __bbs_singular_callback_register(struct bbs_singular_callback *scb, void *cbptr, void *mod, const char *file, int line, const char *func)
{
pthread_rwlock_wrlock(&scb->lock);

if (function_pointer_exists(scb)) {
/* Already a callback function registered. */
__bbs_log(LOG_ERROR, 0, file, line, func, "Could not register callback function %p (already one registered)\n", cbptr);
pthread_rwlock_unlock(&scb->lock);
return -1;
}

/* Yes, it may seem a bit dangerous that we're using void for a function pointer,
* but the idea is that we're being called by a function that accepts that function
* as an argument, so it's already passed type checking in that case. */
set_function_pointer(scb, cbptr);
scb->mod = mod;

pthread_rwlock_unlock(&scb->lock);
return 0;
}

int __bbs_singular_callback_unregister(struct bbs_singular_callback *scb, void *cbptr, const char *file, int line, const char *func)
{
pthread_rwlock_wrlock(&scb->lock);
if (cbptr != function_pointer_value(scb)) {
__bbs_log(LOG_ERROR, 0, file, line, func, "Can't unregister callback function %p (not the one registered)\n", cbptr);
pthread_rwlock_unlock(&scb->lock);
return -1;
}

/* Locking is needed essentially to prevent TOCTOU (Time of Check, Time of Use) bugs.
* Without locking, one thread could check if the callback exists, but that callback
* could be unregistered before it actually executes it using the (now NULL) pointer. */
set_function_pointer(scb, NULL);
scb->mod = NULL;

pthread_rwlock_unlock(&scb->lock);
return 0;
}

int bbs_singular_callback_registered(struct bbs_singular_callback *scb)
{
return function_pointer_exists(scb);
}

int __bbs_singular_callback_execute_pre(struct bbs_singular_callback *scb, void *refmod, const char *file, int line, const char *func)
{
pthread_rwlock_rdlock(&scb->lock);
if (!function_pointer_exists(scb)) {
pthread_rwlock_unlock(&scb->lock);
return -1; /* No callback registered */
}
if (scb->mod) {
__bbs_module_ref(scb->mod, 100, refmod, file, line, func);
}

/* We don't actually execute the callback here, because
* the callbacks allowed by this API are completely arbitrary,
* and could have an arbitrary return type and number (and type) of parameters.
* We'll let the caller do that. */

/* We intentionally return without unlocking scb->lock,
* since the call to bbs_singular_callback_execute_post will unlock.
* If scb->mod could never be NULL, we could technically unlock here,
* because the callback cannot be unregistered by the module providing it
* as long as that module is in use (has a positive refcount).
* However, to support the bbs_singular_callback API within the core,
* we just keep holding the read lock. Doesn't hurt anything, since we
* we wouldn't be able to unregister anyways while this is held,
* and a read lock won't inhibit concurrent invocations of the callback. */
return 0;
}

int __bbs_singular_callback_execute_post(struct bbs_singular_callback *scb, void *refmod, const char *file, int line, const char *func)
{
if (scb->mod) {
__bbs_module_unref(scb->mod, 100, refmod, file, line, func);
}
pthread_rwlock_unlock(&scb->lock);
return 0;
}
Loading

0 comments on commit 90e4bff

Please sign in to comment.