Skip to content

Commit

Permalink
net_msp: Add Message Send Protocol.
Browse files Browse the repository at this point in the history
This adds support for the Message Send Protocol,
which is a simple protocol that may be used to
receive short messages targeted towards system
users. Both TCP and UDP support is present.
The main intended use case of this is to allow
remote servers to deliver messages to the system
via the IRC and notification subsystems, without
requiring that they maintain persistent TCP
connections to the BBS.
  • Loading branch information
InterLinked1 committed Nov 27, 2023
1 parent 3425fb8 commit a6b7512
Show file tree
Hide file tree
Showing 9 changed files with 740 additions and 74 deletions.
10 changes: 7 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ Config files go in :code:`/etc/lbbs` and are as follows:

* :code:`net_irc.conf` - Internet Relay Chat server config

* :code:`net_msp.conf` - Message Send Protocol config

* :code:`net_nntp.conf` - Network News Transfer Protocol (NNTP) server config

* :code:`net_pop3.conf` - POP3 server config
Expand Down Expand Up @@ -237,6 +239,8 @@ The BBS also comes with some network services that aren't intended for terminal

* :code:`net_irc` - Internet Relay Chat server

* :code:`net_msp` - Message Send Protocol server

* :code:`net_nntp` - Network News Transfer Protocol (NNTP) server

* :code:`net_pop3` - POP3 server
Expand Down Expand Up @@ -282,10 +286,10 @@ The first is as simple as explicitly granting the BBS binary the right to do so,
This is the recommended approach if it works for you. If not, you can also explicitly allow
all users to bind to any ports that are at least the specified port number::

sudo sysctl net.ipv4.ip_unprivileged_port_start=21
sudo sysctl net.ipv4.ip_unprivileged_port_start=18

This example would allow any user to bind to ports 21 and above.
The lowest standard port number currently used by the BBS is 21 (FTP).
This example would allow any user to bind to ports 18 and above.
The lowest standard port number currently used by the BBS is 18 (FTP).

Note that this method is not as secure as the first method, but is likely to work even if other methods fail.

Expand Down
169 changes: 102 additions & 67 deletions bbs/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h> /* use sockaddr_in */
#include <net/if.h> /* use ifreq */
#include <sys/un.h> /* use struct sockaddr_un */
#include <arpa/inet.h> /* use inet_ntop */
#include <netdb.h> /* use getnameinfo */
Expand Down Expand Up @@ -120,26 +121,59 @@ int __bbs_make_unix_socket(int *sock, const char *sockfile, const char *perm, ui
return 0;
}

