Skip to content

Commit

Permalink
io.c: Add abstracted I/O transformation interface.
Browse files Browse the repository at this point in the history
Add an I/O abstraction interface, to allow for decoupling
between modules that require transformations and the
transformations themselves, such as TLS, concurrently,
but potentially other transformations in the future,
such as compression.

This also has the nice side effect of simplifying various
code throughout various BBS modules, since they no longer
need to be concerned with the semantics of TLS. Additionally,
TLS support is moved out of the core into its own module,
io_tls (in a new io subdirectory), increasing modularity.
For now, the core still needs to be linked with -lcrypto,
since hash.c uses some hash functions in OpenSSL; however,
the core no longer requires -lssl, and only io_tls needs
to be linked with -lssl.

In the future, additional I/O transformation modules can
be added in a similar manner that will allow for easily
updating node->rfd and node->wfd and using abstracted
file-descriptor transformation phases to transform the
output on its way from an application to the actual network socket.
  • Loading branch information
InterLinked1 committed Apr 9, 2024
1 parent 8f15550 commit 879a730
Show file tree
Hide file tree
Showing 35 changed files with 693 additions and 430 deletions.
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export UNAME_S

LIBS = -lrt -lm -ldl

LIBS += -lbfd -lcrypt -lssl -lcrypto -lcurl -lreadline -luuid -rdynamic
# -lcrypto needed for SHA1_Init in hash.c
LIBS += -lbfd -lcrypt -lcrypto -lcurl -lreadline -luuid -rdynamic

ifeq ($(UNAME_S),Linux)
LIBS += -lbsd -lcap
Expand All @@ -40,7 +41,7 @@ INSTALL = install
# Uncomment this to see all build commands instead of 'quiet' output
#NOISY_BUILD=yes

MOD_SUBDIR:=doors modules nets
MOD_SUBDIR:=doors io modules nets
SUBDIRS:=$(MOD_SUBDIR)
SUBDIRS_INSTALL:=$(SUBDIRS:%=%-install)
SUBDIRS_CLEAN:=$(SUBDIRS:%=%-clean)
Expand Down
4 changes: 0 additions & 4 deletions bbs/bbs.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
#include "include/group.h"
#include "include/variables.h"
#include "include/startup.h"
#include "include/tls.h"
#include "include/event.h"
#include "include/test.h"
#include "include/transfer.h"
Expand Down Expand Up @@ -584,7 +583,6 @@ static void bbs_shutdown(void)

bbs_history_shutdown(); /* Free history. Must be done in the core, not by mod_sysop, since this may only be called once. */
bbs_curl_shutdown(); /* Clean up cURL */
ssl_server_shutdown(); /* Shut down SSL/TLS */
login_cache_cleanup(); /* Clean up any remaining cached logins */
username_cache_flush(); /* Clean up any cached username mappings */
bbs_free_menus(); /* Clean up menus */
Expand Down Expand Up @@ -1015,8 +1013,6 @@ int main(int argc, char *argv[])
CHECK_INIT(bbs_init_doors());
CHECK_INIT(bbs_init_tests());

ssl_server_init(); /* If this fails for some reason, that's okay. Other failures will ensue, but this is not fatal. */

CHECK_INIT(bbs_curl_init());
CHECK_INIT(bbs_history_init());

Expand Down
27 changes: 4 additions & 23 deletions bbs/hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
#include "include/hash.h"

/* For hashing: */
#ifdef HAVE_OPENSSL
#include <openssl/evp.h>
#include <openssl/hmac.h>
#endif

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#undef sprintf

Expand All @@ -34,7 +36,6 @@
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" /* SHA256_Init, SHA256_Update, SHA256_Final deprecated in OpenSSL 3.0 */
int hash_sha256(const char *s, char buf[SHA256_BUFSIZE])
{
#ifdef HAVE_OPENSSL
int i;
unsigned char hash[SHA256_DIGEST_LENGTH];

Expand All @@ -49,17 +50,10 @@ int hash_sha256(const char *s, char buf[SHA256_BUFSIZE])
}
buf[SHA256_BUFSIZE - 1] = '\0';
return 0;
#else
UNUSED(s);
UNUSED(buf);
UNUSED(len);
return -1;
#endif
}

