From 3487ba6a67f4a147effa03ea05ba8712f5a7ec86 Mon Sep 17 00:00:00 2001 From: Klemens Nanni Date: Sat, 27 Jan 2024 01:38:42 +0100 Subject: [PATCH 1/3] Build on OpenBSD Recognise the system to configure, build and start. More work is required to actually work with shairport-sync. Tested on OpenBSD/amd64 7.4-current. --- Makefile.am | 2 -- configure.ac | 17 ++++++++++++++--- nqptp-utilities.c | 2 +- nqptp.c | 4 ++-- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Makefile.am b/Makefile.am index d2b3992..ff86fea 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,5 +39,3 @@ if INSTALL_FREEBSD_STARTUP chmod 555 /usr/local/etc/rc.d/nqptp endif endif - - diff --git a/configure.ac b/configure.ac index ff1947f..503b805 100644 --- a/configure.ac +++ b/configure.ac @@ -8,6 +8,7 @@ AC_CANONICAL_HOST build_linux=no build_freebsd=no +build_openbsd=no # Detect the target system case "${host_os}" in @@ -16,7 +17,10 @@ case "${host_os}" in ;; freebsd*) build_freebsd=yes - ;; + ;; + openbsd*) + build_openbsd=yes + ;; *) AC_MSG_ERROR(["OS $host_os is not supported"]) ;; @@ -25,6 +29,7 @@ esac # Pass the conditionals to automake AM_CONDITIONAL([BUILD_FOR_LINUX], [test "$build_linux" = "yes"]) AM_CONDITIONAL([BUILD_FOR_FREEBSD], [test "$build_freebsd" = "yes"]) +AM_CONDITIONAL([BUILD_FOR_OPENBSD], [test "$build_openbsd" = "yes"]) if test "x$build_linux" = "xyes" ; then AC_DEFINE([CONFIG_FOR_LINUX], 1, [Build for Linux.]) @@ -32,6 +37,9 @@ fi if test "x$build_freebsd" = "xyes" ; then AC_DEFINE([CONFIG_FOR_FREEBSD], 1, [Build for FreeBSD.]) fi +if test "x$build_openbsd" = "xyes" ; then + AC_DEFINE([CONFIG_FOR_OPENBSD], 1, [Build for OpenBSD.]) +fi AC_CHECK_PROGS([GIT], [git]) if test -n "$GIT" && test -e ".git/index" ; then @@ -44,7 +52,7 @@ AM_CONDITIONAL([USE_GIT_VERSION], [test -n "$GIT" && test -e ".git/index" ]) AC_ARG_WITH([systemd-startup],[AS_HELP_STRING([--with-systemd-startup],[install a systemd startup script during a make install])]) AM_CONDITIONAL([INSTALL_SYSTEMD_STARTUP], [test "x$with_systemd_startup" = "xyes"]) -# Check to see if we should include the systemd stuff to define it as a service +# Check to see if we should include the FreeBSD stuff to define it as a service AC_ARG_WITH([freebsd-startup],[AS_HELP_STRING([--with-freebsd-startup],[install a FreeBSD startup script during a make install])]) AM_CONDITIONAL([INSTALL_FREEBSD_STARTUP], [test "x$with_freebsd_startup" = "xyes"]) @@ -58,7 +66,10 @@ AC_PROG_INSTALL # Checks for libraries. AC_CHECK_LIB([pthread],[pthread_create], , AC_MSG_ERROR(pthread library needed)) -AC_CHECK_LIB([rt],[clock_gettime], , AC_MSG_ERROR(librt needed for shared memory library)) +if test "x$build_openbsd" = "xno" ; then + # part of libc + AC_CHECK_LIB([rt],[clock_gettime], , AC_MSG_ERROR(librt needed for shared memory library)) +fi # Checks for header files. AC_CHECK_HEADERS([arpa/inet.h inttypes.h netdb.h stdlib.h string.h sys/socket.h unistd.h]) diff --git a/nqptp-utilities.c b/nqptp-utilities.c index 7c30877..cfbd1ee 100644 --- a/nqptp-utilities.c +++ b/nqptp-utilities.c @@ -28,7 +28,7 @@ #include // sockaddr_ll #endif -#ifdef CONFIG_FOR_FREEBSD +#if defined(CONFIG_FOR_FREEBSD) || defined(CONFIG_FOR_OPENBSD) #include #include #include diff --git a/nqptp.c b/nqptp.c index a1a3c76..6af9495 100644 --- a/nqptp.c +++ b/nqptp.c @@ -48,7 +48,7 @@ #include #include -#ifdef CONFIG_FOR_FREEBSD +#if defined(CONFIG_FOR_FREEBSD) || defined(CONFIG_FOR_OPENBSD) #include #include #endif @@ -206,7 +206,7 @@ int main(int argc, char **argv) { die("failed to set size of shared memory \"%s\".", NQPTP_INTERFACE_NAME); } -#ifdef CONFIG_FOR_FREEBSD +#if defined(CONFIG_FOR_FREEBSD) || defined(CONFIG_FOR_OPENBSD) shared_memory = (struct shm_structure *)mmap(NULL, sizeof(struct shm_structure), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); #endif From 4586bb084a98e3b9f18d101fb91c72112a527323 Mon Sep 17 00:00:00 2001 From: Klemens Nanni Date: Sat, 27 Jan 2024 05:36:29 +0100 Subject: [PATCH 2/3] Hoist PTP socket handling, drop privileges on OpenBSD bind(2)ing ports below 1024 is the only privileged operation NQPTP does. Move its code up in main() before shared memory handling such that root privileges can be dropped immediately after it; no currently supported system does that, thus this should be a NOOP. Do so on OpenBSD where shm_open(3) does not allow access to shared memory objects by multiple UIDs, i.e. to communicate, shairport-sync and NQPTP must create them and run as the very same user. OpenBSD's official audio/shairport-sync user provides an rc.d(8) daemon script that runs as `_shairport` user. --- Makefile.am | 5 +++++ nqptp-utilities.c | 2 +- nqptp.c | 31 +++++++++++++++++++++++++++---- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index ff86fea..5f12222 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,3 +39,8 @@ if INSTALL_FREEBSD_STARTUP chmod 555 /usr/local/etc/rc.d/nqptp endif endif + +if BUILD_FOR_OPENBSD +# NQPTP starts as root on OpenBSD to access ports 319 and 320 +# and drops privileges to the user shairport is running as. +endif diff --git a/nqptp-utilities.c b/nqptp-utilities.c index cfbd1ee..18e5a9c 100644 --- a/nqptp-utilities.c +++ b/nqptp-utilities.c @@ -106,7 +106,7 @@ void open_sockets_at_port(const char *node, uint16_t port, freeaddrinfo(info); if (sockets_opened == 0) { if (errno == EACCES) { - die("nqptp does not have permission to access port %u. It must (a) [Linux only] have been given CAP_NET_BIND_SERVICE capabilities using e.g. setcap or systemd's AmbientCapabilities, or (b) run as root.", port); + die("nqptp does not have permission to access port %u. It must (a) [Linux only] have been given CAP_NET_BIND_SERVICE capabilities using e.g. setcap or systemd's AmbientCapabilities, or (b) start as root.", port); } else { die("nqptp is unable to listen on port %u. The error is: %d, \"%s\".", port, errno, strerror(errno)); } diff --git a/nqptp.c b/nqptp.c index 6af9495..88cf939 100644 --- a/nqptp.c +++ b/nqptp.c @@ -53,6 +53,12 @@ #include #endif +#ifdef CONFIG_FOR_OPENBSD +#include +#include +#include +#endif + #ifndef FIELD_SIZEOF #define FIELD_SIZEOF(t, f) (sizeof(((t *)0)->f)) #endif @@ -177,6 +183,10 @@ int main(int argc, char **argv) { sockets_open_stuff.sockets_open = 0; + // open PTP sockets + open_sockets_at_port(NULL, 319, &sockets_open_stuff); + open_sockets_at_port(NULL, 320, &sockets_open_stuff); + epoll_fd = -1; // control-c (SIGINT) cleanly @@ -191,6 +201,22 @@ int main(int argc, char **argv) { act2.sa_handler = termHandler; sigaction(SIGTERM, &act2, NULL); +#ifdef CONFIG_FOR_OPENBSD + // shm_open(3) prohibits sharing between different UIDs, so nqptp must run as + // the same user shairport-sync does. + struct passwd *pw; + const char *shairport_user = "_shairport"; + pw = getpwnam(shairport_user); + if (pw == NULL) { + die("unknown user %s", shairport_user); + } + if (setgroups(1, &pw->pw_gid) == -1 || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) { + die("cannot drop privileges to %s", shairport_user); + } +#endif + // open the SMI shm_fd = -1; @@ -233,10 +259,7 @@ int main(int argc, char **argv) { char buf[BUFLEN]; - // open sockets 319 and 320 - - open_sockets_at_port(NULL, 319, &sockets_open_stuff); - open_sockets_at_port(NULL, 320, &sockets_open_stuff); + // open control socket open_sockets_at_port("localhost", NQPTP_CONTROL_PORT, &sockets_open_stuff); // this for messages from the client From 6e5f6943f8d3bdd86ed190d34e4f7b1028a1b005 Mon Sep 17 00:00:00 2001 From: Klemens Nanni Date: Sat, 27 Jan 2024 06:44:35 +0100 Subject: [PATCH 3/3] Hoist control socket handling, restrict runtime on OpenBSD Use pledge(2) to prevent fork/exec, filesystem access and other unused subsets of system calls, effectively leaving only shared memory and networking capabilities at runtime. (Those might be further reduced, but that warrants further analysis and most likely more code shuffling.) --- nqptp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nqptp.c b/nqptp.c index 88cf939..73b0d66 100644 --- a/nqptp.c +++ b/nqptp.c @@ -131,6 +131,11 @@ void termHandler(__attribute__((unused)) int k) { } int main(int argc, char **argv) { +#ifdef CONFIG_FOR_OPENBSD + if (pledge("stdio rpath tmppath inet dns id", NULL) == -1) { + die("pledge: %s", strerror(errno)); + } +#endif int debug_level = 0; int i; @@ -215,6 +220,10 @@ int main(int argc, char **argv) { setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) { die("cannot drop privileges to %s", shairport_user); } + + if (pledge("stdio tmppath inet dns", NULL) == -1) { + die("pledge: %s", strerror(errno)); + } #endif // open the SMI