int __bbs_make_tcp_socket(int *sock, int port, const char *file, int line, const char *func)
/*!
* \brief Create and bind a TCP or UDP socket to a specific port
* \param[out] sock Socket file descriptor
* \param rebind Whether to try to force reuse of a particular port if already in use
* \param type SOCK_DGRAM for UDP or SOCK_STREAM for TCP
* \param ip Specific IP/CIDR to which to bind, or NULL for all
* \param interface Specific interface to which to bind, or NULL for all
* \param file
* \param line
* \param func
* \retval 0 on success, -1 on failure
*/
static int __bbs_socket_bind(int *sock, int rebind, int type, int port, const char *ip, const char *interface, const char *file, int line, const char *func)
{
struct sockaddr_in sinaddr; /* Internet socket */
const int enable = 1;
int res;
struct sockaddr_in sinaddr; /* Internet socket */

#if defined(DEBUG_FD_LEAKS) && DEBUG_FD_LEAKS == 1
*sock = __bbs_socket(AF_INET, SOCK_STREAM, 0, file, line, func);
*sock = __bbs_socket(AF_INET, type, 0, file, line, func);
#else
UNUSED(file);
UNUSED(line);
UNUSED(func);
*sock = socket(AF_INET, SOCK_STREAM, 0);
*sock = socket(AF_INET, type, 0);
#endif
if (*sock < 0) {
bbs_error("Unable to create TCP socket: %s\n", strerror(errno));
bbs_error("Unable to create %s socket: %s\n", type == SOCK_STREAM ? "TCP" : "UDP", strerror(errno));
return -1;
}

if (option_rebind) {
memset(&sinaddr, 0, sizeof(sinaddr));
sinaddr.sin_family = AF_INET;
sinaddr.sin_port = htons((uint16_t) port); /* Public port on which to listen */

if (!strlen_zero(ip)) {
sinaddr.sin_addr.s_addr = inet_addr(ip);
} else {
sinaddr.sin_addr.s_addr = INADDR_ANY;
}

if (!strlen_zero(interface)) {
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
safe_strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
if (setsockopt(*sock, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0) {
bbs_error("Failed to set SO_BINDTODEVICE(%s): %s\n", interface, strerror(errno));
close(*sock);
return -1;
}
}

if (rebind && type == SOCK_STREAM) {
const int enable = 1;
/* This is necessary since trying a bind without reuse and then trying with reuse
* can actually still fail (for some reason...).
* If you reuse the first time, though, it should always work.
Expand All @@ -148,87 +182,88 @@ int __bbs_make_tcp_socket(int *sock, int port, const char *file, int line, const
* aren't running, then this may be worth it (e.g. the test framework)
*/
if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
bbs_error("Unable to create setsockopt: %s\n", strerror(errno));
bbs_error("Failed to set SO_REUSEADDR: %s\n", strerror(errno));
close(*sock);
return -1;
}
if (setsockopt(*sock, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) {
bbs_error("Unable to create setsockopt: %s\n", strerror(errno));
bbs_error("Failed to set SO_REUSEPORT: %s\n", strerror(errno));
close(*sock);
return -1;
}
}

memset(&sinaddr, 0, sizeof(sinaddr));
sinaddr.sin_family = AF_INET;
sinaddr.sin_addr.s_addr = INADDR_ANY;
sinaddr.sin_port = htons((uint16_t) port); /* Public TCP port on which to listen */

res = bind(*sock, (struct sockaddr*) &sinaddr, sizeof(sinaddr));
if (res) {
while (errno == EADDRINUSE) {
/* Don't do this by default.
* If somehow multiple instances of the BBS are running,
* then weird things can happen as a result of multiple BBS processes
* running on the same port. Sometimes things will work, usually they won't.
*
* (We do try really hard in bbs.c to prevent multiple instances of the BBS
* from being run at the same time, mostly accidentally, and this usually
* works, but it's not foolproof.)
*
* Therefore, try to bind without reusing first, and only if that fails,
* reuse the port, but make some noise about this just in case. */
if (option_rebind) {
bbs_error("Port %d was already in use, retrying with reuse\n", port);
} else {
bbs_warning("Port %d was already in use, retrying with reuse\n", port);
}
res = errno;
close(*sock);
errno = res; /* Close but preserve the errno from bind failing */
}
return res;
}

/* We can't reuse the original socket after bind fails, make a new one. */
close(*sock);
if (bbs_safe_sleep(500)) {
bbs_verb(4, "Aborting socket bind due to exceptional BBS activity\n");
break;
}
*sock = socket(AF_INET, SOCK_STREAM, 0);
if (*sock < 0) {
bbs_error("Unable to recreate TCP socket: %s\n", strerror(errno));
return -1;
}
if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
bbs_error("Unable to create setsockopt: %s\n", strerror(errno));
return -1;
}
if (setsockopt(*sock, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) {
bbs_error("Unable to create setsockopt: %s\n", strerror(errno));
return -1;
}
static int __bbs_make_ip_socket(int *sock, int port, int type, const char *ip, const char *interface, const char *file, int line, const char *func)
{
int res;

memset(&sinaddr, 0, sizeof(sinaddr));
sinaddr.sin_family = AF_INET;
sinaddr.sin_addr.s_addr = INADDR_ANY;
sinaddr.sin_port = htons((uint16_t) port); /* Public TCP port on which to listen */
res = __bbs_socket_bind(sock, option_rebind, type, port, ip, interface, file, line, func);
while (res && errno == EADDRINUSE) {
/* Don't do this by default.
* If somehow multiple instances of the BBS are running,
* then weird things can happen as a result of multiple BBS processes
* running on the same port. Sometimes things will work, usually they won't.
*
* (We do try really hard in bbs.c to prevent multiple instances of the BBS
* from being run at the same time, mostly accidentally, and this usually
* works, but it's not foolproof.)
*
* Therefore, try to bind without reusing first, and only if that fails,
* reuse the port, but make some noise about this just in case. */
if (option_rebind) {
bbs_error("Port %d was already in use, retrying with reuse\n", port);
} else {
bbs_warning("Port %d was already in use, retrying with reuse\n", port);
}

res = bind(*sock, (struct sockaddr*) &sinaddr, sizeof(sinaddr));
if (!option_rebind) {
break;
}
if (bbs_safe_sleep(500)) {
bbs_verb(4, "Aborting socket bind due to exceptional BBS activity\n");
break;
}
if (res) {
bbs_error("Unable to bind TCP socket to port %d: %s\n", port, strerror(errno));

res = __bbs_socket_bind(sock, option_rebind, type, port, ip, interface, file, line, func);
if (!option_rebind) {
/* If we don't require reuse, try once and then give up */
break;
}
}
if (res) {
bbs_error("Unable to bind %s socket to port %d: %s\n", type == SOCK_STREAM ? "TCP" : "UDP", port, strerror(errno));
*sock = -1;
return -1;
}

if (type == SOCK_STREAM) {
if (listen(*sock, 10) < 0) {
bbs_error("Unable to listen on %s socket on port %d: %s\n", type == SOCK_STREAM ? "TCP" : "UDP", port, strerror(errno));
close(*sock);
*sock = -1;
return -1;
}
}
if (listen(*sock, 10) < 0) {
bbs_error("Unable to listen on TCP socket on port %d: %s\n", port, strerror(errno));
close(*sock);
*sock = -1;
return -1;
}
bbs_debug(1, "Started %s listener on port %d\n", "TCP", port);
bbs_debug(1, "Started %s listener on port %d\n", type == SOCK_STREAM ? "TCP" : "UDP", port);
return 0;
}

int __bbs_make_tcp_socket(int *sock, int port, const char *file, int line, const char *func)
{
return __bbs_make_ip_socket(sock, port, SOCK_STREAM, NULL, NULL, file, line, func);
}

int __bbs_make_udp_socket(int *sock, int port, const char *ip, const char *interface, const char *file, int line, const char *func)
{
return __bbs_make_ip_socket(sock, port, SOCK_DGRAM, ip, interface, file, line, func);
}

int bbs_unblock_fd(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
Expand Down
21 changes: 21 additions & 0 deletions configs/net_msp.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
; net_msp.conf - Message Send Protocol
;
; This protocol can be used to allow various clients (e.g. other servers) to submit
; messages to deliver to local BBS users. This avoids the need to, for example,
; create an IRC user for other clients and set up persistent IRC connections,
; for applications that may only need to send messages to users or channels.
;
; WARNING: This protocol may allow unwanted anonymous and spoofed messages to reach users.
; If you load this module, it is HIGHLY recommend that you configure restrictions on the UDP
; listener to only listen to a private interface, through which trusted messages can be
; sent by other endpoints.
; You are urged to NOT EXPOSE this protocol to the Internet or other public networks.

[ports]
tcp=18
udp=18

; Additional configuration for the UDP listener
[udp]
;ip=127.0.0.1 ; Restrict listener to this IP address
;interface=eth1 ; Specific interface on which to listen
16 changes: 14 additions & 2 deletions include/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,27 @@
int __bbs_make_unix_socket(int *sock, const char *sockfile, const char *perm, uid_t uid, gid_t gid, const char *file, int line, const char *func);

/*!
* \brief Create a TCP socket
* \param sock Pointer to socket
* \brief Create and bind a TCP socket to a particular port
* \param[out] sock Pointer to socket
* \param port Port number on which to create the socket
* \retval 0 on success, -1 on failure
*/
#define bbs_make_tcp_socket(sock, port) __bbs_make_tcp_socket(sock, port, __FILE__, __LINE__, __func__)

int __bbs_make_tcp_socket(int *sock, int port, const char *file, int line, const char *func);

/*!
* \brief Create and bind a UDP socket to a particular port
* \param[out] sock Pointer to socket
* \param port Port number on which to create the socket
* \param ip Specific IP/CIDR to which to bind, or NULL for all
* \param interface Specific interface to which to bind, or NULL for all
* \retval 0 on success, -1 on failure
*/
#define bbs_make_udp_socket(sock, port, ip, interface) __bbs_make_udp_socket(sock, port, ip, interface, __FILE__, __LINE__, __func__)

int __bbs_make_udp_socket(int *sock, int port, const char *ip, const char *interface, const char *file, int line, const char *func);

/*! \brief Put a socket in nonblocking mode */
int bbs_unblock_fd(int fd);

Expand Down
1 change: 0 additions & 1 deletion nets/net_finger.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
#include "include/bbs.h"

#include <string.h>
#include <signal.h>

#include "include/module.h"
#include "include/config.h"
Expand Down
2 changes: 1 addition & 1 deletion nets/net_irc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2811,7 +2811,7 @@ static void handle_client(struct irc_user *user)
if (res <= 0) {
/* Don't set graceful_close to 0 here, since after a QUIT, the client may close the connection first.
* The QUIT message should be whatever the client sent, since it was graceful, not connection closed by remote host. */
bbs_debug(3, "poll/read returned %lu\n", res);
bbs_debug(3, "bbs_readline returned %ld\n", res);
break;
}
bbs_strterm(s, '\r');
Expand Down
Loading

0 comments on commit a6b7512

Please sign in to comment.