int hash_sha1(const char *s, char buf[SHA1_BUFSIZE])
{
#ifdef HAVE_OPENSSL
int i;
unsigned char hash[SHA_DIGEST_LENGTH];

Expand All @@ -74,17 +68,10 @@ int hash_sha1(const char *s, char buf[SHA1_BUFSIZE])
}
buf[SHA1_BUFSIZE - 1] = '\0';
return 0;
#else
UNUSED(s);
UNUSED(buf);
UNUSED(len);
return -1;
#endif
}

int hash_sha1_bytes(const char *s, char buf[SHA1_LEN])
{
#ifdef HAVE_OPENSSL
unsigned char hash[SHA_DIGEST_LENGTH];

/* We already use OpenSSL, just use that */
Expand All @@ -95,11 +82,5 @@ int hash_sha1_bytes(const char *s, char buf[SHA1_LEN])

memcpy(buf, hash, SHA1_LEN);
return 0;
#else
UNUSED(s);
UNUSED(buf);
UNUSED(len);
return -1;
#endif
}
#pragma GCC diagnostic pop /* -Wdeprecated-declarations */
273 changes: 273 additions & 0 deletions bbs/io.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
/*
* 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 Abstract I/O transformations interface
*
* \author Naveen Albert <[email protected]>
*/

#include "include/bbs.h"

#include "include/linkedlists.h"
#include "include/module.h"
#include "include/io.h"

/* Unlike most I/O stream abstractions, such as OpenSSL's BIO,
* Dovecot's read/write streams, and libetpan's "low" interface,
* this is not a truly abstract I/O interface.
* It is an interface that is highly coupled to file descriptors,
* since much of the I/O in the BBS is currently written to depend on that.
* While it would be more performant to be able to call I/O callback functions
* that could, for example, call SSL_write directly under the hood,
* rather than first writing to a pipe which is then drained in another
* thread and passed to SSL_write, at this point, it would require
* substantial work to refactor everything not to use file descriptors directly,
* since initially it was only needed for TLS and nothing else.
*
* This abstraction is still useful, since instead of keeping track
* of multiple read/write file descriptors, we can continue to only
* use one and I/O modules will be responsible for setting up their
* own intermediate layer. This also allows for modularity since
* dependencies for particular kinds of I/O transformations (e.g. TLS, compression)
* need not be embedded in the core, but can be implemented in their own modules.
*/

struct bbs_io_transformer {
const char *name;
enum bbs_io_transform_type type;
enum bbs_io_transform_dir dir;
int (*setup)(int *rfd, int *wfd, enum bbs_io_transform_dir dir, void **restrict data, const void *arg);
int (*query)(struct bbs_io_transformation *tran, int query, void *data);
void (*cleanup)(struct bbs_io_transformation *tran);
void *module;
RWLIST_ENTRY(bbs_io_transformer) entry;
char data[];
};

static RWLIST_HEAD_STATIC(transformers, bbs_io_transformer);

int __bbs_io_transformer_register(const char *name, int (*setup)(int *rfd, int *wfd, enum bbs_io_transform_dir dir, void **restrict data, const void *arg),
int (*query)(struct bbs_io_transformation *tran, int query, void *data),
void (*cleanup)(struct bbs_io_transformation *tran), enum bbs_io_transform_type type, enum bbs_io_transform_dir dir, void *module)
{
struct bbs_io_transformer *t;

RWLIST_WRLOCK(&transformers);
RWLIST_TRAVERSE(&transformers, t, entry) {
if (!strcasecmp(name, t->name)) {
RWLIST_UNLOCK(&transformers);
bbs_error("I/O transformer '%s' already registered\n", name);
return -1;
}
}
t = calloc(1, sizeof(*t) + strlen(name) + 1);
if (ALLOC_FAILURE(t)) {
RWLIST_UNLOCK(&transformers);
return -1;
}
strcpy(t->data, name); /* Safe */
t->name = t->data;
t->module = module;
t->setup = setup;
t->query = query;
t->cleanup = cleanup;
t->type = type;
t->dir = dir;
RWLIST_INSERT_TAIL(&transformers, t, entry);
RWLIST_UNLOCK(&transformers);

return 0;
}

int bbs_io_transformer_unregister(const char *name)
{
struct bbs_io_transformer *t;

RWLIST_WRLOCK(&transformers);
RWLIST_TRAVERSE_SAFE_BEGIN(&transformers, t, entry) {
if (!strcasecmp(name, t->name)) {
RWLIST_REMOVE_CURRENT(entry);
free(t);
break;
}
}
RWLIST_TRAVERSE_SAFE_END;
RWLIST_UNLOCK(&transformers);

return t ? 0 : -1;
}

