├── .github └── workflows │ ├── codeql.yml │ └── main.yml ├── .gitignore ├── Doxyfile.in ├── LICENSE ├── Makefile ├── README.rst ├── bbs ├── Makefile ├── alertpipe.c ├── alloc.c ├── ansi.c ├── auth.c ├── backtrace.c ├── base64.c ├── bbs.c ├── callback.c ├── cli.c ├── config.c ├── crypt.c ├── crypt_blowfish.c ├── door.c ├── editor.c ├── event.c ├── fd.c ├── group.c ├── handler.c ├── hash.c ├── io.c ├── kvs.c ├── lock.c ├── logger.c ├── mail.c ├── menu.c ├── module.c ├── net.c ├── node.c ├── notify.c ├── oauth.c ├── os.c ├── parallel.c ├── pty.c ├── range.c ├── ratelimit.c ├── readline.c ├── socket.c ├── startup.c ├── string.c ├── stringlist.c ├── system.c ├── tcp.c ├── term.c ├── test.c ├── thread.c ├── transfer.c ├── user.c ├── utils.c └── variables.c ├── configs ├── .rules ├── auth.conf ├── bbs.conf ├── groups.conf ├── lbbs.service ├── mail.conf ├── menus.conf ├── mod_asterisk_ami.conf ├── mod_asterisk_queues.conf ├── mod_auth_mysql.conf ├── mod_auth_static.conf ├── mod_chanserv.conf ├── mod_discord.conf ├── mod_http_proxy.conf ├── mod_ip_blocker.conf ├── mod_irc_client.conf ├── mod_irc_relay.conf ├── mod_mail.conf ├── mod_mail_events.conf ├── mod_mysql.conf ├── mod_oauth.conf ├── mod_slack.conf ├── mod_smtp_fetchmail.conf ├── mod_smtp_filter_dkim.conf ├── mod_smtp_filter_dmarc.conf ├── mod_smtp_greylisting.conf ├── mod_smtp_mailing_lists.conf ├── mod_webmail.conf ├── modules.conf ├── net_finger.conf ├── net_ftp.conf ├── net_gopher.conf ├── net_http.conf ├── net_imap.conf ├── net_irc.conf ├── net_msp.conf ├── net_nntp.conf ├── net_pop3.conf ├── net_rlogin.conf ├── net_smtp.conf ├── net_ssh.conf ├── net_telnet.conf ├── net_ws.conf ├── nodes.conf ├── system.conf ├── templates │ ├── .imapremote.sample │ └── .oauth.conf.sample ├── tls.conf ├── transfers.conf └── variables.conf ├── doors ├── Makefile ├── door_chat.c ├── door_evergreen.c ├── door_ibbs.c ├── door_irc.c ├── door_msg.c ├── door_stats.c ├── door_tutorial.c ├── door_usermgmt.c └── door_utilities.c ├── external ├── Makefile ├── filemgr.c ├── isoroot.c ├── leak.c ├── modman.c ├── modseqdecode.c ├── rsysop.c ├── saslencode.c ├── sigwinch.c └── tcplog.c ├── include ├── alertpipe.h ├── ansi.h ├── auth.h ├── base64.h ├── bbs.h ├── callback.h ├── cli.h ├── config.h ├── crypt.h ├── definitions.h ├── dlinkedlists.h ├── door.h ├── editor.h ├── event.h ├── fd.h ├── group.h ├── handler.h ├── hash.h ├── io.h ├── json.h ├── keys.h ├── kvs.h ├── linkedlists.h ├── lock.h ├── logger.h ├── mail.h ├── menu.h ├── mod_asterisk_ami.h ├── mod_asterisk_queues.h ├── mod_curl.h ├── mod_history.h ├── mod_http.h ├── mod_irc_client.h ├── mod_mail.h ├── mod_mimeparse.h ├── mod_mysql.h ├── mod_ncurses.h ├── mod_smtp_client.h ├── mod_smtp_filter_dkim.h ├── mod_uuid.h ├── module.h ├── net.h ├── net_irc.h ├── net_smtp.h ├── net_ws.h ├── node.h ├── notify.h ├── oauth.h ├── os.h ├── parallel.h ├── pty.h ├── range.h ├── ratelimit.h ├── readline.h ├── reload.h ├── socket.h ├── startup.h ├── string.h ├── stringlist.h ├── system.h ├── tcp.h ├── term.h ├── test.h ├── thread.h ├── time.h ├── transfer.h ├── user.h ├── utils.h ├── variables.h └── version.h ├── io ├── Makefile ├── io_compress.c ├── io_log.c └── io_tls.c ├── modules ├── Makefile ├── mod_asterisk_ami.c ├── mod_asterisk_queues.c ├── mod_auth_mysql.c ├── mod_auth_static.c ├── mod_chanserv.c ├── mod_curl.c ├── mod_discord.c ├── mod_history.c ├── mod_http.c ├── mod_http_proxy.c ├── mod_ip_blocker.c ├── mod_irc_client.c ├── mod_irc_relay.c ├── mod_lmdb.c ├── mod_mail.c ├── mod_mail_events.c ├── mod_mail_trash.c ├── mod_mailscript.c ├── mod_menu_handlers.c ├── mod_mimeparse.c ├── mod_mysql.c ├── mod_ncurses.c ├── mod_node_callbacks.c ├── mod_oauth.c ├── mod_operator.c ├── mod_sendmail.c ├── mod_sieve.c ├── mod_slack.c ├── mod_smtp_client.c ├── mod_smtp_delivery_external.c ├── mod_smtp_delivery_local.c ├── mod_smtp_fetchmail.c ├── mod_smtp_filter.c ├── mod_smtp_filter_arc.c ├── mod_smtp_filter_dkim.c ├── mod_smtp_filter_dmarc.c ├── mod_smtp_filter_spf.c ├── mod_smtp_greylisting.c ├── mod_smtp_mailing_lists.c ├── mod_smtp_recipient_monitor.c ├── mod_spamassassin.c ├── mod_sysop.c ├── mod_systemd.c ├── mod_test_backtrace.c ├── mod_test_config.c ├── mod_test_http.c ├── mod_test_range.c ├── mod_tests.c ├── mod_uuid.c ├── mod_version.c └── mod_webmail.c ├── nets ├── Makefile ├── net_finger.c ├── net_ftp.c ├── net_gopher.c ├── net_http.c ├── net_imap.c ├── net_imap │ ├── Makefile │ ├── imap.h │ ├── imap_client.c │ ├── imap_client.h │ ├── imap_client_list.c │ ├── imap_client_list.h │ ├── imap_client_status.c │ ├── imap_client_status.h │ ├── imap_server_acl.c │ ├── imap_server_acl.h │ ├── imap_server_fetch.c │ ├── imap_server_fetch.h │ ├── imap_server_flags.c │ ├── imap_server_flags.h │ ├── imap_server_list.c │ ├── imap_server_list.h │ ├── imap_server_maildir.c │ ├── imap_server_maildir.h │ ├── imap_server_notify.c │ ├── imap_server_notify.h │ ├── imap_server_search.c │ └── imap_server_search.h ├── net_irc.c ├── net_msp.c ├── net_nntp.c ├── net_pop3.c ├── net_rlogin.c ├── net_sieve.c ├── net_smtp.c ├── net_ssh.c ├── net_telnet.c ├── net_unix.c └── net_ws.c ├── scripts ├── .indent.pro ├── .suppress.cppcheck ├── backup.sh ├── bbs_dumper.sh ├── cppcheck.sh ├── dbcreate.sql ├── disablewall.sh ├── evergreen.sh ├── gen_rootfs.sh ├── indent.sh ├── install_prereq.sh ├── ircbot.php ├── libcami.sh ├── libdiscord.sh ├── libetpan.sh ├── libjansson.sh ├── libopenarc.sh ├── libopendmarc_reporting.sh ├── libslackrtm.sh ├── libwss.sh ├── lirc.sh ├── oauth_helper.sh ├── run_tests.sh ├── server_setup.sh ├── setup_wizard.sh └── valgrind.sh ├── tests ├── Makefile ├── ansi.c ├── ansi.h ├── compress.c ├── compress.h ├── configs │ ├── .imapremote │ ├── .rules │ ├── .sieve │ ├── before.rules │ ├── dsn │ │ └── net_smtp.conf │ ├── menus.conf │ ├── mod_auth_mysql.conf │ ├── mod_chanserv.conf │ ├── mod_irc_client.conf │ ├── mod_irc_relay.conf │ ├── mod_mail.conf │ ├── mod_mail_events.conf │ ├── mod_mysql.conf │ ├── mod_smtp_mailing_lists.conf │ ├── net_finger.conf │ ├── net_ftp.conf │ ├── net_gopher.conf │ ├── net_http.conf │ ├── net_imap.conf │ ├── net_irc.conf │ ├── net_msp.conf │ ├── net_nntp.conf │ ├── net_pop3.conf │ ├── net_smtp.conf │ ├── net_ssh.conf │ ├── net_telnet.conf │ ├── net_ws.conf │ ├── tls.conf │ ├── tls │ │ ├── net_ftp.conf │ │ └── net_imap.conf │ └── transfers.conf ├── email.c ├── email.h ├── messages │ ├── alternative.eml │ ├── multipart.eml │ └── multipart2.eml ├── test.c ├── test.h ├── test_alloc.c ├── test_auth_mysql.c ├── test_autoload.c ├── test_finger.c ├── test_ftp.c ├── test_ftps.c ├── test_gopher.c ├── test_home.c ├── test_http.c ├── test_imap.c ├── test_imap_auth_plain.c ├── test_imap_compress.c ├── test_imap_fetch.c ├── test_imap_filter.c ├── test_imap_msn.c ├── test_imap_notify.c ├── test_imap_pop3_compat.c ├── test_imap_proxy.c ├── test_irc.c ├── test_irc_chanserv.c ├── test_irc_relay.c ├── test_mailscript.c ├── test_menus.c ├── test_msp.c ├── test_nntp_reader.c ├── test_nntp_transit.c ├── test_pop3.c ├── test_sftp.c ├── test_sieve.c ├── test_smtp_dsn.c ├── test_smtp_filters.c ├── test_smtp_mailing_lists.c ├── test_smtp_msa.c ├── test_smtp_mta.c ├── test_ssh.c ├── test_sysop.c ├── test_telnet.c ├── test_terminals.c ├── test_unit.c ├── test_webmail.c ├── tls.c └── tls.h └── valgrind.supp /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !/**/ 3 | !*.* 4 | *.d 5 | *.o 6 | *.so 7 | doors/lirc/ 8 | -------------------------------------------------------------------------------- /bbs/Makefile: -------------------------------------------------------------------------------- 1 | 2 | MAIN_SRC := $(wildcard *.c) 3 | MAIN_OBJ = $(MAIN_SRC:.c=.o) 4 | DEPENDS := $(patsubst %.c,%.d,$(MAIN_SRC)) 5 | 6 | # the include directory is in the parent 7 | INC = -I.. 8 | 9 | LIBS = -lrt -lm -ldl -lcrypt -rdynamic 10 | 11 | # -lcrypto needed for SHA1_Init in hash.c 12 | # XXX Would be nice to move hash.c out into a module (mod_hash) 13 | # The only users of hash.h are mod_http and auth.c, one call each. 14 | LIBS += -lcrypto 15 | 16 | # -lbfd and friends 17 | # On SUSE, the remaining libraries are needed to link successfully 18 | # However, on other platforms they are generally not, and -liberty is likely to cause issues 19 | # Furthermore, some of the other libraries are also needed, if present, but must not be specified if not 20 | # On FreeBSD, there is leading whitespace, so also trim for good measure 21 | LIBS += -lbfd 22 | LIBERTY_CHECK = $(shell gcc -liberty 2>&1 | grep "cannot find" | wc -l | tr -d ' ' ) 23 | LZSTD_CHECK = $(shell gcc -lzstd 2>&1 | grep "cannot find" | wc -l | tr -d ' ' ) 24 | LSFRAME_CHECK = $(shell gcc -lsframe 2>&1 | grep "cannot find" | wc -l | tr -d ' ' ) 25 | ifneq ($(LIBERTY_CHECK),1) 26 | LIBS += -liberty -lz -lopcodes 27 | endif 28 | ifneq ($(LZSTD_CHECK),1) 29 | LIBS += -lzstd 30 | endif 31 | ifneq ($(LSFRAME_CHECK),1) 32 | LIBS += -lsframe 33 | endif 34 | 35 | ifeq ($(ALPINE_LINUX),1) 36 | LIBS += -lcap 37 | else ifeq ($(UNAME_S),Linux) 38 | LIBS += -lbsd -lcap 39 | endif 40 | 41 | ifeq ($(UNAME_S),FreeBSD) 42 | LIBS += -lexecinfo -lintl 43 | else ifeq ($(ALPINE_LINUX),1) 44 | LIBS += -lexecinfo 45 | endif 46 | 47 | # musl doesn't support dlclose (it's just a noop), so we can only dlopen modules once 48 | # See: https://wiki.musl-libc.org/functional-differences-from-glibc.html#Unloading_libraries 49 | ifeq ($(ALPINE_LINUX),1) 50 | CFLAGS += -DDLOPEN_ONLY_ONCE 51 | endif 52 | 53 | all: $(EXE) 54 | 55 | $(EXE) : $(MAIN_OBJ) 56 | @echo " [LD] $^ -> $@" 57 | $(CC) $(CORE_LDFLAGS) -o $(EXE) *.o $(LIBS) 58 | 59 | -include $(DEPENDS) 60 | 61 | # Empty target so that if the .d file doesn't already exist, the %.d dependency is implicitly satisfied. 62 | $(DEPENDS): 63 | 64 | %.o : %.c %.d 65 | @echo " [CC] $< -> $@" 66 | $(CC) $(CFLAGS) -DBBS_IN_CORE -MMD -MP $(INC) -c $< 67 | 68 | # Don't automatically remove intermediate .o files, to prevent unnecessary recompilations 69 | .SECONDARY: $(patsubst %.c,%.o,$(MOD_SRC)) 70 | 71 | .PHONY: bbs 72 | -------------------------------------------------------------------------------- /bbs/alertpipe.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Alertpipe 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #include 23 | #include 24 | #include /* use eventfd */ 25 | #include /* use uint64_t */ 26 | #include /* use O_NONBLOCK */ 27 | #include 28 | 29 | #include "include/alertpipe.h" 30 | 31 | ssize_t bbs_alertpipe_write(int alert_pipe[2]) 32 | { 33 | uint64_t tmp = 1; 34 | bbs_assert(alert_pipe[1] != -1); 35 | return write(alert_pipe[1], &tmp, sizeof(tmp)) != sizeof(tmp); 36 | } 37 | 38 | int bbs_alertpipe_read(int alert_pipe[2]) 39 | { 40 | uint64_t tmp; 41 | 42 | bbs_assert(alert_pipe[0] != -1); 43 | if (read(alert_pipe[0], &tmp, sizeof(tmp)) < 0) { 44 | if (errno != EINTR && errno != EAGAIN) { 45 | bbs_error("read() failed: %s\n", strerror(errno)); 46 | return -1; 47 | } 48 | } 49 | 50 | return 0; 51 | } 52 | 53 | int __bbs_alertpipe_create(int alert_pipe[2], const char *file, int line, const char *func) 54 | { 55 | /* Prefer eventfd to pipe since it's more efficient (only 1 fd needed, rather than 2) */ 56 | int fd; 57 | 58 | #if defined(DEBUG_FD_LEAKS) && DEBUG_FD_LEAKS == 1 59 | fd = __bbs_eventfd(0, EFD_NONBLOCK | EFD_SEMAPHORE, file, line, func); 60 | #else 61 | UNUSED(file); 62 | UNUSED(line); 63 | UNUSED(func); 64 | fd = eventfd(0, EFD_NONBLOCK | EFD_SEMAPHORE); 65 | #endif 66 | 67 | if (fd > -1) { 68 | alert_pipe[0] = alert_pipe[1] = fd; 69 | return 0; 70 | } 71 | bbs_warning("Failed to create alert pipe with eventfd(), falling back to pipe(): %s\n", strerror(errno)); 72 | bbs_alertpipe_clear(alert_pipe); 73 | if (pipe2(alert_pipe, O_NONBLOCK)) { 74 | bbs_error("Failed to create alert pipe: %s\n", strerror(errno)); 75 | return -1; 76 | } 77 | return 0; 78 | } 79 | 80 | int bbs_alertpipe_close(int alert_pipe[2]) 81 | { 82 | if (alert_pipe[0] == -1 && alert_pipe[1] == -1) { 83 | bbs_error("Alert pipe is already closed\n"); 84 | return -1; 85 | } 86 | if (alert_pipe[0] == alert_pipe[1]) { 87 | /* eventfd */ 88 | close_if(alert_pipe[0]); 89 | } else { 90 | /* pipe2 */ 91 | close_if(alert_pipe[0]); 92 | close_if(alert_pipe[1]); 93 | } 94 | bbs_alertpipe_clear(alert_pipe); 95 | return 0; 96 | } 97 | 98 | int bbs_alertpipe_poll(int alert_pipe[2], int ms) 99 | { 100 | int res; 101 | for (;;) { 102 | struct pollfd pfd = { alert_pipe[0], POLLIN, 0 }; 103 | res = poll(&pfd, 1, ms); 104 | if (res < 0) { 105 | if (errno != EINTR) { 106 | bbs_warning("poll returned error: %s\n", strerror(errno)); 107 | break; 108 | } 109 | continue; 110 | } 111 | if (pfd.revents) { 112 | return 1; 113 | } 114 | break; 115 | } 116 | return res; 117 | } 118 | -------------------------------------------------------------------------------- /bbs/hash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Hashing functions 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #include "include/hash.h" 23 | 24 | /* For hashing: */ 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | #undef sprintf 33 | 34 | /*! \todo Migrate to EVP functions or newer wrappers */ 35 | 36 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" /* SHA256_Init, SHA256_Update, SHA256_Final deprecated in OpenSSL 3.0 */ 37 | int hash_sha256(const char *s, char buf[SHA256_BUFSIZE]) 38 | { 39 | int i; 40 | unsigned char hash[SHA256_DIGEST_LENGTH]; 41 | 42 | /* We already use OpenSSL, just use that */ 43 | SHA256_CTX sha256; 44 | SHA256_Init(&sha256); 45 | SHA256_Update(&sha256, s, strlen(s)); 46 | SHA256_Final(hash, &sha256); 47 | 48 | for(i = 0; i < SHA256_DIGEST_LENGTH; i++) { 49 | sprintf(buf + (i * 2), "%02x", hash[i]); /* Safe */ 50 | } 51 | buf[SHA256_BUFSIZE - 1] = '\0'; 52 | return 0; 53 | } 54 | 55 | int hash_sha1(const char *s, char buf[SHA1_BUFSIZE]) 56 | { 57 | int i; 58 | unsigned char hash[SHA_DIGEST_LENGTH]; 59 | 60 | /* We already use OpenSSL, just use that */ 61 | SHA_CTX sha1; 62 | SHA1_Init(&sha1); 63 | SHA1_Update(&sha1, s, strlen(s)); 64 | SHA1_Final(hash, &sha1); 65 | 66 | for(i = 0; i < SHA_DIGEST_LENGTH; i++) { 67 | sprintf(buf + (i * 2), "%02x", hash[i]); /* Safe */ 68 | } 69 | buf[SHA1_BUFSIZE - 1] = '\0'; 70 | return 0; 71 | } 72 | 73 | int hash_sha1_bytes(const char *s, char buf[SHA1_LEN]) 74 | { 75 | unsigned char hash[SHA_DIGEST_LENGTH]; 76 | 77 | /* We already use OpenSSL, just use that */ 78 | SHA_CTX sha1; 79 | SHA1_Init(&sha1); 80 | SHA1_Update(&sha1, s, strlen(s)); 81 | SHA1_Final(hash, &sha1); 82 | 83 | memcpy(buf, hash, SHA1_LEN); 84 | return 0; 85 | } 86 | #pragma GCC diagnostic pop /* -Wdeprecated-declarations */ 87 | -------------------------------------------------------------------------------- /bbs/oauth.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief OAuth2 Authentication interface 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #include 23 | #include 24 | 25 | #include "include/module.h" 26 | #include "include/linkedlists.h" 27 | #include "include/oauth.h" 28 | 29 | /* Allow multiple OAuth2 providers to be registered */ 30 | struct oauth_provider { 31 | int (*get_token)(OAUTH_PROVIDER_PARAMS); 32 | struct bbs_module *module; 33 | RWLIST_ENTRY(oauth_provider) entry; 34 | }; 35 | 36 | static RWLIST_HEAD_STATIC(providers, oauth_provider); 37 | 38 | int __bbs_register_oauth_provider(int (*provider)(OAUTH_PROVIDER_PARAMS), void *mod) 39 | { 40 | struct oauth_provider *p; 41 | 42 | RWLIST_WRLOCK(&providers); 43 | RWLIST_TRAVERSE(&providers, p, entry) { 44 | if (p->get_token == provider) { 45 | break; 46 | } 47 | } 48 | if (p) { 49 | bbs_error("OAuth provider is already registered\n"); 50 | RWLIST_UNLOCK(&providers); 51 | return -1; 52 | } 53 | p = calloc(1, sizeof(*p)); 54 | if (ALLOC_FAILURE(p)) { 55 | RWLIST_UNLOCK(&providers); 56 | return -1; 57 | } 58 | p->get_token = provider; 59 | p->module = mod; 60 | RWLIST_INSERT_TAIL(&providers, p, entry); 61 | RWLIST_UNLOCK(&providers); 62 | return 0; 63 | } 64 | 65 | int bbs_unregister_oauth_provider(int (*provider)(OAUTH_PROVIDER_PARAMS)) 66 | { 67 | struct oauth_provider *p; 68 | 69 | p = RWLIST_WRLOCK_REMOVE_BY_FIELD(&providers, get_token, provider, entry); 70 | if (!p) { 71 | bbs_error("Failed to unregister OAuth provider: not currently registered\n"); 72 | return -1; 73 | } else { 74 | free(p); 75 | } 76 | return 0; 77 | } 78 | 79 | int bbs_get_oauth_token(struct bbs_user *user, const char *name, char *buf, size_t len) 80 | { 81 | int c = 0, res = -1; 82 | struct oauth_provider *p; 83 | 84 | RWLIST_RDLOCK(&providers); 85 | RWLIST_TRAVERSE(&providers, p, entry) { 86 | bbs_module_ref(p->module, 1); 87 | res = p->get_token(user, name, buf, len); 88 | bbs_module_unref(p->module, 1); 89 | c++; 90 | if (!res) { 91 | break; /* Somebody granted the login. Stop. */ 92 | } 93 | } 94 | RWLIST_UNLOCK(&providers); 95 | 96 | /* If there weren't any providers, well, we have a problem. */ 97 | if (c == 0) { 98 | bbs_warning("No OAuth providers are currently registered!\n"); 99 | } 100 | return res; 101 | } 102 | -------------------------------------------------------------------------------- /bbs/os.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief OS details 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #include /* use snprintf */ 23 | #include /* use strerror */ 24 | #include /* use uname */ 25 | #include /* use statvfs */ 26 | 27 | #include "include/os.h" 28 | 29 | static char osver[96]; 30 | 31 | const char *bbs_get_osver(void) 32 | { 33 | return osver; 34 | } 35 | 36 | int bbs_init_os_info(void) 37 | { 38 | int res; 39 | struct utsname buf; 40 | 41 | if (uname(&buf)) { 42 | bbs_error("uname: %s\n", strerror(errno)); 43 | return -1; 44 | } 45 | 46 | res = snprintf(osver, sizeof(osver), "%s %s", buf.sysname, buf.release); 47 | if (res >= (int) sizeof(osver)) { 48 | bbs_error("Truncation occured when trying to write %d bytes\n", res); 49 | return -1; 50 | } 51 | bbs_debug(5, "OS info: %s\n", osver); 52 | return 0; 53 | } 54 | 55 | long bbs_disk_bytes_free(void) 56 | { 57 | struct statvfs stat; 58 | const char *path = "/"; /* Root partition */ 59 | 60 | if (statvfs(path, &stat)) { 61 | bbs_error("statvfs failed: %s\n", strerror(errno)); 62 | return -1; 63 | } 64 | 65 | return (long) (stat.f_bsize * stat.f_bavail); /* Return number of free bytes */ 66 | } 67 | -------------------------------------------------------------------------------- /bbs/startup.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Startup Callbacks 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #include 23 | 24 | #include "include/linkedlists.h" 25 | #include "include/startup.h" 26 | 27 | struct startup_callback { 28 | int (*execute)(void); /*!< Callback function */ 29 | int priority; /*!< Priority */ 30 | RWLIST_ENTRY(startup_callback) entry; /*!< Next entry */ 31 | }; 32 | 33 | static RWLIST_HEAD_STATIC(callbacks, startup_callback); 34 | 35 | static int started = 0; 36 | 37 | int bbs_register_startup_callback(int (*execute)(void), int priority) 38 | { 39 | struct startup_callback *cb; 40 | 41 | RWLIST_WRLOCK(&callbacks); 42 | if (started) { 43 | bbs_error("BBS is already fully started: startup callbacks cannot be registered anymore\n"); 44 | RWLIST_UNLOCK(&callbacks); 45 | return -1; 46 | } 47 | cb = calloc(1, sizeof(*cb)); 48 | if (ALLOC_FAILURE(cb)) { 49 | RWLIST_UNLOCK(&callbacks); 50 | return -1; 51 | } 52 | cb->execute = execute; 53 | cb->priority = priority; 54 | /* Tail insert, so they run in the order registered */ 55 | RWLIST_INSERT_SORTED(&callbacks, cb, entry, priority); 56 | bbs_debug(3, "Registered startup callback %p\n", execute); 57 | RWLIST_UNLOCK(&callbacks); 58 | return 0; 59 | } 60 | 61 | int bbs_run_when_started(int (*execute)(void), int priority) 62 | { 63 | if (bbs_is_fully_started()) { 64 | return execute(); 65 | } else { 66 | return bbs_register_startup_callback(execute, priority); 67 | } 68 | } 69 | 70 | int bbs_run_startup_callbacks(void) 71 | { 72 | struct startup_callback *cb; 73 | 74 | RWLIST_WRLOCK(&callbacks); 75 | while ((cb = RWLIST_REMOVE_HEAD(&callbacks, entry))) { 76 | cb->execute(); 77 | free(cb); 78 | } 79 | started = 1; 80 | RWLIST_UNLOCK(&callbacks); 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /configs/auth.conf: -------------------------------------------------------------------------------- 1 | ; auth.conf - Authentication configuration 2 | 3 | [reserved_usernames] 4 | ; List of reserved usernames, protected against user registration. 5 | ; Key = reserved username, value = does not matter 6 | ; General 7 | root = yes 8 | admin = yes 9 | sysop = yes 10 | bbs = yes 11 | anonymous = yes 12 | public = yes 13 | ; IRC 14 | ChanServ = yes 15 | NickServ = yes 16 | MessageServ = yes 17 | services = yes 18 | ; SMTP/NNTP 19 | ; Some of these may be aliases in mod_mail.conf, 20 | ; in which case defining them here is redundant, but doesn't hurt. 21 | postmaster = yes 22 | newsmaster = yes 23 | noreply = yes 24 | dmarc-noreply = yes 25 | -------------------------------------------------------------------------------- /configs/bbs.conf: -------------------------------------------------------------------------------- 1 | ; bbs.conf 2 | 3 | [logger] 4 | verbose = 10 ; Default (startup) verbose log level 5 | ;debug = 5 ; Default (startup) debug log level 6 | ;logfile_debug = 1 ; Maximum debug level for log file. 7 | ; Debug messages with a level greater than this will still log to the console but not to the log file. 8 | ; Regardless, messages are never logged if their level is greater than the system debug level, configured by "debug". 9 | ; Default is 10 (maximum log level). 10 | 11 | [run] 12 | ; *** WARNING WARNING WARNING *** 13 | ; You are *SERIOUSLY* advised and strongly encouraged to 14 | ; NOT run the BBS as root. Doing so will likely put your 15 | ; system at significant risk. Because BBSes are designed 16 | ; to accept guest connections and you can very easily configure 17 | ; the BBS to spawn external system commands, running the BBS as root 18 | ; may compromise your system if you don't know what you are doing. 19 | ; Seriously, just create a bbs user and run the BBS as that. 20 | ; You can add sudoers entries for specific commands if you 21 | ; need to execute certain things as root. 22 | ; e.g. 23 | ; adduser -c "BBS" bbs --disabled-password --gecos "" 24 | 25 | ;user = bbs ; User under which to run BBS 26 | ;group = bbs ; Group under which to run BBS 27 | dumpcore = yes ; Whether to dump core on crash. Recommended for debugging and development. 28 | -------------------------------------------------------------------------------- /configs/groups.conf: -------------------------------------------------------------------------------- 1 | ; groups.conf 2 | ; Statically defined user groups 3 | 4 | ; Each config section defines one group. 5 | ; Add group members one per line with the username as the key. 6 | ; The value does not matter. 7 | ; These groups can be referenced in other BBS modules or subsystems, e.g. in menus.conf for access control. 8 | ; This allows for more granular ACLs, but for simple hierarchical user restrictions, setting the privilege level may be simpler. 9 | 10 | ;[mygroup] 11 | ;sysop=yes 12 | ;jsmith=yes 13 | -------------------------------------------------------------------------------- /configs/lbbs.service: -------------------------------------------------------------------------------- 1 | # LBBS service file 2 | # If you use this, it's recommended you add require = mod_systemd.so to /etc/lbbs/modules.conf 3 | 4 | [Unit] 5 | Description=LBBS bulletin board system daemon 6 | After=network.target 7 | 8 | [Service] 9 | # If you have a newer version of systemd (>= 253, as reported by systemctl --version) 10 | # then you can use the newer 'notify-reload' type and 'ReloadSignal'. 11 | # Otherwise, use 'notify' and 'ExecReload' for compatibility. 12 | #Type=notify-reload 13 | Type=notify 14 | 15 | # SIGHUP is ignored since remote console disconnects trigger it. 16 | # Therefore, we pick another unused signal to use for reload. 17 | # SIGTERM is implicitly used for shutdown so it's not specified here. 18 | #ReloadSignal=SIGUSR2 19 | 20 | NotifyAccess=main 21 | Environment=HOME=/home/bbs 22 | WorkingDirectory=/home/bbs 23 | User=bbs 24 | Group=bbs 25 | 26 | # Only one of these two prestart commands is needed. 27 | # The first one doesn't seem to work as reliably, but the latter is less secure. 28 | #ExecStartPre=+setcap CAP_NET_BIND_SERVICE=+eip /usr/sbin/lbbs 29 | ExecStartPre=+sysctl net.ipv4.ip_unprivileged_port_start=18 30 | 31 | # Since ExecStart will start with the BBS user's privileges (not root), 32 | # tasks that the BBS normally does prior to dropping privileges, 33 | # if started as root, won't succeed. Thus, we do them beforehand. 34 | ExecStartPre=+install -d -m 0755 -o bbs -g bbs /var/run/lbbs 35 | ExecStartPre=+install -d -m 0744 -o bbs -g bbs /var/log/lbbs 36 | 37 | ExecStart=/usr/sbin/lbbs -gcb 38 | 39 | # Only needed if Type is notify, not needed if notify-reload 40 | ExecReload=kill -USR2 $MAINPID 41 | 42 | LimitCORE=infinity 43 | Restart=on-failure 44 | RestartSec=5 45 | PrivateTmp=false 46 | 47 | # Since logs are already saved to disk, there's normally no need for output 48 | # For debugging (e.g. on startup failure), it may be helpful to remove this 49 | # (to enable logging by systemd) 50 | StandardOutput=null 51 | 52 | [Install] 53 | WantedBy=multi-user.target 54 | -------------------------------------------------------------------------------- /configs/mail.conf: -------------------------------------------------------------------------------- 1 | ; mail.conf - Email sending 2 | 3 | [general] 4 | ;errorsto = postmaster@example.com ; Errors-To email address 5 | 6 | [defaults] 7 | ;to = Sysop ; Default recipient (sysop's email address) 8 | ;from = LBBS ; Default sending address: this is what people will see as the sender. 9 | ; Some form of "noreply" address is recommended, unless you plan to regularly 10 | ; check the email address from which messages are sent (you don't, do you?) 11 | ; The Reply-To address can be overridden on a per message basis. 12 | -------------------------------------------------------------------------------- /configs/mod_asterisk_ami.conf: -------------------------------------------------------------------------------- 1 | ; mod_asterisk_ami.conf 2 | 3 | ;[ami] ; Connection information for Asterisk Manager Interface 4 | ;hostname=127.0.0.1 5 | ;username=amiuser 6 | ;password=amipass 7 | -------------------------------------------------------------------------------- /configs/mod_asterisk_queues.conf: -------------------------------------------------------------------------------- 1 | ; mod_asterisk_queues.conf 2 | 3 | [general] 4 | title = OPERATOR SERVICE POSITION SYSTEM 5 | callmenutitle = OPERATOR SERVICE POSITION SYSTEM (CHOOSE CALL) 6 | queueidvar = queueuniq ; Name of Asterisk channel variable on queue calls that will contain a unique, numeric ID for the call 7 | 8 | ; Agents must have the variable ASTERISK_AGENT_ID defined. Only these users may use the module. 9 | ; You can define these variables statically in variables.conf. 10 | 11 | ;[sales] 12 | ;title = SALES ; Queue name that will display to the agent. 13 | ;handler = sales ; The name of a queue call handler that will handle this call. 14 | ; Currently, these handlers are implemented in custom C handlers that must be written for each module. 15 | ; These are generally proprietary business logic - so you will likely have to implement your own. 16 | 17 | ;[engineering] 18 | ;title = ENGINEERING 19 | ;handler = engineering 20 | -------------------------------------------------------------------------------- /configs/mod_auth_mysql.conf: -------------------------------------------------------------------------------- 1 | ; mod_auth_mysql.conf 2 | 3 | [db] ; Connection info for database 4 | ; DON'T FORGET to create the bbs.users table as in dbcreate.sql 5 | hostname=localhost 6 | username=bbs 7 | password=P@ssw0rdUShouldChAngE! 8 | database=bbs 9 | 10 | [registration] ; This section allows you to prompt for certain information on registration. 11 | ; By default, all information is required. You can specify "no" to disable certain fields. 12 | ; Note that your SQL database schema must allow NULL entries for any fields that are disabled. 13 | phone=yes 14 | address=yes 15 | zip=yes 16 | dob=yes 17 | gender=yes 18 | howheard=yes 19 | verifyemail=no ; Whether to verify new users' Internet email addresses. Default is no. 20 | ; Reserved usernames are configured in auth.conf 21 | -------------------------------------------------------------------------------- /configs/mod_auth_static.conf: -------------------------------------------------------------------------------- 1 | ; mod_auth_static.conf - Static user configuration 2 | 3 | ; NOTE: This module should NOT be used for production systems. 4 | ; Use mod_auth_mysql instead. 5 | 6 | [users] ; List of usernames and bcrypt password hashes 7 | ; All users have privilege level 1. 8 | ; IDs are assigned when the module loads using an auto-incrementing strategy. 9 | ; Yet another reason why this should not be used in production! (IDs could change if the order here changes) 10 | ;username = $2y$10$1vtttulZgw5Sz.Ks8PePFumnPCztHfp0YzgHLnuIQ1vAb0mSQpv2q 11 | -------------------------------------------------------------------------------- /configs/mod_chanserv.conf: -------------------------------------------------------------------------------- 1 | ; mod_chanserv - ChanServ configuration 2 | 3 | [db] ; MySQL/MariaDB database connection information 4 | ; If the IRC network contains multiple servers, they should all use the same database details. 5 | ; All parameters are mandatory. 6 | ; DON'T FORGET to create the irc database as in dbcreate.sql 7 | ;hostname=localhost 8 | ;username=bbs 9 | ;password=P@ssw0rdUShouldChAngE! 10 | database=irc 11 | -------------------------------------------------------------------------------- /configs/mod_http_proxy.conf: -------------------------------------------------------------------------------- 1 | ; mod_http_proxy.conf 2 | 3 | [clients] ; One entry for each IP, CIDR range, or hostname authorized to proxy requests. Proxying using CONNECT is only allowed to ports 80 and 443. 4 | ;10.1.1.1 = * ; allow proxying to all destinations 5 | ;10.1.1.2 = example.com ; allow proxying only to example.com 6 | -------------------------------------------------------------------------------- /configs/mod_ip_blocker.conf: -------------------------------------------------------------------------------- 1 | ; mod_ip_blocker.conf - BBS event handlers 2 | 3 | [whitelist] 4 | ; List of IP addresses or CIDR ranges to whitelist from autoblocking. 5 | ; These IP addresses will not be automatically blocked 6 | ; by mod_ip_blocker if the abuse threshold is reached. 7 | ; Key contains the IP/CIDR, the value does not matter and is ignored. 8 | ; 9 | ; You should add any trusted IP addresses here (e.g. sysop network): 10 | ;10.0.0.0/24 = private 11 | ;1.1.1.1 = cloudflare 12 | -------------------------------------------------------------------------------- /configs/mod_mail_events.conf: -------------------------------------------------------------------------------- 1 | ; mod_mail_events.conf 2 | 3 | ; This module can be used to log all RFC 5423 mailbox events to a log file. 4 | ; This can be useful for auditing or debugging purposes. 5 | ; If you don't need this functionality, you should either not load this module 6 | ; or not configure a log file (in which case it will automatically decline to load). 7 | 8 | [general] 9 | ;logfile=/var/log/lbbs/mail.log ; Optional log file for all RFC 5423 mailbox events. By default, logging is disabled. 10 | -------------------------------------------------------------------------------- /configs/mod_mysql.conf: -------------------------------------------------------------------------------- 1 | ; mod_mysql.conf 2 | 3 | [mysql] 4 | ;socket=/run/mysqld/mysqld.sock ; Custom socket file to use for client connections, if not using the default. 5 | -------------------------------------------------------------------------------- /configs/mod_smtp_fetchmail.conf: -------------------------------------------------------------------------------- 1 | ; mod_smtp_fetchmail.conf - RFC 1985 Remote Message Queuing client 2 | 3 | ; This section is used to configure upstream SMTP servers from which 4 | ; we will request queued mail be delivered when this module loads. 5 | ; A CLI command can also be used to fetch mail from these servers on demand. 6 | 7 | ; Each entry defines the SMTP server to which to connect as the key, 8 | ; and as the value, a comma-separated list of domains for which 9 | ; we will request any queued mail be flushed. 10 | 11 | ;[upstreams] 12 | ;10.1.1.1 = internal.example.com 13 | ;smtp.example.com = internal.example.com,internal.example.net:2525 14 | -------------------------------------------------------------------------------- /configs/mod_smtp_filter_dkim.conf: -------------------------------------------------------------------------------- 1 | ; mod_smtp_filter_dkim.conf - DKIM signing configuration 2 | ; This module uses libopendkim directly, it does not use the OpenDKIM milter, so there is no need to modify /etc/opendkim.conf. 3 | 4 | [general] ; Reserved, but currently unused 5 | 6 | ; Setup Guidance: 7 | ; A domain may have more than one DKIM selector. We recommend using a unique private/public keypair for each mail server and domain. 8 | ; You can use the opendkim tool to generate DKIM keys for you: 9 | ; See: https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-dkim-with-postfix-on-debian-wheezy#generate-the-public-and-private-keys 10 | ; apt-get install -y opendkim-tools 11 | ; cd /etc/dkimkeys 12 | ; mkdir example.com && cd example.com 13 | ; opendkim-genkey -s mail -d example.com 14 | ; chown -R bbs /etc/opendkim/keys/example.com # assuming bbs is the user under which the BBS runs 15 | ; mail.txt contains your DNS record, that you'll need to add in DNS. 16 | 17 | ; One or more sections for each domain from which outgoing emails will be signed with DKIM 18 | 19 | ;[example.com] 20 | ;selector=foobar ; DKIM selector 21 | ;key=abcdefg ; DKIM secret key. It's recommended that you use keyfile instead, but ensure this file is readable by the BBS user. 22 | ;keyfile=/etc/opendkim/keys/example.com/mail.private ; Alternately, path to a file containing the DKIM secret key 23 | ;strictheaders=no ; Whether to use simple canonicalization for headers instead of relaxed canonicalization. Default is no. 24 | ;strictbody=no ; Same as above, but for body. 25 | ;alg=sha256 ; Signing algorithm to use. Possible values are sha1 and sha256. Default is sha256 26 | -------------------------------------------------------------------------------- /configs/mod_smtp_greylisting.conf: -------------------------------------------------------------------------------- 1 | ; mod_smtp_greylisting - SMTP message greylisting 2 | ; This module is active as long as this config file is present. 3 | ; Greylisting should only be done on the mail server that receives mail directly from the Internet. 4 | ; Spam filtering, if being done, should be done on the same server so that the spam score is available for greylisting checks. 5 | [general] 6 | ; Define conditions required to evaluate messages for greylisting. 7 | ; Messages meeting this criteria will be greylisted. You can fine tune these to control what messages get greylisted. 8 | ; On one extreme, set both to 0 to greylist every incoming message. On the other, to greylist only the most obviously spammy messages, increase min_spamscore. 9 | ; Since greylisting may incur delays in receiving legitimate mail, it is recommended that min_spamscore be set to at least 1, to avoid unnecessarily delaying ALL messages. 10 | ; It is recommended that you change min_failcount only in response to observation of real traffic. 11 | min_spamscore = 2 ; Minimum rounded X-Spam-Score value required to consider greylisting a message. The header value is a float (e.g. 7.3) but is rounded down for comparison. Default is 2. 12 | min_failcount = 1 ; Minimum SMTP failure count (~protocol violations or suspicious activity) to consider greylisting a message. Default is 1. 13 | -------------------------------------------------------------------------------- /configs/mod_smtp_mailing_lists.conf: -------------------------------------------------------------------------------- 1 | ; mod_smtp_mailing_lists.conf - Simple mailing list exploder. 2 | 3 | [general] ; Reserved, but currently unused 4 | 5 | ; Define a section per mailing list. The name of the section is the reflector name. All settings are optional. 6 | ; The special * notation is a shortcut that indicates "all local users with mailboxes". 7 | ; This does NOT include users that exist but do not have a maildir, e.g. because they have never/not yet used their email. 8 | 9 | ;[everyone@example.com] ; Public discussion list 10 | ;recipients = *,bob@example.net ; All local users receive these posts, along with external user bob@example.net 11 | ;senders = * ; All local users may post to this list. 12 | 13 | ;[announcements] ; Announcement only list. Since no domain is specified, this reflector applies to all domains. 14 | ;recipients = * 15 | ;senders = sysop ; Only user 'sysop' may post. 16 | ;name=Announcements ; List name 17 | ; NOTE: The following options modify the message itself, and will invalidate DKIM signatures. 18 | ; This may cause delivery failures if DKIM is validated for DMARC but ARC is not. Use with caution: 19 | ;tag=Announce ; If specified, a tag that is prepended to the subject, e.g. [Announce] Big announcement! 20 | ;footer=Thank you for subscribing to the announcements list! ; Footer added after body, may not work properly for non plain-text messages. 21 | ;archive=yes ; Whether to archive a copy of messages sent to this list in a dedicated folder in the lists directory. 22 | ; If this is disabled, there will not be any universal record of all messages posted to the mailing list anywhere. 23 | ; However, enabling this will increase storage requirements since the message is duplicated to this folder (in addition to each recipient). 24 | ; Default is yes. 25 | 26 | ;[public@example.com] ; Distribution list 27 | ;recipients = sysop,sysop2,sysop3 ; These users receive messages posted to the list. 28 | ; No senders list defined, so anyone (local or external) may post to this distribution list. 29 | ;replyto=list ; List reply behavior. Options are 'list' (default) - reply to list, 'sender' - reply to sender, 'both' - reply to list and sender 30 | ;ptonly = yes ; Only allow plain text messages 31 | ;maxsize=10000 ; Maximum message size permitted on this mailing list, in bytes. 32 | 33 | ;[sysops@example.com] ; An email list just for the sysops 34 | ;recipients = sysop,sysop2,sysop3 35 | ;samesenders = yes ; Shortcut so that recipients doesn't have to be repeated verbatim for senders. Allow any recipient to post to this list. 36 | -------------------------------------------------------------------------------- /configs/mod_webmail.conf: -------------------------------------------------------------------------------- 1 | ; mod_webmail.conf 2 | 3 | ; IMAP log file for debugging. 4 | [logging] 5 | logfile=/var/log/lbbs/webmail.log ; Optional log file for IMAP logging. 6 | loglevel=5 ; Log level (0=off, 10=maximum). 7 | -------------------------------------------------------------------------------- /configs/net_finger.conf: -------------------------------------------------------------------------------- 1 | ; net_finger.conf - Finger protocol configuration 2 | 3 | ; *** WARNING *** Finger has no authentication, confidentiality, or security of any kind. 4 | ; *** Use at your (and your users') own risk. 5 | 6 | [finger] 7 | port=79 8 | allusersallowed=no ; Whether to allow listings of all users. Default is no. WARNING: Enabling this will leak information about your users. 9 | -------------------------------------------------------------------------------- /configs/net_ftp.conf: -------------------------------------------------------------------------------- 1 | ; net_ftp.conf 2 | 3 | ; Native FTP server. 4 | ; Only allows passive mode transfers, active mode is not supported. 5 | ; All transfers are binary. 6 | 7 | ; IMPORTANT: Additional settings must also be configured in transfers.conf 8 | 9 | [ftp] 10 | port = 21 ; Port on which to run the FTP service 11 | 12 | [ftps] 13 | enabled = no ; Whether to enable Implicit TLS. Default is no (explicit TLS is always supported on the normal FTP port, if TLS is available). 14 | port = 990 ; Port on which to run Implicit FTPS service. 15 | requirereuse=no ; Whether to require TLS session reuse from the control connection to the data connection. 16 | ; If you don't know what this means, you should probably leave this alone. 17 | ; Default is no, as not all FTP clients support session reuse. However, this may be a security vulnerability. 18 | ; This applies to both Implicit and Explicit FTPS. 19 | 20 | [pasv] ; Port range to use for passive mode transfers 21 | ; These settings have no default. They must be specified explicitly. 22 | minport=10000 23 | maxport=20000 24 | ; If your machine has multiple interfaces, or is behind NAT, you will want to specify the IP addresses 25 | ; to be used for PASV connections so clients establish a connection to the same interface as the original connection. 26 | ; If not specified, the FTP server will attempt to determine the address of the interface 27 | ; that matches the client's connection and use that, which will likely not work behind NAT. 28 | ; 29 | ;public_ip = 192.0.2.21 ; Public IP address to advertise in PASV, for clients connecting with public IP addresses 30 | ;private_ip = 10.1.1.21 ; Private IP address to advertise in PASV, for clients connecting with private IP addresses 31 | -------------------------------------------------------------------------------- /configs/net_gopher.conf: -------------------------------------------------------------------------------- 1 | ; net_gopher.conf - Gopher protocol 2 | 3 | [gopher] 4 | port=70 ; Port on which to run Gopher server. Default is 70. 5 | root=/home/bbs/www ; Root directory for Gopher server. Note that the Gopher server does not support any kind of authentication. 6 | -------------------------------------------------------------------------------- /configs/net_http.conf: -------------------------------------------------------------------------------- 1 | ; net_http.conf - HTTP/HTTPS web server with CGI and Basic Auth support 2 | 3 | ; This is a simple, lightweight, self-contained web server that runs within the BBS. 4 | 5 | [general] 6 | ;docroot=/home/bbs/www ; Root web directory. If a request is for a directory with no path, index.html is the implicitly requested file. 7 | ; If you just want to enable file access via HTTP/HTTPS, you can set this to the same root used in transfers.conf. 8 | ; The public_html directories inside user home directories are specially accessible at /~username/ 9 | ;cgi=yes ; Whether to enable CGI script execution. Scripts may be any name, but must be executable. Default is no. 10 | ; CGI script execution is not allowed within home directories, for security reasons. 11 | ;authonly=yes ; Whether to require all visitors to authenticate to the BBS using HTTP Basic Authentication. WARNING: Insecure with HTTP, please use HTTPS! 12 | ; This option should be considered deprecated, as it applies to ALL HTTP requests. 13 | ;forcehttps=yes ; Redirect all HTTP requests to HTTPS. 14 | ; The default for this setting is whether TLS and HTTP are both enabled, 15 | ; i.e. if TLS is available and HTTPS is enabled, this is enabled by default, otherwise it is not. 16 | ;hsts=63072000 ; Enable HSTS (HTTP Strict Transport Security), which will instruct all supporting browsers to only use HTTPS 17 | ; for any page on your sites. The value is the maxage (in seconds) to use in the HSTS request. 18 | ; (0 will disable this setting; if positive, it will be enabled, and enable includeSubdomains and preload). 19 | ; WARNING: Enabling this option is hard to undo once you have "unleashed" it on clients. 20 | ; Enabling this is recommended, but only if you fully understand the implications. 21 | ;forcesessions=no ; Whether to always start HTTP sessions (potentially useful for linking requests together for various purposes). 22 | ; By default, sessions are only started if a handler explicitly starts sessions; this will force the default handlers to do so. 23 | 24 | [http] 25 | ;enabled=yes ; Enable HTTP listener. Default is no. 26 | port=80 ; Port on which to run HTTP. Default is 80. 27 | 28 | [https] 29 | ; NOTE: Additional configuration in tls.conf is also required. 30 | ;enabled=yes ; Enable HTTPS listener. Default is no. 31 | port=443 ; Port on which to run HTTPS. Default is 443. 32 | -------------------------------------------------------------------------------- /configs/net_imap.conf: -------------------------------------------------------------------------------- 1 | ; net_imap.conf - IMAP server configuration 2 | 3 | ; NOTE: Additional configuration is required in mod_mail.conf and tls.conf 4 | ; NOTE: You can add DNS records for your domain to ease client configuration for users. See RFC 6186. 5 | 6 | [general] 7 | allowidle=yes ; Whether to allow the use of IDLE for clients to receive push notifications from the server in realtime. 8 | ; Currently, this is only *optimally* supported for the INBOX (and partially for all other folders). Default is yes. 9 | ;idlenotifyinterval=600 ; How often (in seconds) IDLE notification messages are sent to the client (IDLE keepalive). 0 to disable. 10 | ;maxappendsize=5242880 ; Maximum APPEND size allowed (determines APPENDLIMIT). Default is 25 MB. 11 | ; Note that mailbox quotas are configured in mod_mail.conf and/or individual maildirs. 12 | ;maxuserproxies=10 ; Maximum number of remote IMAP client connections a user can simultaneously proxy. 13 | ; Default is 10. Set to 0 to disable this functionality or 1 to limit each user to a single proxied client at any given time. 14 | ; In general, a higher number will result in better performance with multiple remote servers but will use more resources, 15 | ; so you should set this to a sufficiently high (but not inappropriately too high) value for your traffic and usage. 16 | 17 | [imap] 18 | enabled=no ; Whether or not cleartext IMAP is enabled. This should not be needed for most IMAP clients. Default is no. 19 | ; WARNING: The cleartext IMAP port does not support STARTTLS. Use of this protocol will 20 | ; send credentials in the clear over the network. Do not enable this unless you really need it! 21 | port=143 ; Port for cleartext IMAP. 22 | 23 | [imaps] 24 | enabled=yes ; Whether or not secure IMAP is enabled. (This is what most, if not all, modern IMAP clients use.) Default is yes. 25 | port=993 ; Port for IMAP using implicit TLS. 26 | 27 | [preauth] ; IMAP users to preauthenticate. 28 | ; WARNING: Do not configure entries in this section without understanding the security implications. 29 | ; IMAP preauthentication allows for users to be automatically authenticated upon connection 30 | ; since they were authenticated by another mechanism. 31 | ; Here, that mechanism is IP address: if you have a static IP address and don't believe 32 | ; there is any risk of others being able to utilize your network, you may want to configure a mapping 33 | ; for yourself for ease of debugging. However, DO NOT add the loopback address (127.0.0.1). 34 | ; Any entries here SHOULD be strictly non-overlapping. It is not guaranteed that the most specific match will apply, 35 | ; only that a matching mapping is used. Overlapping entries may lead to non-deterministic behavior. 36 | ; Otherwise, in general, there are NO GOOD REASONS to utilize this and the security risks could allow 37 | ; unhampered access to accounts configured here. 38 | ;10.10.10.10/32 = sysop ; Allow connections from CIDR range 10.10.10.10/32 to automatically authenticate as sysop. 39 | -------------------------------------------------------------------------------- /configs/net_irc.conf: -------------------------------------------------------------------------------- 1 | ; net_irc.conf - Internet Relay Chat server 2 | ; IRC server that uses regular BBS account logins for authentication. 3 | 4 | [general] 5 | ;hostname=irc.example.com ; IRC hostname. If not specified, this will default to the BBS hostname or the local IP address. 6 | logchannels=no ; Whether to log all channel conversation to a log file, by channel. Default is no. 7 | ; You are advised to alert IRC network participants if channel activity is being logged, 8 | ; as a courtesy, but that is your responsibility. 9 | requiresasl=yes ; Whether SASL authentication is required. Clients will not be able to connect without 10 | ; using SASL authentication (traditional authentication is not used). 11 | ; Encouraged for security and simplicity, but 12 | ; disable if you need to support clients that do not support SASL. Default is yes. 13 | requirechanserv=yes ; Whether ChanServ must be loaded in order for users to connect to the IRC server. 14 | ; In practice, you will likely want this value 'yes' if you use ChanServ (mod_chanserv), 15 | ; to prevent users from joining channels before ChanServ joins guarded channels, 16 | ; and 'no' if you do not, or it will prevent users from connecting if mod_chanserv is not running. 17 | ; Default is 'yes'. 18 | ;motdfile=/home/bbs/ircmotd.txt ; A file containing custom "Message of the Day" for IRC. May contain multiple lines. 19 | 20 | [irc] 21 | enabled=yes ; Whether plain text IRC is enabled. Default is yes. 22 | port=6667 ; Port for insecure IRC. Default is 6667. 23 | 24 | [ircs] 25 | ; NOTE: Additional configuration in tls.conf is also required. 26 | enabled=yes ; Whether secure IRC is enabled. Default is yes. 27 | port=6697 ; Port for secure IRC. Default is 6697. 28 | 29 | ;[opers] ; IRC operators. Network operators can op themselves using the OPER command with credentials listed here. 30 | ; Note that passwords here are not hashed (e.g. using mkpasswd). It is recommended to use nativeopers instead if possible. 31 | ;admin=P@ss0rd 32 | ;bob=B0B5p@sSw0rd 33 | 34 | ;[nativeopers] ; Same as [opers], but just specify the BBS users that are able to become operators. 35 | ; This is intended for convenience if you want to make certain BBS users operators, without needing to use separate credentials. 36 | ; It's also more secure since you don't need to put any credentials in this file. 37 | ;sysop=sysop ; Values don't matter, just need to exist. The operator's password is his/her regular BBS password. 38 | -------------------------------------------------------------------------------- /configs/net_msp.conf: -------------------------------------------------------------------------------- 1 | ; net_msp.conf - Message Send Protocol 2 | ; 3 | ; This protocol can be used to allow various clients (e.g. other servers) to submit 4 | ; messages to deliver to local BBS users. This avoids the need to, for example, 5 | ; create an IRC user for other clients and set up persistent IRC connections, 6 | ; for applications that may only need to send messages to users or channels. 7 | ; 8 | ; WARNING: This protocol may allow unwanted anonymous and spoofed messages to reach users. 9 | ; If you load this module, it is HIGHLY recommend that you configure restrictions on the UDP 10 | ; listener to only listen to a private interface, through which trusted messages can be 11 | ; sent by other endpoints. 12 | ; You are urged to NOT EXPOSE this protocol to the Internet or other public networks. 13 | 14 | [ports] 15 | tcp=18 ; Port for MSP over TCP. 16 | udp=18 ; Port for MSP over UDP. 17 | 18 | ; Additional configuration for the UDP listener 19 | [udp] 20 | ;ip=127.0.0.1 ; Restrict listener to this IP address 21 | ;interface=eth1 ; Specific interface on which to listen 22 | -------------------------------------------------------------------------------- /configs/net_nntp.conf: -------------------------------------------------------------------------------- 1 | ; net_nntp.conf - Network News Transfer Protocol server 2 | 3 | [general] 4 | newsdir=/home/bbs/newsgroups ; Directory containing news groups. 5 | ; Each subdirectory of this directory is the name of a newsgroup. 6 | ; You must manually create these directories for any newsgroups that should exist. 7 | requirelogin=yes ; Whether users must log in to access newsgroups. 8 | requiresecurelogin=no ; Whether users must log in using an encrypted connection. 9 | requireloginforposting=yes ; Whether users must log in to post messages. This is implicitly enabled if requirelogin=yes. 10 | minpostpriv=1 ; Minimum privilege level required to post. (Allows more granular control than requireloginforposting.) Default is 1. 11 | maxpostsize=100000 ; Maximum post size, in bytes. Default is 100000 (appx. 100 KB) 12 | 13 | [nntp] 14 | enabled=yes 15 | port=119 16 | 17 | [nntps] 18 | enabled=yes 19 | port=563 20 | 21 | [nnsp] 22 | enabled=yes 23 | port=433 24 | 25 | ; Additional settings for NNSP (Network News Submission Protocol): 26 | 27 | [relayin] ; Settings for accepting articles from other sites 28 | requiretls=yes ; Whether TLS is required for article submission. Default is yes. 29 | 30 | [relayout] ; Settings for sending articles to other sites 31 | frequency=3600 ; Frequency of article relays. Default is every hour. 32 | maxage=86400 ; Maximum article age of articles to relay. Default is only relay articles newer than 1 day. 33 | 34 | [trusted] ; Specify other newsgroup servers that are trusted to send us articles. 35 | ; You can specify user= for a user on this server or ip= for an IPv4 address (CIDR range allowed) 36 | ; You may also specify host= for a hostname match, but this may caused degraded performance 37 | ; if used excessively (ip= is preferred). 38 | ; Hosts without static IP addresses will need an account on this server to submit articles. 39 | ;user=sysop 40 | ;ip=127.0.0.1/32 41 | ;host=nntp.example.com 42 | host=bbs.phreaknet.org 43 | 44 | [relayto] ; Other newsgroup servers to which we should periodically relay our articles. 45 | ; Note that these servers must be configured to accept articles from this server! 46 | ; You can optionally specify a username and password to use for article submission, in [relayout] 47 | ; Otherwise you may need to coordinate with other newsgroup servers to be able to exchange posts. 48 | ;relay=username:password@nntp.example.com 49 | relay=bbs.phreaknet.org 50 | -------------------------------------------------------------------------------- /configs/net_pop3.conf: -------------------------------------------------------------------------------- 1 | ; net_pop3.conf - POP3 server configuration 2 | 3 | ; NOTE: Additional configuration is required in mod_mail.conf and tls.conf 4 | 5 | [pop3] 6 | enabled=no ; Whether or not cleartext POP3 is enabled. This should not be needed for most POP3 clients. Default is no. 7 | ; WARNING: The cleartext POP3 port does not support STARTTLS. Use of this protocol will 8 | ; send credentials in the clear over the network. Do not enable this unless you really need it! 9 | port=110 ; Port for cleartext POP3. 10 | 11 | [pop3s] 12 | enabled=yes ; Whether or not secure POP3 is enabled. (This is what most, if not all, modern POP3 clients use.) Default is yes. 13 | port=995 ; Port for POP3 using implicit TLS. 14 | -------------------------------------------------------------------------------- /configs/net_rlogin.conf: -------------------------------------------------------------------------------- 1 | ; net_rlogin.conf 2 | 3 | [rlogin] 4 | port = 513 5 | -------------------------------------------------------------------------------- /configs/net_ssh.conf: -------------------------------------------------------------------------------- 1 | ; net_ssh.conf 2 | 3 | [ssh] 4 | port = 22 5 | 6 | [sftp] 7 | enabled=yes ; Whether SFTP subsystem is enabled (on the same port as SSH). Default is yes. 8 | ; IMPORTANT: Additional settings must also be configured in transfers.conf 9 | 10 | [keys] 11 | ; These defaults should work on most systems. If a key has an issue loadng, you can change it to "no". 12 | ; The keys must be located in /etc/ssh and use the OpenSSH naming convention. 13 | ; Users may also authenticate to the BBS using a keypair for both SSH and SFTP. 14 | ; Users' public keys should be in a file called ssh.pub in their home directories, e.g. /home/1/ssh.pub 15 | ; These keypairs can be generated using ssh-keygen. See transfers.conf for more info about home directories. 16 | rsa = yes 17 | dsa = yes 18 | ecdsa = yes 19 | ed25519 = yes 20 | -------------------------------------------------------------------------------- /configs/net_telnet.conf: -------------------------------------------------------------------------------- 1 | ; net_telnet.conf 2 | 3 | [telnet] 4 | port = 23 5 | ;ttyport = 2245 ; Enable unencrypted TTY/TDD optimized access on a separate port. 6 | ; You will need something like this: https://github.com/InterLinked1/phreakscript/blob/master/apps/app_softmodem.c 7 | 8 | [telnets] 9 | enabled = no ; Whether to enable TELNETS (Secure Telnet) 10 | port = 992 11 | ;ttyport = 2246 ; Same as ttyport in [telnet], but encrypted using TLS (useful if your modem bank is on a different server). 12 | -------------------------------------------------------------------------------- /configs/nodes.conf: -------------------------------------------------------------------------------- 1 | ; nodes.conf 2 | 3 | [bbs] 4 | name=My BBS ; name of the BBS 5 | tagline=Chat, Games, and More! ; BBS tagline. If empty, will not be displayed. 6 | hostname=bbs.example.com ; BBS Internet hostname. If empty, will not be displayed. 7 | sysop=Sysop ; Sysop name / username. If empty, will not be displayed. 8 | ;minuptimedisplayed=3600 ; Minimum time to display the current uptime on the node connect screen. Default is 0 (always display). 9 | exitmsg=${COLOR_GREEN}Thank you for visiting today, please call again soon!${CRLF}${CRLF}${COLOR_BLUE}Goodbye. ; Message to be displayed when users exit the BBS. Can include variables. 10 | 11 | [nodes] 12 | maxnodes=64 ; Maximum number of nodes. Once capacity is reached, new connections will be denied. Default is 64. 13 | ;maxnodes_perip=32 ; Maximum number of nodes allowed to connect from each IP address. Default is maxnodes / 2. 14 | ;defaultbps=300 ; Throttle node output to a specified speed on connection (bps = bits per second). 15 | ; Useful if you want to emulate a lower speed system, but at the cost of less efficient CPU utilization. 16 | ; Default is 0 (unthrottled), and input is never throttled. 17 | ;defaultrows=24 ; Default number of terminal rows, if not received from client. Default is 24. 18 | ;defaultcols=80 ; Default number of terminal columns, if not received from client. Default is 80. 19 | ;askdimensions=yes ; Prompt users for their terminal dimensions if they are not received automatically. Default is yes. 20 | idlemins=30 ; The amount of time a user may idle on certain screens before the node is timed out for inactivity. 21 | ; Note that some parts of the BBS user their own timers and ignore this setting. 22 | ; Default is 30 minutes. Specify 0 for unlimited (disable timeout, at least for prompts that use this timer). 23 | 24 | [guests] 25 | allow=yes ; Whether to allow guest logins to the BBS. Default is yes. 26 | askinfo=yes ; Whether to ask guests to provide some basic information about themselves. Default is yes. 27 | ; Valid options are 'no', 'yes', and 'always'. 'yes' will ask guests if they are not using a TDD; 28 | ; 'always' will prompt users even if they are using a TDD. 29 | ; WARNING: If you disable this, guest users will not be able to receive email replies. 30 | -------------------------------------------------------------------------------- /configs/templates/.imapremote.sample: -------------------------------------------------------------------------------- 1 | ### .imapremote - Personal virtual mappings for remote IMAP mailboxes 2 | 3 | ### Any mailboxes defined here will be available to you when logged in to the IMAP server, as if they were local. 4 | ### The sysop may have configured a maximum number of remote connections that may be defined here 5 | 6 | ### Plain LOGIN auth syntax: Other Users.|imaps://:@: 7 | ### OAuth2 auth syntax: Other Users.|imaps://:oauth:@: 8 | ### OAuth authentication also requires an OAuth profile to be configured in your .oauth.conf or the BBS's global OAuth config. 9 | 10 | # Other Users.foobar|imaps://bob@example.com:password@example.com:993 11 | # Other Users.foobar2|imaps://bob@example.net:oauth:bobexamplenet@example.net:993 12 | -------------------------------------------------------------------------------- /configs/templates/.oauth.conf.sample: -------------------------------------------------------------------------------- 1 | ; .oauth.conf - Personal OAuth authentication provider 2 | 3 | ; OAuth clients defined here are only accessible to you, not to any other users. 4 | 5 | ; You MAY specify an access token but SHOULD NOT because these are short lived (only good for an hour). 6 | ; This may be useful for testing if you know if you have a valid access token and want to use that for something, 7 | ; but the refresh token is needed to renew to get new access tokens regularly. 8 | ; Generally, do not specify an accesstoken since the refreshtoken can be used to get one automatically. 9 | 10 | ; This is an example OAuth client for Gmail. 11 | ;[gmail] ; The name of the OAuth client, which may be referenced in .imapremote for authentication 12 | ;clientid=406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com 13 | ;clientsecret=kSmqreRr0qwBWJgbf5Y-PjSU 14 | ;accesstoken= 15 | ;refreshtoken=1//01357485r_o8rwySUOIGFSD8dfgd-L8eyfdufOf8uysfodshfiewyar8382roeyhfdsfisdfo_Asdas980d90asdud-OASDyddfo 16 | ;expires=3600 ; How long access tokens are valid for. Default is 1 hour. The refresh token will be used to obtain a new access token if the old one has expired. 17 | ;posturl=https://oauth2.googleapis.com/token ; Endpoint to get new access tokens using the refresh token 18 | 19 | ;[microsoft] 20 | ;clientid=9e5f94bc-e8a4-4e73-b8be-63364c29d753 ; This is Thunderbird's client ID. (Re)use at your own risk. 21 | ;clientsecret= 22 | ;accesstoken= 23 | ;refreshtoken=M.C503_SN1.0.U.-ASIfsudfywoeufwuofwoeyf0e8yfsdifhpsidfyowse8yf8iweyf8iedyhf08wefy0ow4yr8iwyh8idfvyiaeyhdplIWhfujeoghvowseuf ; it's a lot longer than this... 24 | ;posturl=https://login.microsoftonline.com/common/oauth2/v2.0/token 25 | -------------------------------------------------------------------------------- /configs/tls.conf: -------------------------------------------------------------------------------- 1 | ; tls.conf 2 | 3 | [tls] ; Configures the TLS certificate used by any BBS modules that use TLS 4 | ; If you use Let's Encrypt with certbot, run "certbot certificates" to show which domains use which certs 5 | ; This section configures the default certificate, used prior to receiving any Server Name Indication. 6 | ; These arguments are MANDATORY if you wish to enable any TLS servers. 7 | ; If no Server Name Indication is provided by the client, this is the certificate that will be used. 8 | ;rootcerts=/etc/ssl/certs/ca-certificates.crt ; Root certs file used for verifying TLS certificates. 9 | ; (should work for Debian-based distros; change accordingly if needed). 10 | ;cert=/etc/letsencrypt/live/example.com/fullchain.pem ; TLS certificate 11 | ;key=/etc/letsencrypt/live/example.com/privkey.pem ; TLS private key 12 | 13 | [sni] ; Optional: Server Name Indication is used to support TLS on multiple hostnames. 14 | ; If you are supporting multiple hostnames, add pairs of hostnames here with format hostname=cert:privkey, e.g. 15 | ; example.com=/etc/letsencrypt/live/example.com/fullchain.pem:/etc/letsencrypt/live/example.com/privkey.com 16 | ; If no match exists for a provided SNI, the default certificate will be used. 17 | -------------------------------------------------------------------------------- /configs/variables.conf: -------------------------------------------------------------------------------- 1 | ; variables.conf 2 | 3 | [variables] ; Global variables to initialize on startup. See menus.conf for why this could be useful. 4 | ; Variable names without lowercase letters should be avoided, since reserved and predefined BBS variables will always be all caps. 5 | ; Some variables are defined automatically, run /variables from the sysop console to see available variables. 6 | ;foo=bar 7 | 8 | ; Per-user variables that are automatically defined when a user logs in. 9 | ;[sysop] ; Username for which these variables are defined. 10 | ;SUPERADMIN=1 11 | ;abc=def 12 | 13 | ;[pat] 14 | ;ASTERISK_AGENT_ID=111 15 | -------------------------------------------------------------------------------- /doors/Makefile: -------------------------------------------------------------------------------- 1 | 2 | MOD_SRC := $(wildcard *.c) 3 | MOD_SO := $(MOD_SRC:.c=.so) 4 | DEPENDS := $(patsubst %.c,%.d,$(MOD_SRC)) 5 | 6 | # the include directory is in the parent 7 | INC = -I.. 8 | 9 | all: $(MOD_SO) 10 | 11 | -include $(DEPENDS) 12 | 13 | $(DEPENDS): 14 | 15 | %.o : %.c %.d 16 | @echo " [CC] $< -> $@" 17 | $(CC) $(CFLAGS) -fPIC -DBBS_MODULE=\"$(basename $<)\" -DBBS_MODULE_SELF_SYM=__internal_$(basename $<)_self -MMD -MP $(INC) -c $< 18 | 19 | %.so : %.o 20 | @echo " [LD] $^ -> $@" 21 | $(CC) $(MOD_LDFLAGS) -o $(basename $^).so $^ 22 | 23 | door_irc.so : door_irc.o 24 | @echo " [LD] $^ -> $@" 25 | $(CC) $(MOD_LDFLAGS) -o $(basename $^).so $^ -lirc 26 | 27 | # Don't automatically remove intermediate .o files, to prevent unnecessary recompilations 28 | .SECONDARY: $(patsubst %.c,%.o,$(MOD_SRC)) 29 | 30 | .PHONY: all 31 | -------------------------------------------------------------------------------- /doors/door_evergreen.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2024, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Evergreen email client wrapper 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #include "include/module.h" 23 | #include "include/node.h" 24 | #include "include/user.h" 25 | #include "include/auth.h" 26 | #include "include/door.h" 27 | #include "include/term.h" 28 | #include "include/system.h" 29 | #include "include/utils.h" /* use safe_strncpy */ 30 | 31 | static int evergreen_exec(struct bbs_node *node, const char *args) 32 | { 33 | struct bbs_exec_params x; 34 | char username[64]; 35 | char fromname[64]; 36 | char fromaddr[64]; 37 | char passwordbuf[TEMP_PASSWORD_TOKEN_BUFLEN]; 38 | /* Default is plaintext ports, localhost, no security, so we're good for plaintext */ 39 | #pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" 40 | /* Moderate debug to log file */ 41 | char *const argv[] = { "evergreen", "-ddddd", "-l" "/var/log/lbbs/evergreen.log", "--fromname", fromname, "--fromaddr", fromaddr, "--imap-username", username, "--imap-password", passwordbuf, "--smtp-password", passwordbuf, NULL }; 42 | #pragma GCC diagnostic pop 43 | 44 | UNUSED(args); 45 | 46 | if (!bbs_user_is_registered(node->user)) { 47 | bbs_node_writef(node, "You must be registered to check email"); 48 | bbs_node_wait_key(node, MIN_MS(1)); 49 | return 0; 50 | } 51 | 52 | safe_strncpy(username, bbs_username(node->user), sizeof(username)); 53 | if (!strlen_zero(node->user->fullname)) { 54 | safe_strncpy(fromname, node->user->fullname, sizeof(fromname)); 55 | } 56 | snprintf(fromaddr, sizeof(fromaddr), "%s@%s", bbs_username(node->user), bbs_hostname()); 57 | bbs_str_tolower(fromaddr); 58 | 59 | /* Create a temporary password for the user for IMAP/SMTP authentication. 60 | * Token needs to linger, not for IMAP, but for SMTP. */ 61 | if (bbs_user_semiperm_authorization_token(node->user, passwordbuf, sizeof(passwordbuf))) { 62 | return 0; 63 | } 64 | 65 | EXEC_PARAMS_INIT(x); 66 | bbs_execvp(node, &x, "evergreen", argv); 67 | bbs_user_semiperm_authorization_token_purge(passwordbuf); 68 | return 0; /* Don't return -1 or the node will abort */ 69 | } 70 | 71 | static int load_module(void) 72 | { 73 | return bbs_register_door("mail", evergreen_exec); 74 | } 75 | 76 | static int unload_module(void) 77 | { 78 | return bbs_unregister_door("mail"); 79 | } 80 | 81 | BBS_MODULE_INFO_STANDARD("Evergreen Mail Client"); 82 | -------------------------------------------------------------------------------- /doors/door_ibbs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Telnet BBS Guide listing 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #include 23 | #include /* use R_OK */ 24 | 25 | #include "include/module.h" 26 | #include "include/node.h" 27 | #include "include/door.h" 28 | #include "include/term.h" 29 | #include "include/system.h" 30 | #include "include/editor.h" 31 | 32 | #include "include/mod_curl.h" 33 | 34 | static int ibbs_exec(struct bbs_node *node, const char *args) 35 | { 36 | time_t now; 37 | struct tm nowdate; 38 | char mmyy[5]; 39 | char tmpzip[20]; 40 | char mon[5]; 41 | char listfile[48]; 42 | 43 | UNUSED(args); 44 | 45 | now = time(NULL); 46 | localtime_r(&now, &nowdate); 47 | #pragma GCC diagnostic ignored "-Wformat-y2k" 48 | strftime(mmyy, sizeof(mmyy), "%m%y", &nowdate); /* 2-digit month, 2-digit year */ 49 | strftime(mon, sizeof(mon), "%b", &nowdate); /* For the target filename. The full listing is in full_Mon_Yr.txt */ 50 | #pragma GCC diagnostic pop 51 | 52 | snprintf(tmpzip, sizeof(tmpzip), "/tmp/ibbs%s.zip", mmyy); 53 | snprintf(listfile, sizeof(listfile), "/tmp/full_%s_%s.txt", mon, mmyy + 2); 54 | 55 | #pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" 56 | if (access(listfile, R_OK)) { 57 | struct bbs_exec_params x; 58 | char *const argv[] = { "unzip", tmpzip, "-d", "/tmp", NULL }; 59 | if (access(tmpzip, R_OK)) { 60 | char url[54]; 61 | struct bbs_curl c = { 62 | .url = url, 63 | .forcefail = 1, 64 | }; 65 | /* File doesn't already exist. Download it. */ 66 | snprintf(url, sizeof(url), "https://www.telnetbbsguide.com/bbslist/ibbs%s.zip", mmyy); 67 | if (bbs_curl_get_file(&c, tmpzip)) { 68 | return 0; /* Don't return -1 or the node will abort */ 69 | } 70 | bbs_curl_free(&c); /* Technically, since we wrote to a file, there's nothing to free, but for consistency... */ 71 | } /* else, ZIP already exists */ 72 | 73 | /* Extract the files in the ZIP into the /tmp directory. 74 | * Even though we have a handle to the node, pass NULL for node since we don't need to link STDIN/STDOUT to the unzip command. 75 | * We just need it to execute, and this is more efficient (and safer!) than using system() 76 | */ 77 | EXEC_PARAMS_INIT_HEADLESS(x); 78 | if (bbs_execvp(node, &x, "unzip", argv)) { 79 | return 0; /* Don't return -1 or the node will abort */ 80 | } 81 | } /* else, file already exists */ 82 | #pragma GCC diagnostic pop 83 | 84 | return bbs_node_term_browse(node, listfile); 85 | } 86 | 87 | static int load_module(void) 88 | { 89 | return bbs_register_door("telnetbbsguide", ibbs_exec); 90 | } 91 | 92 | static int unload_module(void) 93 | { 94 | return bbs_unregister_door("telnetbbsguide"); 95 | } 96 | 97 | BBS_MODULE_INFO_DEPENDENT("Telnet BBS Guide", "mod_curl.so"); 98 | -------------------------------------------------------------------------------- /doors/door_msg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Messaging 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #include 23 | 24 | #include "include/module.h" 25 | #include "include/node.h" 26 | #include "include/user.h" 27 | #include "include/editor.h" 28 | #include "include/door.h" 29 | #include "include/notify.h" 30 | #include "include/utils.h" 31 | 32 | static int msg_sysop_exec(struct bbs_node *node, const char *args) 33 | { 34 | int res; 35 | char buf[1024]; 36 | 37 | UNUSED(args); 38 | 39 | res = bbs_line_editor(node, "Compose message, then process to send", buf, sizeof(buf) - 1); 40 | if (res < 0) { 41 | return res; 42 | } else if (res > 0) { 43 | NEG_RETURN(bbs_node_wait_key(node, MIN_MS(2))); 44 | return 0; 45 | } 46 | 47 | if (strlen(buf) < 10 || !bbs_str_isprint(buf)) { 48 | bbs_node_writef(node, "%sMessage rejected, aborting%s\n", COLOR(COLOR_FAILURE), COLOR_RESET); 49 | NEG_RETURN(bbs_node_wait_key(node, MIN_MS(2))); 50 | return 0; 51 | } 52 | 53 | if (bbs_user_is_guest(node->user)) { 54 | res = bbs_sysop_email(node->user, "New Sysop Inquiry", "Guest user %s has just messaged you:\r\n\r\n%s\r\n", bbs_user_alias(node->user), buf); 55 | } else { 56 | res = bbs_sysop_email(node->user, "New Sysop Inquiry", "User %s (#%d) has just messaged you:\r\n\r\n%s\r\n", bbs_username(node->user), node->user->id, buf); 57 | } 58 | if (res) { 59 | bbs_node_writef(node, "%sSystem error, message not sent.%s\n", COLOR(COLOR_FAILURE), COLOR_RESET); 60 | NEG_RETURN(bbs_node_wait_key(node, MIN_MS(2))); 61 | return 0; 62 | } 63 | bbs_node_writef(node, "%sYour message has been sent!%s\n", COLOR(COLOR_SUCCESS), COLOR_RESET); 64 | NEG_RETURN(bbs_node_wait_key(node, MIN_MS(2))); 65 | return 0; 66 | } 67 | 68 | static int load_module(void) 69 | { 70 | return bbs_register_door("msgsysop", msg_sysop_exec); 71 | } 72 | 73 | static int unload_module(void) 74 | { 75 | return bbs_unregister_door("msgsysop"); 76 | } 77 | 78 | BBS_MODULE_INFO_STANDARD("Direct Messaging"); 79 | -------------------------------------------------------------------------------- /doors/door_stats.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief User and Node Statistics 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #include 23 | 24 | #include "include/module.h" 25 | #include "include/node.h" 26 | #include "include/user.h" 27 | #include "include/auth.h" 28 | #include "include/door.h" 29 | #include "include/editor.h" 30 | 31 | static int users_exec(struct bbs_node *node, const char *args) 32 | { 33 | int index = 0; 34 | int activeonly = 0; 35 | int totalonline = 0, total = 0; 36 | struct pager_info pginfo; 37 | struct bbs_user *user, **userlist = bbs_user_list(); 38 | 39 | if (!userlist) { 40 | return 0; 41 | } 42 | 43 | if (!strlen_zero(args) && !strcasecmp(args, "active")) { 44 | activeonly = 1; 45 | } 46 | 47 | /* Don't be deceived: this is an O(n^2) operation, since we call bbs_user_online for each user */ 48 | bbs_node_clear_screen(node); 49 | memset(&pginfo, 0, sizeof(pginfo)); 50 | while ((user = userlist[index++])) { 51 | char buf[96]; 52 | int len; 53 | int active; 54 | active = bbs_user_online(user->id); 55 | total++; 56 | if (!active && activeonly) { 57 | continue; 58 | } 59 | if (index == 1) { 60 | bbs_node_writef(node, COLOR(COLOR_PRIMARY) " %4s %-15s %s\r\n", "#", "USERNAME", "ONLINE"); 61 | } 62 | if (active) { 63 | totalonline++; 64 | } 65 | len = snprintf(buf, sizeof(buf), COLOR(COLOR_SECONDARY) " %4d " COLOR_RESET "%-15s %s\r\n", user->id, bbs_username(user), (active ? " * " : "")); 66 | if (bbs_pager(node, &pginfo, MIN_MS(3), buf, (size_t) len)) { 67 | break; 68 | } 69 | } 70 | 71 | bbs_user_list_destroy(userlist); 72 | bbs_node_writef(node, "%d user%s online (%d total)\n", totalonline, ESS(totalonline), total); 73 | NEG_RETURN(bbs_node_wait_key(node, MIN_MS(2))); 74 | return 0; 75 | } 76 | 77 | static int nodes_exec(struct bbs_node *node, const char *args) 78 | { 79 | bbs_node_clear_screen(node); 80 | bbs_node_statuses(node, args); 81 | NEG_RETURN(bbs_node_wait_key(node, MIN_MS(2))); 82 | return 0; 83 | } 84 | 85 | static int unload_module(void) 86 | { 87 | bbs_unregister_door("listusers"); 88 | bbs_unregister_door("listnodes"); 89 | return 0; 90 | } 91 | 92 | static int load_module(void) 93 | { 94 | int res = 0; 95 | res |= bbs_register_door("listusers", users_exec); 96 | res |= bbs_register_door("listnodes", nodes_exec); 97 | REQUIRE_FULL_LOAD(res); 98 | } 99 | 100 | BBS_MODULE_INFO_STANDARD("User and Node Statistics"); 101 | -------------------------------------------------------------------------------- /external/Makefile: -------------------------------------------------------------------------------- 1 | # External programs, not part of the BBS itself 2 | 3 | SRCS := $(wildcard *.c) 4 | OBJS = $(SRCS:.c=.o) 5 | EXES = $(patsubst %.o,%,$(OBJS)) 6 | OBJS_NOFILEMGR = $(filter-out filemgr.o,$(OBJS)) 7 | EXES_NOFLEMGR = $(patsubst %.o,%,$(OBJS_NOFILEMGR)) 8 | 9 | CC = gcc 10 | CFLAGS = -Wall -Werror -Wunused -Wextra -Wmaybe-uninitialized -Wstrict-prototypes -Wmissing-prototypes -Wdeclaration-after-statement -Wmissing-declarations -Wmissing-format-attribute -Wnull-dereference -Wformat=2 -Wshadow -Wsizeof-pointer-memaccess -std=gnu99 -pthread -O3 -g -Wstack-protector -Wno-unused-result -fno-omit-frame-pointer -fwrapv -D_FORTIFY_SOURCE=2 11 | RM = rm -f 12 | INSTALL = install 13 | NCURSES_FLAGS=$(shell pkg-config --cflags ncurses) 14 | 15 | ARCH = $(shell uname -m) 16 | CFLAGS += -DBBS_LINUX_ARCH=$(ARCH) 17 | 18 | all: $(EXES) 19 | 20 | $(EXES_NOFLEMGR): %: %.o 21 | @echo " [LD] $@.o -> $@" 22 | $(CC) $(CFLAGS) -o $@ $@.o 23 | 24 | filemgr: filemgr.o 25 | @echo " [LD] $@.o -> $@" 26 | $(CC) $(CFLAGS) -o $@ $@.o -lmenu -lncurses 27 | 28 | %.o : %.c 29 | @echo " [CC] $< -> $@" 30 | $(CC) $(CFLAGS) -c $^ 31 | 32 | filemgr.o : filemgr.c 33 | @echo " [CC] $< -> $@" 34 | $(CC) $(CFLAGS) $(NCURSES_FLAGS) -c $^ 35 | 36 | install : all 37 | @for i in $(EXES); do \ 38 | echo " -> Installing external program $${i}"; \ 39 | $(INSTALL) -m 755 $${i} /var/lib/lbbs/external/; \ 40 | done 41 | 42 | clean : 43 | $(RM) *.i *.o $(EXES) 44 | 45 | .PHONY: all 46 | .PHONY: install 47 | .PHONY: clean 48 | -------------------------------------------------------------------------------- /external/leak.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Memory leaker 16 | * 17 | * \note Create a perpetually growing memory leak until stopped (for testing resource limits) 18 | * 19 | * \author Naveen Albert 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | int main(int argc, char *argv[]) 27 | { 28 | size_t allocated = 0; 29 | 30 | (void) argc; 31 | (void) argv; 32 | 33 | for (;;) { 34 | char *s = malloc(1024 * 1024); /* 1 MB at a time */ 35 | if (!s) { 36 | printf("Allocation failed\n"); 37 | break; 38 | } 39 | allocated++; 40 | printf("%6lu MB now allocated\n", allocated); 41 | usleep(50000); /* Pause 50ms for every MB allocated */ 42 | } 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /external/modseqdecode.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Modification Sequence file (.modseqs) decoder 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | int main(int argc, char *argv[]) 25 | { 26 | FILE *fp; 27 | int res; 28 | unsigned int uid; 29 | unsigned long modseq; 30 | 31 | if (argc != 2) { 32 | fprintf(stderr, "Usage: modseqdecode \n"); 33 | exit(EXIT_FAILURE); 34 | } 35 | 36 | fp = fopen(argv[1], "rb"); 37 | if (!fp) { 38 | fprintf(stderr, "Failed to open %s\n", argv[1]); 39 | exit(errno); 40 | } 41 | 42 | res = fread(&modseq, sizeof(unsigned long), 1, fp); 43 | if (res != 1) { 44 | fprintf(stderr, "File corrupted: failed to read HIGHESTMODSEQ\n"); 45 | exit(EXIT_FAILURE); 46 | } 47 | 48 | printf("HIGHESTMODSEQ: %lu\n", modseq); 49 | 50 | for (;;) { 51 | res = fread(&uid, sizeof(unsigned int), 1, fp); 52 | if (res != 1) { 53 | break; 54 | } 55 | res = fread(&modseq, sizeof(unsigned long), 1, fp); 56 | if (res != 1) { 57 | break; 58 | } 59 | printf("UID - %8u => MODSEQ %12lu\n", uid, modseq); 60 | } 61 | 62 | fclose(fp); 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /external/saslencode.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief RFC 4616 PLAIN SASL Encoder 16 | * 17 | * \note Creates a base64 encoded PLAIN SASL authentication string from identity 18 | * 19 | * \author Naveen Albert 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | static char encoding_table[] = 27 | {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 28 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 29 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 30 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 31 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 32 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 33 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', 34 | '4', '5', '6', '7', '8', '9', '+', '/'}; 35 | 36 | static int mod_table[] = {0, 2, 1}; 37 | 38 | /*! \brief Based on https://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c/6782480#6782480 */ 39 | static char *base64_encode(const char *data, int input_length, int *outlen) 40 | { 41 | char *encoded_data; 42 | int i, j, output_len; 43 | 44 | output_len = 4 * ((input_length + 2) / 3); 45 | encoded_data = malloc(output_len); 46 | if (!encoded_data) { 47 | return NULL; 48 | } 49 | 50 | for (i = 0, j = 0; i < input_length; ) { 51 | uint32_t octet_a = i < input_length ? (unsigned char) data[i++] : 0; 52 | uint32_t octet_b = i < input_length ? (unsigned char) data[i++] : 0; 53 | uint32_t octet_c = i < input_length ? (unsigned char) data[i++] : 0; 54 | uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; 55 | encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; 56 | encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; 57 | encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; 58 | encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; 59 | } 60 | 61 | for (i = 0; i < mod_table[input_length % 3]; i++) { 62 | encoded_data[output_len - 1 - i] = '='; 63 | } 64 | 65 | *outlen = output_len; 66 | return encoded_data; 67 | } 68 | 69 | int main(int argc, char *argv[]) 70 | { 71 | char *encoded; 72 | int outlen; 73 | char decoded[1024]; 74 | unsigned long len; 75 | 76 | if (argc != 4) { 77 | fprintf(stderr, "Usage: saslencode \n"); 78 | exit(EXIT_FAILURE); 79 | } 80 | 81 | len = snprintf(decoded, sizeof(decoded), "%s%c%s%c%s", argv[1], '\0', argv[2], '\0', argv[3]); 82 | if (len >= sizeof(decoded)) { 83 | fprintf(stderr, "Truncation occured (arguments too long!)\n"); 84 | return -1; 85 | } 86 | encoded = base64_encode(decoded, len, &outlen); 87 | if (!encoded) { 88 | fprintf(stderr, "base64 encoding failed\n"); 89 | return -1; 90 | } 91 | printf("Base64: %s\n", encoded); 92 | free(encoded); 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /external/sigwinch.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief SIGWINCH demo program 16 | * 17 | * \note This program is used to test the window resizing functionality of the BBS. 18 | * It serves no other useful purpose. 19 | * 20 | * \author Naveen Albert 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | static int last_sig = 0; 31 | 32 | static void sigwinch_handler(int sig) 33 | { 34 | last_sig = sig; 35 | return; /* Don't even log here, printf isn't safe in a signal handler */ 36 | } 37 | 38 | #define print_winsize(ws) printf("Window size is %d cols and %d rows (x: %d, y: %d)\n", ws.ws_col, ws.ws_row, ws.ws_xpixel, ws.ws_ypixel) 39 | 40 | int main(int argc, char *argv[]) 41 | { 42 | struct winsize winp; 43 | 44 | /* Not used */ 45 | (void) argc; 46 | (void) argv; 47 | 48 | signal(SIGWINCH, sigwinch_handler); /* Set up the signal handler */ 49 | printf("Waiting for SIGWINCH... press ^C to exit.\n"); 50 | 51 | /* Get current window size */ 52 | if (ioctl(0, TIOCGWINSZ, &winp)) { 53 | fprintf(stderr, "%d: ioctl failed: %s\n", __LINE__, strerror(errno)); 54 | } 55 | print_winsize(winp); 56 | 57 | /* Loop until we get a SIGINT (^C) */ 58 | for (;;) { 59 | pause(); 60 | if (last_sig == SIGWINCH) { 61 | /* Get new window size */ 62 | if (ioctl(0, TIOCGWINSZ, &winp)) { 63 | fprintf(stderr, "%d: ioctl failed: %s\n", __LINE__, strerror(errno)); 64 | } 65 | print_winsize(winp); 66 | } else { 67 | printf("Got some non-SIGWINCH signal?\n"); 68 | } 69 | } 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /include/alertpipe.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Alertpipe 13 | * 14 | * \author Naveen Albert 15 | */ 16 | 17 | /*! 18 | * \brief Signal alertpipe 19 | * \retval Same as write() 20 | */ 21 | ssize_t bbs_alertpipe_write(int alert_pipe[2]); 22 | 23 | /*! 24 | * \brief Read from an alertpipe. 25 | * \note Must be done whenever an alertpipe has been written to 26 | * \retval Same as read() 27 | */ 28 | int bbs_alertpipe_read(int alert_pipe[2]); 29 | 30 | /*! \brief Initialize an alertpipe */ 31 | #define bbs_alertpipe_create(alert_pipe) __bbs_alertpipe_create(alert_pipe, __FILE__, __LINE__, __func__) 32 | 33 | int __bbs_alertpipe_create(int alert_pipe[2], const char *file, int line, const char *func); 34 | 35 | /*! \brief Close an alertpipe */ 36 | int bbs_alertpipe_close(int alert_pipe[2]); 37 | 38 | /*! \brief Mark alertpipe's file descriptors as closed */ 39 | #define bbs_alertpipe_clear(p) p[0] = -1; p[1] = -1; 40 | 41 | /*! 42 | * \brief Wait indefinitely for traffic on an alertpipe 43 | * \param ms Same as poll() 44 | * \retval Same as poll() 45 | */ 46 | int bbs_alertpipe_poll(int alert_pipe[2], int ms); 47 | -------------------------------------------------------------------------------- /include/ansi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief ANSI escape sequences 13 | * 14 | */ 15 | 16 | /*! 17 | * \brief Strip all ANSI characters from a string and put the output in a new buffer 18 | * \param in A null terminated string. Any ANSI escape sequences will be removed from the output. 19 | * \param inlen Length of in 20 | * \param out Buffer in which the stripped string will be placed 21 | * \param outlen Size of out. Should be at least inlen, but no more than it. 22 | * \param strippedlen Will be set to the actual length of the stripped output, not including null terminator. 23 | * \retval 0 on success, -1 on failure 24 | */ 25 | int bbs_ansi_strip(const char *restrict in, size_t inlen, char *restrict out, size_t outlen, int *restrict strippedlen); 26 | -------------------------------------------------------------------------------- /include/base64.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Base64 encoding and decoding 13 | * 14 | */ 15 | 16 | /*! 17 | * \brief base64 encode a file by full file path 18 | * \param filename Path of input file to base64 encode 19 | * \param outputfile FILE* pointer into which output will be dumped 20 | * \param endl Line ending to use (CR LF or just LF) 21 | * \retval -1 on failure, 0 on success * 22 | */ 23 | int base64_encode_file(const char *restrict filename, FILE *restrict outputfile, const char *restrict endl); 24 | 25 | /*! 26 | * \brief base64 decode a string 27 | * \param data Data to decode 28 | * \param input_length Size of input 29 | * \param outlen Pointer to int in which output length will be stored 30 | * \returns decoded string on success (which must be freed), NULL on failure 31 | */ 32 | unsigned char *base64_decode(const unsigned char *restrict data, int input_length, int *restrict outlen); 33 | 34 | /*! 35 | * \brief base64 encode a buffer (which may contain NUL characters as part of the data itself) 36 | * \param data Data to encode 37 | * \param input_length Length of data 38 | * \param[out] outlen Length of encoded data 39 | * \retval encoded data on success (which must be freed), NULL on failure 40 | */ 41 | char *base64_encode(const char *restrict data, int input_length, int *restrict outlen); 42 | -------------------------------------------------------------------------------- /include/crypt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Password cryptography functions 13 | * 14 | */ 15 | 16 | #define BCRYPT_FULL_HASH_LEN 60 17 | 18 | /*! 19 | * \brief Generate a cryptographically secure random string containing only alphanumeric characters 20 | * \param[out] buf 21 | * \param len Size of buffer. The length of the random string will be one less than this, as the string will be NUL terminated. 22 | * \retval 0 on success, -1 on failure 23 | */ 24 | int bbs_rand_alnum(char *buf, size_t len); 25 | 26 | /*! 27 | * \brief Generate a bcrypt salt 28 | * \retval salt, which is dynamically allocated and must be freed 29 | */ 30 | char *bbs_password_salt(void); 31 | 32 | /*! 33 | * \brief Generate a hash 34 | * \param password Password to hash 35 | * \param salt Salt to use in hash, generated by password_salt 36 | * \retval hash, which must be freed 37 | */ 38 | char *bbs_password_hash(const char *password, const char *salt); 39 | 40 | /*! 41 | * \brief Generate a salt and hash 42 | * \param password Password to hash 43 | * \param buf Buffer 44 | * \param len Length of buf (at least 61) 45 | * \retval 0 on success, -1 on failure 46 | */ 47 | int bbs_password_salt_and_hash(const char *password, char *buf, size_t len); 48 | 49 | /*! 50 | * \brief Verify a user-provided password against the correct password hash 51 | * \param password Password to verify for match against combined salt + hash 52 | * \param salt The salt used to hash the password 53 | * \param hash The password hash itself 54 | * \retval 0 on success (match), -1 on failure (mismatch) 55 | */ 56 | int bbs_password_verify(const char *password, const char *salt, const char *hash); 57 | 58 | /*! 59 | * \brief Provided the stored bcrypt salt + hash of a password, verify an input password 60 | * \param password Password to verify for match against combined salt + hash 61 | * \param combined The full hash i.e. salt + hash 62 | * \retval 0 on success (match), -1 on failure (mismatch) 63 | */ 64 | int bbs_password_verify_bcrypt(const char *password, const char *combined); 65 | -------------------------------------------------------------------------------- /include/definitions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Definitions 13 | * 14 | */ 15 | 16 | #ifndef _DEFINITIONS_H 17 | #define _DEFINITIONS_H 18 | 19 | #include "include/version.h" 20 | 21 | #define BBS_NAME "lbbs" 22 | #define BBS_AUTHOR "Naveen Albert" 23 | #define BBS_TAGLINE "Lightweight BBS For Linux" 24 | #define BBS_SHORTNAME "LBBS" 25 | #define BBS_SOURCE_URL "https://github.com/InterLinked1/lbbs" 26 | #endif 27 | -------------------------------------------------------------------------------- /include/door.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief BBS doors 13 | * 14 | */ 15 | 16 | /* Forward declarations */ 17 | struct bbs_node; 18 | struct bbs_module; 19 | 20 | #define DOOR_PARAMS struct bbs_node *node, const char *args 21 | 22 | /*! \brief Registers a door 23 | * \param name Name of door (must be unique globally) 24 | * \param exec Function 25 | * \retval 0 on success 26 | * \retval -1 on failure 27 | */ 28 | #define bbs_register_door(name, exec) __bbs_register_door(name, exec, BBS_MODULE_SELF) 29 | 30 | int __bbs_register_door(const char *name, int (*execute)(DOOR_PARAMS), void *mod); 31 | 32 | /*! \brief Unregister a door */ 33 | int bbs_unregister_door(const char *name); 34 | 35 | /*! 36 | * \brief Execute a named door 37 | * \param node 38 | * \param name Name of door 39 | * \param args Optional door arguments 40 | * \retval 0 on success, -1 on failure 41 | */ 42 | int bbs_door_exec(struct bbs_node *node, const char *name, const char *args); 43 | 44 | /*! \brief Initialize doors */ 45 | int bbs_init_doors(void); 46 | -------------------------------------------------------------------------------- /include/editor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Terminal editor: editor, paging, navigation, etc. 13 | * 14 | */ 15 | 16 | /* Forward declarations */ 17 | struct bbs_node; 18 | 19 | /*! 20 | * \brief Invoke the BBS line editor. This is a primitive line editor that allows 21 | * composition of a multiline message, but only allows editing the current line. 22 | * Previous lines cannot be edited. 23 | * The user can choose to process, abort, or continue at any time. 24 | * \param node 25 | * \param instr If non-NULL, a line of instructions to be displayed to the user. Do not terminate with LF. 26 | * \param[out] buf Buffer in which input will be stored. Should be sufficiently large for its purpose. 27 | * \param len Size of buf 28 | * \retval -1 on disconnect, 0 on success (continue processing), 1 on abort (discard buffer) 29 | */ 30 | int bbs_line_editor(struct bbs_node *node, const char *restrict instr, char *restrict buf, size_t len); 31 | 32 | struct pager_info { 33 | int line; 34 | size_t want; 35 | const char *header; 36 | const char *footer; 37 | }; 38 | 39 | /*! 40 | * \brief Page through lines 41 | * \param node BBS node 42 | * \param pginfo pginfo struct 43 | * \param ms poll timeout 44 | * \param s Single line of output to display. Should not contain a new line. NULL if end of file. 45 | * \param len Length of s 46 | * \retval 2 on quit, 1 on timeout, 0 if input received, -1 on failure 47 | * \note The return values from this function are non-standard, please pay attention! 48 | */ 49 | int bbs_pager(struct bbs_node *node, struct pager_info *pginfo, int ms, const char *restrict s, size_t len); 50 | 51 | /*! \brief Display a file to a node */ 52 | int bbs_node_term_browse(struct bbs_node *node, const char *filename); 53 | -------------------------------------------------------------------------------- /include/event.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Internal Event Bus 13 | * 14 | */ 15 | 16 | enum bbs_event_type { 17 | EVENT_STARTUP = 0, /*!< BBS is fully started */ 18 | EVENT_SHUTDOWN, /*!< BBS is going to shut down */ 19 | EVENT_RELOAD, /*!< BBS reloaded configuration */ 20 | EVENT_NODE_START, /*!< Node started */ 21 | EVENT_NODE_SHUTDOWN, /*!< Node stopped */ 22 | EVENT_NODE_ENCRYPTION_FAILED, /*!< TLS setup failed */ 23 | EVENT_NODE_LOGIN_FAILED, /*!< Authentication failed */ 24 | EVENT_NODE_BAD_REQUEST, /*!< Bad request received */ 25 | EVENT_NODE_INTERACTIVE_START, /*!< Interactive terminal session started */ 26 | EVENT_NODE_INTERACTIVE_LOGIN, /*!< Interactive terminal login */ 27 | EVENT_USER_REGISTRATION, /*!< New user registration */ 28 | EVENT_USER_LOGIN, /*!< Successful authentication (any protocol) */ 29 | EVENT_USER_LOGOFF, /*!< User logout (any protocol) */ 30 | EVENT_USER_PASSWORD_CHANGE, /*!< User password change */ 31 | EVENT_FILE_DOWNLOAD_START, /*!< File download from BBS started */ 32 | EVENT_FILE_DOWNLOAD_COMPLETE, /*!< File download from BBS completed. Does not apply to HTTP downloads (e.g. from a user's home directory) */ 33 | EVENT_FILE_UPLOAD_START, /*!< File upload to BBS started */ 34 | EVENT_FILE_UPLOAD_COMPLETE, /*!< File upload to BBS completed */ 35 | }; 36 | 37 | struct bbs_event { 38 | enum bbs_event_type type; 39 | unsigned int nodenum; 40 | unsigned int userid; 41 | struct bbs_node *node; /*!< Only set for EVENT_NODE_SHUTDOWN, EVENT_NODE_INTERACTIVE_START, EVENT_NODE_INTERACTIVE_LOGIN */ 42 | const void *cdata; /*!< Custom callback data (only for certain events) */ 43 | char protname[10]; 44 | char username[64]; 45 | char ipaddr[64]; 46 | }; 47 | 48 | /* Event custom data types for specific event types, accessible via event->cdata */ 49 | /*! \brief Custom callback data for EVENT_FILE_ events */ 50 | struct bbs_file_transfer_event { 51 | const char *userpath; 52 | const char *diskpath; 53 | size_t size; /* Size (COMPLETE events only) */ 54 | }; 55 | 56 | /* Forward declaration */ 57 | struct bbs_node; 58 | 59 | /*! \note Callback should return 0 if not handled, 1 if handled, and 2 if handled and no other callbacks should be called */ 60 | #define bbs_register_event_consumer(callback) __bbs_register_event_consumer(callback, BBS_MODULE_SELF) 61 | 62 | int __bbs_register_event_consumer(int (*callback)(struct bbs_event *event), void *mod); 63 | 64 | int bbs_unregister_event_consumer(int (*callback)(struct bbs_event *event)); 65 | 66 | /*! \brief Get a string representation of an event type's name */ 67 | const char *bbs_event_name(enum bbs_event_type type); 68 | 69 | /*! \brief Broadcast an event to all event consumers */ 70 | int bbs_event_broadcast(struct bbs_event *event); 71 | 72 | /*! \brief Build and dispatch an event to all event consumers */ 73 | int bbs_event_dispatch(struct bbs_node *node, enum bbs_event_type type); 74 | 75 | /*! \brief Same as bbs_event_dispatch, but provide optional custom data, used depending on the type */ 76 | int bbs_event_dispatch_custom(struct bbs_node *node, enum bbs_event_type type, const void *data); 77 | -------------------------------------------------------------------------------- /include/group.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief BBS user groups 13 | * 14 | */ 15 | 16 | /*! 17 | * \brief Check whether a group contains a user 18 | * \param group Name of group 19 | * \param user Username 20 | * \retval 1 if in group, 0 if not in group or group does not exist 21 | */ 22 | int bbs_group_contains_user(const char *group, const char *user); 23 | 24 | /*! \brief Clean up groups */ 25 | int bbs_groups_cleanup(void); 26 | 27 | /*! \brief Initialize groups */ 28 | int bbs_groups_init(void); 29 | -------------------------------------------------------------------------------- /include/handler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief BBS menu handlers 13 | * 14 | */ 15 | 16 | /* Forward declarations */ 17 | struct bbs_node; 18 | struct bbs_module; 19 | 20 | /*! 21 | * \brief Registers a menu handler 22 | * \param name Name of menu handler (must be unique globally) 23 | * \param exec Function 24 | * \param needargs Whether this handler requires arguments (1 = yes, 0 = optional). If 1, the handler function will never be called without arguments. 25 | * \retval 0 on success 26 | * \retval -1 on failure 27 | */ 28 | #define bbs_register_menu_handler(name, exec, needargs) __bbs_register_menu_handler(name, exec, needargs, BBS_MODULE_SELF) 29 | 30 | int __bbs_register_menu_handler(const char *name, int (*execute)(struct bbs_node *node, char *args), int needargs, void *mod); 31 | 32 | /*! \brief Unregister a menu handler */ 33 | int bbs_unregister_menu_handler(const char *name); 34 | 35 | /*! \brief Print list of hmenu andlers */ 36 | int bbs_list_menu_handlers(int fd); 37 | 38 | /*! 39 | * \brief Whether a menu handler exists for a given menu action name 40 | * \param name Menu handler name 41 | * \param needargs If provided, will be set to whether or not this handler requires arguments. 42 | * \retval 1 if exists, 0 if doesn't exist 43 | */ 44 | int menu_handler_exists(const char *name, int *needargs); 45 | 46 | /*! 47 | * \brief Execute a named menu handler (for use by menu.c) 48 | * \param node 49 | * \param name Name of menu handler to execute 50 | * \param args Arguments to menu handler. This is intentionally char and not const char 51 | * since many handlers need to parse the arguments into chunks and this allows them to do that in place. 52 | * \retval -3 return from all menus and set res to 0 (will break menu loop in menu.c) 53 | * \retval -2 return from current menu and set res to 0 (will break menu loop in menu.c) 54 | * \retval -1 immediately disconnect 55 | * \retval 0 normal menu item return (success) 56 | */ 57 | int menu_handler_exec(struct bbs_node *node, const char *name, char *args); 58 | 59 | /*! 60 | * \brief Initialize menu handlers 61 | * \retval 0 on success, -1 on failure 62 | */ 63 | int bbs_init_menu_handlers(void); 64 | -------------------------------------------------------------------------------- /include/hash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Hashing functions 13 | * 14 | */ 15 | 16 | #define SHA256_BUFSIZE 65 17 | #define SHA1_LEN 20 18 | #define SHA1_BUFSIZE 41 /* 160 bits = 20 bytes = 40 hex digits + NUL */ 19 | 20 | /*! 21 | * \brief Hash a string using SHA256 22 | * \param s String to hash 23 | * \param buf Buffer that is at least 65 bytes (larger is unnecessary) 24 | * \retval 0 on success, -1 on failure 25 | */ 26 | int hash_sha256(const char *s, char buf[SHA256_BUFSIZE]); 27 | 28 | /*! 29 | * \brief Hash a string using SHA1 30 | * \param s String to hash 31 | * \param buf Buffer that is at least 41 bytes (larger is unnecessary) 32 | * \retval 0 on success, -1 on failure 33 | */ 34 | int hash_sha1(const char *s, char buf[SHA1_BUFSIZE]); 35 | 36 | /*! 37 | * \brief Hash a string using SHA1, but get the actual bytes in return 38 | * \param s String to hash 39 | * \param buf Buffer that is at least 20 bytes (larger is unnecessary) 40 | * \retval 0 on success, -1 on failure 41 | */ 42 | int hash_sha1_bytes(const char *s, char buf[SHA1_LEN]); 43 | -------------------------------------------------------------------------------- /include/json.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief JSON 13 | * 14 | */ 15 | 16 | #include 17 | 18 | #define json_object_string_value(json, key) (json_string_value(json_object_get(json, key))) 19 | #define json_object_number_value(json, key) (json_number_value(json_object_get(json, key))) 20 | #define json_object_int_value(json, key) ((int) json_object_number_value(json, key)) 21 | #define json_object_bool_value(json, key) (json_boolean_value(json_object_get(json, key))) 22 | -------------------------------------------------------------------------------- /include/keys.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Key definitions 13 | * 14 | */ 15 | 16 | #define KEY_ESC 27 17 | 18 | /* 19 | * These are taken directly from ncurses.h. 20 | * We don't include , even for these key definitions, 21 | * because some of the macros we use (e.g. TERM_COLOR_RED) conflict 22 | * with macros ncurses uses, in different ways than we use them. */ 23 | #define KEY_DOWN 0402 /* down-arrow key */ 24 | #define KEY_UP 0403 /* up-arrow key */ 25 | #define KEY_LEFT 0404 /* left-arrow key */ 26 | #define KEY_RIGHT 0405 /* right-arrow key */ 27 | #define KEY_HOME 0406 /* home key */ 28 | #define KEY_BACKSPACE 0407 /* backspace key */ 29 | #define KEY_F0 0410 /* Function keys. Space for 64 */ 30 | #define KEY_F(n) (KEY_F0+(n)) /* Value of function key n */ 31 | #define KEY_DL 0510 /* delete-line key */ 32 | #define KEY_IL 0511 /* insert-line key */ 33 | #define KEY_DC 0512 /* delete-character key */ 34 | #define KEY_IC 0513 /* insert-character key */ 35 | #define KEY_NPAGE 0522 /* next-page key */ 36 | #define KEY_PPAGE 0523 /* previous-page key */ 37 | #define KEY_ENTER 0527 /* enter/send key */ 38 | #define KEY_PRINT 0532 /* print key */ 39 | #define KEY_END 0550 /* end key */ 40 | -------------------------------------------------------------------------------- /include/kvs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Key Value Store 13 | * 14 | */ 15 | 16 | struct kvs_callbacks { 17 | int (*get)(const char *key, size_t keylen, char *buf, size_t len, char **outbuf, size_t *outlen); 18 | int (*put)(const char *key, size_t keylen, const char *value, size_t valuelen); 19 | int (*del)(const char *key, size_t keylen); 20 | }; 21 | 22 | /*! 23 | * \brief Register a KVS backend provider 24 | * \param cb kvs_callbacks structure 25 | * \param priority Priority (lower priority indicates higher precedence). Not currently used. 26 | * \retval 0 on success, -1 on failure (another backend already registered) 27 | * \note Currently only one KVS backend provider may be registered at a time 28 | */ 29 | #define bbs_register_kvs_backend(cb, priority) __bbs_register_kvs_backend(cb, priority, BBS_MODULE_SELF) 30 | 31 | int __bbs_register_kvs_backend(struct kvs_callbacks *cb, int priority, void *mod); 32 | 33 | /*! 34 | * \brief Unregister a KVS backend provider, previously registered using bbs_register_kvs_backend 35 | * \param cb kvs_callbacks structure 36 | * \retval 0 on success, -1 on failure (backend not currently registered) 37 | */ 38 | int bbs_unregister_kvs_backend(struct kvs_callbacks *cb); 39 | 40 | /*! 41 | * \brief Store a value from the key-value store into a provided buffer 42 | * \param[in] key Key name 43 | * \param[in] keylen Length of key 44 | * \param[out] buf Buffer for value 45 | * \param[in] len Length of buf 46 | * \param[out] outlen Length of value 47 | * \retval 0 on success, -1 on failure 48 | * \note If a value would be truncated, the operation is failed, but you can get the real length from outlen. 49 | */ 50 | int bbs_kvs_get(const char *key, size_t keylen, char *buf, size_t len, size_t *outlen); 51 | 52 | /*! 53 | * \brief Retrieve a value from the key-value store into an allocated buffer 54 | * \param[in] key Key name 55 | * \param[in] keylen Length of key 56 | * \param[out] outlen Length of value 57 | * \return Value on success 58 | * \return NULL on failure 59 | */ 60 | char *bbs_kvs_get_allocated(const char *key, size_t keylen, size_t *outlen); 61 | 62 | /*! 63 | * \brief Store a value into the kvs-value store 64 | * \param[in] key Key name 65 | * \param[in] keylen Length of key 66 | * \param[in] value Value to store 67 | * \param[in] valuelen Length of value 68 | * \retval 0 on success, -1 on failure 69 | */ 70 | int bbs_kvs_put(const char *key, size_t keylen, const char *value, size_t valuelen); 71 | 72 | /*! 73 | * \brief Store a value into the kvs-value store 74 | * \param[in] key Key name 75 | * \param[in] keylen Length of key 76 | * \retval 0 on success, -1 on failure (including key didn't exist) 77 | */ 78 | int bbs_kvs_del(const char *key, size_t keylen); 79 | -------------------------------------------------------------------------------- /include/menu.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief BBS menus 13 | * 14 | */ 15 | 16 | /*! \brief Default menu name is main */ 17 | #define DEFAULT_MENU "main" 18 | 19 | /*! \brief Maximum number of times menus can recurse */ 20 | #define BBS_MAX_MENUSTACK 12 21 | 22 | /*! \note We don't use 1-26 to save those for easy CTRL key shortcuts. This is ^^ */ 23 | #define MENU_REFRESH_KEY 30 24 | 25 | /* Forward declaration for bbs_menu_run */ 26 | struct bbs_node; 27 | 28 | /*! 29 | * \brief Run the BBS on a node 30 | * \param node 31 | * \retval -1 on error, 0 on successful user exit of the BBS main menu 32 | */ 33 | int bbs_node_menuexec(struct bbs_node *node); 34 | 35 | /*! \brief Destroy all menus */ 36 | void bbs_free_menus(void); 37 | 38 | /*! \brief Load or reload menus */ 39 | int bbs_load_menus(int reload); 40 | -------------------------------------------------------------------------------- /include/mod_asterisk_ami.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Asterisk Manager Interface 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | /*! 21 | * \brief Get the global AMI session 22 | * \return NULL if no AMI session active 23 | * \return ami session 24 | */ 25 | struct ami_session *bbs_ami_session(void); 26 | 27 | int __bbs_ami_callback_register(int (*callback)(struct ami_event *event, const char *eventname), void *mod); 28 | 29 | /*! 30 | * \brief Register an AMI callback 31 | * \param callback Callback function to execute on AMI events 32 | * \retval 0 on success, -1 on failure 33 | */ 34 | #define bbs_ami_callback_register(callback) __bbs_ami_callback_register(callback, BBS_MODULE_SELF) 35 | 36 | /*! 37 | * \brief Unregister an AMI callback previously registered using bbs_ami_callback_register 38 | * \param callback 39 | * \retval 0 on success, -1 on failure 40 | */ 41 | int bbs_ami_callback_unregister(int (*callback)(struct ami_event *event, const char *eventname)); 42 | -------------------------------------------------------------------------------- /include/mod_asterisk_queues.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Asterisk Queue Position System 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | struct queue_call_handle { 21 | /* Agent info */ 22 | struct bbs_node *node; /*!< Node of agent handling call */ 23 | int agentid; /*!< ID of agent handling call */ 24 | /* Call info */ 25 | int id; /*!< Queue call ID */ 26 | const char *queuetitle; /*!< Queue title */ 27 | const char *channel; /*!< Channel name */ 28 | int ani2; /*!< ANI II */ 29 | unsigned long ani; /*!< ANI */ 30 | unsigned long dnis; /*!< DNIS */ 31 | const char *cnam; /*!< CNAM */ 32 | }; 33 | 34 | int __bbs_queue_call_handler_register(const char *name, int (*handler)(struct queue_call_handle *qch), void *mod); 35 | 36 | /*! 37 | * \brief Register a queue call handler 38 | * \param name Name of handler to register 39 | * \retval 0 on success, -1 on failure 40 | */ 41 | #define bbs_queue_call_handler_register(name, handler) __bbs_queue_call_handler_register(name, handler, BBS_MODULE_SELF) 42 | 43 | /*! 44 | * \brief Unregister a queue call handler 45 | * \param name Name of registered handler 46 | * \retval 0 on success, -1 on failure 47 | */ 48 | int bbs_queue_call_handler_unregister(const char *name); 49 | -------------------------------------------------------------------------------- /include/mod_curl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief cURL 13 | * 14 | */ 15 | 16 | #define HTTP_RESPONSE_SUCCESS(code) (code / 100 == 2) 17 | 18 | struct bbs_curl { 19 | /* Input fields */ 20 | const char *url; /*!< URL to request */ 21 | const char *postfields; /*!< POST request body */ 22 | const char *ranges; /*!< Range header ranges */ 23 | const char *cookies; /*!< Request cookies */ 24 | /* Output fields */ 25 | int http_code; 26 | char *response; /*!< Response body */ 27 | /* Input flags */ 28 | unsigned int forcefail:1; /* Return failure if return code is not a success (2xx) code */ 29 | }; 30 | 31 | /*! \note Doesn't free acurl itself, just dynamically allocated things inside it */ 32 | void bbs_curl_free(struct bbs_curl *c); 33 | 34 | /*! \brief HTTP GET request */ 35 | int bbs_curl_get(struct bbs_curl *c); 36 | 37 | /*! 38 | * \brief Make an HTTP GET request, saving the output to a file 39 | */ 40 | int bbs_curl_get_file(struct bbs_curl *c, const char *filename); 41 | 42 | /*! \brief HTTP POST request */ 43 | int bbs_curl_post(struct bbs_curl *c); 44 | -------------------------------------------------------------------------------- /include/mod_history.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Command History 13 | * 14 | */ 15 | 16 | /*! 17 | * \brief Reset the history index to the most recent element 18 | * \retval 0 on success, -1 on failure 19 | */ 20 | int bbs_history_reset(void); 21 | 22 | /*! 23 | * \brief Retrieves the next oldest history entry, updating index 24 | * \returns History entry, NULL if no such index 25 | */ 26 | const char *bbs_history_older(void); 27 | 28 | /*! 29 | * \brief Retrieves the next most recent history entry, updating index 30 | * \returns History entry, NULL if no such index 31 | */ 32 | const char *bbs_history_newer(void); 33 | 34 | /*! 35 | * \brief Add a string to history 36 | * \retval 0 on success, -1 on failure 37 | */ 38 | int bbs_history_add(const char *s); 39 | -------------------------------------------------------------------------------- /include/mod_mimeparse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief MIME Parser Supplements for IMAP Server 13 | * 14 | */ 15 | 16 | struct bbs_mime_message; 17 | 18 | /*! 19 | * \brief Create a MIME message structure by parsing a message file 20 | * \param filename Path to email message 21 | * \return NULL on failure, opaque MIME structure on success which must be destroyed using bbs_mime_message_parse 22 | */ 23 | struct bbs_mime_message *bbs_mime_message_parse(const char *filename) __attribute__((nonnull (1))); 24 | 25 | /*! 26 | * \brief Destroy a MIME message structure 27 | */ 28 | void bbs_mime_message_destroy(struct bbs_mime_message *mime) __attribute__((nonnull (1))); 29 | 30 | /*! 31 | * \brief Generate the BODY/BODYSTRUCTURE data item for FETCH responses 32 | * \param mime 33 | * \returns NULL on failure, BODYSTRUCTURE text on success, which must be be freed using free() 34 | */ 35 | char *bbs_mime_make_bodystructure(struct bbs_mime_message *mime) __attribute__((nonnull (1))); 36 | 37 | enum mime_part_filter { 38 | MIME_PART_FILTER_ALL = 0, /* Retrieve the entire part */ 39 | MIME_PART_FILTER_MIME, /* Retrieve just the MIME of the part */ 40 | MIME_PART_FILTER_HEADERS, /* Retrieve the headers, but only if the part's Content-Type is message/rfc822 */ 41 | MIME_PART_FILTER_TEXT, /* Retrieve the text, but only if the part's Content-Type is message/rfc822 */ 42 | }; 43 | 44 | /*! 45 | * \brief Retrieve a particular part of the body, by part specification 46 | * \param mime 47 | * \param spec Part specification, e.g. 2.3. This should be ONLY the part number portion of the spec (e.g. not including .MIME, .TEXT, etc.) 48 | * \param[out] outlen Length of returned part, if return value is non-NULL 49 | * \param[in] filter What to retrieve and return 50 | * \returns NULL on failure, requested section on success, which must be freed using free() 51 | */ 52 | char *bbs_mime_get_part(struct bbs_mime_message *mime, const char *spec, size_t *restrict outlen, enum mime_part_filter filter) __attribute__((nonnull (1, 2, 3))); 53 | -------------------------------------------------------------------------------- /include/mod_smtp_filter_dkim.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief RFC6376 DomainKeys Identified Mail (DKIM) Signing and Verification 13 | * \note Used for both DKIM and ARC signing 14 | */ 15 | 16 | #include "include/linkedlists.h" 17 | 18 | struct dkim_domain { 19 | const char *domain; 20 | const char *selector; 21 | const char *key; 22 | size_t keylen; 23 | RWLIST_ENTRY(dkim_domain) entry; 24 | unsigned int strictheaders:1; 25 | unsigned int strictbody:1; 26 | unsigned int sha256:1; 27 | char data[]; 28 | }; 29 | 30 | /*! 31 | * \brief Get a domain that can be signed using DKIM/ARC 32 | * \param name Domain name 33 | * \return dkim_domain if found 34 | * \return NULL if domain not found 35 | */ 36 | struct dkim_domain *smtp_get_dkim_domain(const char *name); 37 | -------------------------------------------------------------------------------- /include/mod_uuid.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief UUID support 13 | * 14 | * \author Naveen Albert 15 | */ 16 | 17 | /*! 18 | * \brief Generate a UUID (universally unique identifier), all lowercase 19 | * \return UUID on success, NULL on failure 20 | */ 21 | char *bbs_uuid(void); 22 | -------------------------------------------------------------------------------- /include/net.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Network Protocols 13 | * 14 | */ 15 | 16 | /*! \brief Register network protocol */ 17 | int bbs_register_network_protocol(const char *name, unsigned int port); 18 | 19 | /*! \brief Unregister network protocol */ 20 | int bbs_unregister_network_protocol(unsigned int port); 21 | 22 | /*! 23 | * \brief Get the port associated with a network protocol 24 | * \param name Name of network protocol as registered using bbs_register_network_protocol 25 | * \retval 0 on failure (no such protocol name), positive port number on success 26 | */ 27 | int bbs_protocol_port(const char *name); 28 | 29 | /*! \brief List all registered network protocols */ 30 | int bbs_list_network_protocols(int fd); 31 | 32 | /*! \brief Initialize network protocols */ 33 | int bbs_init_nets(void); 34 | -------------------------------------------------------------------------------- /include/notify.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief System and User Notifications 13 | * 14 | */ 15 | 16 | /*! 17 | * \brief Email the sysop 18 | * \param user Sending user. NULL for general system notifications. 19 | * \param subject 20 | * \param fmt Email printf-style format string 21 | * \retval 0 on success, -1 on failure 22 | */ 23 | int bbs_sysop_email(struct bbs_user *user, const char *subject, const char *fmt, ...) __attribute__ ((format (gnu_printf, 3, 4))) ; 24 | 25 | enum notify_delivery_type { 26 | DELIVERY_GUARANTEED = 0, 27 | DELIVERY_EPHEMERAL, 28 | }; 29 | 30 | /*! 31 | * \brief Deliver an alert message to a user 32 | * \param userid 33 | * \param persistence Delivery type. If guaranteed, an email will be sent to the user if the alert cannot be delivered via an ephemeral channel. 34 | * \param fmt printf-style format string 35 | * \note This should only be used for *short* messages, i.e. a sentence or two at most. Do not include a trailing CR LF. 36 | */ 37 | int bbs_alert_user(unsigned int userid, enum notify_delivery_type persistence, const char *fmt, ...) __attribute__ ((format (gnu_printf, 3, 4))) ; 38 | 39 | #define ALERTER_PARAMS unsigned int userid, const char *msg 40 | 41 | /*! 42 | * \brief Register an alerter 43 | * \param alerter Callback that will attempt to deliver an alert message to a user. Should return 0 if successful and nonzero otherwise. 44 | * \param priority A lower priority callback will be preferred first over higher number priorities 45 | * \retval 0 on success, -1 on failure 46 | */ 47 | #define bbs_register_alerter(alerter, priority) __bbs_register_alerter(alerter, BBS_MODULE_SELF, priority) 48 | 49 | int __bbs_register_alerter(int (*alerter)(ALERTER_PARAMS), void *mod, int priority); 50 | 51 | int bbs_unregister_alerter(int (*alerter)(ALERTER_PARAMS)); 52 | -------------------------------------------------------------------------------- /include/oauth.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief OAuth2 token interface 13 | * 14 | */ 15 | 16 | /* Forward declaration */ 17 | struct bbs_user; 18 | 19 | #define OAUTH_PROVIDER_PARAMS struct bbs_user *user, const char *name, char *buf, size_t len 20 | 21 | /*! 22 | * \brief Register an OAuth token provider that obtains an OAuth2 token 23 | * \param provider Callback function to execute that will provide the OAuth2 access token 24 | * for a given profile name, restricted to the current user as appropriate. 25 | * \note At least one OAuth token provider must be registered for user authentication to be possible 26 | */ 27 | #define bbs_register_oauth_provider(provider) __bbs_register_oauth_provider(provider, BBS_MODULE_SELF) 28 | 29 | int __bbs_register_oauth_provider(int (*provider)(OAUTH_PROVIDER_PARAMS), void *mod); 30 | 31 | /*! 32 | * \brief Unregister an OAuth token provider 33 | * \param provider Callback function to unregister 34 | */ 35 | int bbs_unregister_oauth_provider(int (*provider)(OAUTH_PROVIDER_PARAMS)); 36 | 37 | /*! 38 | * \brief Get the current OAuth token for a given user and profile 39 | * \param user 40 | * \param name Profile name 41 | * \param[out] buf 42 | * \param len Size of buf. Should be at least 300 (these tokens can be quite long!) 43 | * \retval 0 on success, -1 if not found or on error 44 | */ 45 | int bbs_get_oauth_token(struct bbs_user *user, const char *name, char *buf, size_t len); 46 | -------------------------------------------------------------------------------- /include/os.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief OS details 13 | * 14 | * \author Naveen Albert 15 | */ 16 | 17 | /*! \brief Get OS name and version */ 18 | const char *bbs_get_osver(void); 19 | 20 | /*! \brief Initialize OS name and version */ 21 | int bbs_init_os_info(void); 22 | 23 | /*! 24 | * \brief Get the number of free bytes available on the system 25 | * \return Number of free bytes 26 | * \return -1 on failure 27 | * \note This will only check the root partition (/) 28 | */ 29 | long bbs_disk_bytes_free(void); 30 | -------------------------------------------------------------------------------- /include/parallel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Parallel Task Framework 13 | * 14 | */ 15 | 16 | #include "include/linkedlists.h" 17 | 18 | struct bbs_parallel_task { 19 | struct bbs_parallel *p; /* Parent to which this task belongs */ 20 | unsigned long hash; /* Prefix hash value */ 21 | int (*cb)(void *data); /* Callback to execute the task */ 22 | void (*cleanup)(void *data); /* Cleanup function */ 23 | void *data; /* Callback data for the task */ 24 | pthread_t thread; /* Thread responsible for the task */ 25 | int res; /* Return code of task */ 26 | RWLIST_ENTRY(bbs_parallel_task) entry; 27 | unsigned int started:1; /* Task has been started (i.e. is either running or completed) */ 28 | unsigned int completed:1; /* Task has been completed */ 29 | }; 30 | 31 | RWLIST_HEAD(parallel_tasks, bbs_parallel_task); 32 | 33 | struct bbs_parallel { 34 | struct parallel_tasks tasks; 35 | int alertpipe[2]; 36 | unsigned int min_parallel_tasks; /* Minimum number of tasks to run in parallel */ 37 | unsigned int max_parallel_tasks; /* Maximum number of tasks to run in parallel */ 38 | unsigned int waiting:1; /* In the "waiting" phase, i.e. there are no more tasks to schedule */ 39 | unsigned int initialized:1; 40 | }; 41 | 42 | /*! 43 | * \brief Init a bbs_parallel 44 | * \param min Minimum number of tasks to run in parallel instead of serially. This option is currently ignored. 45 | * \param max Maximum number of tasks that may run in parallel at once 46 | */ 47 | void bbs_parallel_init(struct bbs_parallel *p, unsigned int min, unsigned int max); 48 | 49 | /*! 50 | * \brief Schedule a task for execution. The task may be executed immediately or delayed, in a separate thread 51 | * \param p Parallel job series structure 52 | * \param prefix Prefix unique for concurrency restrictions. No tasks with the same prefix will be concurrently scheduled. 53 | * \param data Callback data to pass to callback functions 54 | * \param cb Callback function to execute task 55 | * \param duplicate Function that duplicates data and returns a heap allocated version of it. NULL if data is heap-allocated already and this is not necessary. 56 | * \param cleanup Function to destroy a heap allocated callback data structure. NULL if data does not need to be destroyed. 57 | * \return Task return code, if executed immediately 58 | * \return Scheduler return code, if not being executed immediately. 59 | * \note You must call bbs_parallel_join to ensure all tasks finish execeution, at some point before p goes out of scope 60 | * \note This function should only be called from the (same) parent thread 61 | */ 62 | int bbs_parallel_schedule_task(struct bbs_parallel *p, const char *restrict prefix, void *data, int (*cb)(void *data), void *(*duplicate)(void *data), void (*cleanup)(void *data)); 63 | 64 | /*! 65 | * \brief Wait for all pending tasks to finish execution 66 | * \retval Bitwise OR of all task return values, ORed with the status of this function (0 on success, nonzero on failure) 67 | */ 68 | int bbs_parallel_join(struct bbs_parallel *p); 69 | -------------------------------------------------------------------------------- /include/pty.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Pseudoterminals 13 | * 14 | */ 15 | 16 | /* Forward declarations */ 17 | struct winsize; 18 | struct termios; 19 | 20 | /*! 21 | * \brief POSIX version of BSD openpty function 22 | * \param amaster Will be set to master fd 23 | * \param aslave Will be set to slave fd 24 | * \param name Will be set to slave name, if non-NULL 25 | * \param termp Terminal attributes to set, if non-NULL 26 | * \param winp Window size to set, if non-NULL 27 | * \retval 0 on success, -1 on failure 28 | */ 29 | int bbs_openpty(int *amaster, int *aslave, char *name, const struct termios *termp, const struct winsize *winp); 30 | 31 | /*! 32 | * \brief Create and spawn a generic PTY master relay for arbitrary file descriptors 33 | * \param fd Socket file descriptor 34 | * \param[out] fd The PTY master file descriptor 35 | * \param[out] thread On success, the thread to use for bbs_pthread_join 36 | * \note This thread will continue until either file descriptor closes. It should NOT be created detached. 37 | * \retval -1 on failure, slave file descriptor on success 38 | */ 39 | int __bbs_spawn_pty_master(int fd, int *amaster, pthread_t *thread); 40 | 41 | /*! 42 | * \brief Create and spawn a generic PTY master relay for arbitrary file descriptors 43 | * \param fd Socket file descriptor 44 | * \param[out] thread On success, the thread to use for bbs_pthread_join 45 | * \note This thread will continue until either file descriptor closes. It should NOT be created detached. 46 | * \retval -1 on failure, slave file descriptor on success 47 | */ 48 | int bbs_spawn_pty_master(int fd, pthread_t *thread); 49 | 50 | /*! \brief Allocate and set up a pseudoterminal for a BBS node */ 51 | int bbs_pty_allocate(struct bbs_node *node); 52 | 53 | /*! 54 | * \brief Spy on the input and output of a node 55 | * \param fdin Spyer's input file descriptor (e.g. STDIN_FILENO) 56 | * \param fdout Spyer's output file descriptor (e.g. STDOUT_FILENO) 57 | * \param nodenum ID of node on which to spy 58 | * \retval 0 on success, -1 on failure 59 | */ 60 | int bbs_node_spy(int fdin, int fdout, unsigned int nodenum); 61 | 62 | /*! 63 | * \brief PTY master side handling 64 | * \param varg BBS node 65 | */ 66 | void *pty_master(void *varg); 67 | -------------------------------------------------------------------------------- /include/ratelimit.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2024, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Rate Limiting 13 | * 14 | */ 15 | 16 | struct bbs_rate_limit { 17 | struct timespec a; /* Time at beginning of interval */ 18 | struct timespec b; /* Time of last hit */ 19 | int interval; /* Duration of interval in ms */ 20 | int max; /* Max number of hits per interval */ 21 | int reqcount; /* Number of hits so far this interval */ 22 | }; 23 | 24 | /*! 25 | * \brief Initialize rate limit 26 | * \param[out] r 27 | * \param interval The size of the sliding window, in milliseconds 28 | * \param max The maximum number of requests per sliding window 29 | * \retval 0 on success, -1 on failure 30 | * \note Rate limiting is not 100% precise, but these parameters are useful for tuning the rate limiter. 31 | */ 32 | int bbs_rate_limit_init(struct bbs_rate_limit *r, int interval, int max); 33 | 34 | /*! 35 | * \brief Check if this request exceeds a rate limit 36 | * \param r 37 | * \retval 0 if doesn't exceed or an error occured (since we fail safe) 38 | * \retval 1 if exceeds rate limit 39 | * \note There are no false positives but there may be false negatives (requests allowed that are not desired) 40 | * \note Calling bbs_rate_limit_exceeded if bbs_rate_limit_init returns nonzero is undefined behavior. 41 | * The rate limiting code is written to be fast and will only work correctly when used properly. 42 | * \note This is not multithread safe and should be surrounded with locking if needed. 43 | */ 44 | int bbs_rate_limit_exceeded(struct bbs_rate_limit *r); 45 | -------------------------------------------------------------------------------- /include/reload.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2024, Naveen Albert 5 | * 6 | * Naveen Albert 7 | */ 8 | 9 | /*! \file 10 | * 11 | * \brief Core reload handlers 12 | * 13 | * \author Naveen Albert 14 | */ 15 | 16 | /*! 17 | * \brief Register a reload handler 18 | * \param name Name of reload handler. Must be unique. 19 | * \param description A description of what this handler reloads. 20 | * \param reloader Reload callback that should return 0 on success 21 | * \retval 0 on success, -1 on failure 22 | * \note This API is for use in the core only. Module CANNOT use this API. 23 | * Reload handlers are automatically unregistered only at shutdown. 24 | */ 25 | int bbs_register_reload_handler(const char *name, const char *description, int (*reloader)(int fd)); 26 | 27 | /*! 28 | * \brief Execute reload handler(s) 29 | * \param name Handler to execute. If NULL, all handlers will be executed. 30 | * \param fd File descriptor for output messages from handlers, -1 to discard 31 | * \retval 0 on success, -1 if no handlers could be executed, 1 if any handlers returned nonzero 32 | */ 33 | int bbs_reload(const char *name, int fd); 34 | -------------------------------------------------------------------------------- /include/startup.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Startup Callbacks 13 | * 14 | */ 15 | 16 | /*! \brief Urgent startup priority */ 17 | #define STARTUP_PRIORITY_URGENT 0 18 | 19 | /*! \brief Default priority when it doesn't matter at all */ 20 | #define STARTUP_PRIORITY_DEFAULT 10 21 | 22 | /*! \brief Dependent startup priority */ 23 | #define STARTUP_PRIORITY_DEPENDENT 50 24 | 25 | /*! 26 | * \brief Register a function to execute once the BBS has fully started 27 | * This is useful when modules or parts of the core are unable 28 | * to complete some initialization or task until all other modules 29 | * have finished loading. 30 | * \param execute Callback function to run 31 | * \param priority Startup callback priority. Lower number priorities will be run earlier. 32 | * Simple callbacks with no dependencies (especially those that may be dependencies of other callbacks) must be run first. 33 | * \note This function can only be called during startup. 34 | * \retval 0 on success, -1 on failure 35 | */ 36 | int bbs_register_startup_callback(int (*execute)(void), int priority); 37 | 38 | /*! 39 | * \brief Helper function to execute a callback function when the BBS is fully started. 40 | * If the BBS is still starting, this is equivalent to calling bbs_register_startup_callback. 41 | * If the BBS is already started, the callback is run immediately. 42 | * \param execute Callback function to run 43 | * \param priority Startup callback priority. Lower number priorities will be run earlier. 44 | * Simple callbacks with no dependencies (especially those that may be dependencies of other callbacks) must be run first. 45 | * \retval 0 on success, -1 on failure 46 | */ 47 | int bbs_run_when_started(int (*execute)(void), int priority); 48 | 49 | /*! \brief Run all startup callbacks */ 50 | int bbs_run_startup_callbacks(void); 51 | -------------------------------------------------------------------------------- /include/tcp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief High-level TCP client 13 | * 14 | * \author Naveen Albert 15 | */ 16 | 17 | #include "include/io.h" 18 | 19 | struct bbs_url; 20 | 21 | struct bbs_tcp_client { 22 | char *buf; 23 | size_t len; 24 | struct readline_data rldata; 25 | int fd; 26 | int rfd; 27 | int wfd; 28 | struct bbs_io_transformations trans; /*!< I/O transformations */ 29 | unsigned int secure:1; 30 | }; 31 | 32 | /*! \brief Clean up a TCP client */ 33 | void bbs_tcp_client_cleanup(struct bbs_tcp_client *client); 34 | 35 | /*! 36 | * \brief Establish a TCP client connection to a server 37 | * \param[out] client This is filled in, but memset this to 0 first. 38 | * \param url Server address 39 | * \param secure Whether to use implicit TLS when establishing the connection 40 | * \param buf Buffer for readline operations 41 | * \param len Size of buf 42 | * \retval 0 on success, -1 on failure 43 | */ 44 | int bbs_tcp_client_connect(struct bbs_tcp_client *client, struct bbs_url *url, int secure, char *buf, size_t len); 45 | 46 | /*! 47 | * \brief Perform STARTTLS on a TCP client 48 | * \param client 49 | * \param hostname 50 | * \retval 0 on success, -1 on failure 51 | * \note The readline buffer is reset on success to prevent response injection attacks 52 | */ 53 | int bbs_tcp_client_starttls(struct bbs_tcp_client *client, const char *hostname); 54 | 55 | /*! 56 | * \brief Send data on a TCP client connection 57 | * \param client 58 | * \param fmt printf-style format string 59 | * \retval same as write 60 | */ 61 | ssize_t bbs_tcp_client_send(struct bbs_tcp_client *client, const char *fmt, ...) __attribute__ ((format (gnu_printf, 2, 3))) ; 62 | 63 | /*! 64 | * \brief Expect a response containing a substring on a TCP connection 65 | * \param client 66 | * \param delim Delimiter to use for readline operations (CR LF is typical) 67 | * \param attempts Maximum number of responses (typically lines) that will be parsed. Typically 1. 68 | * \param ms argument to poll() 69 | * \param str Substring to expect 70 | * \retval 0 on success (substring contained in response), -1 on failure, 1 if max attempts reached 71 | */ 72 | #define bbs_tcp_client_expect(client, delim, attempts, ms, str) __bbs_tcp_client_expect(client, delim, attempts, ms, str, __FILE__, __LINE__, __func__) 73 | 74 | int __bbs_tcp_client_expect(struct bbs_tcp_client *client, const char *delim, int attempts, int ms, const char *str, const char *file, int line, const char *func) __attribute__((nonnull (1,2, 5))); 75 | -------------------------------------------------------------------------------- /include/term.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief BBS terminal manipulation 13 | * 14 | */ 15 | 16 | /*! 17 | * \brief Disable input buffering on a fd 18 | * \param fd 19 | * \param echo Whether to enable local echo 20 | * \retval 0 on success, -1 on failure 21 | */ 22 | int bbs_unbuffer_input(int fd, int echo); 23 | 24 | /*! 25 | * \brief Enable input buffering on a fd 26 | * \param fd 27 | * \param echo Whether to enable local echo 28 | * \retval 0 on success, -1 on failure 29 | */ 30 | int bbs_buffer_input(int fd, int echo); 31 | 32 | /*! 33 | * \brief Set echo on/off on a fd 34 | * \param fd 35 | * \param echo Whether to enable local echo 36 | * \retval 0 on success, -1 on failure 37 | */ 38 | int bbs_echo(int fd, int echo); 39 | 40 | /*! \brief Wrapper for bbs_node_unbuffer_input that disables canonical mode and echo */ 41 | #define bbs_node_unbuffer(node) bbs_node_unbuffer_input(node, 0) 42 | 43 | /*! \brief Wrapper for bbs_node_buffer_input that enables canonical mode and echo */ 44 | #define bbs_node_buffer(node) bbs_node_buffer_input(node, 1) 45 | 46 | /*! 47 | * \brief Disable input buffering for BBS node 48 | * \param node 49 | * \param echo Whether to enable local echo 50 | * \retval 0 on success, -1 on failure 51 | */ 52 | int bbs_node_unbuffer_input(struct bbs_node *node, int echo); 53 | 54 | /*! 55 | * \brief Enable input buffering for BBS node 56 | * \param node 57 | * \param echo Whether to enable local echo 58 | * \retval 0 on success, -1 on failure 59 | */ 60 | int bbs_node_buffer_input(struct bbs_node *node, int echo); 61 | 62 | #define bbs_node_echo_on(node) bbs_node_echo(node, 1) 63 | #define bbs_node_echo_off(node) bbs_node_echo(node, 0) 64 | 65 | /*! 66 | * \brief Set echo on/off for a BBS node 67 | * \param node 68 | * \param echo Whether to enable local echo 69 | * \retval 0 on success, -1 on failure 70 | */ 71 | int bbs_node_echo(struct bbs_node *node, int echo); 72 | 73 | /*! 74 | * \brief Make a TTY fd raw (pass most input unaltered) 75 | * \param fd PTY master fd 76 | * \retval 0 on success, -1 on failure 77 | */ 78 | int bbs_term_makeraw(int fd); 79 | 80 | /*! 81 | * \brief Set line discipline appropriately 82 | * \param fd 83 | * \retval 0 on success, -1 on failure 84 | */ 85 | int tty_set_line_discipline(int fd); 86 | -------------------------------------------------------------------------------- /include/test.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Unit Test Framework 13 | * 14 | */ 15 | 16 | #define bbs_test_assert(x) if (!(x)) { bbs_warning("Test assertion failed: %s\n", #x); goto cleanup; } 17 | #define bbs_test_assert_equals(x, y) if (!((x) == (y))) { bbs_warning("Test assertion failed: (%d != %d)\n", (x), (y)); goto cleanup; } 18 | #define bbs_test_assert_size_equals(x, y) if (!((x) == (y))) { bbs_warning("Test assertion failed: (%lu != %lu)\n", (x), (y)); goto cleanup; } 19 | #define bbs_test_assert_exists(x) if ((x) == NULL) { bbs_warning("Test assertion failed: (%s != NULL)\n", #x); goto cleanup; } 20 | #define bbs_test_assert_null(x) if (!((x) == NULL)) { bbs_warning("Test assertion failed: (%p == NULL)\n", (x)); goto cleanup; } 21 | #define bbs_test_assert_mem_equals(x, y, sz) if (memcmp(x, y, sz)) { bbs_warning("Test assertion failed: '%s' != '%s'\n", x, y); goto cleanup; } 22 | #define bbs_test_assert_str_equals(x, y) if (strcmp(x, y)) { bbs_warning("Test assertion failed: '%s' != '%s'\n", x, y); goto cleanup; } 23 | 24 | /*! 25 | * \param x Actual 26 | * \param y Expected. Must not be NULL. 27 | */ 28 | #define bbs_test_assert_str_exists_equals(x, y) if (strlen_zero(x) || strcmp(x, y)) { bbs_warning("Test assertion failed: '%s' != '%s'\n", x, y); goto cleanup; } 29 | 30 | /*! 31 | * \param x Actual 32 | * \param y Expected. Must not be NULL. 33 | */ 34 | #define bbs_test_assert_str_exists_contains(x, y) if (strlen_zero(x) || !strstr(x, y)) { bbs_warning("Test assertion failed: '%s' does not contain '%s'\n", x, y); goto cleanup; } 35 | 36 | /*! \brief Run all registered unit tests */ 37 | int bbs_run_tests(int fd); 38 | 39 | /*! 40 | * \brief Run a specific unit test 41 | * \param fd 42 | * \param name Name of test to run 43 | * \retval 0 on success, -1 on failure 44 | */ 45 | int bbs_run_test(int fd, const char *name); 46 | 47 | /*! 48 | * \brief Registers a unit test 49 | * \param name Name of unit test 50 | * \param exec Function 51 | * \retval 0 on success, non-zero on failure 52 | */ 53 | #define bbs_register_test(name, exec) __bbs_register_test(name, exec, BBS_MODULE_SELF) 54 | 55 | int __bbs_register_test(const char *name, int (*execute)(void), void *mod); 56 | 57 | /*! \brief Unregister a unit test */ 58 | int bbs_unregister_test(int (*execute)(void)); 59 | 60 | struct bbs_unit_test { 61 | const char *name; 62 | int (*callback)(void); 63 | }; 64 | 65 | int __bbs_register_tests(struct bbs_unit_test tests[], unsigned int len, void *mod); 66 | 67 | /*! \brief Register multiple unit tests */ 68 | #define bbs_register_tests(tests) __bbs_register_tests(tests, ARRAY_LEN(tests), BBS_MODULE_SELF) 69 | 70 | /*! \brief Unregister multiple unit tests */ 71 | int __bbs_unregister_tests(struct bbs_unit_test tests[], unsigned int len); 72 | 73 | #define bbs_unregister_tests(tests) __bbs_unregister_tests(tests, ARRAY_LEN(tests)) 74 | 75 | /*! \brief Initialize tests */ 76 | int bbs_init_tests(void); 77 | -------------------------------------------------------------------------------- /include/thread.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Thread management 13 | * 14 | * \author Naveen Albert 15 | */ 16 | 17 | /*! \brief Get thread ID of current thread */ 18 | int bbs_gettid(void); 19 | 20 | /*! 21 | * \brief Disable cancellability of a thread 22 | * \warning This function should be avoided if possible, but must be used if it is needed 23 | */ 24 | void bbs_pthread_disable_cancel(void); 25 | 26 | /*! 27 | * \brief Restore cancellability of a thread 28 | */ 29 | void bbs_pthread_enable_cancel(void); 30 | 31 | /*! 32 | * \brief Cancel and kill a thread 33 | * \deprecated 34 | * \warning Avoid this function if possible, as threads may not clean up properly if cancelled/killed in the wrong place 35 | * \retval 0 on success, errno on failure 36 | */ 37 | int bbs_pthread_cancel_kill(pthread_t thread); 38 | 39 | /*! 40 | * \brief Signal a thread to interrupt a blocking system call it may be blocked on 41 | * \retval 0 on success, errno on failure 42 | */ 43 | int bbs_pthread_interrupt(pthread_t thread); 44 | 45 | int __bbs_pthread_join(pthread_t thread, void **retval, const char *file, const char *func, int line); 46 | 47 | /*! \brief Join a non-detached thread */ 48 | #define bbs_pthread_join(thread, retval) __bbs_pthread_join(thread, retval, __FILE__, __func__, __LINE__) 49 | 50 | int __bbs_pthread_timedjoin(pthread_t thread, void **retval, const char *file, const char *func, int line, int waitms); 51 | 52 | /*! 53 | * \brief Join a non-detached thread, with timeout 54 | * \param thread 55 | * \param[out] retval 56 | * \param waitms Number of ms to wait, maximum 57 | */ 58 | #define bbs_pthread_timedjoin(thread, retval, waitms) __bbs_pthread_timedjoin(thread, retval, __FILE__, __func__, __LINE__, waitms) 59 | 60 | int __bbs_pthread_create_detached(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *data, const char *file, const char *func, int line, const char *start_fn); 61 | 62 | /*! 63 | * \brief Create a detached pthread 64 | * \retval 0 on success, -1 on failure 65 | */ 66 | #define bbs_pthread_create_detached(thread, attr, start_routine, data) __bbs_pthread_create_detached(thread, attr, start_routine, data, __FILE__, __func__, __LINE__, #start_routine) 67 | 68 | int __bbs_pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *data, const char *file, const char *func, int line, const char *start_fn); 69 | 70 | /*! 71 | * \brief Create a non-detached pthread 72 | * \retval 0 on success, -1 on failure 73 | */ 74 | #define bbs_pthread_create(thread, attr, start_routine, data) __bbs_pthread_create(thread, attr, start_routine, data, __FILE__, __func__, __LINE__, #start_routine) 75 | 76 | /*! \brief Destroy thread registrations (on shutdown) */ 77 | void bbs_thread_cleanup(void); 78 | 79 | /*! 80 | * \brief Get the thread ID (LWP) of a registered thread 81 | * \param thread pthread_t handle 82 | * \retval -1 if thread not currently registered, LWP/thread ID otherwise 83 | */ 84 | int bbs_pthread_tid(pthread_t thread); 85 | 86 | /*! \brief Initialize threads */ 87 | int bbs_init_threads(void); 88 | -------------------------------------------------------------------------------- /include/time.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2024, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Time and locale 13 | * 14 | * \author Naveen Albert 15 | */ 16 | 17 | /* This is a BSD macro, so probably not available by default on most systems */ 18 | #ifndef timespecsub 19 | #define timespecsub(tsp, usp, vsp) \ 20 | do { \ 21 | (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ 22 | (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ 23 | if ((vsp)->tv_nsec < 0) { \ 24 | (vsp)->tv_sec--; \ 25 | (vsp)->tv_nsec += 1000000000L; \ 26 | } \ 27 | } while (0) 28 | #endif 29 | -------------------------------------------------------------------------------- /include/version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Version 13 | * 14 | */ 15 | 16 | #define BBS_MAJOR_VERSION 0 17 | #define BBS_MINOR_VERSION 7 18 | #define BBS_PATCH_VERSION 3 19 | -------------------------------------------------------------------------------- /io/Makefile: -------------------------------------------------------------------------------- 1 | 2 | MOD_SRC := $(wildcard *.c) 3 | MOD_SO := $(MOD_SRC:.c=.so) 4 | DEPENDS := $(patsubst %.c,%.d,$(MOD_SRC)) 5 | 6 | # the include directory is in the parent 7 | INC = -I.. 8 | 9 | all: $(MOD_SO) 10 | 11 | -include $(DEPENDS) 12 | 13 | $(DEPENDS): 14 | 15 | %.o : %.c %.d 16 | @echo " [CC] $< -> $@" 17 | $(CC) $(CFLAGS) -fPIC -DBBS_MODULE=\"$(basename $<)\" -DBBS_MODULE_SELF_SYM=__internal_$(basename $<)_self -MMD -MP $(INC) -c $< 18 | 19 | %.so : %.o 20 | @echo " [LD] $^ -> $@" 21 | $(CC) $(MOD_LDFLAGS) -o $(basename $^).so $^ 22 | 23 | io_compress.so : io_compress.o 24 | @echo " [LD] $^ -> $@" 25 | $(CC) $(MOD_LDFLAGS) -o $(basename $^).so $^ -lz 26 | 27 | io_tls.so : io_tls.o 28 | @echo " [LD] $^ -> $@" 29 | $(CC) $(MOD_LDFLAGS) -o $(basename $^).so $^ -lssl -lcrypto 30 | 31 | # Don't automatically remove intermediate .o files, to prevent unnecessary recompilations 32 | .SECONDARY: $(patsubst %.c,%.o,$(MOD_SRC)) 33 | 34 | .PHONY: all 35 | -------------------------------------------------------------------------------- /modules/mod_systemd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief systemd signal support 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #include 23 | 24 | #include "include/module.h" 25 | #include "include/event.h" 26 | 27 | static int event_cb(struct bbs_event *event) 28 | { 29 | switch (event->type) { 30 | case EVENT_STARTUP: 31 | sd_notifyf(0, "READY=1\nMAINPID=%lu", (unsigned long) getpid()); 32 | break; 33 | case EVENT_SHUTDOWN: 34 | sd_notify(0, "STOPPING=1"); 35 | break; 36 | case EVENT_RELOAD: 37 | sd_notify(0, "READY=1"); 38 | break; 39 | default: 40 | return 0; 41 | } 42 | return 1; 43 | } 44 | 45 | static int load_module(void) 46 | { 47 | return bbs_register_event_consumer(event_cb); 48 | } 49 | 50 | static int unload_module(void) 51 | { 52 | return bbs_unregister_event_consumer(event_cb); 53 | } 54 | 55 | BBS_MODULE_INFO_STANDARD("systemd support"); 56 | -------------------------------------------------------------------------------- /modules/mod_test_backtrace.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Backtrace Test 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #include 23 | #include 24 | 25 | #include "include/module.h" 26 | #include "include/test.h" 27 | #include "include/cli.h" 28 | 29 | static int test_backtrace(void) 30 | { 31 | /* This test is mainly used to help determine if we have a buggy version of libbfd, 32 | * i.e. one that leaks memory on every backtrace. */ 33 | bbs_log_backtrace(); 34 | return 0; 35 | } 36 | 37 | static struct bbs_unit_test tests[] = 38 | { 39 | { "Backtrace Test", test_backtrace }, 40 | }; 41 | 42 | static int cli_abort(struct bbs_cli_args *a) 43 | { 44 | /* Test CLI command to manually abort to test if we can dump core successfully 45 | * This can be used to confirm a core would be dumped if the BBS were to crash for another reason. */ 46 | UNUSED(a); 47 | abort(); 48 | return -1; /* Never reached */ 49 | } 50 | 51 | static struct bbs_cli_entry cli_commands_backtrace[] = { 52 | BBS_CLI_COMMAND(cli_abort, "abort", 1, "Abort BBS and dump core", NULL), 53 | }; 54 | 55 | static int unload_module(void) 56 | { 57 | bbs_cli_unregister_multiple(cli_commands_backtrace); 58 | return bbs_unregister_tests(tests); 59 | } 60 | 61 | static int load_module(void) 62 | { 63 | int res = bbs_register_tests(tests); 64 | if (!res) { 65 | bbs_cli_register_multiple(cli_commands_backtrace); 66 | } 67 | REQUIRE_FULL_LOAD(res); 68 | } 69 | 70 | BBS_MODULE_INFO_STANDARD("Backtrace Unit Tests"); 71 | -------------------------------------------------------------------------------- /modules/mod_uuid.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief UUID support 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "include/bbs.h" 21 | 22 | #ifdef __linux__ 23 | #include /* use uuid_generate, uuid_unparse */ 24 | #endif 25 | 26 | #include "include/module.h" 27 | 28 | #include "include/mod_uuid.h" 29 | 30 | char *bbs_uuid(void) 31 | { 32 | #ifdef UUID_STR_LEN 33 | char *uuid; 34 | uuid_t binary_uuid; 35 | 36 | uuid_generate_random(binary_uuid); 37 | uuid = malloc(UUID_STR_LEN + 1); 38 | if (ALLOC_FAILURE(uuid)) { 39 | return NULL; 40 | } 41 | uuid_unparse_lower(binary_uuid, uuid); 42 | return uuid; 43 | #else 44 | bbs_error("uuid not supported on this platform\n"); 45 | return NULL; 46 | #endif 47 | } 48 | 49 | static int unload_module(void) 50 | { 51 | return 0; 52 | } 53 | 54 | static int load_module(void) 55 | { 56 | return 0; 57 | } 58 | 59 | BBS_MODULE_INFO_FLAGS("UUID Support", MODFLAG_GLOBAL_SYMBOLS); 60 | -------------------------------------------------------------------------------- /nets/Makefile: -------------------------------------------------------------------------------- 1 | 2 | MOD_SRC := $(wildcard *.c) 3 | MOD_SO := $(MOD_SRC:.c=.so) 4 | DEPENDS := $(patsubst %.c,%.d,$(MOD_SRC)) 5 | 6 | # the include directory is in the parent 7 | INC = -I.. 8 | 9 | # Since we don't use autoconf: 10 | ARCH = $(shell uname -m) 11 | SFTP_SERVER_FREE_EXISTS = $(shell objdump -T /usr/lib/$(ARCH)-linux-gnu/libssh.so /lib/$(ARCH)-linux-gnu/libssh.so /usr/lib64/libssh.so 2>&1 | grep "sftp_server_free" | wc -l) 12 | 13 | ifneq ($(SFTP_SERVER_FREE_EXISTS),0) 14 | CFLAGS += -DHAVE_SFTP_SERVER_FREE 15 | endif 16 | 17 | all: $(MOD_SO) 18 | 19 | -include $(DEPENDS) 20 | 21 | $(DEPENDS): 22 | 23 | %.o : %.c %.d 24 | @echo " [CC] $< -> $@" 25 | $(CC) $(CFLAGS) -fPIC -DBBS_MODULE=\"$(basename $<)\" -DBBS_MODULE_SELF_SYM=__internal_$(basename $<)_self -MMD -MP $(INC) -c $< 26 | 27 | %.so : %.o 28 | @echo " [LD] $^ -> $@" 29 | $(CC) $(MOD_LDFLAGS) -o $(basename $^).so $^ 30 | 31 | IMAP_SRC = $(wildcard net_imap/*.c) 32 | IMAP_OBJ = $(patsubst %.c,%.o,$(IMAP_SRC)) 33 | 34 | # XXX if nets/net_imap/imap.h is modified, only net_imap.c is automatically recompiled, 35 | # not any nets/net_imap/*.c source files that include it 36 | # XXX If using -j[>1], some files in net_imap/ are recompiled multiple times (parallel build bug) 37 | 38 | # Subdirectory with components for net_imap 39 | net_imap/%.o: $(IMAP_SRC) 40 | @+$(SUBMAKE) --no-builtin-rules -C net_imap $(basename $(notdir $@)).o 41 | 42 | net_imap.so : net_imap.o $(IMAP_OBJ) 43 | @echo " [LD] $< $(IMAP_OBJ) -> $@" 44 | $(CC) $(MOD_LDFLAGS) -o $(basename $<).so $(IMAP_OBJ) $< 45 | 46 | # SSHLIB=$(pkg-config --libs libssh) 47 | net_ssh.so : net_ssh.o 48 | @echo " [LD] $^ -> $@" 49 | $(CC) $(MOD_LDFLAGS) -o $(basename $^).so $^ -lssh 50 | 51 | net_ws.so : net_ws.o 52 | @echo " [LD] $^ -> $@" 53 | $(CC) $(MOD_LDFLAGS) -o $(basename $^).so $^ -lwss 54 | 55 | # Don't automatically remove intermediate .o files, to prevent unnecessary recompilations 56 | .SECONDARY: $(patsubst %.c,%.o,$(MOD_SRC)) 57 | 58 | .PHONY: all 59 | .PHONY: net_imap 60 | -------------------------------------------------------------------------------- /nets/net_imap/Makefile: -------------------------------------------------------------------------------- 1 | 2 | IMAP_SRC := $(wildcard *.c) 3 | IMAP_OBJ = $(IMAP_SRC:.c=.o) 4 | DEPENDS := $(patsubst %.c,%.d,$(IMAP_SRC)) 5 | 6 | # the include directory is in the parent's parent 7 | INC = -I../.. 8 | 9 | -include $(DEPENDS) 10 | 11 | $(DEPENDS): 12 | 13 | %.o : %.c %.d 14 | @echo " [CC] $< -> $@" 15 | $(CC) $(CFLAGS) -fPIC -DBBS_MODULE_SUBFILE -MMD -MP $(INC) -c $< 16 | 17 | all: $(IMAP_OBJ) 18 | 19 | # Don't automatically remove intermediate .o files, to prevent unnecessary recompilations 20 | .SECONDARY: $(patsubst %.c,%.o,$(IMAP_SRC)) 21 | 22 | .PHONY: all 23 | -------------------------------------------------------------------------------- /nets/net_imap/imap_client_list.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief IMAP Client LIST 13 | * 14 | */ 15 | 16 | /*! \brief Allow a LIST against mailboxes on other mail servers, configured in the .imapremote file in a user's home directory */ 17 | /*! \note XXX Virtual mailboxes already have a meaning in some IMAP contexts, so maybe "remote mailboxes" would be a better name? */ 18 | int list_virtual(struct imap_session *imap, struct list_command *lcmd); 19 | -------------------------------------------------------------------------------- /nets/net_imap/imap_client_status.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief Remote IMAP STATUS 13 | * 14 | */ 15 | 16 | char *remove_size(char *restrict s); 17 | 18 | ssize_t remote_status(struct imap_client *client, const char *remotename, const char *items, int size); 19 | 20 | int imap_client_send_converted_status_response(struct imap_client *client, const char *remotename, const char *response); 21 | -------------------------------------------------------------------------------- /nets/net_imap/imap_server_fetch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief IMAP Server FETCH 13 | * 14 | */ 15 | 16 | /* There can be multiple of these in a request */ 17 | struct fetch_body_request { 18 | const char *bodyarg; /*!< BODY arguments */ 19 | int substart; /*!< For BODY and BODY.PEEK partial fetch, the beginning octet */ 20 | long sublength; /*!< For BODY and BODY.PEEK partial fetch, number of bytes to fetch */ 21 | unsigned int peek:1; /*!< Whether this is a BODY.PEEK */ 22 | RWLIST_ENTRY(fetch_body_request) entry; 23 | }; 24 | 25 | RWLIST_HEAD(body_fetches, fetch_body_request); 26 | 27 | struct fetch_request { 28 | const char *flags; 29 | unsigned long changedsince; 30 | unsigned int envelope:1; 31 | unsigned int body:1; 32 | unsigned int bodystructure:1; 33 | unsigned int internaldate:1; 34 | unsigned int rfc822:1; 35 | unsigned int rfc822header:1; 36 | unsigned int rfc822size:1; 37 | unsigned int rfc822text:1; 38 | unsigned int uid:1; 39 | unsigned int modseq:1; 40 | unsigned int vanished:1; 41 | unsigned int nopeek:1; /*!< An operation was requested that will implicitly mark seen */ 42 | struct body_fetches bodyfetches; 43 | }; 44 | 45 | /*! \brief strsep-like FETCH items tokenizer */ 46 | char *fetchitem_sep(char **s); 47 | 48 | /*! 49 | * \brief Retrieve data associated with a message 50 | * \param imap 51 | * \param s FETCH command arguments 52 | * \param usinguid UID FETCH instead of FETCH 53 | * \param tagged Whether to send a tagged reply at the end of the command 54 | */ 55 | int handle_fetch_full(struct imap_session *imap, char *s, int usinguid, int tagged); 56 | -------------------------------------------------------------------------------- /nets/net_imap/imap_server_maildir.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief maildir++ interface 13 | * 14 | */ 15 | 16 | /* Forward declaration for imap_uidsort */ 17 | #include 18 | 19 | /*! \brief Wrapper around uidsort */ 20 | int imap_uidsort(const struct dirent **da, const struct dirent **db); 21 | 22 | /*! 23 | * \brief Translate an IMAP directory path to the full path of the IMAP mailbox on disk 24 | * \param imap 25 | * \param directory The mailbox name (a misnomer, it's not referring to any actual directory path) 26 | * \param buf 27 | * \param len 28 | * \param[out] acl 29 | * \retval 0 on success, -1 on failure 30 | */ 31 | int imap_translate_dir(struct imap_session *imap, const char *directory, char *buf, size_t len, int *acl); 32 | 33 | /*! \brief Same as imap_translate_dir, but do not update the currently active mbox */ 34 | int imap_translate_dir_readonly(struct imap_session *imap, const char *directory, char *buf, size_t len, int *acl); 35 | 36 | /*! \brief Set the current maildir (e.g. SELECT, EXAMINE) */ 37 | int set_maildir(struct imap_session *imap, const char *mailbox); 38 | 39 | /*! \brief Set a maildir without making it the current maildir (e.g. STATUS) */ 40 | int set_maildir_readonly(struct imap_session *imap, struct imap_traversal *traversal, const char *mailbox); 41 | 42 | long parse_modseq_from_filename(const char *filename, unsigned long *modseq); 43 | 44 | int parse_size_from_filename(const char *filename, unsigned long *size); 45 | 46 | /*! \brief Find the disk filename of a message, given its sequence number or UID in a cur maildir folder */ 47 | int imap_msg_to_filename(const char *directory, int seqno, unsigned int uid, char *buf, size_t len); 48 | -------------------------------------------------------------------------------- /nets/net_imap/imap_server_search.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | */ 9 | 10 | /*! \file 11 | * 12 | * \brief IMAP Server SEARCH, SORT, THREAD 13 | * 14 | */ 15 | 16 | int handle_search(struct imap_session *imap, char *s, int usinguid); 17 | 18 | int handle_sort(struct imap_session *imap, char *s, int usinguid); 19 | 20 | int test_thread_orderedsubject(void); 21 | 22 | int test_thread_references(void); 23 | 24 | int handle_thread(struct imap_session *imap, char *s, int usinguid); 25 | -------------------------------------------------------------------------------- /scripts/.indent.pro: -------------------------------------------------------------------------------- 1 | --k-and-r-style 2 | --dont-format-comments 3 | --use-tabs 4 | --tab-size 4 5 | --line-length 200 6 | --blank-lines-after-procedures 7 | --braces-after-func-def-line 8 | --braces-on-if-line 9 | --braces-on-struct-decl-line 10 | --cuddle-else 11 | --remove-preprocessor-space 12 | --no-blank-lines-after-declarations 13 | --no-blank-lines-after-commas 14 | --dont-break-function-decl-args 15 | --no-space-after-function-call-names 16 | --no-space-after-parentheses 17 | --space-after-for 18 | --space-after-if 19 | --space-after-while 20 | --left-justify-declarations 21 | -------------------------------------------------------------------------------- /scripts/.suppress.cppcheck: -------------------------------------------------------------------------------- 1 | missingInclude 2 | unusedFunction 3 | unusedLabel 4 | unknownMacro 5 | -------------------------------------------------------------------------------- /scripts/bbs_dumper.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | 3 | # bbs_dumper 4 | # (C) Copyright 2023 Naveen Albert 5 | 6 | # $1 = REQUIRED Sub-command to run: pid|threads|term|quit|postdump|livedump|gdb 7 | # $2 = OPTIONAL For postdump command, custom path to core file. Default is 'core' in current directory. 8 | # For backtraces, the full backtrace is saved to full.txt in the current directory 9 | 10 | ps -aux | grep "lbbs" | grep -v "strace" | grep -v "grep" 11 | 12 | bbspid=`cat /var/run/lbbs/bbs.pid` 13 | printf "BBS PID: %d\n" $bbspid 14 | 15 | install_gdb() { 16 | # Assume the package manager has already been updated if needed. 17 | OS=$( uname -s ) 18 | OS_DIST_INFO="(lsb_release -ds || cat /etc/*release || uname -om ) 2>/dev/null | head -n1 | cut -d'=' -f2" 19 | OS_DIST_INFO=$(eval "$OS_DIST_INFO" | tr -d '"') 20 | if [ -f /etc/debian_version ]; then 21 | apt-get install -y gdb 22 | elif [ -f /etc/fedora-release ] || [ -f /etc/redhat-release ]; then 23 | dnf install -y gdb 24 | elif [ "$OS_DIST_INFO" = "SLES" ] || [ "$OS_DIST_INFO" = "openSUSE Tumbleweed" ]; then 25 | zypper install --no-confirm gdb 26 | elif [ -r /etc/arch-release ]; then 27 | pacman -Sy --noconfirm gdb 28 | export DEBUGINFOD_URLS="https://debuginfod.archlinux.org" 29 | elif [ -r /etc/alpine-release ]; then 30 | apk add gdb 31 | elif [ "$OS" = "FreeBSD" ]; then 32 | pkg install -y gdb 33 | else 34 | printf "Could not automatically install gdb (unsupported distro?)\n" "$OS" >&2 # to stderr 35 | return 36 | fi 37 | } 38 | 39 | ensure_gdb_installed() { 40 | # For some reason, using which is not sufficient and will lead to things like: /usr/bin/gdb does not support python 41 | # That's because gdb isn't really installed. 42 | # Use a technique aside from which/path/binary detection to see if we find something we expect: 43 | helplines=`gdb --help 2> /dev/null | grep "GDB manual" | wc -l` 44 | if [ "$helplines" != "1" ]; then 45 | printf "GDB does not appear to be currently installed, trying to install it now...\n" 46 | install_gdb 47 | fi 48 | } 49 | 50 | if [ "$1" = "pid" ]; then 51 | echo $bbspid 52 | elif [ "$1" = "threads" ]; then 53 | ps -o pid,lwp,pcpu,pmem,comm,cmd -L $bbspid 54 | elif [ "$1" = "term" ]; then 55 | kill -9 $bbspid 56 | elif [ "$1" = "quit" ]; then 57 | kill -3 $bbspid 58 | elif [ "$1" = "postdump" ]; then 59 | ensure_gdb_installed 60 | if [ "$2" != "" ]; then 61 | CORE_FILE=$2 62 | else 63 | CORE_FILE=core 64 | fi 65 | gdb /usr/sbin/lbbs "$CORE_FILE" -ex "thread apply all bt full" -ex "quit" > full.txt 66 | # gdb can return nonzero even if it succeeded, so don't check the return code 67 | if [ -f full.txt ]; then 68 | printf "Backtrace saved to full.txt\n" 69 | else 70 | printf "Failed to obtain backtrace\n" 71 | fi 72 | elif [ "$1" = "livedump" ]; then 73 | ensure_gdb_installed 74 | gdb /usr/sbin/lbbs --batch -q -p $bbspid -ex 'thread apply all bt full' -ex 'quit' > full.txt 75 | if [ -f full.txt ]; then 76 | printf "Backtrace saved to full.txt\n" 77 | else 78 | printf "Failed to obtain backtrace\n" 79 | fi 80 | elif [ "$1" = "gdb" ]; then 81 | ensure_gdb_installed 82 | exec gdb /usr/sbin/lbbs -p $bbspid 83 | else 84 | echo "Invalid command!" 85 | exit 1 86 | fi 87 | -------------------------------------------------------------------------------- /scripts/cppcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # NOTE: This script should be run from the current (scripts) directory 3 | apt-get install -y cppcheck 4 | cppcheck .. --std=c99 -I ../include -I ../tests -f --enable=all --error-exitcode=1 -UTEST_MODULE_INFO_STANDARD -UTEST_MODULE_SELF_SYM --suppressions-list=.suppress.cppcheck 5 | -------------------------------------------------------------------------------- /scripts/dbcreate.sql: -------------------------------------------------------------------------------- 1 | 2 | /* Create user first, if necessary */ 3 | /* Uncomment the below if you need to create the BBS user (and change the password!!!) */ 4 | 5 | --CREATE USER 'bbs'@'localhost' IDENTIFIED BY 'P@ssw0rdUShouldChAngE!'; 6 | 7 | /* BBS database */ 8 | 9 | CREATE DATABASE `bbs`; 10 | USE `bbs`; 11 | 12 | CREATE TABLE `users` ( 13 | `id` int(11) NOT NULL AUTO_INCREMENT, 14 | `username` varchar(512) NOT NULL, 15 | `password` varchar(512) NOT NULL, 16 | `date_registered` datetime NOT NULL DEFAULT current_timestamp(), 17 | `last_login` datetime DEFAULT NULL, 18 | `priv` int(11) NOT NULL DEFAULT 1, 19 | `name` varchar(512) NOT NULL, 20 | `email` varchar(512) NOT NULL, 21 | `phone` varchar(512) DEFAULT NULL, 22 | `address` varchar(512) DEFAULT NULL, 23 | `city` varchar(512) NOT NULL, 24 | `state` varchar(512) NOT NULL, 25 | `zip` varchar(9) DEFAULT NULL, 26 | `dob` date DEFAULT NULL, 27 | `gender` char(1) DEFAULT NULL, 28 | PRIMARY KEY (`id`), 29 | UNIQUE KEY `username` (`username`) 30 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 31 | 32 | GRANT ALL PRIVILEGES ON bbs.* TO 'bbs'@'localhost'; 33 | 34 | 35 | /* IRC database */ 36 | 37 | CREATE DATABASE `irc`; 38 | USE `irc`; 39 | 40 | CREATE TABLE `channels` ( 41 | `name` varchar(64) NOT NULL, 42 | `founder` varchar(64) NOT NULL, 43 | `topic` varchar(390) DEFAULT NULL, 44 | `entrymsg` varchar(256) DEFAULT NULL, 45 | `modelock` varchar(64) DEFAULT NULL, 46 | `registered` datetime NOT NULL DEFAULT current_timestamp(), 47 | `guard` tinyint(1) NOT NULL DEFAULT 0, 48 | `keeptopic` tinyint(1) NOT NULL DEFAULT 0, 49 | PRIMARY KEY (`name`) 50 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 51 | 52 | CREATE TABLE `channel_flags` ( 53 | `channel` varchar(64) NOT NULL, 54 | `nickname` varchar(512) NOT NULL, 55 | `flag` char(1) NOT NULL, 56 | PRIMARY KEY (`channel`,`nickname`,`flag`), 57 | CONSTRAINT `channel_flags_ibfk_1` FOREIGN KEY (`channel`) REFERENCES `channels` (`name`) ON DELETE CASCADE ON UPDATE CASCADE 58 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 59 | 60 | GRANT ALL PRIVILEGES ON irc.* TO 'bbs'@'localhost'; 61 | 62 | /* Flush privileges */ 63 | 64 | FLUSH PRIVILEGES; 65 | -------------------------------------------------------------------------------- /scripts/disablewall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Disable broadcast wall messages from syslog to avoid interfering with terminal sessions 4 | # Won't affect BBS nodes either way, but could be annoying to the sysop if not done 5 | # Recommended if syslog is enabled on this server 6 | 7 | # Test before 8 | logger -p local0.emerg "This is a test, not a real emergency" 9 | 10 | # Disable default emergency rule: *.emerg :omusrmsg:* 11 | sed -e '/*.emerg/ s/^#*/#/' -i /etc/rsyslog.conf 12 | 13 | # Disable forwarding to wall for journald 14 | sed -i 's/#ForwardToWall=yes/ForwardToWall=no/' /etc/systemd/journald.conf 15 | 16 | # Reload 17 | systemctl force-reload systemd-journald 18 | systemctl restart rsyslog 19 | 20 | # Test again: this should no longer generate a broadcast 21 | logger -p local0.emerg "This is a test, not a real emergency" 22 | -------------------------------------------------------------------------------- /scripts/evergreen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | OS=$( uname -s ) 6 | MAKE=make 7 | if [ "$OS" = "FreeBSD" ]; then 8 | MAKE=gmake 9 | fi 10 | 11 | cd /usr/local/src 12 | if [ ! -d evergreen ]; then 13 | git clone https://github.com/InterLinked1/evergreen.git 14 | cd evergreen 15 | else 16 | cd evergreen 17 | git stash 18 | git pull 19 | fi 20 | $MAKE 21 | $MAKE install 22 | -------------------------------------------------------------------------------- /scripts/indent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # WARNING: DO NOT USE THIS SCRIPT. It is not currently suitable for use. 4 | 5 | # This is a helper script meant to highlight 6 | # formatting inconsistencies in the codebase. 7 | # 8 | # For example, Notepad++ has a bug where when a file 9 | # is opened or focused, it will change the tab alignment 10 | # of the currently selected line. If the programmer does 11 | # not catch this, this will introduce erroneous malformatting. 12 | # 13 | # The actual result of running this script should 14 | # not be directly commited. The results should be 15 | # analyzed manually, with changes made as needed 16 | # (git diff, followed by git stash) 17 | 18 | if ! which "indent" > /dev/null; then 19 | apt-get install -y indent 20 | fi 21 | 22 | if [ ! -d .git ]; then 23 | printf "Must be run inside a Git repository\n" 24 | exit 1 25 | fi 26 | 27 | # All the options are specified here 28 | export INDENT_PROFILE=scripts/.indent.pro 29 | 30 | # We're inside a Git repo and just do "git stash", don't make backup copies 31 | export VERSION_CONTROL=never 32 | 33 | # indent seems to be buggy, in that 34 | # --dont-format-comments seems to have no effect, 35 | # and comments are STILL modified. Thus, most of 36 | # the diff from indent is unwanted and superflous, 37 | # and must be ignored. 38 | 39 | # git diff is not capable of matching only certain changes in the staging area. 40 | # We could use some fancy post-processing to do this, though this is hard. 41 | # grepdiff (part of patchutils) does not support negative matches, which are needed to exclude comments. 42 | 43 | indent -nfca bbs/*.c doors/*.c modules/*.c nets/*.c nets/net_imap/*.c tests/*.c 44 | git stash 45 | -------------------------------------------------------------------------------- /scripts/libcami.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | cd /usr/local/src 5 | if [ ! -d cami ]; then 6 | git clone https://github.com/InterLinked1/cami.git 7 | cd cami 8 | else 9 | cd cami 10 | git stash 11 | git pull 12 | fi 13 | make library 14 | make install 15 | -------------------------------------------------------------------------------- /scripts/libdiscord.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Install libdiscord as a shared library 4 | 5 | set -e 6 | 7 | # libcurl 7.86.0 or higher is required 8 | curlv=$( curl-config --version | cut -d' ' -f2 | tr -d '\n' ) 9 | if [ $? -eq 0 ]; then 10 | printf "$curlv\n7.86.0" | sort -V | head -n1 | grep -qF 7.86.0 && ret=$? || ret=$? # capture failure since we have set -e 11 | if [ $ret -ne 0 ]; then 12 | printf "libcurl $curlv (< 7.86.0) is currently installed, need to build from source\n" 13 | CURL_SRC_VER="8.13.0" 14 | else 15 | printf "libcurl $curlv (>= 7.86.0) is currently installed, package okay\n" 16 | fi 17 | else 18 | printf "libcurl is not currently installed?\n" 19 | CURL_SRC_VER="8.13.0" 20 | fi 21 | 22 | if [ "$CURL_SRC_VER" != "" ]; then 23 | # Based on https://github.com/Cogmasters/concord/commit/04027977b6e1fe06e2d1ff589edb425223197046 24 | cd /usr/local/src 25 | if [ -f curl-${CURL_SRC_VER}.tar.gz ]; then 26 | rm curl-${CURL_SRC_VER}.tar.gz 27 | fi 28 | wget https://curl.se/download/curl-${CURL_SRC_VER}.tar.gz 29 | tar -xzf curl-${CURL_SRC_VER}.tar.gz 30 | # libpsl doesn't seem to be available on all distros (e.g. Rocky Linux 8.9) 31 | # https://daniel.haxx.se/blog/2024/01/10/psl-in-curl/ 32 | cd curl-${CURL_SRC_VER} && ./configure --with-openssl --enable-websockets --without-libpsl 33 | make -j$(nproc) 34 | make install 35 | ldconfig 36 | fi 37 | 38 | cd /usr/local/src 39 | if [ ! -d concord ]; then 40 | git clone https://github.com/cogmasters/concord.git 41 | cd concord 42 | else 43 | cd concord 44 | git stash 45 | git pull 46 | make clean 47 | fi 48 | # Use the dev branch for latest bug fixes 49 | git checkout dev 50 | printf "Compiling libdiscord\n" 51 | CFLAGS="-fPIC" make shared 52 | make install 53 | if [ -f /etc/alpine-release ]; then 54 | ldconfig /etc/ld.so.conf.d 55 | else 56 | ldconfig 57 | fi 58 | -------------------------------------------------------------------------------- /scripts/libetpan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script is used to compile libetpan from source, 4 | # with modifications that are required for LBBS and other software. 5 | 6 | set -e 7 | 8 | cd /usr/local/src 9 | if [ ! -d libetpan ]; then 10 | git clone --depth 1 --recursive --shallow-submodule https://github.com/dinhvh/libetpan.git 11 | cd libetpan 12 | else 13 | cd libetpan 14 | git stash 15 | git pull 16 | # make clean isn't valid if the directory already exists, 17 | # but the Makefile hasn't yet been generated 18 | make clean || git reset --hard origin/master 19 | fi 20 | wget "https://github.com/dinhvh/libetpan/commit/5ea630e6482422ffa2e26b9afe5fb47a9eb673a2.diff" 21 | wget "https://github.com/dinhvh/libetpan/commit/4226610e3dc19f58345ae7c5146fa8cf249ca97b.patch" 22 | git apply "5ea630e6482422ffa2e26b9afe5fb47a9eb673a2.diff" # IMAP STATUS=SIZE 23 | git apply "4226610e3dc19f58345ae7c5146fa8cf249ca97b.patch" # SMTP AUTH 24 | ./autogen.sh --with-poll 25 | make 26 | make install 27 | -------------------------------------------------------------------------------- /scripts/libjansson.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | cd /usr/local/src 5 | wget http://digip.org/jansson/releases/jansson-2.13.1.tar.gz 6 | tar -xvzf jansson-2.13.1.tar.gz 7 | cd jansson-2.13.1 8 | ./configure 9 | make 10 | make install 11 | -------------------------------------------------------------------------------- /scripts/libopenarc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd /usr/local/src 4 | if [ -d OpenARC ]; then 5 | cd OpenARC 6 | git stash 7 | git pull 8 | else 9 | git clone https://github.com/trusteddomainproject/OpenARC.git 10 | cd OpenARC 11 | fi 12 | git checkout develop # master branch is behind 13 | # https://github.com/trusteddomainproject/OpenARC/issues/118 14 | aclocal && autoconf && autoreconf --install && automake --add-missing && ./configure && make all 15 | make install 16 | -------------------------------------------------------------------------------- /scripts/libslackrtm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | cd /usr/local/src 5 | if [ ! -d slack-rtm ]; then 6 | git clone https://github.com/InterLinked1/slack-rtm.git 7 | cd slack-rtm 8 | else 9 | cd slack-rtm 10 | git stash 11 | git pull 12 | fi 13 | make 14 | make install 15 | -------------------------------------------------------------------------------- /scripts/libwss.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | cd /usr/local/src 5 | if [ ! -d libwss ]; then 6 | git clone https://github.com/InterLinked1/libwss.git 7 | cd libwss 8 | else 9 | cd libwss 10 | git stash 11 | git pull 12 | fi 13 | printf "Compiling libwss\n" 14 | make 15 | make install 16 | -------------------------------------------------------------------------------- /scripts/lirc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Install lirc as a shared library 4 | 5 | set -e 6 | 7 | cd /usr/local/src 8 | if [ ! -d lirc ]; then 9 | git clone https://github.com/InterLinked1/lirc.git 10 | cd lirc 11 | else 12 | cd lirc 13 | git stash 14 | git pull 15 | fi 16 | printf "Compiling lirc\n" 17 | make library 18 | make install 19 | -------------------------------------------------------------------------------- /scripts/valgrind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! which valgrind > /dev/null; then 4 | printf "valgrind is not installed on your system\n" 5 | exit 1 6 | fi 7 | 8 | VALGRIND_VERSION_MAJOR=`valgrind --version | cut -d'-' -f2 | cut -d'.' -f1` 9 | VALGRIND_VERSION_MINOR=`valgrind --version | cut -d'-' -f2 | cut -d'.' -f2` 10 | 11 | printf "Valgrind version: %s.%d\n" "$VALGRIND_VERSION_MAJOR" "$VALGRIND_VERSION_MINOR" 12 | 13 | if [ $VALGRIND_VERSION_MAJOR -ge 3 ] && [ $VALGRIND_VERSION_MINOR -ge 15 ]; then 14 | printf "Newer version of valgrind detected\n" 15 | VALGRIND="valgrind --show-error-list=yes --keep-debuginfo=yes" 16 | else 17 | printf "Older version of valgrind detected\n" 18 | VALGRIND="valgrind --keep-debuginfo=yes" 19 | fi 20 | 21 | if [ "$1" = "valgrindfg" ]; then 22 | exec $VALGRIND --leak-check=full --track-fds=yes --track-origins=yes --show-leak-kinds=all --child-silent-after-fork=yes --suppressions=valgrind.supp /usr/sbin/$EXE -cb 23 | elif [ "$1" = "valgrind" ]; then 24 | exec $VALGRIND --leak-check=full --track-fds=yes --track-origins=yes --show-leak-kinds=all --child-silent-after-fork=yes --suppressions=valgrind.supp --log-fd=9 /usr/sbin/$EXE -cb 9>valgrind.txt 25 | elif [ "$1" = "valgrindsupp" ]; then 26 | exec $VALGRIND --leak-check=full --track-fds=yes --track-origins=yes --show-leak-kinds=all --child-silent-after-fork=yes --suppressions=valgrind.supp --gen-suppressions=all --log-fd=9 /usr/sbin/$EXE -cb 9>valgrind.txt 27 | elif [ "$1" = "helgrind" ]; then 28 | $VALGRIND --tool=helgrind /usr/sbin/$EXE -c 29 | else 30 | printf "Invalid valgrind target\n" 31 | fi 32 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | 2 | MOD_SRC := $(wildcard test_*.c) 3 | MOD_SO := $(MOD_SRC:.c=.so) 4 | ALL_SRC := $(wildcard *.c) 5 | MAIN_SRC := $(filter-out $(MOD_SRC),$(ALL_SRC)) 6 | MAIN_OBJ = $(MAIN_SRC:.c=.o) 7 | 8 | TEST_EXE := test 9 | 10 | INCLUDE_FILES := $(wildcard ../include/*.h) 11 | 12 | # the include directory is in the parent 13 | INC = -I.. 14 | 15 | TEST_LDFLAGS = -shared -fPIC 16 | 17 | # Older versions of valgrind do not have the --show-error-list option 18 | VALGRIND_VERSION_MM := $(shell valgrind --version 2> /dev/null | cut -d'-' -f2 | cut -d'.' -f1-2) 19 | ifdef VALGRIND_VERSION_MM 20 | ifeq ($(shell expr $(VALGRIND_VERSION_MM) \>= 3.15), 1) 21 | EXTRA_FLAGS := -DHAVE_VALGRIND_SHOW_ERROR_LIST 22 | endif 23 | endif 24 | 25 | all: $(TEST_EXE) $(MOD_SO) 26 | 27 | # This is for all files in the binary, except readline.c 28 | # A more specific rule matches all of the module files. 29 | %.o : %.c 30 | @echo " [LD] $^ -> $@" 31 | $(CC) $(CFLAGS) -Wno-unused-result -DTEST_IN_CORE -DTEST_DIR=$(CURDIR) $(EXTRA_FLAGS) $(INC) -c $^ 32 | 33 | readline.o : ../bbs/readline.c 34 | cp ../bbs/readline.c . 35 | $(CC) $(CFLAGS) -fPIC -DTEST_MODULE=\"$(basename $^)\" -DTEST_MODULE_SELF_SYM=__internal_$(basename $^)_self $(INC) -c $^ 36 | 37 | $(TEST_EXE) : $(MAIN_OBJ) readline.o 38 | $(CC) $(CFLAGS) -Wl,--export-dynamic -o $(TEST_EXE) $^ -ldl -lpthread -lssl -lcrypto -lz 39 | 40 | test_%.o : test_%.c 41 | @echo " [CC] $< -> $@" 42 | $(CC) $(CFLAGS) -Wno-unused-result -fPIC -DTEST_MODULE=\"$(basename $^)\" -DTEST_MODULE_SELF_SYM=__internal_$(basename $^)_self $(INC) -c $^ 43 | 44 | test_%.so : test_%.o 45 | @echo " [LD] $^ -> $@" 46 | $(CC) $(TEST_LDFLAGS) -o $(basename $^).so $^ 47 | 48 | test_sftp.so : test_sftp.o 49 | @echo " [LD] $^ -> $@" 50 | $(CC) $(TEST_LDFLAGS) -o $(basename $^).so $^ -lssh 51 | 52 | test_ssh.so : test_ssh.o 53 | @echo " [LD] $^ -> $@" 54 | $(CC) $(TEST_LDFLAGS) -o $(basename $^).so $^ -lssh 55 | 56 | test_webmail.so : test_webmail.o 57 | @echo " [LD] $^ -> $@" 58 | $(CC) $(TEST_LDFLAGS) -o $(basename $^).so $^ -lwss 59 | 60 | # Don't automatically remove intermediate .o files, to prevent unnecessary recompilations 61 | # For tests, mainly needed to ensure the .so target isn't rebuilt when a dummy target created by modman is encountered. 62 | .SECONDARY: $(patsubst %.c,%.o,$(MOD_SRC)) 63 | 64 | .PHONY: all 65 | -------------------------------------------------------------------------------- /tests/ansi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief ANSI helpers 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "test.h" 21 | #include "ansi.h" 22 | 23 | #include 24 | 25 | int test_ansi_handshake(int clientfd) 26 | { 27 | static char tmpbuf[64]; 28 | int bytes; 29 | 30 | FMT_EXPECT(TERM_CURSOR_POS_QUERY); 31 | SEND_CURSOR_POS(3, 1); 32 | FMT_EXPECT(TERM_CURSOR_POS_QUERY); /* After TERM_RESET_LINE */ 33 | SEND_CURSOR_POS(6, 1); 34 | FMT_EXPECT(TERM_CURSOR_POS_QUERY); /* After TERM_UP_ONE_LINE */ 35 | SEND_CURSOR_POS(5, 1); 36 | FMT_EXPECT(TERM_CURSOR_POS_QUERY); /* After TERM_COLOR_GREEN */ 37 | SEND_CURSOR_POS(5, 1); 38 | FMT_EXPECT(TERM_CURSOR_POS_QUERY); /* After TERM_TITLE_FMT */ 39 | SEND_CURSOR_POS(5, 1); 40 | FMT_EXPECT(TERM_CURSOR_POS_QUERY); /* After TERM_CURSOR_POS_SET_FMT */ 41 | SEND_CURSOR_POS(4, 6); 42 | FMT_EXPECT(TERM_CURSOR_POS_QUERY); /* After TERM_CLEAR */ 43 | SEND_CURSOR_POS(1, 1); 44 | 45 | return 0; 46 | 47 | cleanup: 48 | return -1; 49 | } 50 | -------------------------------------------------------------------------------- /tests/ansi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | */ 8 | 9 | /*! \file 10 | * 11 | * \brief ANSI helpers 12 | * 13 | * \author Naveen Albert 14 | */ 15 | 16 | #define FMT_EXPECT(fmt, ...) \ 17 | sprintf(tmpbuf, fmt, ## __VA_ARGS__); \ 18 | CLIENT_EXPECT_EVENTUALLY(clientfd, tmpbuf); 19 | 20 | #define FMT_SEND(fmt, ...) \ 21 | bytes = sprintf(tmpbuf, fmt, ## __VA_ARGS__); \ 22 | write(clientfd, tmpbuf, (size_t) bytes); 23 | 24 | #define SEND_CURSOR_POS(n, m) \ 25 | bytes = sprintf(tmpbuf, "\e[%d;%dR", n, m); \ 26 | write(clientfd, tmpbuf, (size_t) bytes); 27 | 28 | int test_ansi_handshake(int clientfd); 29 | -------------------------------------------------------------------------------- /tests/compress.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | */ 8 | 9 | /*! \file 10 | * 11 | * \brief Compression Functions for Test Suite 12 | * 13 | * \author Naveen Albert 14 | */ 15 | 16 | struct z_data *z_client_new(int fd); 17 | 18 | #define REQUIRE_ZLIB_CLIENT(z) if (!z) { goto cleanup; } 19 | 20 | void z_client_free(struct z_data *z); 21 | 22 | #define ZLIB_CLIENT_SHUTDOWN(z) if (z) { z_client_free(z); z = NULL; } 23 | 24 | ssize_t zlib_write(struct z_data *z, int line, const char *buf, size_t len); 25 | 26 | ssize_t zlib_read(struct z_data *z, int line, char *buf, size_t len); 27 | 28 | int test_z_client_expect(struct z_data *z, int ms, const char *s, int line); 29 | int test_z_client_expect_buf(struct z_data *z, int ms, const char *s, int line, char *buf, size_t len); 30 | int test_z_client_expect_eventually(struct z_data *z, int ms, const char *s, int line); 31 | int test_z_client_expect_eventually_buf(struct z_data *z, int ms, const char *s, int line, char *buf, size_t len); 32 | 33 | #define ZLIB_CLIENT_EXPECT(z, s) if (test_z_client_expect(z, SEC_MS(5), s, __LINE__)) { goto cleanup; } 34 | #define ZLIB_CLIENT_EXPECT_BUF(z, s, buf) if (test_z_client_expect_buf(z, SEC_MS(5), s, __LINE__, buf, sizeof(buf))) { goto cleanup; } 35 | #define ZLIB_CLIENT_EXPECT_EVENTUALLY(z, s) if (test_z_client_expect_eventually(z, SEC_MS(5), s, __LINE__)) { goto cleanup; } 36 | 37 | #define ZLIB_SWRITE(z, s) if (zlib_write(z, __LINE__, s, STRLEN(s)) < 0) { goto cleanup; } 38 | -------------------------------------------------------------------------------- /tests/configs/.imapremote: -------------------------------------------------------------------------------- 1 | Other Users.testuser2|imap://testuser2:P@ssw0rD@127.0.0.1:143 2 | -------------------------------------------------------------------------------- /tests/configs/.rules: -------------------------------------------------------------------------------- 1 | RULE 2 | MATCH DIRECTION OUT 3 | MATCH HEADER Subject EQUALS Relayed Message 4 | ACTION RELAY smtp://testuser2@bbs.example.com:P@ssw0rD@127.0.0.1:587 5 | ACTION DISCARD 6 | ACTION EXIT # Stop processing all rules 7 | ENDRULE 8 | -------------------------------------------------------------------------------- /tests/configs/.sieve: -------------------------------------------------------------------------------- 1 | require [ "envelope", "subaddress", "fileinto", "reject", "notify", "vacation" ]; 2 | 3 | if size :under 70 { 4 | fileinto "Junk"; 5 | stop; 6 | } 7 | 8 | if header :is "Subject" "Test Subject 1" { 9 | fileinto "Trash"; 10 | stop; 11 | } 12 | 13 | if header :is "From" "Some string here" { 14 | fileinto "Trash"; 15 | stop; 16 | } 17 | 18 | if header :is "Subject" "Test Subject 2" { 19 | reject ""; 20 | discard; 21 | } 22 | 23 | if header :is "Subject" "Test Subject 3" { 24 | reject "This is a custom bounce message"; 25 | discard; 26 | } 27 | 28 | if header :is "Subject" "Test Subject 4" { 29 | if header :is "Subject" "No equal to this" { 30 | fileinto "Trash"; 31 | discard; 32 | } 33 | fileinto "Junk"; # This is a comment 34 | } 35 | 36 | if allof (header :is "Subject" "Test Subject 5", header :contains "Cc" "example.org") { 37 | discard; 38 | stop; 39 | } 40 | 41 | if exists "X-Drop-Message" { 42 | discard; 43 | stop; 44 | } 45 | 46 | if allof (header :is "Subject" "Test Subject 7", header :is "From" "external@example.net") { 47 | discard; 48 | stop; 49 | } 50 | 51 | if allof (header :is "Subject" "Test Subject 8", header :is "From" "external@example.net") { 52 | redirect "testuser2@bbs.example.com"; 53 | keep; 54 | } 55 | 56 | if allof (header :is "Subject" "Test Subject 9", envelope :all :is "from" "external@example.net") { 57 | redirect "testuser2@bbs.example.com"; 58 | redirect "testuser@bbs.example.com"; 59 | } 60 | 61 | if header :is "Subject" "Test Subject 12" { 62 | vacation :subject "Out of the Office" 63 | :days 2 64 | "I am currently out of the office and will not be available until further notice."; 65 | } 66 | -------------------------------------------------------------------------------- /tests/configs/dsn/net_smtp.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | relayin=yes 3 | relayout=yes 4 | requirefromhelomatch=no 5 | maxsize=500000 6 | 7 | [smtps] 8 | enabled=no 9 | 10 | [msa] 11 | requirestarttls=no 12 | 13 | [blacklist] 14 | example.org = no 15 | -------------------------------------------------------------------------------- /tests/configs/menus.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | title = Main Menu 3 | q = quit|Logoff 4 | a = menu:a|Menu A1 5 | b = menu:b|Menu B 6 | u = menu:u|Menu U 7 | 8 | [a] 9 | title = Menu A1 10 | a = menu:aa|Menu A2 11 | q = return|Back 12 | 13 | [aa] 14 | title = Menu A2 15 | q = return|Back 16 | 17 | [b] 18 | title = Menu B 19 | q = return|Back 20 | 21 | [u] 22 | title = Menu U 23 | a = return|Test Token 24 | q = return|Back -------------------------------------------------------------------------------- /tests/configs/mod_auth_mysql.conf: -------------------------------------------------------------------------------- 1 | [db] 2 | hostname=localhost 3 | username=bbs 4 | password=P@ssw0rdUShouldChAngE! 5 | database=bbs 6 | 7 | [registration] 8 | phone=no 9 | address=no 10 | zip=no 11 | dob=no 12 | gender=no 13 | howheard=no 14 | verifyemail=no 15 | -------------------------------------------------------------------------------- /tests/configs/mod_chanserv.conf: -------------------------------------------------------------------------------- 1 | [db] 2 | hostname=localhost 3 | username=bbs 4 | password=P@ssw0rdUShouldChAngE! 5 | database=irc 6 | -------------------------------------------------------------------------------- /tests/configs/mod_irc_client.conf: -------------------------------------------------------------------------------- 1 | ; mod_irc_client.conf 2 | 3 | [general] 4 | 5 | [bbs] 6 | hostname = 127.0.0.1 7 | port = 6667 ; Since this is a loopback connection, it's not necessary to use TLS for security. 8 | tls = no 9 | sasl = yes 10 | autojoin = #test1,#test2 11 | username = testuser3 12 | password = P@ssw0rD 13 | logfile = no 14 | callbacks = yes ; Must be enabled for mod_irc_relay integration. 15 | -------------------------------------------------------------------------------- /tests/configs/mod_irc_relay.conf: -------------------------------------------------------------------------------- 1 | ; mod_irc_relay.conf 2 | 3 | [general] 4 | exposemembers=yes 5 | startupjoinignore=0 6 | 7 | [many2many] 8 | channel1=#test1 9 | channel2=#test2 10 | relaysystem=yes 11 | -------------------------------------------------------------------------------- /tests/configs/mod_mail.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | maildir=/tmp/test_lbbs/maildir 3 | 4 | [aliases] 5 | aliasuser = testuser 6 | -------------------------------------------------------------------------------- /tests/configs/mod_mail_events.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | logfile=/tmp/test_lbbs/mailevents.log 3 | -------------------------------------------------------------------------------- /tests/configs/mod_mysql.conf: -------------------------------------------------------------------------------- 1 | [mysql] 2 | socket=/tmp/test_lbbs_mysql/mysqld.sock 3 | -------------------------------------------------------------------------------- /tests/configs/mod_smtp_mailing_lists.conf: -------------------------------------------------------------------------------- 1 | 2 | [list1] 3 | recipients = * 4 | samesenders = yes 5 | tag=My List 6 | ptonly=yes 7 | 8 | [small] 9 | recipients = * 10 | senders = * 11 | maxsize = 10 ; This is very silly, only allow messages smaller than 10 bytes, i.e. reject everything 12 | 13 | [limitedsender] 14 | recipients = testuser,testuser2 15 | senders = testuser2 16 | 17 | [oneandtwo] 18 | recipients = testuser,testuser2 19 | senders = * 20 | -------------------------------------------------------------------------------- /tests/configs/net_finger.conf: -------------------------------------------------------------------------------- 1 | [finger] 2 | allusersallowed=yes 3 | -------------------------------------------------------------------------------- /tests/configs/net_ftp.conf: -------------------------------------------------------------------------------- 1 | ; file just needs to exist 2 | -------------------------------------------------------------------------------- /tests/configs/net_gopher.conf: -------------------------------------------------------------------------------- 1 | [gopher] 2 | root=/tmp/test_lbbs/gopherdir 3 | -------------------------------------------------------------------------------- /tests/configs/net_http.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | docroot=/tmp/test_lbbs/www 3 | cgi=no 4 | authonly=no 5 | 6 | [http] 7 | port=8080 8 | enabled=yes 9 | -------------------------------------------------------------------------------- /tests/configs/net_imap.conf: -------------------------------------------------------------------------------- 1 | [imap] 2 | enabled=yes 3 | 4 | [imaps] 5 | enabled=no 6 | -------------------------------------------------------------------------------- /tests/configs/net_irc.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | requirechanserv=no 3 | 4 | [ircs] 5 | enabled=no 6 | 7 | [nativeopers] 8 | testuser2=testuser2 9 | -------------------------------------------------------------------------------- /tests/configs/net_msp.conf: -------------------------------------------------------------------------------- 1 | ; net_msp.conf - Message Send Protocol 2 | 3 | [ports] 4 | tcp=18 5 | udp=18 6 | -------------------------------------------------------------------------------- /tests/configs/net_nntp.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | newsdir=/tmp/test_lbbs/newsdir 3 | 4 | [nntps] 5 | enabled=no 6 | 7 | [relayin] 8 | requiretls=no 9 | 10 | [trusted] 11 | ip=127.0.0.1/32 12 | -------------------------------------------------------------------------------- /tests/configs/net_pop3.conf: -------------------------------------------------------------------------------- 1 | [pop3] 2 | enabled=yes 3 | 4 | [pop3s] 5 | enabled=no 6 | -------------------------------------------------------------------------------- /tests/configs/net_smtp.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | relayin=yes 3 | relayout=no ; Don't let email leave the server for testing purposes 4 | requirefromhelomatch=no 5 | maxsize=500000 6 | 7 | [smtps] 8 | enabled=no 9 | 10 | [msa] 11 | requirestarttls=no 12 | 13 | [blacklist] 14 | example.org = no 15 | -------------------------------------------------------------------------------- /tests/configs/net_ssh.conf: -------------------------------------------------------------------------------- 1 | [ssh] 2 | port = 2222 3 | 4 | [sftp] 5 | enabled=yes 6 | 7 | [keys] 8 | rsa = yes 9 | dsa = yes 10 | ecdsa = yes 11 | ed25519 = yes 12 | -------------------------------------------------------------------------------- /tests/configs/net_telnet.conf: -------------------------------------------------------------------------------- 1 | [telnet] 2 | port = 23 3 | ttyport = 2245 -------------------------------------------------------------------------------- /tests/configs/net_ws.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | 3 | [origins] 4 | http://localhost = allow 5 | 6 | [ws] 7 | port=8143 8 | -------------------------------------------------------------------------------- /tests/configs/tls.conf: -------------------------------------------------------------------------------- 1 | [tls] 2 | cert=/etc/ssl/certs/ssl-cert-snakeoil.pem 3 | key=/etc/ssl/private/ssl-cert-snakeoil.key 4 | -------------------------------------------------------------------------------- /tests/configs/tls/net_ftp.conf: -------------------------------------------------------------------------------- 1 | [ftp] 2 | port = 21 3 | 4 | [ftps] 5 | enabled = yes 6 | port = 990 7 | requirereuse=no 8 | 9 | [pasv] 10 | minport=10000 11 | maxport=20000 12 | -------------------------------------------------------------------------------- /tests/configs/tls/net_imap.conf: -------------------------------------------------------------------------------- 1 | [imap] 2 | enabled=yes 3 | 4 | [imaps] 5 | enabled=yes 6 | -------------------------------------------------------------------------------- /tests/configs/transfers.conf: -------------------------------------------------------------------------------- 1 | [transfers] 2 | rootdir=/tmp/test_lbbs/ftp 3 | homedirtemplate=/var/lib/lbbs/templates 4 | show_all_home_dirs=yes 5 | 6 | [privs] 7 | access=0 8 | download=0 9 | upload=1 10 | newdirs=1 11 | delete=1 12 | -------------------------------------------------------------------------------- /tests/email.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | */ 8 | 9 | /*! \file 10 | * 11 | * \brief Email Message Generation 12 | * 13 | * \author Naveen Albert 14 | */ 15 | 16 | extern int send_count; 17 | 18 | /*! 19 | * \brief Send a sample payload for the DATA command 20 | * \param clientfd 21 | * \param from 22 | * \retval 0 on success, -1 on failure 23 | */ 24 | int test_send_sample_body(int clientfd, const char *from); 25 | 26 | #define test_send_message(clientfd, recipient) test_send_message_with_extra_bytes(clientfd, recipient, 0) 27 | 28 | /*! 29 | * \brief Send a test email message to TEST_USER 30 | * \param clientfd Client file descriptor for SMTP transaction (should be connected to port 25) 31 | * \param recipient Recipient email 32 | * \param extrabytes Number of extra 'a' bytes to include 33 | * \retval 0 on success, -1 on failure 34 | */ 35 | int test_send_message_with_extra_bytes(int clientfd, const char *recipient, size_t extrabytes); 36 | 37 | /*! 38 | * \brief Send n sample email messages to TEST_USER 39 | * \param recipient Recipient email 40 | * \param nummsg Number of messages to send 41 | * \retval 0 on success, nonzero on total or partial failure 42 | */ 43 | int test_make_messages(const char *recipient, int nummsg); 44 | -------------------------------------------------------------------------------- /tests/messages/alternative.eml: -------------------------------------------------------------------------------- 1 | Return-Path: user@example.com 2 | Received: from [HIDDEN] (Authenticated sender: user@example.com) 3 | by example.com with ESMTP 4 | for ; Sun, Nov 10 2024 19:58:16 -0500 5 | To: user@example.com 6 | From: User 7 | Subject: Alternative test 8 | Message-ID: <5e539fce-2d97-a7fc-ec3d-c13b63325dc7@example.com> 9 | Date: Sun, 10 Nov 2024 19:58:17 -0500 10 | MIME-Version: 1.0 11 | Content-Type: multipart/mixed; 12 | boundary="------------B56ACAA1D42503EF82F52166" 13 | Content-Language: en-US 14 | 15 | This is a multi-part message in MIME format. 16 | --------------B56ACAA1D42503EF82F52166 17 | Content-Type: multipart/alternative; 18 | boundary="------------4E58F4DE7C0534A99765CD4D" 19 | 20 | 21 | --------------4E58F4DE7C0534A99765CD4D 22 | Content-Type: text/plain; charset=utf-8; format=flowed 23 | Content-Transfer-Encoding: 7bit 24 | 25 | This is a short message, whose /purpose/ is to have both a plain text, 26 | and an *HTML* component. 27 | 28 | 29 | --------------4E58F4DE7C0534A99765CD4D 30 | Content-Type: text/html; charset=utf-8 31 | Content-Transfer-Encoding: 7bit 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |

This is a short message, whose purpose 40 | is to have both a plain text, and an HTML component.
41 |

42 | 43 | 44 | 45 | --------------4E58F4DE7C0534A99765CD4D-- 46 | 47 | --------------B56ACAA1D42503EF82F52166 48 | Content-Type: text/plain; charset=UTF-8; 49 | name="attachment.txt" 50 | Content-Transfer-Encoding: base64 51 | Content-Disposition: attachment; 52 | filename="attachment.txt" 53 | 54 | VGVzdCB0ZXh0IGZpbGUgYXR0YWNobWVudA== 55 | --------------B56ACAA1D42503EF82F52166-- 56 | -------------------------------------------------------------------------------- /tests/messages/multipart.eml: -------------------------------------------------------------------------------- 1 | Return-Path: <> 2 | Date: Sat, 9 Nov 2024 19:47:01 +0000 (UTC) 3 | From: postmaster@example.com 4 | To: postmaster@exmaple.com 5 | Message-ID: <227366741.17305.1731181621979@example.com> 6 | Subject: Test 7 | MIME-Version: 1.0 8 | Content-Type: multipart/report; 9 | boundary="----=_Part_17304_1769721302.1731181621976"; 10 | report-type=delivery-status 11 | Auto-Submitted: auto-replied 12 | X-Auto-Response-Suppress: All 13 | 14 | ------=_Part_17304_1769721302.1731181621976 15 | Content-Type: text/plain; charset=us-ascii 16 | Content-Transfer-Encoding: 7bit 17 | 18 | We could not deliver the attached mail for the following recipients. (550) 19 | 20 | ------=_Part_17304_1769721302.1731181621976 21 | Content-Type: message/delivery-status; name=status.dat 22 | Content-Transfer-Encoding: 7bit 23 | Content-Description: Delivery Status Notification 24 | Content-Disposition: attachment; filename=status.dat 25 | 26 | Reporting-MTA: dns;example.com 27 | Arrival-Date: Sat, 09 Nov 2024 19:47:01 +0000 28 | 29 | Final-Recipient: postmaster@example.com 30 | Action: failed 31 | Status: 550 32 | Diagnostic-Code: error; 550 No such person at this address. 33 | 34 | Remote-MTA: dns; example.com. (192.0.2.25) 35 | Last-Attempt-Date: Sat, 09 Nov 2024 19:47:01 +0000 36 | 37 | ------=_Part_17304_1769721302.1731181621976 38 | Content-Type: text/rfc822-headers 39 | Content-Transfer-Encoding: 7bit 40 | 41 | Received: by example.com (SMTP) with ESMTPSA id 1234 42 | for 43 | (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); 44 | Sat, 09 Nov 2024 19:46:57 +0000 (UTC) 45 | To: postmaster@example.com 46 | From: Postmaster 47 | Subject: Test 48 | Message-ID: <854bff2f-0f60-fead-c9c5-5d22cd7b6dd6@example.com> 49 | Date: Sat, 9 Nov 2024 14:46:55 -0500 50 | MIME-Version: 1.0 51 | Content-Type: text/plain; charset=utf-8; format=flowed 52 | Content-Transfer-Encoding: 7bit 53 | Content-Language: en-US 54 | ------=_Part_17304_1769721302.1731181621976-- 55 | -------------------------------------------------------------------------------- /tests/messages/multipart2.eml: -------------------------------------------------------------------------------- 1 | Return-Path: <> 2 | Received: from localhost (localhost [127.0.0.1]) 3 | Date: Fri, 08 Nov 2024 14:34:06 +0000 4 | From: "Mail Delivery Subsystem" 5 | Subject: Delivery Status Notification (Failure) 6 | To: 7 | Auto-Submitted: auto-replied 8 | Message-ID: 9 | MIME-Version: 1.0 10 | Content-Type: multipart/report; report-type=delivery-status; 11 | boundary="----attachment_774801610416369" 12 | 13 | This is a multi-part message in MIME format. 14 | 15 | ------attachment_774801610416369 16 | Content-Description: Notification 17 | Content-Type: text/plain; charset=utf-8 18 | 19 | This is the mail system at host example.com. 20 | 21 | I'm sorry to have to inform you that your message could not 22 | be delivered to one or more recipients. It's attached below. 23 | 24 | For further assistance, please send mail to postmaster. 25 | 26 | If you do so, please include this problem report. You can delete your own text from the attached returned message. 27 | 28 | Please, do not reply to this message. 29 | 30 | 31 | : 32 | host example.com[192.0.2.25] said: 33 | 550 No such user 34 | 35 | ------attachment_774801610416369 36 | Content-Description: Delivery report 37 | Content-Type: message/delivery-status 38 | 39 | Reporting-MTA: dns; example.com 40 | Arrival-Date: Fri, 08 Nov 2024 14:34:06 +0000 41 | 42 | Final-Recipient: rfc822; 43 | Action: failed 44 | Remote-MTA: dns; example.net 45 | Diagnostic-Code: x-unknown; 550 [S10] Blocked 46 | 47 | ------attachment_774801610416369 48 | Content-Description: Undelivered message 49 | Content-Type: message/rfc822 50 | 51 | Received: from [10.1.1.1] 52 | by example.com with ESMTPSA 53 | for ; Fri, Nov 8 2024 14:34:06 +0000 54 | To: user@example.net 55 | From: 56 | Subject: Test 57 | Message-ID: 58 | Date: Fri, 8 Nov 2024 09:34:05 -0500 59 | MIME-Version: 1.0 60 | Content-Type: text/plain; charset=utf-8; format=flowed 61 | Content-Transfer-Encoding: 7bit 62 | Content-Language: en-US 63 | 64 | Test 65 | 66 | 67 | ------attachment_774801610416369-- 68 | -------------------------------------------------------------------------------- /tests/test_alloc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Allocation Recovery Test 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "test.h" 21 | 22 | #include 23 | 24 | extern int test_autorun; 25 | extern int rand_alloc_fails; 26 | 27 | static int pre(void) 28 | { 29 | test_autorun = 0; 30 | rand_alloc_fails = 1; /* Randomly fail some allocations */ 31 | test_autoload_all(); 32 | return 0; 33 | } 34 | 35 | static int run(void) 36 | { 37 | int i; 38 | unsigned long f; 39 | int ports[] = { 23, 25, 80, 110, 143 }; 40 | int clientfds[ARRAY_LEN(ports)]; 41 | 42 | /* The purpose of this test is not to ensure correctness of any particular functionality, 43 | * since memory allocation failures will likely lead to things NOT working correctly. 44 | * Rather, the goal is to stress test the BBS, cause some allocation failures, and ensure that 45 | * a) we don't crash 46 | * b) memory leaks don't ensue as a result of the allocation failures. 47 | * 48 | */ 49 | 50 | /* At least a few allocation errors should happen in this case */ 51 | for (i = 0; i < 75; i++) { 52 | for (f = 0; f < ARRAY_LEN(ports); f++) { 53 | clientfds[f] = test_make_socket(ports[f]); 54 | } 55 | for (f = 0; f < ARRAY_LEN(ports); f++) { 56 | close_if(clientfds[f]); 57 | } 58 | } 59 | 60 | usleep(1000); 61 | 62 | return 0; 63 | } 64 | 65 | TEST_MODULE_INFO_STANDARD("Allocation Tests"); 66 | -------------------------------------------------------------------------------- /tests/test_autoload.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Module Autoload Test 16 | * 17 | * This simply autoloads all the modules for a test 18 | * and then shuts everything back down again 19 | * after a brief delay. 20 | * 21 | * Since we autoload as many modules as possible, 22 | * this can be useful for narrowing the scope of 23 | * any issues that may arise just from a module 24 | * loading, since this test does nothing else. 25 | * 26 | * \author Naveen Albert 27 | */ 28 | 29 | #include "test.h" 30 | 31 | static int pre(void) 32 | { 33 | test_autoload_all(); /* Load everything since multiple modules contain unit tests */ 34 | return 0; 35 | } 36 | 37 | static int run(void) 38 | { 39 | /* After the BBS starts, wait a few seconds, then shut it down again 40 | * This gives enough time for the mod_irc_client -> net_irc connection 41 | * to be established. */ 42 | sleep(4); 43 | return 0; 44 | } 45 | 46 | TEST_MODULE_INFO_STANDARD("Autoload Tests"); 47 | -------------------------------------------------------------------------------- /tests/test_finger.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Finger Tests 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "test.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | static int pre(void) 29 | { 30 | test_preload_module("mod_mail.so"); 31 | test_load_module("net_finger.so"); 32 | 33 | TEST_ADD_CONFIG("net_finger.conf"); 34 | 35 | return 0; 36 | } 37 | 38 | static int run(void) 39 | { 40 | int clientfd = -1; 41 | int res = -1; 42 | 43 | /* List all users */ 44 | clientfd = test_make_socket(79); 45 | REQUIRE_FD(clientfd); 46 | SWRITE(clientfd, ENDL); 47 | CLIENT_EXPECT_EVENTUALLY(clientfd, TEST_USER); 48 | close_if(clientfd); 49 | 50 | /* List a specific user */ 51 | clientfd = test_make_socket(79); 52 | REQUIRE_FD(clientfd); 53 | SWRITE(clientfd, TEST_USER ENDL); 54 | CLIENT_EXPECT_EVENTUALLY(clientfd, TEST_USER); 55 | close_if(clientfd); 56 | 57 | res = 0; 58 | 59 | cleanup: 60 | close_if(clientfd); 61 | return res; 62 | } 63 | 64 | TEST_MODULE_INFO_STANDARD("Finger Tests"); 65 | -------------------------------------------------------------------------------- /tests/test_gopher.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Gopher Tests 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "test.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | static int pre(void) 29 | { 30 | test_load_module("net_gopher.so"); 31 | 32 | TEST_ADD_CONFIG("net_gopher.conf"); 33 | 34 | TEST_RESET_MKDIR(TEST_GOPHER_DIR); 35 | TEST_MKDIR(TEST_GOPHER_DIR "/testdir"); 36 | 37 | /* Not efficient, but I feel lazy right now */ 38 | system("echo 'This is a test page' > " TEST_GOPHER_DIR "/file1.txt"); 39 | system("echo 'This is another test page' > " TEST_GOPHER_DIR "/file2.txt"); 40 | return 0; 41 | } 42 | 43 | static int run(void) 44 | { 45 | int clientfd = -1; 46 | int res = -1; 47 | 48 | clientfd = test_make_socket(70); 49 | REQUIRE_FD(clientfd); 50 | 51 | SWRITE(clientfd, ENDL); /* "List what you have" */ 52 | CLIENT_EXPECT_EVENTUALLY(clientfd, "1testdir"); 53 | close_if(clientfd); 54 | 55 | clientfd = test_make_socket(70); 56 | REQUIRE_FD(clientfd); 57 | SWRITE(clientfd, ENDL); /* "List what you have" */ 58 | CLIENT_EXPECT_EVENTUALLY(clientfd, "0file1.txt"); 59 | close_if(clientfd); 60 | 61 | clientfd = test_make_socket(70); 62 | REQUIRE_FD(clientfd); 63 | SWRITE(clientfd, ENDL); /* "List what you have" */ 64 | CLIENT_EXPECT_EVENTUALLY(clientfd, "0file2.txt"); 65 | 66 | /* Try retrieving a file */ 67 | clientfd = test_make_socket(70); 68 | REQUIRE_FD(clientfd); 69 | SWRITE(clientfd, "/file2.txt" ENDL); 70 | CLIENT_EXPECT_EVENTUALLY(clientfd, "This is another test page"); 71 | 72 | res = 0; 73 | 74 | cleanup: 75 | close_if(clientfd); 76 | return res; 77 | } 78 | 79 | TEST_MODULE_INFO_STANDARD("Gopher Tests"); 80 | -------------------------------------------------------------------------------- /tests/test_home.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Home Directory Tests 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "test.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | static int pre(void) 30 | { 31 | test_preload_module("mod_mail.so"); 32 | test_preload_module("mod_mimeparse.so"); 33 | test_load_module("net_imap.so"); 34 | 35 | /* If 'make templates' (run by 'make samples') has never been run on this system, 36 | * the templates won't exist at runtime to copy. */ 37 | TEST_REQUIRE_FILE("/var/lib/lbbs/templates/.config/.imapremote.sample"); 38 | 39 | TEST_ADD_CONFIG("mod_mail.conf"); 40 | TEST_ADD_CONFIG("net_imap.conf"); 41 | TEST_ADD_CONFIG("transfers.conf"); 42 | 43 | TEST_RESET_MKDIR(TEST_MAIL_DIR); 44 | TEST_RESET_MKDIR(TEST_TRANSFER_DIR); 45 | return 0; 46 | } 47 | 48 | static int run(void) 49 | { 50 | int client1 = -1; 51 | int res = -1; 52 | 53 | client1 = test_make_socket(143); 54 | REQUIRE_FD(client1); 55 | 56 | CLIENT_EXPECT(client1, "OK"); 57 | SWRITE(client1, "a1 LOGIN \"" TEST_USER "\" \"" TEST_PASS "\"" ENDL); 58 | CLIENT_EXPECT(client1, "a1 OK"); 59 | 60 | /* Logging in to the IMAP server will trigger a check for .imapremote (when doing LIST), 61 | * which will autocreate a user's home directory, if needed, 62 | * copying any template files. */ 63 | 64 | SWRITE(client1, "a2 LIST \"\" \"*\"" ENDL); 65 | CLIENT_EXPECT_EVENTUALLY(client1, "a2 OK"); 66 | 67 | if (eaccess(TEST_HOME_DIR_ROOT "/1/.config/.imapremote.sample", R_OK)) { 68 | bbs_error("eaccess(%s) failed: %s\n", TEST_HOME_DIR_ROOT "/1/.config/.imapremote.sample", strerror(errno)); 69 | goto cleanup; 70 | } 71 | 72 | res = 0; 73 | 74 | cleanup: 75 | close_if(client1); 76 | return res; 77 | } 78 | 79 | TEST_MODULE_INFO_STANDARD("Home Directory Tests"); 80 | -------------------------------------------------------------------------------- /tests/test_http.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief HTTP Server Tests 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "test.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | static int pre(void) 29 | { 30 | test_preload_module("mod_http.so"); 31 | test_load_module("net_http.so"); 32 | 33 | TEST_ADD_CONFIG("net_http.conf"); 34 | 35 | TEST_RESET_MKDIR(TEST_WWW_DIR); 36 | TEST_MKDIR(TEST_WWW_DIR "/testdir"); 37 | 38 | /* Not efficient, but I feel lazy right now */ 39 | system("echo 'This is a test page' > " TEST_WWW_DIR "/file1.txt"); 40 | system("echo 'This is another test page' > " TEST_WWW_DIR "/file2.txt"); 41 | return 0; 42 | } 43 | 44 | static int run(void) 45 | { 46 | int clientfd = -1; 47 | int res = -1; 48 | 49 | /* The HTTP server is running on (non standard) port 8080. 50 | * In a clean testing environment, it should be okay to bind on port 80, 51 | * but Apache is already listening on port 80 on this machine here, so avoid conflicting with that. */ 52 | 53 | clientfd = test_make_socket(8080); 54 | REQUIRE_FD(clientfd); 55 | 56 | SWRITE(clientfd, "GET / HTTP/1.1" ENDL); 57 | SWRITE(clientfd, "Host: localhost:8080" ENDL); 58 | SWRITE(clientfd, ENDL); /* End of headers */ 59 | CLIENT_EXPECT_EVENTUALLY(clientfd, "file2.txt"); 60 | close_if(clientfd); 61 | 62 | clientfd = test_make_socket(8080); 63 | REQUIRE_FD(clientfd); 64 | 65 | /* XXX It would be significantly more rigorous to read the response line by line and, after the headers, make an exact comparison of the entire body */ 66 | 67 | SWRITE(clientfd, "GET /file1.txt HTTP/1.1" ENDL); 68 | SWRITE(clientfd, "Host: localhost:8080" ENDL); 69 | SWRITE(clientfd, "Connection: keep-alive" ENDL); 70 | SWRITE(clientfd, ENDL); /* End of headers */ 71 | CLIENT_EXPECT_EVENTUALLY(clientfd, "This is a test page"); 72 | 73 | /* Test connection reuse */ 74 | SWRITE(clientfd, "GET /file2.txt HTTP/1.1" ENDL); 75 | SWRITE(clientfd, "Host: localhost:8080" ENDL); 76 | SWRITE(clientfd, "Connection: keep-alive" ENDL); 77 | SWRITE(clientfd, ENDL); /* End of headers */ 78 | CLIENT_EXPECT_EVENTUALLY(clientfd, "This is another test page"); 79 | 80 | /* Close the connection early */ 81 | SWRITE(clientfd, "GET /file2.txt HTTP/1.1" ENDL); 82 | SWRITE(clientfd, "Host: localhost:8080" ENDL); 83 | SWRITE(clientfd, "Connection: keep-alive" ENDL); 84 | SWRITE(clientfd, ENDL); /* End of headers */ 85 | CLOSE(clientfd); 86 | 87 | res = 0; 88 | 89 | cleanup: 90 | close_if(clientfd); 91 | return res; 92 | } 93 | 94 | TEST_MODULE_INFO_STANDARD("HTTP Server Tests"); 95 | -------------------------------------------------------------------------------- /tests/test_imap_auth_plain.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief IMAP AUTH PLAIN Tests 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "test.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | static int pre(void) 29 | { 30 | test_preload_module("mod_mail.so"); 31 | test_preload_module("mod_mimeparse.so"); 32 | test_load_module("net_imap.so"); 33 | 34 | TEST_ADD_CONFIG("mod_mail.conf"); 35 | TEST_ADD_CONFIG("net_imap.conf"); 36 | 37 | TEST_RESET_MKDIR(TEST_MAIL_DIR); 38 | return 0; 39 | } 40 | 41 | static int run(void) 42 | { 43 | int clientfd = -1; 44 | int res = -1; 45 | 46 | clientfd = test_make_socket(143); 47 | REQUIRE_FD(clientfd); 48 | 49 | /* Connect and log in */ 50 | CLIENT_EXPECT(clientfd, "* OK [CAPABILITY"); 51 | 52 | SWRITE(clientfd, "1 authenticate PLAIN" ENDL); 53 | CLIENT_EXPECT(clientfd, "+"); 54 | 55 | SWRITE(clientfd, TEST_SASL ENDL); 56 | CLIENT_EXPECT(clientfd, "1 OK [CAPABILITY"); 57 | 58 | SWRITE(clientfd, "2 ID (\"name\" \"MailNews\" \"version\" \"52.9.8629a1\")" ENDL); 59 | CLIENT_EXPECT_EVENTUALLY(clientfd, "2 OK"); 60 | 61 | SWRITE(clientfd, "3 select \"INBOX\"" ENDL); 62 | CLIENT_EXPECT_EVENTUALLY(clientfd, "3 OK"); 63 | 64 | /* LOGOUT */ 65 | SWRITE(clientfd, "4 LOGOUT" ENDL); 66 | CLIENT_EXPECT_EVENTUALLY(clientfd, "* BYE"); 67 | res = 0; 68 | 69 | cleanup: 70 | close_if(clientfd); 71 | return res; 72 | } 73 | 74 | TEST_MODULE_INFO_STANDARD("IMAP AUTHENTICATE PLAIN Tests"); 75 | -------------------------------------------------------------------------------- /tests/test_imap_compress.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief IMAP COMPRESS Tests 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "test.h" 21 | #include "compress.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | static int pre(void) 30 | { 31 | test_preload_module("io_compress.so"); 32 | test_preload_module("mod_mail.so"); 33 | test_preload_module("mod_mimeparse.so"); 34 | test_load_module("net_imap.so"); 35 | 36 | TEST_ADD_CONFIG("mod_mail.conf"); 37 | TEST_ADD_CONFIG("net_imap.conf"); 38 | 39 | TEST_RESET_MKDIR(TEST_MAIL_DIR); 40 | return 0; 41 | } 42 | 43 | static int run(void) 44 | { 45 | struct z_data *z = NULL; 46 | int clientfd = -1; 47 | int res = -1; 48 | 49 | clientfd = test_make_socket(143); 50 | REQUIRE_FD(clientfd); 51 | 52 | /* Connect and log in */ 53 | CLIENT_EXPECT(clientfd, "OK"); 54 | SWRITE(clientfd, "1 LOGIN \"" TEST_USER "\" \"" TEST_PASS "\"" ENDL); 55 | CLIENT_EXPECT(clientfd, "1 OK"); 56 | 57 | SWRITE(clientfd, "2 COMPRESS DEFLATE" ENDL); 58 | CLIENT_EXPECT_EVENTUALLY(clientfd, "2 OK"); /* The tagged response is without compress. After this, we use compression for the remainder of the session. */ 59 | 60 | z = z_client_new(clientfd); 61 | REQUIRE_ZLIB_CLIENT(z); 62 | 63 | ZLIB_SWRITE(z, "3 ID (\"name\" \"LBBS Tester\" \"version\" \"123\")" ENDL); 64 | ZLIB_CLIENT_EXPECT_EVENTUALLY(z, "3 OK"); 65 | 66 | ZLIB_SWRITE(z, "4 select \"INBOX\"" ENDL); 67 | ZLIB_CLIENT_EXPECT_EVENTUALLY(z, "4 OK"); 68 | 69 | /* LOGOUT */ 70 | ZLIB_SWRITE(z, "5 LOGOUT" ENDL); 71 | ZLIB_CLIENT_EXPECT_EVENTUALLY(z, "* BYE"); 72 | res = 0; 73 | 74 | cleanup: 75 | ZLIB_CLIENT_SHUTDOWN(z); 76 | close_if(clientfd); 77 | return res; 78 | } 79 | 80 | TEST_MODULE_INFO_STANDARD("IMAP COMPRESS Tests"); 81 | -------------------------------------------------------------------------------- /tests/test_menus.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Menu Tests 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "test.h" 21 | #include "ansi.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | static int pre(void) 30 | { 31 | test_load_module("mod_menu_handlers.so"); 32 | test_load_module("mod_node_callbacks.so"); 33 | test_load_module("net_telnet.so"); 34 | 35 | /* no net_telnet.conf needed, defaults are sufficient */ 36 | TEST_ADD_CONFIG("menus.conf"); 37 | 38 | return 0; 39 | } 40 | 41 | static int run(void) 42 | { 43 | int clientfd = -1; 44 | int res = -1; 45 | 46 | clientfd = test_make_socket(23); 47 | REQUIRE_FD(clientfd); 48 | 49 | /* Emulate ANSI-capable client to speed up the test (avoid ANSI autodetection timeout) */ 50 | if (test_ansi_handshake(clientfd)) { 51 | goto cleanup; 52 | } 53 | 54 | CLIENT_EXPECT_EVENTUALLY(clientfd, "Hit a key"); 55 | SWRITE(clientfd, " "); /* Hit a key */ 56 | 57 | /* Must always use CLIENT_EXPECT_EVENTUALLY since our input is also echoed back to us */ 58 | 59 | CLIENT_EXPECT_EVENTUALLY(clientfd, "Login"); 60 | SWRITE(clientfd, TEST_USER "\n"); 61 | CLIENT_EXPECT_EVENTUALLY(clientfd, "Password"); 62 | SWRITE(clientfd, TEST_PASS "\n"); 63 | CLIENT_EXPECT_EVENTUALLY(clientfd, "at welcome menu via T"); 64 | CLIENT_DRAIN(clientfd); 65 | SWRITE(clientfd, " "); /* Hit a key */ 66 | 67 | CLIENT_EXPECT_EVENTUALLY(clientfd, "Main Menu"); 68 | SWRITE(clientfd, "a"); /* Choose option 'a' */ 69 | 70 | CLIENT_EXPECT_EVENTUALLY(clientfd, "Menu A1"); 71 | SWRITE(clientfd, "q"); /* Go back to main menu */ 72 | 73 | /* Test submenu skip navigation */ 74 | CLIENT_EXPECT_EVENTUALLY(clientfd, "Main Menu"); 75 | SWRITE(clientfd, "/aa\n"); /* Go to the same submenu, and into the submenu A2 */ 76 | 77 | CLIENT_EXPECT_EVENTUALLY(clientfd, "Menu A2"); 78 | SWRITE(clientfd, "qq"); /* Go back to main menu */ 79 | 80 | CLIENT_EXPECT_EVENTUALLY(clientfd, "Main Menu"); 81 | 82 | res = 0; 83 | 84 | cleanup: 85 | close_if(clientfd); 86 | return res; 87 | } 88 | 89 | TEST_MODULE_INFO_STANDARD("Menu Tests"); 90 | -------------------------------------------------------------------------------- /tests/test_nntp_transit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief NNTP Transit Tests 16 | * 17 | * \author Naveen Albert 18 | */ 19 | 20 | #include "test.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | static int pre(void) 29 | { 30 | test_load_module("net_nntp.so"); 31 | 32 | TEST_ADD_CONFIG("net_nntp.conf"); 33 | 34 | TEST_RESET_MKDIR(TEST_NEWS_DIR); 35 | TEST_MKDIR(TEST_NEWS_DIR "/misc.test"); 36 | TEST_MKDIR(TEST_NEWS_DIR "/misc.empty"); 37 | return 0; 38 | } 39 | 40 | static int run(void) 41 | { 42 | const char *s; 43 | int clientfd; 44 | int res = -1; 45 | 46 | s = "From: \"Demo User\" <" TEST_EMAIL_UNAUTHORIZED ">" ENDL 47 | "Newsgroups: misc.test" ENDL 48 | "Subject: I am just a test article" ENDL 49 | ENDL 50 | "This is just a test article." ENDL 51 | "." ENDL; 52 | 53 | clientfd = test_make_socket(433); 54 | REQUIRE_FD(clientfd); 55 | 56 | #define TEST_MESSAGE_ID "test.message@" TEST_HOSTNAME 57 | 58 | /* Initial connection */ 59 | CLIENT_EXPECT(clientfd, "200 " TEST_HOSTNAME); 60 | SWRITE(clientfd, "CAPABILITIES\r\n"); 61 | CLIENT_EXPECT(clientfd, "101"); 62 | CLIENT_EXPECT_EVENTUALLY(clientfd, ".\r\n"); 63 | 64 | /* Offer new article that we don't currently have. */ 65 | SWRITE(clientfd, "IHAVE <" TEST_MESSAGE_ID ">\r\n"); 66 | CLIENT_EXPECT(clientfd, "335"); 67 | write(clientfd, s, strlen(s)); 68 | CLIENT_EXPECT_EVENTUALLY(clientfd, "235"); 69 | 70 | /* Offer the same article again, it should be rejected. */ 71 | SWRITE(clientfd, "IHAVE <" TEST_MESSAGE_ID ">\r\n"); 72 | CLIENT_EXPECT(clientfd, "435"); 73 | 74 | res = 0; 75 | 76 | cleanup: 77 | close(clientfd); 78 | return res; 79 | } 80 | 81 | TEST_MODULE_INFO_STANDARD("NNTP Transit Tests"); 82 | -------------------------------------------------------------------------------- /tests/test_unit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2023, Naveen Albert 5 | * 6 | * Naveen Albert 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! \file 14 | * 15 | * \brief Unit Test Executions 16 | * 17 | * This simply executes the unit tests from a test framework module, 18 | * useful since the test framework is run by the CI easily, 19 | * but the unit tests don't lend themselves as easily to that 20 | * on their own. 21 | * 22 | * \author Naveen Albert 23 | */ 24 | 25 | #include "test.h" 26 | 27 | extern int startup_run_unit_tests; 28 | 29 | static int pre(void) 30 | { 31 | startup_run_unit_tests = 1; 32 | test_autoload_all(); /* Load everything since multiple modules contain unit tests */ 33 | return 0; 34 | } 35 | 36 | static int run(void) 37 | { 38 | /* Unit tests are typically fast, so shouldn't take very long to execute them all. 39 | * mod_test_backtrace can take a moment under valgrind though, so add some time for that one. */ 40 | int res = test_bbs_expect(COLOR(COLOR_SUCCESS) "100%" COLOR_RESET, SEC_MS(30)); 41 | if (res) { 42 | bbs_error("Failed to receive expected output\n"); 43 | } 44 | return res; 45 | } 46 | 47 | TEST_MODULE_INFO_STANDARD("Unit Tests"); 48 | -------------------------------------------------------------------------------- /tests/tls.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LBBS -- The Lightweight Bulletin Board System 3 | * 4 | * Copyright (C) 2025, Naveen Albert 5 | * 6 | * Naveen Albert 7 | */ 8 | 9 | /*! \file 10 | * 11 | * \brief TLS Functions for Test Suite 12 | * 13 | * \author Naveen Albert 14 | */ 15 | 16 | /* For SSL* */ 17 | #include 18 | 19 | /*! 20 | * \brief Start a TLS client session on the provided file descriptor 21 | * \param fd File descriptor of the socket that will be doing encrypted communications 22 | * \note The server certificate is NOT verified 23 | * \retval NULL on failure 24 | * \return SSL session on success 25 | */ 26 | SSL *__tls_client_new(int fd, SSL *ssl, int line); 27 | 28 | #define tls_client_new(fd) __tls_client_new(fd, NULL, __LINE__) 29 | #define tls_client_new_reuse_session(fd, ssl) __tls_client_new(fd, ssl, __LINE__) 30 | 31 | /*! \brief Destroy a TLS session */ 32 | void __tls_free(SSL *ssl, int line); 33 | 34 | #define tls_free(ssl) __tls_free(ssl, __LINE__) 35 | 36 | #define SSL_SHUTDOWN(ssl) if (ssl) { tls_free(ssl); ssl = NULL; } 37 | 38 | #define REQUIRE_SSL(ssl) if (!ssl) { goto cleanup; } 39 | 40 | ssize_t tls_write(SSL *ssl, int line, const char *buf, size_t len); 41 | 42 | ssize_t tls_read(SSL *ssl, int line, char *buf, size_t len); 43 | 44 | int test_tls_client_expect(SSL *ssl, int ms, const char *s, int line); 45 | int test_tls_client_expect_buf(SSL *ssl, int ms, const char *s, int line, char *buf, size_t len); 46 | int test_tls_client_expect_eventually(SSL *ssl, int ms, const char *s, int line); 47 | int test_tls_client_expect_eventually_buf(SSL *ssl, int ms, const char *s, int line, char *buf, size_t len); 48 | 49 | #define TLS_CLIENT_EXPECT(ssl, s) if (test_tls_client_expect(ssl, SEC_MS(5), s, __LINE__)) { goto cleanup; } 50 | #define TLS_CLIENT_EXPECT_BUF(ssl, s, buf) if (test_tls_client_expect_buf(ssl, SEC_MS(5), s, __LINE__, buf, sizeof(buf))) { goto cleanup; } 51 | #define TLS_CLIENT_EXPECT_EVENTUALLY(ssl, s) if (test_tls_client_expect_eventually(ssl, SEC_MS(5), s, __LINE__)) { goto cleanup; } 52 | 53 | #define TLS_SWRITE(ssl, s) if (tls_write(ssl, __LINE__, s, STRLEN(s)) < 0) { goto cleanup; } 54 | --------------------------------------------------------------------------------