int bbs_io_named_transformer_available(const char *name)
{
struct bbs_io_transformer *t;

RWLIST_RDLOCK(&transformers);
RWLIST_TRAVERSE(&transformers, t, entry) {
if (!strcmp(name, t->name)) {
break;
}
}
RWLIST_UNLOCK(&transformers);

if (!t) {
bbs_debug(3, "No such transformer named '%s'\n", name);
}

return t ? 1 : 0;
}

int bbs_io_transformer_available(enum bbs_io_transform_type transform_type)
{
struct bbs_io_transformer *t;

RWLIST_RDLOCK(&transformers);
RWLIST_TRAVERSE(&transformers, t, entry) {
if (t->type == transform_type) {
break;
}
}
RWLIST_UNLOCK(&transformers);

if (!t) {
bbs_debug(3, "No such transformer of type %d\n", transform_type);
}

return t ? 1 : 0;
}

static int io_transform_slots_free(struct bbs_io_transformations *trans)
{
int i;

for (i = 0; i < MAX_IO_TRANSFORMS; i++) {
if (!trans->transformations[i].transformer) {
/* Not in use */
return 1;
}
}
return 0;
}

static int io_transform_store(struct bbs_io_transformations *trans, struct bbs_io_transformer *t, void *data)
{
int i;

for (i = 0; i < MAX_IO_TRANSFORMS; i++) {
if (!trans->transformations[i].transformer) {
trans->transformations[i].data = data;
trans->transformations[i].transformer = t;
bbs_debug(7, "Set up node I/O transformer at index %d\n", i);
return 0;
}
}
/* Shouldn't happen since only one thread is really handling a node's I/O at a time */
bbs_error("Failed to store transformation\n");
return -1;
}

int bbs_io_transform_setup(struct bbs_io_transformations *trans, enum bbs_io_transform_type type, enum bbs_io_transform_dir direction, int *rfd, int *wfd, const void *arg)
{
int res;
void *data = NULL;
struct bbs_io_transformer *t;

RWLIST_RDLOCK(&transformers);
if (!io_transform_slots_free(trans)) {
RWLIST_UNLOCK(&transformers);
bbs_error("Already at max transformations (%d)\n", MAX_IO_TRANSFORMS);
return -1;
}
RWLIST_TRAVERSE(&transformers, t, entry) {
if (!(t->dir & direction)) {
continue;
}
if (t->type == type) {
break;
}
}

if (!t) {
/* Should use bbs_io_transformer_available before to check.
* Yes, that is TOCTOU, but this should happen infrequently,
* although it is possible, hence a warning, not an error: */
RWLIST_UNLOCK(&transformers);
bbs_warning("No suitable transformer found (type %d)\n", type);
return -1;
}

res = t->setup(rfd, wfd, direction, &data, arg);

/* Store transform private data on node */
if (!res) {
if (io_transform_store(trans, t, data)) {
struct bbs_io_transformation tran;
tran.transformer = t;
tran.data = data;
t->cleanup(&tran);
res = 1;
} else {
bbs_module_ref(t->module, 1);
}
}
RWLIST_UNLOCK(&transformers);

return res;
}

int bbs_io_transform_query(struct bbs_io_transformations *trans, enum bbs_io_transform_type type, int query, void *data)
{
int i;
int res = -1;

RWLIST_RDLOCK(&transformers);
for (i = 0; i < MAX_IO_TRANSFORMS; i++) {
if (trans->transformations[i].data) {
struct bbs_io_transformer *t = trans->transformations[i].transformer;
if (t->type == type) {
if (t->query) {
res = t->query(&trans->transformations[i], query, data);
} else {
res = 1;
}
break;
}
}
}
RWLIST_UNLOCK(&transformers);

return res;
}

static void teardown_transformation(struct bbs_io_transformation *tran)
{
struct bbs_io_transformer *t = tran->transformer;
t->cleanup(tran);
tran->data = NULL;
tran->transformer = NULL;
bbs_module_unref(t->module, 1);
}

void bbs_io_teardown_all_transformers(struct bbs_io_transformations *trans)
{
int i;

RWLIST_RDLOCK(&transformers);
for (i = 0; i < MAX_IO_TRANSFORMS; i++) {
if (trans->transformations[i].data) {
bbs_debug(7, "Removing I/O transformer at index %d\n", i);
teardown_transformation(&trans->transformations[i]);
}
}
RWLIST_UNLOCK(&transformers);
}
Loading

0 comments on commit 879a730

Please sign in to comment.