diff options
author | lns <matzeton@googlemail.com> | 2018-04-11 14:28:18 +0200 |
---|---|---|
committer | Toni Uhlig <matzeton@googlemail.com> | 2018-06-13 18:23:43 +0200 |
commit | f2f11e477a489ac25a4c4be064eddc26fc9d677c (patch) | |
tree | d4f679146a61b28056e772e30570c53fb4721b80 | |
parent | ebabaa69c0a3ba992895c7a66729e81e0923d5f1 (diff) |
POTD skeleton.
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | COPYING | 3 | ||||
-rw-r--r-- | README | 20 | ||||
-rw-r--r-- | configure.ac | 229 | ||||
-rw-r--r-- | src/Makefile.am | 6 | ||||
-rw-r--r-- | src/capabilities.c | 344 | ||||
-rw-r--r-- | src/capabilities.h | 25 | ||||
-rw-r--r-- | src/forward.c | 172 | ||||
-rw-r--r-- | src/forward.h | 35 | ||||
-rw-r--r-- | src/jail.c | 553 | ||||
-rw-r--r-- | src/jail.h | 34 | ||||
-rw-r--r-- | src/log.c | 2 | ||||
-rw-r--r-- | src/log.h | 42 | ||||
-rw-r--r-- | src/log_colored.c | 95 | ||||
-rw-r--r-- | src/log_colored.h | 10 | ||||
-rw-r--r-- | src/log_file.c | 171 | ||||
-rw-r--r-- | src/log_file.h | 24 | ||||
-rw-r--r-- | src/main.c | 383 | ||||
-rw-r--r-- | src/options.c | 405 | ||||
-rw-r--r-- | src/options.h | 33 | ||||
-rw-r--r-- | src/pevent.c | 232 | ||||
-rw-r--r-- | src/pevent.h | 46 | ||||
-rw-r--r-- | src/protocol.c | 54 | ||||
-rw-r--r-- | src/protocol.h | 37 | ||||
-rw-r--r-- | src/protocol_ssh.c | 596 | ||||
-rw-r--r-- | src/protocol_ssh.h | 14 | ||||
-rw-r--r-- | src/pseccomp.c | 223 | ||||
-rw-r--r-- | src/pseccomp.h | 27 | ||||
-rw-r--r-- | src/redirector.c | 418 | ||||
-rw-r--r-- | src/redirector.h | 31 | ||||
-rw-r--r-- | src/server.c | 38 | ||||
-rw-r--r-- | src/server.h | 46 | ||||
-rw-r--r-- | src/server_ssh.c | 77 | ||||
-rw-r--r-- | src/server_ssh.h | 27 | ||||
-rw-r--r-- | src/socket.c | 316 | ||||
-rw-r--r-- | src/socket.h | 44 | ||||
-rw-r--r-- | src/utils.c | 868 | ||||
-rw-r--r-- | src/utils.h | 73 |
39 files changed, 5473 insertions, 282 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5761abc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.o @@ -0,0 +1 @@ +Toni Uhlig <matzeton@googlemail.com> @@ -0,0 +1,3 @@ +Non-free proprietary license! + +(C) 2018 Toni Uhlig <matzeton@googlemail.com> @@ -0,0 +1,20 @@ +honey[potd]aemon +================ + +This project is part of a BA thesis. It is all but not finished yet. +(Currently) Supported protocols: + ssh with libssh + +Suits perfect for your favoured Desktop/Server/OpenWrt Linux system. + + +TODOs +===== + +Priority == Item order! +1. more/other jail options (e.g. per jail filesystem w/ image managment) +2. RESTful listener for output sampled data from different processes + (send (real-time)statistics about protocols/jails/etc to highler level apps) +3. ptrace support for jailed apps (trace syscalls) +4. implement more protocols such as SCADA/MySQL/telnet/Jabber/IRC/IMAP/POP +5. improved event handling (maybe libevent?) diff --git a/configure.ac b/configure.ac index 69ae2b1..a152440 100644 --- a/configure.ac +++ b/configure.ac @@ -4,9 +4,236 @@ AC_CANONICAL_TARGET AM_INIT_AUTOMAKE AM_SILENT_RULES([yes]) AM_MAINTAINER_MODE + +AS_IF([test -z "$CFLAGS"], [CFLAGS="-Os -g"]) + +AC_CANONICAL_HOST AC_PROG_CC AC_PROG_CC_STDC +AC_PROG_RANLIB +AC_PROG_INSTALL +AC_TYPE_SIZE_T + +AC_CHECK_TOOL([PKGCONFIG], [pkg-config], [:]) +AS_IF([test "x${PKGCONFIG}" = x], [ AC_MSG_ERROR([pkg-config not found]) ]) + +# check for spectre mitigation +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -mindirect-branch=thunk" +AC_MSG_CHECKING([if ${CC} supports -mindirect-branch=thunk spectre mitigation]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])], + [ AC_MSG_RESULT([yes]) + SPECTRE_MIT="-mindirect-branch=thunk" ], + AC_MSG_RESULT([no])) +CFLAGS="$saved_CFLAGS" +AC_SUBST([SPECTRE_MIT]) + +# check for -fvisibility=hidden compiler support (GCC >= 4) +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -fvisibility=hidden -fvisibility-inlines-hidden" +AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden -fvisibility-inlines-hidden]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])], + [ AC_MSG_RESULT([yes]) + SYMBOL_VISIBILITY="-fvisibility=hidden" ], + AC_MSG_RESULT([no])) +CFLAGS="$saved_CFLAGS" +AC_SUBST([SYMBOL_VISIBILITY]) + +AC_CHECK_LIB([socket], [connect]) +AC_CHECK_LIB([pthread], [pthread_create]) + +dnl libssh-dev +PKG_CHECK_MODULES([libssh], [libssh >= 0.7.3], [], + [ AC_MSG_ERROR([libssh >= 0.7.3 not found]) ]) +AC_SUBST([libssh_CFLAGS]) +AC_SUBST([libssh_LIBS]) +dnl Some libssh versions require libssl,libcrypto,zlib. +dnl This is because the libssh.pc file does not sets additional required shlibs. +additional_libssh_libs="" + +CFLAGS="$CFLAGS $libssh_CFLAGS" +LIBS="$LIBS $libssh_LIBS" + +AC_MSG_CHECKING([if libssh requires -lcrypto]) +AC_TRY_LINK([#include <libssh/libssh.h>], + [ return ssh_init(); ], [ libssh_require_libcrypto="no" ], + [ libssh_require_libcrypto="yes"]) +AC_MSG_RESULT([${libssh_require_libcrypto}]) +AS_IF([test "x${libssh_require_libcrypto}" = xyes], + [ additional_libssh_libs="${additional_libssh_libs} -lcrypto" + AC_CHECK_LIB([crypto], [RSA_new], [], + [AC_MSG_ERROR([libcrypto not found])]) + ]) + +AC_MSG_CHECKING([if libssh requires -lssl]) +AC_TRY_LINK([#include <libssh/libssh.h>], + [ return ssh_init(); ], [ libssh_require_libssl="no" ], + [ libssh_require_libssl="yes"]) +AC_MSG_RESULT([${libssh_require_libssl}]) +AS_IF([test "x${libssh_require_libssl}" = xyes], + [ additional_libssh_libs="${additional_libssh_libs} -lssl" + AC_CHECK_LIB([ssl], [SSL_new], [], + [AC_MSG_ERROR([libssl not found])]) + ]) + +AC_MSG_CHECKING([if libssh requires -lz]) +AC_TRY_LINK([#include <libssh/libssh.h>], + [ return ssh_init(); ], [ libssh_require_libz="no" ], + [ libssh_require_libz="yes"]) +AC_MSG_RESULT([${libssh_require_libz}]) +AS_IF([test "x${libssh_require_libz}" = xyes], + [ additional_libssh_libs="${additional_libssh_libs} -lz" + AC_CHECK_LIB([z], [inflate], [], + [AC_MSG_ERROR([libz not found])]) + ]) + +AC_CHECK_LIB([ssh], [ssh_init], [], [AC_MSG_ERROR([final link against libssh failed])]) +AC_CHECK_LIB([seccomp], [seccomp_init], [], [AC_MSG_ERROR([final link against libseccomp failed])]) + + +dnl libseccomp-dev +PKG_CHECK_MODULES([libseccomp], [libseccomp >= 2.2.1], [], + [ AC_MSG_ERROR([libseccomp >= 2.2.1 not found]) ]) +AC_SUBST([libseccomp_CFLAGS]) +AC_SUBST([libseccomp_LIBS]) + +dnl Check for valgrind +PKG_CHECK_MODULES([valgrind], [valgrind >= 3.12.0], + [ AC_DEFINE([HAVE_VALGRIND], [1], + [Define to 1 if you have/want valgrind support]) + valgrind_enabled="yes" ], + [ valgrind_enabled="no" ]) +AC_SUBST([valgrind_CFLAGS]) +AC_SUBST([valgrind_LIBS]) + +dnl Check for std header files +AC_CHECK_HEADERS([stdio.h ctype.h assert.h sched.h signal.h time.h errno.h pwd.h], [], + [ AC_MSG_ERROR([required std header not available]) ]) + +dnl Check for system specific header files +AC_CHECK_HEADERS([pty.h linux/capability.h sys/wait.h sys/ioctl.h net/if.h netinet/in.h libgen.h], [], + [ AC_MSG_ERROR([required system specific header not available]) ]) +AC_CHECK_HEADERS([libutil.h pthread.h syslog.h sys/prctl.h linux/limits.h \ + sys/uio.h poll.h sys/epoll.h sys/sysmacros.h sys/mount.h util.h]) + +dnl Check for GAI header +AC_CHECK_HEADERS([netdb.h]) + +AC_MSG_CHECKING([working time]) +AC_COMPILE_IFELSE([ + AC_LANG_SOURCE([#include <time.h> + int fn(void) \ + { time_t s0 = time(NULL); \ + time_t s1 = time(NULL); \ + double r = difftime(s0, s1); }]) + ], + AC_MSG_RESULT([yes]), + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([time is not available on your platform]) ]) + +AC_MSG_CHECKING([for working epoll]) +AC_COMPILE_IFELSE([ + AC_LANG_SOURCE([#include <sys/epoll.h> + #include <signal.h> + int fn(void) \ + { int fd = epoll_create1(0); \ + struct epoll_event ev = {0,{0}}; \ + struct epoll_event polled[[16]]; \ + sigset_t eset; sigemptyset(&eset); \ + epoll_ctl(fd, EPOLL_CTL_ADD, 0, &ev); \ + epoll_pwait(fd, polled, 16, -1, &eset); \ + close(fd); }]) + ], + AC_MSG_RESULT([yes]), + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([epoll is not available on your platform]) ]) + +AC_MSG_CHECKING([for working va_arg]) +AC_COMPILE_IFELSE([ + AC_LANG_SOURCE([#include <stdio.h> + #include <stdarg.h> + int fn(const char *fmt, ...) \ + { char buf[[32]] = {0}; va_list arglist; \ + va_start(arglist, fmt); \ + vsnprintf(buf, sizeof buf, fmt, arglist); \ + va_end(arglist); return 0; }]) + ], + AC_MSG_RESULT([yes]), + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([va_arg does not work as expected]) ]) + +AC_MSG_CHECKING([for assert]) +AC_COMPILE_IFELSE([ + AC_LANG_SOURCE([#include <assert.h> + int fn(void) \ + { assert(0); return 0; }]) + ], + AC_MSG_RESULT([yes]), + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([assertion macro missing]) ]) + +AC_MSG_CHECKING([for working capability drop]) +AC_COMPILE_IFELSE([ + AC_LANG_SOURCE([#include <sys/prctl.h> + #include <linux/capability.h> + int fn(void) \ + { int caps[[]] = {CAP_SYS_MODULE,CAP_SYS_RAWIO,CAP_SYS_BOOT, \ + CAP_SYS_NICE, CAP_SYS_TTY_CONFIG, CAP_MKNOD, CAP_SYS_ADMIN, \ + CAP_SYS_RESOURCE, CAP_SYS_TIME, CAP_AUDIT_CONTROL, \ + CAP_AUDIT_READ, CAP_AUDIT_WRITE, CAP_SYS_PTRACE, \ + CAP_SYS_PACCT, CAP_SYS_CHROOT}; \ + int i; \ + for (i = 0; i < sizeof(caps)/sizeof(caps[[0]]); ++i) \ + prctl(PR_CAPBSET_DROP, caps[[i]], 0, 0, 0); \ + return 0; }]) + ], + AC_MSG_RESULT([yes]), + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([required capability drop does not work]) ]) + +AC_MSG_CHECKING([for working unshare]) +AC_COMPILE_IFELSE([ + AC_LANG_SOURCE([#define _GNU_SOURCE 1 + #include <sched.h> + int fn(void) \ + { int unshare_flags = CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWIPC| \ + CLONE_NEWNS|CLONE_NEWNET; \ + return unshare(unshare_flags); }]) + ], + AC_MSG_RESULT([yes]), + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([required unshare function does not work]) ]) + +dnl Most systems require linking against libutil.so in order to get forkpty() +AC_CHECK_FUNCS([forkpty], [], + [AC_CHECK_LIB(util, forkpty, + [LIBS="-lutil $LIBS" + AC_DEFINE(HAVE_FORKPTY)])]) +dnl minimum required functions +AC_CHECK_FUNCS([open read write close malloc free memset memcpy fork unshare \ + getpwnam getgrnam setreuid setregid \ + wait waitpid isprint remove unlink mkdir access stat chroot chdir mount umount mknod \ + strdup strcasecmp strncat strncpy snprintf vsnprintf printf fprintf getpid \ + prctl signal signalfd fcntl getenv kill exit \ + setsockopt socket connect accept bind listen \ + time difftime strtol strtoll getopt_long_only], [], + [ AC_MSG_ERROR([required function not available]) ]) +dnl GAI functions +AC_CHECK_FUNCS([getaddrinfo getnameinfo freeaddrinfo], [], + [ AC_MSG_ERROR([required GAI function not available]) ]) +dnl epoll functions +AC_CHECK_FUNCS([epoll_create1 epoll_ctl epoll_pwait], [], + [ AC_MSG_ERROR([required epoll function not available]) ]) + -PKG_CHECK_MODULES([libssh], [libssh >= 0.7.3]) +potd_logfile="/var/log/potd.log" +AC_DEFINE_UNQUOTED([POTD_LOGFILE], ["$potd_logfile"], + [default path to the log file]) +potd_defroot="/var/run/potd-root" +AC_DEFINE_UNQUOTED([POTD_DEFROOT], ["$potd_defroot"], + [default path to potd rootfs image/directory]) +potd_netns_run_dir="/var/run/potd-netns" +AC_DEFINE_UNQUOTED([POTD_NETNS_RUN_DIR], ["$potd_netns_run_dir"], + [default path to network namespace run directory]) AC_OUTPUT(Makefile src/Makefile) diff --git a/src/Makefile.am b/src/Makefile.am index db612f1..b6e48f3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ -AM_CFLAGS = -pedantic -Wall -std=gnu99 -D_GNU_SOURCE=1 -AM_LDFLAGS = -lssh +AM_CFLAGS = -pedantic -Wall -std=gnu99 -fstrict-aliasing -D_GNU_SOURCE=1 $(libssh_CFLAGS) $(libseccomp_CFLAGS) $(valgrind_CFLAGS) $(SPECTRE_MIT) $(SYMBOL_VISIBILITY) +AM_LDFLAGS = $(libssh_LIBS) $(libseccomp_LIBS) $(valgrind_LIBS) sbin_PROGRAMS = potd -potd_SOURCES = main.c utils.c log.c log_colored.c socket.c server.c server_ssh.c +potd_SOURCES = utils.c options.c log.c log_colored.c log_file.c socket.c pevent.c capabilities.c pseccomp.c jail.c forward.c redirector.c protocol.c protocol_ssh.c main.c diff --git a/src/capabilities.c b/src/capabilities.c new file mode 100644 index 0000000..57658cc --- /dev/null +++ b/src/capabilities.c @@ -0,0 +1,344 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <errno.h> +#include <linux/capability.h> +#include <sys/prctl.h> + +#include "capabilities.h" +#include "utils.h" +#include "log.h" + +typedef struct { + const char *name; + int nr; +} CapsEntry; + +static CapsEntry capslist[] = { +#ifdef CAP_CHOWN + {"chown", CAP_CHOWN }, +#endif +#ifdef CAP_DAC_OVERRIDE + {"dac_override", CAP_DAC_OVERRIDE }, +#endif +#ifdef CAP_DAC_READ_SEARCH + {"dac_read_search", CAP_DAC_READ_SEARCH }, +#endif +#ifdef CAP_FOWNER + {"fowner", CAP_FOWNER }, +#endif +#ifdef CAP_FSETID + {"fsetid", CAP_FSETID }, +#endif +#ifdef CAP_KILL + {"kill", CAP_KILL }, +#endif +#ifdef CAP_SETGID + {"setgid", CAP_SETGID }, +#endif +#ifdef CAP_SETUID + {"setuid", CAP_SETUID }, +#endif +#ifdef CAP_SETPCAP + {"setpcap", CAP_SETPCAP }, +#endif +#ifdef CAP_LINUX_IMMUTABLE + {"linux_immutable", CAP_LINUX_IMMUTABLE }, +#endif +#ifdef CAP_NET_BIND_SERVICE + {"net_bind_service", CAP_NET_BIND_SERVICE }, +#endif +#ifdef CAP_NET_BROADCAST + {"net_broadcast", CAP_NET_BROADCAST }, +#endif +#ifdef CAP_NET_ADMIN + {"net_admin", CAP_NET_ADMIN }, +#endif +#ifdef CAP_NET_RAW + {"net_raw", CAP_NET_RAW }, +#endif +#ifdef CAP_IPC_LOCK + {"ipc_lock", CAP_IPC_LOCK }, +#endif +#ifdef CAP_IPC_OWNER + {"ipc_owner", CAP_IPC_OWNER }, +#endif +#ifdef CAP_SYS_MODULE + {"sys_module", CAP_SYS_MODULE }, +#endif +#ifdef CAP_SYS_RAWIO + {"sys_rawio", CAP_SYS_RAWIO }, +#endif +#ifdef CAP_SYS_CHROOT + {"sys_chroot", CAP_SYS_CHROOT }, +#endif +#ifdef CAP_SYS_PTRACE + {"sys_ptrace", CAP_SYS_PTRACE }, +#endif +#ifdef CAP_SYS_PACCT + {"sys_pacct", CAP_SYS_PACCT }, +#endif +#ifdef CAP_SYS_ADMIN + {"sys_admin", CAP_SYS_ADMIN }, +#endif +#ifdef CAP_SYS_BOOT + {"sys_boot", CAP_SYS_BOOT }, +#endif +#ifdef CAP_SYS_NICE + {"sys_nice", CAP_SYS_NICE }, +#endif +#ifdef CAP_SYS_RESOURCE + {"sys_resource", CAP_SYS_RESOURCE }, +#endif +#ifdef CAP_SYS_TIME + {"sys_time", CAP_SYS_TIME }, +#endif +#ifdef CAP_SYS_TTY_CONFIG + {"sys_tty_config", CAP_SYS_TTY_CONFIG }, +#endif +#ifdef CAP_MKNOD + {"mknod", CAP_MKNOD }, +#endif +#ifdef CAP_LEASE + {"lease", CAP_LEASE }, +#endif +#ifdef CAP_AUDIT_WRITE + {"audit_write", CAP_AUDIT_WRITE }, +#endif +#ifdef CAP_AUDIT_CONTROL + {"audit_control", CAP_AUDIT_CONTROL }, +#endif +#ifdef CAP_SETFCAP + {"setfcap", CAP_SETFCAP }, +#endif +#ifdef CAP_MAC_OVERRIDE + {"mac_override", CAP_MAC_OVERRIDE }, +#endif +#ifdef CAP_MAC_ADMIN + {"mac_admin", CAP_MAC_ADMIN }, +#endif +#ifdef CAP_SYSLOG + {"syslog", CAP_SYSLOG }, +#endif +#ifdef CAP_WAKE_ALARM + {"wake_alarm", CAP_WAKE_ALARM }, +#endif +#ifdef CAP_BLOCK_SUSPEND + {"block_suspend", CAP_BLOCK_SUSPEND }, +#else + {"block_suspend", 36 }, +#endif +#ifdef CAP_AUDIT_READ + {"audit_read", CAP_AUDIT_READ }, +#else + {"audit_read", 37 }, +#endif +}; // end of capslist + + +static int caps_find_name(const char *name) +{ + int i; + int elems = SIZEOF(capslist); + + for (i = 0; i < elems; i++) { + if (strcmp(name, capslist[i].name) == 0) + return capslist[i].nr; + } + + W2("Capability \"%s\" not found or not available on your system", name); + return -1; +} + +void caps_check_list(const char *clist, void (*callback)(int)) +{ + char *str = NULL; + char *ptr = NULL; + char *start = NULL; + int nr; + + assert(clist && *clist != '\0'); + str = strdup(clist); + assert(str); + + ptr = str; + start = str; + while (*ptr != '\0') { + if (islower(*ptr) || isdigit(*ptr) || *ptr == '_') { + } else if (*ptr == ',') { + *ptr = '\0'; + nr = caps_find_name(start); + if (nr == -1) + goto errexit; + else if (callback != NULL) + callback(nr); + + start = ptr + 1; + } + ptr++; + } + if (*start != '\0') { + nr = caps_find_name(start); + if (nr == -1) + goto errexit; + else if (callback != NULL) + callback(nr); + } + + free(str); + return; + +errexit: + E2("Error: capability \"%s\" not found", start); + exit(EXIT_FAILURE); +} + +void caps_print(void) +{ + int i; + int elems = SIZEOF(capslist); + int cnt = 0; + unsigned long cap; + int code; + + for (cap=0; cap <= 63; cap++) { + code = prctl(PR_CAPBSET_DROP, cap, 0, 0, 0); + if (code == 0) + cnt++; + } + D("Your kernel supports %d capabilities.", cnt); + + for (i = 0; i < elems; i++) { + D("%d\t- %s", capslist[i].nr, capslist[i].name); + } +} + +void caps_drop_dac_override(int noprofile) +{ + if (getuid() == 0 && !noprofile) { + if (prctl(PR_CAPBSET_DROP, CAP_DAC_OVERRIDE, 0, 0, 0)) { + } else { + D("%s", "Drop CAP_DAC_OVERRIDE"); + } + + if (prctl(PR_CAPBSET_DROP, CAP_DAC_READ_SEARCH, 0, 0, 0)) { + } else { + D("%s", "Drop CAP_DAC_READ_SEARCH"); + } + } +} + +int caps_default_filter(void) +{ + size_t i; + int code; + const char *const capstrs[] = { + "sys_module", "sys_rawio", "sys_boot", + "sys_nice", "sys_tty_config", + "mknod", "sys_admin", "sys_resource", + "sys_time" + }; + + for (i = 0; i < SIZEOF(capstrs); ++i ) { + code = caps_find_name(capstrs[i]); + if (code < 0) + goto errexit; + if (prctl(PR_CAPBSET_DROP, code, 0, 0, 0) < 0) + goto errexit; + } + + return 0; +errexit: + E("%s", "Can not drop capabilities"); + exit(EXIT_FAILURE); +} + +int caps_jail_filter(void) +{ + size_t i; + int code; + const char *const capstrs[] = { +#ifdef CAP_SYSLOG + "syslog", +#endif + "audit_control", "audit_read", "audit_write", + "sys_ptrace", "sys_pacct", "sys_chroot", "sys_nice", + "sys_tty_config" + }; + + for (i = 0; i < SIZEOF(capstrs); ++i) { + code = caps_find_name(capstrs[i]); + if (code < 0) + goto errexit; + if (prctl(PR_CAPBSET_DROP, code, 0, 0, 0) < 0) + goto errexit; + } + + return 0; +errexit: + E("%s", "Can not drop capabilities"); + exit(EXIT_FAILURE); +} + +void caps_drop_all(void) +{ + unsigned long cap; + int code; + + D("%s", "Dropping all capabilities"); + for (cap = 0; cap <= 63; cap++) { + code = prctl(PR_CAPBSET_DROP, cap, 0, 0, 0); + if (code == -1 && errno != EINVAL) { + FATAL("%s", "PR_CAPBSET_DROP"); + } + } +} + + +void caps_set(uint64_t caps) +{ + unsigned long i; + uint64_t mask = 1LLU; + int code; + + D("Set caps filter %llx\n", (unsigned long long) caps); + for (i = 0; i < 64; i++, mask <<= 1) { + if ((mask & caps) == 0) { + code = prctl(PR_CAPBSET_DROP, i, 0, 0, 0); + if (code == -1 && errno != EINVAL) + FATAL("%s", "PR_CAPBSET_DROP"); + } + } +} + +static uint64_t filter; + +static void caps_set_bit(int nr) +{ + uint64_t mask = 1LLU << nr; + filter |= mask; +} + +static void caps_reset_bit(int nr) +{ + uint64_t mask = 1LLU << nr; + filter &= ~mask; +} + +void caps_drop_list(const char *clist) +{ + filter = 0; + filter--; + caps_check_list(clist, caps_reset_bit); + caps_set(filter); +} + +void caps_keep_list(const char *clist) +{ + filter = 0; + caps_check_list(clist, caps_set_bit); + caps_set(filter); +} diff --git a/src/capabilities.h b/src/capabilities.h new file mode 100644 index 0000000..e345804 --- /dev/null +++ b/src/capabilities.h @@ -0,0 +1,25 @@ +#ifndef POTD_CAPABILITIES_H +#define POTD_CAPABILITIES_H 1 + +#include <stdint.h> + + +void caps_check_list(const char *clist, void (*callback)(int)); + +void caps_print(void); + +void caps_drop_dac_override(int noprofile); + +int caps_default_filter(void); + +int caps_jail_filter(void); + +void caps_drop_all(void); + +void caps_set(uint64_t caps); + +void caps_drop_list(const char *clist); + +void caps_keep_list(const char *clist); + +#endif diff --git a/src/forward.c b/src/forward.c new file mode 100644 index 0000000..ec71995 --- /dev/null +++ b/src/forward.c @@ -0,0 +1,172 @@ +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "forward.h" +#include "log.h" + + +int fwd_init_ctx(forward_ctx **ctx) +{ + assert(ctx); + if (!*ctx) + *ctx = (forward_ctx *) malloc(sizeof(**ctx)); + assert(*ctx); + + memset(*ctx, 0, sizeof(**ctx)); + + return 0; +} + +int fwd_setup_client(forward_ctx *ctx, const char *host, const char *port) +{ + int s; + + assert(ctx); + ctx->fwd_type = FT_CLIENT; + + s = socket_init_in(host, port, &ctx->ai); + if (s) { + E_GAIERR(s, "Could not initialise client forward socket"); + return 1; + } + + s = socket_connectaddr_in(&ctx->sock, &ctx->ai, + ctx->host_buf, + ctx->service_buf); + switch (s) { + case -1: + E_STRERR("Connection to forward socket %s:%s", host, port); + break; + case 0: + break; + default: + E_GAIERR(s, "Convert forward socket address to string"); + break; + } + + if (socket_close(&ctx->sock)) { + E_STRERR("Forward socket to %s:%s close", + ctx->host_buf, ctx->service_buf); + return 1; + } + + return s != 0; +} + +int fwd_setup_client_silent(forward_ctx *ctx, const char *host, + const char *port) +{ + int s; + + assert(ctx); + ctx->fwd_type = FT_CLIENT; + + s = socket_init_in(host, port, &ctx->ai); + if (s) { + E_GAIERR(s, "Could not initialise client forward socket"); + return 1; + } + + return 0; +} + +int fwd_setup_server(forward_ctx *ctx, const char *listen_addr, + const char *listen_port) +{ + int s; + struct addrinfo *fwd_addr = NULL; + + assert(ctx); + ctx->fwd_type = FT_SERVER; + + s = socket_init_in(listen_addr, listen_port, &fwd_addr); + if (s) { + E_GAIERR(s, "Initialising server forward socket"); + return 1; + } + if (socket_bind_in(&ctx->sock, &fwd_addr)) { + E_STRERR("Binding forward server socket to %s:%s", + listen_addr, listen_port); + return 1; + } + s = socket_addrtostr_in(&ctx->sock, ctx->host_buf, ctx->service_buf); + if (s) { + E_GAIERR(s, "Convert server forward socket address"); + return 1; + } + + return 0; +} + +int fwd_validate_ctx(const forward_ctx *ctx) +{ + assert(ctx); + assert(ctx->fwd_type == FT_CLIENT || + ctx->fwd_type == FT_SERVER); + assert(ctx->sock.addr_len > 0); + assert(strnlen(ctx->host_buf, NI_MAXHOST) > 0); + assert(strnlen(ctx->service_buf, NI_MAXSERV) > 0); + + return 0; +} + +int fwd_connect_sock(forward_ctx *ctx, psocket *fwd_client) +{ + int s; + psocket *dst; + + assert(ctx); + assert(ctx->fwd_type == FT_CLIENT); + if (fwd_client) { + dst = fwd_client; + socket_clone(&ctx->sock, fwd_client); + } else { + dst = &ctx->sock; + } + + if (ctx->ai) { + s = socket_connectaddr_in(dst, &ctx->ai, + ctx->host_buf, + ctx->service_buf); + switch (s) { + case -1: + E_STRERR("Connection to forward socket with fd %d", + dst->fd); + break; + case 0: + if (ctx->ai) + s = 1; + break; + default: + E_GAIERR(s, "Convert forward socket address to string"); + break; + } + + return s != 0; + } else { + return socket_reconnect_in(dst); + } +} + +int fwd_listen_sock(forward_ctx *ctx, psocket *fwd_server) +{ + psocket *dst; + + assert(ctx); + assert(ctx->fwd_type == FT_SERVER); + if (fwd_server) { + dst = fwd_server; + socket_clone(&ctx->sock, fwd_server); + } else { + dst = &ctx->sock; + } + + if (socket_listen_in(dst)) { + E_STRERR("Could not listen on forward server socket on %s:%s", + ctx->host_buf, ctx->service_buf); + return 1; + } + + return 0; +} diff --git a/src/forward.h b/src/forward.h new file mode 100644 index 0000000..3d2a89a --- /dev/null +++ b/src/forward.h @@ -0,0 +1,35 @@ +#ifndef POTD_FORWARD_H +#define POTD_FORWARD_H 1 + +#include "socket.h" + +typedef enum forward_type { + FT_NONE = 0, FT_CLIENT, FT_SERVER +} forward_type; + +typedef struct forward_ctx { + forward_type fwd_type; + psocket sock; + char host_buf[NI_MAXHOST], service_buf[NI_MAXSERV]; + struct addrinfo *ai; + void *data; +} forward_ctx; + + +int fwd_init_ctx(forward_ctx **ctx); + +int fwd_setup_client(forward_ctx *ctx, const char *host, const char *port); + +int fwd_setup_client_silent(forward_ctx *ctx, const char *host, + const char *port); + +int fwd_setup_server(forward_ctx *ctx, const char *listen_addr, + const char *listen_port); + +int fwd_validate_ctx(const forward_ctx *ctx); + +int fwd_connect_sock(forward_ctx *ctx, psocket *fwd_client); + +int fwd_listen_sock(forward_ctx *ctx, psocket *fwd_server); + +#endif diff --git a/src/jail.c b/src/jail.c new file mode 100644 index 0000000..e94d069 --- /dev/null +++ b/src/jail.c @@ -0,0 +1,553 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sched.h> +#include <signal.h> +#include <pty.h> +#include <sys/signalfd.h> +#include <sys/wait.h> +#include <sys/prctl.h> +#include <sys/stat.h> +#include <assert.h> + +#include "jail.h" +#include "socket.h" +#include "pseccomp.h" +#include "capabilities.h" +#include "utils.h" +#include "log.h" +#include "options.h" + +typedef struct prisoner_process { + psocket client_psock; + char host_buf[NI_MAXHOST], service_buf[NI_MAXSERV]; + char *newroot; +} prisoner_process; + +typedef struct server_event { + const jail_ctx **jail_ctx; + const size_t siz; +} server_event; + +typedef struct client_event { + psocket *client_sock; + char *host_buf; + char *service_buf; + int tty_fd; + int signal_fd; + char tty_logbuf[BUFSIZ]; + size_t off_logbuf; + char *tty_logbuf_escaped; + size_t tty_logbuf_size; +} client_event; + +static int jail_mainloop(event_ctx **ev_ctx, const jail_ctx *ctx[], size_t siz) + __attribute__((noreturn)); +static int jail_accept_client(event_ctx *ev_ctx, int fd, void *user_data); +static int jail_childfn(prisoner_process *ctx) + __attribute__((noreturn)); +static int jail_socket_tty(prisoner_process *ctx, int tty_fd); +static int jail_socket_tty_io(event_ctx *ev_ctx, int src_fd, void *user_data); +static int jail_log_input(event_ctx *ev_ctx, int src_fd, int dst_fd, + char *buf, size_t siz, void *user_data); + + +void jail_init_ctx(jail_ctx **ctx, size_t stacksize) +{ + assert(ctx); + if (stacksize > MAX_STACKSIZE || + stacksize < MIN_STACKSIZE) + { + stacksize = MAX_STACKSIZE; + } + if (!*ctx) + *ctx = (jail_ctx *) malloc(sizeof(**ctx)); + assert(*ctx); + + memset(*ctx, 0, sizeof(**ctx)); + (*ctx)->stacksize = stacksize; + (*ctx)->stack_ptr = calloc(1, (*ctx)->stacksize); + (*ctx)->stack_beg = + (unsigned char *) (*ctx)->stack_ptr + + (*ctx)->stacksize; +} + +int jail_setup(jail_ctx *ctx, + const char *listen_addr, const char *listen_port) +{ + assert(ctx); + assert(listen_addr || listen_port); + + D2("Try to listen on %s:%s", + (listen_addr ? listen_addr : "*"), listen_port); + if (fwd_setup_server(&ctx->fwd_ctx, listen_addr, listen_port)) + return 1; + if (fwd_listen_sock(&ctx->fwd_ctx, NULL)) + return 1; + + return 0; +} + +int jail_validate_ctx(const jail_ctx *ctx) +{ + assert(ctx); + assert(ctx->fwd_ctx.sock.addr_len > 0); + assert(ctx->stack_ptr); + assert(ctx->newroot); + + if (access(ctx->newroot, R_OK|X_OK)) { + E_STRERR("new root directory access to '%s'", ctx->newroot); + return 1; + } + + return 0; +} + +int jail_setup_event(jail_ctx *ctx[], size_t siz, event_ctx **ev_ctx) +{ + int s; + + assert(ctx); + assert(siz > 0 && siz < POTD_MAXFD); + + event_init(ev_ctx); + if (event_setup(*ev_ctx)) + return 1; + + for (size_t i = 0; i < siz; ++i) { + if (event_add_sock(*ev_ctx, &ctx[i]->fwd_ctx.sock)) { + return 1; + } + + s = socket_addrtostr_in(&ctx[i]->fwd_ctx.sock, + ctx[i]->host_buf, ctx[i]->service_buf); + if (s) { + E_GAIERR(s, "Convert socket address to string"); + return -2; + } + N("Jail service listening on %s:%s", + ctx[i]->host_buf, ctx[i]->service_buf); + } + + return 0; +} + +pid_t jail_daemonize(event_ctx **ev_ctx, jail_ctx *ctx[], size_t siz) +{ + pid_t p; + int s; + size_t i; + + assert(ev_ctx && *ev_ctx && ctx); + assert(siz > 0 && siz <= POTD_MAXFD); + + for (i = 0; i < siz; ++i) { + assert(ctx[i]); + s = socket_addrtostr_in(&ctx[i]->fwd_ctx.sock, + ctx[i]->host_buf, ctx[i]->service_buf); + if (s) { + E_GAIERR(s, "Could not initialise jail daemon socket"); + return 1; + } + } + + p = fork(); + switch (p) { + case -1: + E_STRERR("%s", "Jail daemonize"); + return -1; + case 0: + caps_jail_filter(); + jail_mainloop(ev_ctx, (const jail_ctx **) ctx, siz); + } + D2("Jail daemon pid: %d", p); + + event_free(ev_ctx); + for (i = 0; i < siz; ++i) + socket_close(&ctx[i]->fwd_ctx.sock); + + return p; +} + +static int jail_mainloop(event_ctx **ev_ctx, const jail_ctx *ctx[], size_t siz) +{ + int rc; + server_event ev_jail = { ctx, siz }; + + set_procname("[potd] jail"); + assert( set_child_sighandler() == 0 ); + + D2("%s", "Setup cgroups"); + if (cgroups_set()) + FATAL("%s", "Setup cgroups"); + + rc = event_loop(*ev_ctx, jail_accept_client, &ev_jail); + event_free(ev_ctx); + + exit(rc); +} + +static int jail_accept_client(event_ctx *ev_ctx, int fd, void *user_data) +{ + size_t i, rc = 0; + int s; + pid_t prisoner_pid; + server_event *ev_jail = (server_event *) user_data; + static prisoner_process *args; + const jail_ctx *jail_ctx; + + (void) ev_ctx; + assert(ev_jail); + + for (i = 0; i < ev_jail->siz; ++i) { + jail_ctx = ev_jail->jail_ctx[i]; + if (jail_ctx->fwd_ctx.sock.fd == fd) { + args = (prisoner_process *) calloc(1, sizeof(*args)); + assert(args); + args->newroot = jail_ctx->newroot; + + if (socket_accept_in(&jail_ctx->fwd_ctx.sock, + &args->client_psock)) + { + E_STRERR("Could not accept client connection for fd %d", + args->client_psock.fd); + goto error; + } + + s = socket_addrtostr_in(&args->client_psock, + args->host_buf, args->service_buf); + if (s) { + E_GAIERR(s, "Convert socket address to string"); + goto error; + } + N2("New connection from %s:%s to %s:%s: %d", + args->host_buf, args->service_buf, + jail_ctx->host_buf, jail_ctx->service_buf, + args->client_psock.fd); + + prisoner_pid = fork(); + switch (prisoner_pid) { + case -1: + W_STRERR("%s", "Jail client fork"); + goto error; + case 0: + jail_childfn(args); + } + N2("Jail prisoner pid %d", prisoner_pid); + + rc = 1; +error: + socket_close(&args->client_psock); + free(args); + args = NULL; + return rc; + } + } + + return rc; +} + +static int jail_childfn(prisoner_process *ctx) +{ + const char *path_dev = "/dev"; + const char *path_devpts = "/dev/pts"; + const char *path_proc = "/proc"; + const char *path_shell = "/bin/sh"; + int i, s, master_fd; + int unshare_flags = CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWIPC| + CLONE_NEWNS/*|CLONE_NEWUSER*/; + //unsigned int ug_map[3] = { 0, 10000, 65535 }; + pid_t self_pid, child_pid; + pseccomp_ctx *psc = NULL; + + assert(ctx); + self_pid = getpid(); + set_procname("[potd] jail-client"); + if (prctl(PR_SET_PDEATHSIG, SIGTERM) != 0) + FATAL("Jail child prctl for pid %d", self_pid); + + if (!ctx->newroot) + FATAL("New root set for pid %d", self_pid); + + if (clearenv()) + FATAL("Clearing ENV for pid %d", self_pid); + + D2("Activating cgroups for pid %d", self_pid); + if (cgroups_activate()) + FATAL("Activating cgroups for pid %d", self_pid); + + D2("Setup network namespace for pid %d", self_pid); + if (setup_network_namespace("default")) + if (switch_network_namespace("default")) + FATAL("Setup network namespace for pid %d", self_pid); + + caps_drop_dac_override(0); + //caps_drop_all(); + + D2("Unshare prisoner %d", self_pid); + if (unshare(unshare_flags)) + FATAL("Unshare prisoner %d", self_pid); + + D2("Safe change root to: '%s'", ctx->newroot); + if (safe_chroot(ctx->newroot)) + FATAL("Safe jail chroot to '%s' failed", ctx->newroot); + + D2("Mounting rootfs to '%s'", ctx->newroot); + mount_root(); + + D2("Checking Shell '%s%s'", ctx->newroot, path_shell); + if (access(path_shell, R_OK|X_OK)) + FATAL("Shell '%s%s' is not accessible", ctx->newroot, path_shell); + + D2("Mounting devtmpfs to '%s%s'", ctx->newroot, path_dev); + s = mkdir(path_dev, S_IRUSR|S_IWUSR|S_IXUSR| + S_IRGRP|S_IXGRP| + S_IROTH|S_IXOTH); + if (s && errno != EEXIST) + FATAL("Create directory '%s'", path_dev); + if (!dir_is_mountpoint(path_dev) && mount_dev(path_dev)) + FATAL("Mount devtmpfs to '%s%s'", ctx->newroot, path_dev); + + D2("Mounting devpts to '%s%s'", ctx->newroot, path_devpts); + s = mkdir(path_devpts, S_IRUSR|S_IWUSR|S_IXUSR| + S_IRGRP|S_IXGRP| + S_IROTH|S_IXOTH); + if (s && errno != EEXIST) + FATAL("Create directory '%s'", path_devpts); + if (!dir_is_mountpoint(path_devpts) && mount_pts(path_devpts)) + FATAL("Mount devpts to '%s%s'", ctx->newroot, path_devpts); + + D2("Mounting proc to '%s%s'", ctx->newroot, path_proc); + s = mkdir(path_proc, S_IRUSR|S_IWUSR|S_IXUSR| + S_IRGRP|S_IXGRP| + S_IROTH|S_IXOTH); + if (s && errno != EEXIST) + FATAL("Create directory '%s'", path_proc); + + D2("Creating device files in '%s%s'", ctx->newroot, path_dev); + if (create_device_files(path_dev)) + FATAL("Device file creation failed for rootfs '%s%s'", + ctx->newroot, path_dev); + + D2("Forking a new pty process for " + "parent %d", self_pid); + child_pid = forkpty(&master_fd, NULL, NULL, NULL); + switch (child_pid) { + case -1: + FATAL("Forking a new process for the slave tty from " + "parent pty with pid %d", + self_pid); + break; + case 0: + if (mount_proc(path_proc)) + exit(EXIT_FAILURE); + socket_set_ifaddr(&ctx->client_psock, "lo", "127.0.0.1", "255.0.0.0"); +/* + if (update_setgroups_self(0)) + exit(EXIT_FAILURE); + if (update_guid_map(getpid(), ug_map, 0)) + exit(EXIT_FAILURE); + if (update_guid_map(getpid(), ug_map, 1)) + exit(EXIT_FAILURE); +*/ + if (close_fds_except(0, 1, 2, -1)) + exit(EXIT_FAILURE); + + if (prctl(PR_SET_PDEATHSIG, SIGKILL) != 0) + exit(EXIT_FAILURE); + + printf("%s", + " _______ ________ __\n" + " | |.-----.-----.-----.| | | |.----.| |_\n" + " | - || _ | -__| || | | || _|| _|\n" + " |_______|| __|_____|__|__||________||__| |____|\n" + " |__| W I R E L E S S F R E E D O M\n" + " -----------------------------------------------------\n" + " ATTITUDE ADJUSTMENT\n" + " -----------------------------------------------------\n" + " * 1/4 oz Vodka Pour all ingredients into mixing\n" + " * 1/4 oz Gin tin with ice, strain into glass.\n" + " * 1/4 oz Amaretto\n" + " * 1/4 oz Triple sec\n" + " * 1/4 oz Peach schnapps\n" + " * 1/4 oz Sour mix\n" + " * 1 splash Cranberry juice\n" + " -----------------------------------------------------\n" + ); + + pseccomp_set_immutable(); + pseccomp_init(&psc, + (getopt_used(OPT_SECCOMP_MINIMAL) ? PS_MINIMUM : 0)); + if (pseccomp_jail_rules(psc)) + FATAL("%s", "SECCOMP: adding jail rules"); + pseccomp_free(&psc); + + sethostname("openwrt", SIZEOF("openwrt")); + if (execl(path_shell, path_shell, (char *) NULL)) + exit(EXIT_FAILURE); + break; + default: + if (set_fd_nonblock(master_fd)) { + E_STRERR("Pty master fd nonblock for prisoner pid %d", + child_pid); + goto finalise; + } + + N("Socket to tty I/O for prisoner pid %d", + child_pid); + if (jail_socket_tty(ctx, master_fd)) + E_STRERR("Socket to tty I/O for prisoner pid %d", + child_pid); + N("Killing prisoner pid %d", child_pid); + + kill(child_pid, SIGTERM); + i = 10; + while (i > 0 && waitpid(child_pid, &s, WNOHANG) > 0) { + if (WIFEXITED(s)) + break; + usleep(250000); + i--; + } + kill(child_pid, SIGKILL); + } + +finalise: + close(master_fd); + exit(EXIT_FAILURE); +} + +static int jail_socket_tty(prisoner_process *ctx, int tty_fd) +{ + static client_event ev_cli = {NULL, NULL, NULL, -1, -1, {0}, 0, 0, 0}; + int s, rc = 1; + event_ctx *ev_ctx = NULL; + sigset_t mask; + + assert(ctx); + ev_cli.tty_fd = tty_fd; + + event_init(&ev_ctx); + if (event_setup(ev_ctx)) { + E_STRERR("Jail event context creation for jail tty fd %d", + tty_fd); + goto finish; + } + s = socket_nonblock(&ctx->client_psock); + if (s) { + E_STRERR("Socket non blocking mode to client %s:%s fd %d", + ctx->host_buf, ctx->service_buf, ctx->client_psock.fd); + goto finish; + } + if (event_add_sock(ev_ctx, &ctx->client_psock)) { + E_STRERR("Jail event context for socket %s:%s", + ctx->host_buf, ctx->service_buf); + goto finish; + } + if (event_add_fd(ev_ctx, tty_fd)) { + E_STRERR("Jail event context for tty fd %d", + tty_fd); + goto finish; + } + + sigemptyset(&mask); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGINT); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) { + E_STRERR("%s", "SIGTERM block"); + goto finish; + } + ev_cli.signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (ev_cli.signal_fd < 0) { + E_STRERR("%s", "SIGNAL fd"); + goto finish; + } + if (event_add_fd(ev_ctx, ev_cli.signal_fd)) { + E_STRERR("Jail SIGNAL fd %d", ev_cli.signal_fd); + goto finish; + } + + ev_cli.client_sock = &ctx->client_psock; + ev_cli.host_buf = &ctx->host_buf[0]; + ev_cli.service_buf = &ctx->service_buf[0]; + rc = event_loop(ev_ctx, jail_socket_tty_io, &ev_cli); +finish: + close(ev_cli.signal_fd); + event_free(&ev_ctx); + return rc; +} + +static int +jail_socket_tty_io(event_ctx *ev_ctx, int src_fd, void *user_data) +{ + int dest_fd; + client_event *ev_cli = (client_event *) user_data; + forward_state fwd_state; + + (void) ev_ctx; + (void) src_fd; + (void) ev_cli; + + if (src_fd == ev_cli->client_sock->fd) { + dest_fd = ev_cli->tty_fd; + } else if (src_fd == ev_cli->tty_fd) { + dest_fd = ev_cli->client_sock->fd; + } else if (src_fd == ev_cli->signal_fd) { + ev_ctx->active = 0; + return 0; + } else return 0; + + fwd_state = event_forward_connection(ev_ctx, dest_fd, jail_log_input, + user_data); + + switch (fwd_state) { + case CON_IN_TERMINATED: + case CON_OUT_TERMINATED: + ev_ctx->active = 0; + case CON_OK: + return 1; + case CON_IN_ERROR: + case CON_OUT_ERROR: + ev_ctx->active = 0; + return 0; + } + + return 1; +} + +static int jail_log_input(event_ctx *ev_ctx, int src_fd, int dst_fd, + char *buf, size_t siz, void *user_data) +{ + size_t idx = 0, slen, ssiz = siz; + client_event *ev_cli = (client_event *) user_data; + + (void) ev_ctx; + (void) src_fd; + + if (ev_cli->tty_fd == dst_fd) { + while (ssiz > 0) { + slen = MIN(sizeof(ev_cli->tty_logbuf) - ev_cli->off_logbuf, ssiz); + if (slen == 0) { + escape_ascii_string(ev_cli->tty_logbuf, ev_cli->off_logbuf, + &ev_cli->tty_logbuf_escaped, &ev_cli->tty_logbuf_size); + C("[%s:%s] %s", ev_cli->host_buf, ev_cli->service_buf, + ev_cli->tty_logbuf_escaped); + ev_cli->off_logbuf = 0; + ev_cli->tty_logbuf[0] = 0; + continue; + } + strncat(ev_cli->tty_logbuf, buf+idx, slen); + ssiz -= slen; + idx += slen; + ev_cli->off_logbuf += slen; + } + if (buf[siz-1] == '\r' || buf[siz-1] == '\n') { + escape_ascii_string(ev_cli->tty_logbuf, ev_cli->off_logbuf, + &ev_cli->tty_logbuf_escaped, &ev_cli->tty_logbuf_size); + C("[%s:%s] %s", ev_cli->host_buf, ev_cli->service_buf, + ev_cli->tty_logbuf_escaped); + ev_cli->off_logbuf = 0; + ev_cli->tty_logbuf[0] = 0; + } + } + + return 0; +} diff --git a/src/jail.h b/src/jail.h new file mode 100644 index 0000000..ef7d61a --- /dev/null +++ b/src/jail.h @@ -0,0 +1,34 @@ +#ifndef POTD_JAIL_H +#define POTD_JAIL_H 1 + +#include <sys/types.h> +#include <unistd.h> + +#include "forward.h" +#include "pevent.h" + +#define MIN_STACKSIZE 2048 +#define MAX_STACKSIZE BUFSIZ + +typedef struct jail_ctx { + forward_ctx fwd_ctx; + char host_buf[NI_MAXHOST], service_buf[NI_MAXSERV]; + size_t stacksize; + void *stack_ptr; + void *stack_beg; + char *newroot; +} jail_ctx; + + +void jail_init_ctx(jail_ctx **ctx, size_t stacksize); + +int jail_setup(jail_ctx *ctx, + const char *listen_addr, const char *listen_port); + +int jail_validate_ctx(const jail_ctx *jail_ctx); + +int jail_setup_event(jail_ctx *ctx[], size_t siz, event_ctx **ev_ctx); + +pid_t jail_daemonize(event_ctx **ev_ctx, jail_ctx *ctx[], size_t siz); + +#endif @@ -2,7 +2,9 @@ #include "log.h" +log_priority log_prio = NOTICE; log_open_cb log_open = NULL; log_close_cb log_close = NULL; log_fmt_cb log_fmt = NULL; log_fmtex_cb log_fmtex = NULL; +log_fmtexerr_cb log_fmtexerr = NULL; @@ -5,11 +5,12 @@ #include <string.h> #include <errno.h> -#define LOGMSG_MAXLEN 255 -#define LOG_SET_FUNCS(open_cb, close_cb, fmt_cb, fmtex_cb) \ +#define LOGMSG_MAXLEN BUFSIZ +#define LOG_SET_FUNCS(open_cb, close_cb, fmt_cb, fmtex_cb, fmtexerr_cb) \ { \ log_open = open_cb; log_close = close_cb; \ log_fmt = fmt_cb; log_fmtex = fmtex_cb; \ + log_fmtexerr = fmtexerr_cb; \ } #define LOG_SET_FUNCS_VA(...) LOG_SET_FUNCS(__VA_ARGS__) #define D(fmt, ...) log_fmt(DEBUG, fmt, __VA_ARGS__) @@ -20,25 +21,50 @@ #define N2(fmt, ...) log_fmtex(NOTICE, __FILE__, __LINE__, fmt, __VA_ARGS__) #define W2(fmt, ...) log_fmtex(WARNING, __FILE__, __LINE__, fmt, __VA_ARGS__) #define E2(fmt, ...) log_fmtex(ERROR, __FILE__, __LINE__, fmt, __VA_ARGS__) -#define W_STRERR(msg) { if (errno) W2("%s failed: %s", msg, strerror(errno)); } -#define E_STRERR(msg) { if (errno) E2("%s failed: %s", msg, strerror(errno)); } +#define W_STRERR(fmt, ...) log_fmtexerr(WARNING, __FILE__, __LINE__, fmt, \ + __VA_ARGS__) +#define E_STRERR(fmt, ...) log_fmtexerr(ERROR, __FILE__, __LINE__, fmt, \ + __VA_ARGS__) +#define E_GAIERR(ret, msg) { if (ret) { E2("%s failed: %s", msg, gai_strerror(ret)); } } +#define FATAL(fmt, ...) { E_STRERR(fmt, __VA_ARGS__); abort(); } #define ABORT_ON_FATAL(expr, msg) \ - { errno = 0; if (expr) { E_STRERR(msg); abort(); } } + { errno = 0; long rv = (long) expr; \ + if (rv) { \ + /* \ + E_STRERR("`%s` returned %ld. %s", \ + #expr, rv, msg); abort(); \ + */ \ + E_STRERR("%s", msg); \ + kill(0, SIGABRT); \ + abort(); \ + } \ + } +#define C(fmt, ...) log_fmt(CMD, fmt, __VA_ARGS__) typedef enum log_priority { - DEBUG = 0, NOTICE, WARNING, ERROR + DEBUG = 0, NOTICE, WARNING, ERROR, CMD } log_priority; typedef int (*log_open_cb) (void); typedef void (*log_close_cb) (void); -typedef void (*log_fmt_cb) (log_priority prio, const char *fmt, ...); + +typedef void (*log_fmt_cb) (log_priority prio, const char *fmt, ...) + __attribute__ ((format (printf, 2, 3))); + typedef void (*log_fmtex_cb) (log_priority prio, const char *srcfile, - size_t line, const char *fmt, ...); + size_t line, const char *fmt, ...) + __attribute__ ((format (printf, 4, 5))); + +typedef void (*log_fmtexerr_cb) (log_priority prio, const char *srcfile, + size_t line, const char *fmt, ...) + __attribute__ ((format (printf, 4, 5))); +extern log_priority log_prio; extern log_open_cb log_open; extern log_close_cb log_close; extern log_fmt_cb log_fmt; extern log_fmtex_cb log_fmtex; +extern log_fmtexerr_cb log_fmtexerr; #endif diff --git a/src/log_colored.c b/src/log_colored.c index a8a26e3..6fa4e7c 100644 --- a/src/log_colored.c +++ b/src/log_colored.c @@ -2,6 +2,8 @@ #include <stdlib.h> #include <stdarg.h> #include <string.h> +#include <sys/types.h> +#include <unistd.h> #include <assert.h> #include "log_colored.h" @@ -29,26 +31,33 @@ void log_close_colored(void) void log_fmt_colored(log_priority prio, const char *fmt, ...) { + pid_t my_pid; char out[LOGMSG_MAXLEN+1] = {0}; va_list arglist; + if (prio < log_prio) + return; assert(fmt); va_start(arglist, fmt); assert( vsnprintf(&out[0], LOGMSG_MAXLEN, fmt, arglist) >= 0 ); va_end(arglist); + my_pid = getpid(); switch (prio) { case DEBUG: - printf("[DEBUG] %s\n", out); + printf("[DEBUG] [%d] %s\n", my_pid, out); break; case NOTICE: - printf("[" GRN "NOTICE" RESET "] %s\n", out); + printf("[" GRN "NOTICE" RESET "] [%d] %s\n", my_pid, out); break; case WARNING: - printf("[" YEL "WARNING" RESET "] %s\n", out); + printf("[" YEL "WARNING" RESET "][%d] %s\n", my_pid, out); break; case ERROR: - printf("[" RED "ERROR" RESET "] %s\n", out); + printf("[" RED "ERROR" RESET "] [%d] %s\n", my_pid, out); + break; + case CMD: + printf("[" BLUE "CMD" RESET "] [%d] %s\n", my_pid, out); break; } } @@ -56,26 +65,96 @@ void log_fmt_colored(log_priority prio, const char *fmt, ...) void log_fmtex_colored(log_priority prio, const char *srcfile, size_t line, const char *fmt, ...) { + pid_t my_pid; + char out[LOGMSG_MAXLEN+1] = {0}; + va_list arglist; + + if (prio < log_prio) + return; + assert(fmt); + va_start(arglist, fmt); + assert( vsnprintf(&out[0], LOGMSG_MAXLEN, fmt, arglist) >= 0 ); + va_end(arglist); + + my_pid = getpid(); + switch (prio) { + case DEBUG: + printf("[DEBUG] [%d] %s.%zu: %s\n", my_pid, srcfile, line, out); + break; + case NOTICE: + printf("[" GRN "NOTICE" RESET "] [%d] %s.%zu: %s\n", + my_pid, srcfile, line, out); + break; + case WARNING: + printf("[" YEL "WARNING" RESET "][%d] %s.%zu: %s\n", + my_pid, srcfile, line, out); + break; + case ERROR: + printf("[" RED "ERROR" RESET "] [%d] %s.%zu: %s\n", + my_pid, srcfile, line, out); + break; + case CMD: + break; + } +} + +void log_fmtexerr_colored(log_priority prio, const char *srcfile, + size_t line, const char *fmt, ...) +{ + pid_t my_pid; + int saved_errno = errno; char out[LOGMSG_MAXLEN+1] = {0}; va_list arglist; + if (prio < log_prio) + return; assert(fmt); va_start(arglist, fmt); assert( vsnprintf(&out[0], LOGMSG_MAXLEN, fmt, arglist) >= 0 ); va_end(arglist); + my_pid = getpid(); switch (prio) { case DEBUG: - printf("[DEBUG] %s.%lu: %s\n", srcfile, line, out); + if (saved_errno) + printf("[DEBUG] [%d] %s.%zu: %s failed: %s\n", + my_pid, srcfile, line, out, + strerror(saved_errno)); + else + printf("[DEBUG] [%d] %s.%zu: %s failed\n", + my_pid, srcfile, line, out); break; case NOTICE: - printf("[" GRN "NOTICE" RESET "] %s.%lu: %s\n", srcfile, line, out); + if (saved_errno) + printf("[" GRN "NOTICE" RESET "] [%d] %s.%zu: %s failed: %s\n", + my_pid, srcfile, + line, out, strerror(saved_errno)); + else + printf("[" GRN "NOTICE" RESET "] [%d] %s.%zu: %s failed\n", + my_pid, srcfile, + line, out); break; case WARNING: - printf("[" YEL "WARNING" RESET "] %s.%lu: %s\n", srcfile, line, out); + if (saved_errno) + printf("[" YEL "WARNING" RESET "][%d] %s.%zu: %s failed: %s\n", + my_pid, srcfile, + line, out, strerror(saved_errno)); + else + printf("[" YEL "WARNING" RESET "][%d] %s.%zu: %s failed\n", + my_pid, srcfile, + line, out); break; case ERROR: - printf("[" RED "ERROR" RESET "] %s.%lu: %s\n", srcfile, line, out); + if (saved_errno) + printf("[" RED "ERROR" RESET "] [%d] %s.%zu: %s failed: %s\n", + my_pid, srcfile, + line, out, strerror(saved_errno)); + else + printf("[" RED "ERROR" RESET "] [%d] %s.%zu: %s failed\n", + my_pid, srcfile, + line, out); + break; + case CMD: break; } } diff --git a/src/log_colored.h b/src/log_colored.h index c7c0014..b372c9b 100644 --- a/src/log_colored.h +++ b/src/log_colored.h @@ -1,5 +1,5 @@ -#ifndef POTD_LOG_COLORED -#define POTD_LOG_COLORED 1 +#ifndef POTD_LOG_COLORED_H +#define POTD_LOG_COLORED_H 1 #include "log.h" @@ -8,9 +8,10 @@ #define GRN "\x1B[32;1m" #define YEL "\x1B[33;1m" #define RED "\x1B[31;1;5m" +#define BLUE "\x1B[34;1;1m" /* LOG_SET_FUNCS comfort */ #define LOG_COLORED_FUNCS log_open_colored, log_close_colored, \ - log_fmt_colored, log_fmtex_colored + log_fmt_colored, log_fmtex_colored, log_fmtexerr_colored int log_open_colored(void); @@ -22,4 +23,7 @@ void log_fmt_colored(log_priority prio, const char *fmt, ...); void log_fmtex_colored(log_priority prio, const char *srcfile, size_t line, const char *fmt, ...); +void log_fmtexerr_colored(log_priority prio, const char *srcfile, + size_t line, const char *fmt, ...); + #endif diff --git a/src/log_file.c b/src/log_file.c new file mode 100644 index 0000000..c9c9f12 --- /dev/null +++ b/src/log_file.c @@ -0,0 +1,171 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdarg.h> +#include <fcntl.h> +#include <assert.h> + +#include "log_file.h" + +char *log_file = NULL; +static FILE *flog = NULL; + + +int log_open_file(void) +{ + if (!log_file) { + fprintf(stderr, "%s\n", "The path to the logfile was not set."); + return 1; + } + + flog = fopen(log_file, "a+"); + if (!flog) { + fprintf(stderr, "Could not open '%s' for writing: %s\n", + log_file, strerror(errno)); + return 1; + } + + if (setvbuf(flog, NULL, _IOLBF, BUFSIZ)) { + log_close_file(); + return 1; + } + + return 0; +} + +void log_close_file(void) +{ + fclose(flog); + flog = NULL; +} + +void log_fmt_file(log_priority prio, const char *fmt, ...) +{ + pid_t my_pid; + char out[LOGMSG_MAXLEN+1] = {0}; + va_list arglist; + + if (prio < log_prio) + return; + assert(fmt); + va_start(arglist, fmt); + assert( vsnprintf(&out[0], LOGMSG_MAXLEN, fmt, arglist) >= 0 ); + va_end(arglist); + + my_pid = getpid(); + switch (prio) { + case DEBUG: + fprintf(flog, "[DEBUG] [%d] %s\n", my_pid, out); + break; + case NOTICE: + fprintf(flog, "[NOTICE] [%d] %s\n", my_pid, out); + break; + case WARNING: + fprintf(flog, "[WARNING][%d] %s\n", my_pid, out); + break; + case ERROR: + fprintf(flog, "[ERROR] [%d] %s\n", my_pid, out); + break; + case CMD: + fprintf(flog, "[CMD] [%d] %s\n", my_pid, out); + break; + } +} + +void log_fmtex_file(log_priority prio, const char *srcfile, + size_t line, const char *fmt, ...) +{ + pid_t my_pid; + char out[LOGMSG_MAXLEN+1] = {0}; + va_list arglist; + + if (prio < log_prio) + return; + assert(fmt); + va_start(arglist, fmt); + assert( vsnprintf(&out[0], LOGMSG_MAXLEN, fmt, arglist) >= 0 ); + va_end(arglist); + + my_pid = getpid(); + switch (prio) { + case DEBUG: + fprintf(flog, "[DEBUG] [%d] %s.%zu: %s\n", my_pid, srcfile, + line, out); + break; + case NOTICE: + fprintf(flog, "[NOTICE] [%d] %s.%zu: %s\n", + my_pid, srcfile, line, out); + break; + case WARNING: + fprintf(flog, "[WARNING][%d] %s.%zu: %s\n", + my_pid, srcfile, line, out); + break; + case ERROR: + fprintf(flog, "[ERROR] [%d] %s.%zu: %s\n", + my_pid, srcfile, line, out); + break; + case CMD: + break; + } +} + +void log_fmtexerr_file(log_priority prio, const char *srcfile, + size_t line, const char *fmt, ...) +{ + pid_t my_pid; + int saved_errno = errno; + char out[LOGMSG_MAXLEN+1] = {0}; + va_list arglist; + + if (prio < log_prio) + return; + assert(fmt); + va_start(arglist, fmt); + assert( vsnprintf(&out[0], LOGMSG_MAXLEN, fmt, arglist) >= 0 ); + va_end(arglist); + + my_pid = getpid(); + switch (prio) { + case DEBUG: + if (saved_errno) + fprintf(flog, "[DEBUG] [%d] %s.%zu: %s failed: %s\n", + my_pid, srcfile, line, out, + strerror(saved_errno)); + else + fprintf(flog, "[DEBUG] [%d] %s.%zu: %s failed\n", + my_pid, srcfile, line, out); + break; + case NOTICE: + if (saved_errno) + fprintf(flog, "[NOTICE] [%d] %s.%zu: %s failed: %s\n", + my_pid, srcfile, + line, out, strerror(saved_errno)); + else + fprintf(flog, "[NOTICE] [%d] %s.%zu: %s failed\n", + my_pid, srcfile, + line, out); + break; + case WARNING: + if (saved_errno) + fprintf(flog, "[WARNING][%d] %s.%zu: %s failed: %s\n", + my_pid, srcfile, + line, out, strerror(saved_errno)); + else + fprintf(flog, "[WARNING][%d] %s.%zu: %s failed\n", + my_pid, srcfile, + line, out); + break; + case ERROR: + if (saved_errno) + fprintf(flog, "[ERROR] [%d] %s.%zu: %s failed: %s\n", + my_pid, srcfile, + line, out, strerror(saved_errno)); + else + fprintf(flog, "[ERROR] [%d] %s.%zu: %s failed\n", + my_pid, srcfile, + line, out); + break; + case CMD: + break; + } +} diff --git a/src/log_file.h b/src/log_file.h new file mode 100644 index 0000000..e119f5d --- /dev/null +++ b/src/log_file.h @@ -0,0 +1,24 @@ +#ifndef POTD_LOG_FILE_H +#define POTD_LOG_FILE_H 1 + +#include "log.h" + +#define LOG_FILE_FUNCS log_open_file, log_close_file, \ + log_fmt_file, log_fmtex_file, log_fmtexerr_file + +extern char *log_file; + + +int log_open_file(void); + +void log_close_file(void); + +void log_fmt_file(log_priority prio, const char *fmt, ...); + +void log_fmtex_file(log_priority prio, const char *srcfile, + size_t line, const char *fmt, ...); + +void log_fmtexerr_file(log_priority prio, const char *srcfile, + size_t line, const char *fmt, ...); + +#endif @@ -1,35 +1,376 @@ -#include "log.h" -#include "log_colored.h" -#include "server.h" -#include "server_ssh.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif +#include <stdio.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "pseccomp.h" +#include "capabilities.h" +#include "log.h" +#include "log_colored.h" +#include "log_file.h" +#include "options.h" +#include "utils.h" +#include "redirector.h" +#include "protocol_ssh.h" +#include "forward.h" +#include "jail.h" + +static size_t jl_siz = 0; +static jail_ctx *jl_ctx = NULL; +static pid_t jl_pid = -1; + +static size_t prt_siz = 0; +static protocol_ctx *prt_ctx = NULL; + +static size_t rdr_siz = 0; +static redirector_ctx *rdr_ctx = NULL; +static pid_t rdr_pid = -1; + +static void jail_preinit(char jail_hosts[][2][NI_MAXHOST], + char jail_ports[][2][NI_MAXSERV], + jail_ctx *ctx[], const size_t siz); +static pid_t jail_init(jail_ctx *ctx[], const size_t siz); +static void ssh_protocol_preinit(char proto_hosts[][2][NI_MAXHOST], + char proto_ports[][2][NI_MAXSERV], + protocol_ctx *ctx[], + const size_t siz); +static void ssh_protocol_init(protocol_ctx *ctx[], const size_t siz); +static void rdr_preinit(char rdr_hosts[][2][NI_MAXHOST], + char rdr_ports[][2][NI_MAXSERV], + redirector_ctx *ctx[], + const size_t siz); +static pid_t rdr_init(redirector_ctx *ctx[], const size_t siz); + +static size_t validate_hostport_option(opt_name on, int process_forward); +static int process_options(int validate_only); + + +static void jail_preinit(char jail_hosts[][2][NI_MAXHOST], + char jail_ports[][2][NI_MAXSERV], + jail_ctx *ctx[], const size_t siz) +{ + for (size_t i = 0; i < siz; ++i) { + D("Initialising jail service on port %s:%s", + jail_hosts[i][0], jail_ports[i][0]); + + jail_init_ctx(&ctx[i], MAX_STACKSIZE); + ctx[i]->newroot = getopt_str(OPT_ROOT); + ABORT_ON_FATAL( jail_setup(ctx[i], jail_hosts[i][0], jail_ports[i][0]), + "Jail daemon setup" ); + ABORT_ON_FATAL( jail_validate_ctx(ctx[i]), + "Jail validation" ); + } +} + +static pid_t jail_init(jail_ctx *ctx[], const size_t siz) +{ + pid_t jail_pid; + event_ctx *event = NULL; + + ABORT_ON_FATAL( jail_setup_event( ctx, siz, &event ), + "Jail daemon epoll setup" ); + jail_pid = jail_daemonize(&event, ctx, siz); + ABORT_ON_FATAL( jail_pid < 1, "Jail daemon startup" ); + + return jail_pid; +} + +static void ssh_protocol_preinit(char proto_hosts[][2][NI_MAXHOST], + char proto_ports[][2][NI_MAXSERV], + protocol_ctx *ctx[], + const size_t siz) +{ + for (size_t i = 0; i < siz; ++i) { + ABORT_ON_FATAL( proto_init_ctx(&ctx[i], ssh_init_cb), + "SSH Protocol init" ); + ABORT_ON_FATAL( proto_setup(ctx[i], + proto_hosts[i][0], proto_ports[i][0], + proto_hosts[i][1], proto_ports[i][1]), + "SSH Protocol setup" ); + ABORT_ON_FATAL( proto_validate_ctx(ctx[i]), + "SSH validation" ); + } +} + +static void ssh_protocol_init(protocol_ctx *ctx[], const size_t siz) +{ + for (size_t i = 0; i < siz; ++i) { + ABORT_ON_FATAL( proto_listen(ctx[i]), + "SSH Protocol listen" ); + } +} + +static void rdr_preinit(char rdr_hosts[][2][NI_MAXHOST], + char rdr_ports[][2][NI_MAXSERV], + redirector_ctx *ctx[], + const size_t siz) +{ + for (size_t i = 0; i < siz; ++i) { + D("Initialising redirector service on %s:%s to %s:%s", + rdr_hosts[i][0], rdr_ports[i][0], + rdr_hosts[i][1], rdr_ports[i][1]); + + ABORT_ON_FATAL( redirector_init_ctx(&ctx[i]), + "Redirector init" ); + ABORT_ON_FATAL( redirector_setup(ctx[i], + rdr_hosts[i][0], rdr_ports[i][0], + rdr_hosts[i][1], rdr_ports[i][1]), + "Redirector setup" ); + ABORT_ON_FATAL( redirector_validate_ctx(ctx[i]), + "Redirector validation" ); + } +} + +static pid_t rdr_init(redirector_ctx *ctx[], const size_t siz) +{ + pid_t rdr_pid; + event_ctx *event = NULL; + + D2("%s", "Redirector event setup"); + ABORT_ON_FATAL( redirector_setup_event( ctx, siz, &event ), + "Redirector event setup" ); + + N("%s", "Redirector epoll mainloop"); + rdr_pid = redirector_daemonize( &event, ctx, siz ); + ABORT_ON_FATAL( rdr_pid < 1, "Server epoll mainloop" ); + + return rdr_pid; +} + +static size_t validate_hostport_option(opt_name on, int process_forward) +{ + char *value; + struct opt_list *ol = NULL; + size_t rc = 0, siz, off; + char hbuf[2][NI_MAXHOST]; + char sbuf[2][NI_MAXSERV]; + + if (!getopt_used(on)) + return 0; + + while ((value = getopt_strlist(on, &ol))) { + siz = parse_hostport_str(value, hbuf[0], sbuf[0]); + if (!siz) { + fprintf(stderr, "%s: invalid listen host:port " + "combination: '%s'\n", + arg0, value); + return 0; + } + + off = siz; + siz = parse_hostport_str(value + off, hbuf[1], sbuf[1]); + if (process_forward) { + if (!siz) { + fprintf(stderr, "%s: invalid forward host:port " + "combination: '%s'\n", + arg0, value + off); + return 0; + } + if (*(value + off + siz)) { + fprintf(stderr, "%s: garbage host:port string '%s'\n", + arg0, value + off + siz); + } + } else { + if (siz) { + fprintf(stderr, "%s: got a forward host:port string when none" + " is allowed '%s'\n", arg0, value + off); + return 0; + } + if (*(value + off + siz)) { + fprintf(stderr, "%s: got an invalid forward host:port string " + "when none" + " is allowed '%s'\n", arg0, value + off); + return 0; + } + + off = 0; + } + + rc++; + } + + return rc; +} + +#define POSITIVE_VALIDATIONS 3 +static int process_options(int validate_only) +{ + char *value = NULL; + struct opt_list *ol; + size_t i, siz, rc = 0; + + siz = validate_hostport_option(OPT_JAIL, 0); + if (siz && !validate_only) { + jl_siz = siz; + jl_ctx = (jail_ctx *) calloc(siz, sizeof *jl_ctx); + assert(jl_ctx); + + ol = NULL; + i = 0; + char hosts[jl_siz][2][NI_MAXHOST]; + char ports[jl_siz][2][NI_MAXSERV]; + while ((value = getopt_strlist(OPT_JAIL, &ol))) { + memset(hosts[i], 0, sizeof hosts[i]); + memset(ports[i], 0, sizeof ports[i]); + + siz = parse_hostport_str(value, hosts[i][0], ports[i][0]); + i++; + } + + jail_preinit(hosts, ports, &jl_ctx, jl_siz); + jl_pid = jail_init(&jl_ctx, jl_siz); + } + if (siz) + rc++; + + siz = validate_hostport_option(OPT_PROTOCOL, 1); + if (siz && !validate_only) { + prt_siz = siz; + prt_ctx = (protocol_ctx *) calloc(siz, sizeof *prt_ctx); + assert(prt_ctx); + + ol = NULL; + i = 0; + char hosts[prt_siz][2][NI_MAXHOST]; + char ports[prt_siz][2][NI_MAXSERV]; + while ((value = getopt_strlist(OPT_PROTOCOL, &ol))) { + memset(hosts[i], 0, sizeof hosts[i]); + memset(ports[i], 0, sizeof ports[i]); + + siz = parse_hostport_str(value, hosts[i][0], ports[i][0]); + siz = parse_hostport_str(value + siz, hosts[i][1], ports[i][1]); + i++; + } + + ssh_protocol_preinit(hosts, ports, &prt_ctx, prt_siz); + ssh_protocol_init(&prt_ctx, prt_siz); + } + if (siz) + rc++; + + siz = validate_hostport_option(OPT_REDIRECT, 1); + if (siz && !validate_only) { + rdr_siz = siz; + rdr_ctx = (redirector_ctx *) calloc(siz, sizeof *rdr_ctx); + assert(rdr_ctx); + + ol = NULL; + i = 0; + char hosts[rdr_siz][2][NI_MAXHOST]; + char ports[rdr_siz][2][NI_MAXSERV]; + while ((value = getopt_strlist(OPT_REDIRECT, &ol))) { + memset(hosts[i], 0, sizeof hosts[i]); + memset(ports[i], 0, sizeof ports[i]); + + siz = parse_hostport_str(value, hosts[i][0], ports[i][0]); + siz = parse_hostport_str(value + siz, hosts[i][1], ports[i][1]); + i++; + } + + rdr_preinit(hosts, ports, &rdr_ctx, rdr_siz); + rdr_init(&rdr_ctx, rdr_siz); + } + if (siz) + rc++; + + return rc; +} int main(int argc, char *argv[]) { - static server_ctx srv = {0}; + char *value; + int proc_status; + pid_t daemon_pid, child_pid; + pseccomp_ctx *psc = NULL; + + (void) argc; + (void) argv; + arg0 = argv[0]; + + if (parse_cmdline(argc, argv)) { + fprintf(stderr, "%s: command line parsing failed\n", argv[0]); + exit(EXIT_FAILURE); + } + + if (process_options(1) != POSITIVE_VALIDATIONS) { + fprintf(stderr, "%s: invalid/missing config detected\n", argv[0]); + exit(EXIT_FAILURE); + } + + if (getopt_used(OPT_LOGTOFILE) || getopt_used(OPT_LOGFILE)) { + log_file = getopt_str(OPT_LOGFILE); + LOG_SET_FUNCS_VA(LOG_FILE_FUNCS); + fprintf(stderr, "Logfile: '%s'\n", log_file); + } else { + LOG_SET_FUNCS_VA(LOG_COLORED_FUNCS); + } + + if (getopt_used(OPT_LOGLEVEL)) { + value = getopt_str(OPT_LOGLEVEL); + if (!strcasecmp(value, "debug")) + log_prio = DEBUG; + else if (!strcasecmp(value, "notice")) + log_prio = NOTICE; + else if (!strcasecmp(value, "warning")) + log_prio = WARNING; + else if (!strcasecmp(value, "error")) + log_prio = ERROR; + else { + fprintf(stderr, "%s: unknown loglevel '%s'\n", argv[0], value); + exit(EXIT_FAILURE); + } + } + + if (log_open()) + exit(EXIT_FAILURE); + +#ifdef HAVE_CONFIG_H + N("%s (C) 2018 Toni Uhlig <%s>", PACKAGE_STRING, PACKAGE_BUGREPORT); +#endif + + if (geteuid() != 0) { + E("%s", "I was made for root!"); + exit(EXIT_FAILURE); + } - (void)argc; - (void)argv; + caps_default_filter(); + pseccomp_init(&psc, + (getopt_used(OPT_SECCOMP_MINIMAL) ? PS_MINIMUM : 0)); + if (pseccomp_default_rules(psc)) + FATAL("%s", "SECCOMP: adding default rules"); + pseccomp_free(&psc); - LOG_SET_FUNCS_VA(LOG_COLORED_FUNCS); - N("%s (C) 2018 Toni Uhlig (%s)", PACKAGE_STRING, PACKAGE_BUGREPORT); + D("%s", "Forking into background/foreground"); + daemon_pid = daemonize(!getopt_used(OPT_DAEMON)); + ABORT_ON_FATAL( daemon_pid > 0, "Forking" ); + if (daemon_pid == 0) { + set_procname("[potd] main"); + } else { + FATAL("Forking (fork returned %d)", daemon_pid); + } + D2("Master pid: %d", getpid()); + ABORT_ON_FATAL( set_master_sighandler(), + "Master sighandler" ); - ABORT_ON_FATAL( server_init_ctx(&srv, ssh_init_cb), - "Server initialisation" ); - server_validate_ctx(&srv); + ABORT_ON_FATAL( process_options(0) != POSITIVE_VALIDATIONS, + "Setup redirect/protocol/jail instances"); - ABORT_ON_FATAL( socket_init_in(&srv.sock, "127.0.0.1", 2222), - "Socket initialisation" ); - ABORT_ON_FATAL( socket_bind_listen(&srv.sock), - "Socket bind and listen" ); - ABORT_ON_FATAL( srv.server_cbs.on_listen(&srv.server_dat), - "Socket on listen callback" ); + while (1) { + child_pid = wait(&proc_status); + if (child_pid == jl_pid || + child_pid == rdr_pid) { + E2("%s daemon with pid %d terminated, exiting", + (child_pid == jl_pid ? "Jail" : "Redirector"), + (child_pid == jl_pid ? jl_pid : rdr_pid)); + kill(getpid(), SIGTERM); + break; + } else W2("Process with pid %d terminated", child_pid); + } - D2("%s", "Server mainloop"); - ABORT_ON_FATAL( server_mainloop(&srv), - "Server mainloop" ); + log_close(); return 0; } diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..a9b96f7 --- /dev/null +++ b/src/options.c @@ -0,0 +1,405 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#else +#define POTD_LOGFILE "/tmp/potd.log" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <assert.h> +#include <getopt.h> +#include <linux/limits.h> +#include <libgen.h> +#include <errno.h> + +#include "options.h" + +typedef enum opt_type { + OT_INVALID = 0, OT_NOARG, OT_L, OT_LL, OT_STR, + OT_PATH +} opt_type; + +struct opt_list; + +typedef union opt_ptr { + long int l; + long long int ll; + const char *str; + char *str_dup; + struct opt_list *list; +} opt_ptr; + +typedef struct opt_list { + opt_ptr value; + struct opt_list *next; +} opt_list; + +struct opt { + opt_type type; + opt_ptr value; + opt_ptr def_value; + int used; + int is_list; + + const char *arg_name; + const char *short_help; + const char *help; +}; + +#define OPT(type, def_value, arg, short_help, help) \ + { type, {0}, {def_value}, 0, 0, arg, short_help, help } +#define OPT_LIST(type, def_values, arg, short_help, help) \ + { type, {0}, {def_values}, 0, 1, arg, short_help, help } +#define OPT_NOARG(arg, short_help, help) \ + OPT(OT_NOARG, .ll = 0, arg, short_help, help) +static struct opt options[OPT_MAX+1] = { + OPT_NOARG("log-to-file", "log to the default logfile path\n", NULL), + OPT(OT_PATH, .str = POTD_LOGFILE, "log-file", "specify a logfile path\n", + NULL), + OPT(OT_STR, .str = "notice", "log-level", "set the loglevel\n", + "error - log only errors\n" + "warning - log errors,warnings\n" + "notice - log errors,warnings,notices\n" + "debug - log all messages\n"), + OPT_NOARG("daemon", "fork into background if possible\n", NULL), + OPT_LIST(OT_STR, .str = NULL, "redirect", "setup redirector service\n", + "format [listen]:[forward-to-protocol]\n" + "where [listen] contains [listen-addr]:[listen-port]\n" + "and [forward-to-protocol] contains [forward-addr]:[forward-port]\n" + "Example: 0.0.0.0:2222:127.0.0.1:22222\n"), + OPT_LIST(OT_STR, .str = NULL, "protocol", "setup (ssh) protocol service\n", + "format [listen]:[forward-to-jail]\n" + "where [listen] contains [listen-addr]:[listen-port]\n" + "and [forward-to-jail] contains [forward-addr]:[forward-port]\n" + "Example: 127.0.0.1:22222:127.0.0.1:33333\n"), + OPT_LIST(OT_STR, .str = NULL, "jail", "setup jail service\n", + "format [listen]\n" + "where [listen] contains [listen-addr]:[listen-port]\n" + "Example: 127.0.0.1:33333\n"), + OPT(OT_PATH, .str = POTD_DEFROOT, "rootfs", + "path to root directory/image\n", NULL), + OPT(OT_PATH, .str = POTD_NETNS_RUN_DIR, "netns-rundir", + "set the network namespace run directory\n", NULL), + OPT_NOARG("seccomp-minimal", "use a minimal seccomp ruleset\n", + "instead of setting an allowed syscall ruleset\n" + "use a minimal set of blocked syscalls e.g.\n" + "mount, umount, ptrace, kernel module syscalls\n" + "and some io syscalls\n" + "(use this if you acknowledge errors on some platforms)\n"), + + OPT_NOARG("help", "this\n", NULL), + OPT(OT_INVALID, .ll = 0, NULL, NULL, NULL) +}; + +static int opt_convert(opt_type t, opt_ptr *d); +static int setopt_list(struct opt *o, const char *optarg); +static int setopt(struct opt *o, const char *optarg); +static size_t snprint_multilined_ljust(const char *prefix, + const char *multiline, + char *buf, size_t siz); +static void usage(const char *arg0, int print_copyright); + + +static int parse_path(opt_ptr *d, char *some_path) +{ + int rc = 1; + char path[PATH_MAX]; + char *dir, *base; + + d->str_dup = realpath(some_path, NULL); + if (!d->str_dup && errno == ENOENT) { + snprintf(path, sizeof path, "%s", some_path); + dir = dirname(path); + if (!dir) + return 1; + dir = realpath(dir, NULL); + if (!dir) + return 1; + snprintf(path, sizeof path, "%s", some_path); + base = basename(path); + if (!base) + goto error; + snprintf(path, sizeof path, "%s/%s", dir, base); + d->str_dup = strndup(path, strnlen(path, sizeof path)); +error: + free(dir); + } + + if (d->str_dup) + rc = 0; + + return rc; +} + +static int opt_convert(opt_type t, opt_ptr *d) +{ + char *endptr = NULL; + + switch (t) { + case OT_L: + d->l = strtol(optarg, &endptr, 10); + break; + case OT_LL: + d->ll = strtoll(optarg, &endptr, 10); + break; + case OT_STR: + d->str_dup = strdup(optarg); + break; + case OT_PATH: + if (parse_path(d, optarg)) + return 1; + break; + case OT_NOARG: + case OT_INVALID: + return 1; + } + + if (endptr && *endptr != 0) + return 1; + + return 0; +} + +static int setopt_list(struct opt *o, const char *optarg) +{ + opt_list **l; + + assert(o && o->type != OT_INVALID); + + if (!optarg || o->type == OT_NOARG || !o->is_list) + return 1; + + l = &o->value.list; + while (*l) l = &(*l)->next; + *l = (opt_list *) calloc(1, sizeof **l); + + if (opt_convert(o->type, &(*l)->value)) + return 1; + + o->used = 1; + + return 0; +} + +static int setopt(struct opt *o, const char *optarg) +{ + assert(o && o->type != OT_INVALID); + if (o->used && !o->is_list) + return 1; + if (!optarg || o->type == OT_NOARG) + goto noarg; + + if (opt_convert(o->type, &o->value)) + return 1; + +noarg: + o->used = 1; + + return 0; +} + +static size_t snprint_multilined_ljust(const char *prefix, + const char *multiline, + char *buf, size_t siz) +{ + const char sep[] = "\n"; + const char *start, *end; + size_t off; + + off = 0; + start = multiline; + end = NULL; + do { + if (start) { + end = strstr(start, sep); + if (end) { + off += snprintf(buf + off, siz - off, "%s%.*s\n", prefix, + (int) (end-start), start); + start = end + strlen(sep); + } + } + } while (start && end); + + return off; +} + +static void usage(const char *arg0, int print_copyright) +{ + int i, has_default; + size_t off; + char spaces[6]; + char spaces_long[28]; + char buf_arg[64]; + char buf_shorthelp[BUFSIZ]; + char buf_help[BUFSIZ]; + char value[32]; + +#ifdef HAVE_CONFIG_H + if (print_copyright) + fprintf(stderr, "\n%s (C) 2018 Toni Uhlig <%s>\n\n", + PACKAGE_STRING, PACKAGE_BUGREPORT); + else +#endif + if (1) + fprintf(stderr, "%s", "\n"); + + memset(spaces, ' ', sizeof spaces); + spaces[sizeof spaces - 1] = 0; + memset(spaces_long, ' ', sizeof spaces_long); + spaces_long[sizeof spaces_long - 1] = 0; + + for (i = 0; i < OPT_MAX; ++i) { + snprintf(buf_arg, sizeof buf_arg, "--%s", options[i].arg_name); + + memset(buf_shorthelp, 0, sizeof buf_shorthelp); + if (options[i].short_help) + snprint_multilined_ljust(spaces, + options[i].short_help, + buf_shorthelp, + sizeof buf_shorthelp); + + memset(buf_help, 0, sizeof buf_help); + off = 0; + if (options[i].help) + off = snprint_multilined_ljust(spaces_long, options[i].help, + buf_help, sizeof buf_help); + + has_default = 0; + switch (options[i].type) { + case OT_L: + snprintf(value, sizeof value, "default: %lld\n", + options[i].def_value.ll); + has_default = 1; + break; + case OT_LL: + snprintf(value, sizeof value, "default: %ld\n", + options[i].def_value.l); + has_default = 1; + break; + case OT_STR: + case OT_PATH: + if (options[i].def_value.str) { + snprintf(value, sizeof value, "default: %s\n", + options[i].def_value.str); + has_default = 1; + } + break; + case OT_INVALID: + case OT_NOARG: + default: + break; + } + if (has_default) + snprint_multilined_ljust(&spaces_long[sizeof spaces_long / 2], + value, buf_help + off, sizeof buf_help - off); + + fprintf(stderr, "%16s %s" + "%s\n", buf_arg, + buf_shorthelp, buf_help); + } + fprintf(stderr, + "For example: %s \\\n" + " --redirect 0.0.0.0:2222:127.0.0.1:22222\n" + " --protocol 127.0.0.1:22222:127.0.0.1:33333\n" + " --jail 127.0.0.1:33333\n" + " will process/filter all incoming traffic\n" + " at 0.0.0.0:2222 and redirect it\n" + " to 127.0.0.1:22222 (libssh) which will redirect it finally\n" + " to 127.0.0.1:33333 (jail service).\n\n", + arg0); +} + +int parse_cmdline(int argc, char **argv) +{ + int rc, i, option, option_index; + struct option *o = (struct option *) calloc(OPT_MAX+1, sizeof *o); + + assert(o); + for (i = 0; i < OPT_MAX; ++i) { + o[i].name = options[i].arg_name; + if (options[i].def_value.ll) + o[i].has_arg = required_argument; + else + o[i].has_arg = + (options[i].type == OT_NOARG ? no_argument : required_argument); + } + + rc = 0; + while (1) { + option_index = -1; + option = getopt_long_only(argc, argv, "", o, &option_index); + + if (option_index == -1 && option != -1) { + rc = 1; + continue; + } + if (option == -1) + break; + + if (!option) { + if (!setopt_list(&options[option_index], optarg)) { + } else if (setopt(&options[option_index], optarg)) { + rc = 1; + goto error; + } + } else { + fprintf(stderr, "%s: unknown option '%c' [0x%X]\n", + argv[0], option, option); + } + } + +error: + free(o); + + if (rc) + usage(argv[0], 0); + else if (getopt_used(OPT_HELP)) { + usage(argv[0], 1); + exit(EXIT_SUCCESS); + } + + return rc; +} + +int getopt_used(opt_name on) +{ + return options[on].used; +} + +char * +getopt_str(opt_name on) +{ + char *str; + + assert(options[on].type == OT_STR || + options[on].type == OT_PATH); + assert(getopt_used(on) || options[on].def_value.str); + str = options[on].value.str_dup; + if (!str) + str = options[on].def_value.str_dup; + assert(str); + + return str; +} + +char * +getopt_strlist(opt_name on, opt_list **ol) +{ + opt_list *o; + + assert(options[on].is_list && ol); + assert(options[on].type == OT_STR || + options[on].type == OT_PATH); + assert(getopt_used(on) && !options[on].def_value.str); + + if (*ol) { + o = (*ol)->next; + } else { + o = options[on].value.list; + } + *ol = o; + + return (o ? o->value.str_dup : NULL); +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..4c81c11 --- /dev/null +++ b/src/options.h @@ -0,0 +1,33 @@ +#ifndef POTD_OPTIONS_H +#define POTD_OPTIONS_H 1 + +struct opt_list; + +typedef enum opt_name { + OPT_LOGTOFILE = 0, OPT_LOGFILE, OPT_LOGLEVEL, + OPT_DAEMON, + OPT_REDIRECT, + OPT_PROTOCOL, + OPT_JAIL, + OPT_ROOT, + OPT_NETNS_RUN_DIR, + OPT_SECCOMP_MINIMAL, + + OPT_HELP, + OPT_MAX +} opt_name; + +typedef int check_opt; + + +int parse_cmdline(int argc, char **argv); + +int getopt_used(opt_name on); + +char * +getopt_str(opt_name on); + +char * +getopt_strlist(opt_name on, struct opt_list **ol); + +#endif diff --git a/src/pevent.c b/src/pevent.c new file mode 100644 index 0000000..e377f29 --- /dev/null +++ b/src/pevent.c @@ -0,0 +1,232 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/epoll.h> +#include <assert.h> + +#include "pevent.h" +#include "log.h" + +static int epoll_event_string(uint32_t event, char *buf, size_t siz); + + +static int epoll_event_string(uint32_t event, char *buf, size_t siz) +{ + if (event & EPOLLERR) { + snprintf(buf, siz, "%s", "EPOLLERR"); + } else if (event & EPOLLHUP) { + snprintf(buf, siz, "%s", "EPOLLHUP"); + } else if (event & EPOLLRDHUP) { + snprintf(buf, siz, "%s", "EPOLLRDHUP"); + } else if (event & EPOLLIN) { + snprintf(buf, siz, "%s", "EPOLLIN"); + } else return 1; + + return 0; +} + +void event_init(event_ctx **ctx) +{ + assert(ctx); + if (!*ctx) + *ctx = (event_ctx *) malloc(sizeof(**ctx)); + assert(*ctx); + + memset(*ctx, 0, sizeof(**ctx)); + (*ctx)->epoll_fd = -1; +} + +void event_free(event_ctx **ctx) +{ + assert(ctx && *ctx); + + close((*ctx)->epoll_fd); + free((*ctx)); + *ctx = NULL; +} + +int event_setup(event_ctx *ctx) +{ + assert(ctx); + + if (ctx->epoll_fd < 0) + /* flags == 0 -> obsolete size arg is dropped */ + ctx->epoll_fd = epoll_create1(0); + + return ctx->epoll_fd < 0; +} + +int event_add_sock(event_ctx *ctx, psocket *sock) +{ + int s; + struct epoll_event ev = {0,{0}}; + + assert(ctx && sock); + + ev.data.fd = sock->fd; + ev.events = EPOLLIN /*| EPOLLET*/; /* EPOLLET: broken */ + s = epoll_ctl(ctx->epoll_fd, EPOLL_CTL_ADD, sock->fd, &ev); + if (s) + return 1; + + return 0; +} + +int event_add_fd(event_ctx *ctx, int fd) +{ + int s; + struct epoll_event ev = {0,{0}}; + + assert(ctx); + + ev.data.fd = fd; + ev.events = EPOLLIN | EPOLLET; + s = epoll_ctl(ctx->epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (s) + return 1; + + return 0; +} + +int event_loop(event_ctx *ctx, on_event_cb on_event, void *user_data) +{ + int n, i, saved_errno; + char ev_err[16]; + sigset_t eset; + + assert(ctx && on_event); + sigemptyset(&eset); + ctx->active = 1; + ctx->has_error = 0; + + while (ctx->active && !ctx->has_error) { + errno = 0; + n = epoll_pwait(ctx->epoll_fd, ctx->events, POTD_MAXEVENTS, -1, &eset); + saved_errno = errno; + if (errno == EINTR) + continue; + if (n < 0) { + ctx->has_error = 1; + break; + } + + for (i = 0; i < n; ++i) { + ctx->current_event = i; + + if ((ctx->events[i].events & EPOLLERR) || + (ctx->events[i].events & EPOLLHUP) || + (ctx->events[i].events & EPOLLRDHUP) || + (!(ctx->events[i].events & EPOLLIN))) + { + if (epoll_event_string(ctx->events[i].events, ev_err, sizeof ev_err)) { + errno = saved_errno; + E_STRERR("Event for descriptor %d", + ctx->events[i].data.fd); + } else { + errno = saved_errno; + E_STRERR("Event [%s] for descriptor %d", + ev_err, ctx->events[i].data.fd); + } + + ctx->has_error = 1; + } else { + if (!on_event(ctx, ctx->events[i].data.fd, user_data) && !ctx->has_error) + W2("Event callback failed: [fd: %d , npoll: %d]", + ctx->events[i].data.fd, n); + } + + if (!ctx->active || ctx->has_error) + break; + } + } + + return ctx->has_error != 0; +} + +forward_state +event_forward_connection(event_ctx *ctx, int dest_fd, on_data_cb on_data, + void *user_data) +{ + int data_avail = 1; + int has_input; + int saved_errno; + forward_state rc = CON_OK; + ssize_t siz; + char buf[BUFSIZ]; + struct epoll_event *ev; + + assert(ctx->current_event >= 0 && + ctx->current_event < POTD_MAXEVENTS); + ev = &ctx->events[ctx->current_event]; + + while (data_avail) { + has_input = 0; + saved_errno = 0; + siz = -1; + + if (ev->events & EPOLLIN) { + has_input = 1; + errno = 0; + siz = read(ev->data.fd, &buf[0], BUFSIZ); + saved_errno = errno; + } else break; + if (saved_errno == EAGAIN) + break; + + switch (siz) { + case -1: + E_STRERR("Client read from fd %d", ev->data.fd); + ctx->has_error = 1; + rc = CON_IN_ERROR; + break; + case 0: + rc = CON_IN_TERMINATED; + break; + default: + D2("Read %zu bytes from fd %d", siz, ev->data.fd); + break; + } + + if (rc != CON_OK) + break; + + if (on_data && + on_data(ctx, ev->data.fd, dest_fd, buf, siz, user_data)) + { + W2("On data callback failed, not forwarding from %d to %d", + ev->data.fd, dest_fd); + continue; + } + + if (has_input) { + errno = 0; + siz = write(dest_fd, &buf[0], siz); + + switch (siz) { + case -1: + ctx->has_error = 1; + rc = CON_OUT_ERROR; + break; + case 0: + rc = CON_OUT_TERMINATED; + break; + default: + D2("Written %zu bytes from fd %d to fd %d", + siz, ev->data.fd, dest_fd); + break; + } + } + + if (rc != CON_OK) + break; + } + + D2("Connection state: %d", rc); + if (rc != CON_OK) { + shutdown(ev->data.fd, SHUT_RDWR); + shutdown(dest_fd, SHUT_RDWR); + } + return rc; +} diff --git a/src/pevent.h b/src/pevent.h new file mode 100644 index 0000000..5f9b4fd --- /dev/null +++ b/src/pevent.h @@ -0,0 +1,46 @@ +#ifndef POTD_EVENT_H +#define POTD_EVENT_H 1 + +#include <sys/epoll.h> + +#include "socket.h" + +#define POTD_MAXFD 32 +#define POTD_MAXEVENTS 64 + +typedef enum forward_state { + CON_OK, CON_IN_TERMINATED, CON_OUT_TERMINATED, + CON_IN_ERROR, CON_OUT_ERROR +} forward_state; + +typedef struct event_ctx { + int active; + int has_error; + + int epoll_fd; + struct epoll_event events[POTD_MAXEVENTS]; + int current_event; +} event_ctx; + +typedef int (*on_event_cb) (event_ctx *ev_ctx, int fd, void *user_data); +typedef int (*on_data_cb) (event_ctx *ev_ctx, int src_fd, int dst_fd, + char *buf, size_t siz, void *user_data); + + +void event_init(event_ctx **ctx); + +void event_free(event_ctx **ctx); + +int event_setup(event_ctx *ctx); + +int event_add_sock(event_ctx *ctx, psocket *sock); + +int event_add_fd(event_ctx *ctx, int fd); + +int event_loop(event_ctx *ctx, on_event_cb on_event, void *user_data); + +forward_state +event_forward_connection(event_ctx *ctx, int dest_fd, on_data_cb on_data, + void *user_data); + +#endif diff --git a/src/protocol.c b/src/protocol.c new file mode 100644 index 0000000..ad291de --- /dev/null +++ b/src/protocol.c @@ -0,0 +1,54 @@ +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "protocol.h" +#include "log.h" +#include "socket.h" + + +int proto_init_ctx(protocol_ctx **ctx, proto_init_cb init_fn) +{ + assert(ctx && init_fn); + if (!*ctx) + *ctx = (protocol_ctx *) malloc(sizeof(**ctx)); + assert(*ctx); + + memset(*ctx, 0, sizeof(**ctx)); + if (init_fn(*ctx)) + return 1; + + return 0; +} + +int proto_setup(protocol_ctx *ctx, const char *listen_addr, + const char *listen_port, const char *jail_host, + const char *jail_port) +{ + assert(ctx); + + if (fwd_setup_server(&ctx->src, listen_addr, listen_port)) + return 1; + if (fwd_setup_client_silent(&ctx->dst, jail_host, jail_port)) + return 1; + + return 0; +} + +int proto_listen(protocol_ctx *ctx) +{ + if (!ctx->cbs.on_listen) + return 1; + if (ctx->cbs.on_listen(ctx)) + return 1; + + return 0; +} + +int proto_validate_ctx(const protocol_ctx *ctx) +{ + assert(ctx); + assert(ctx->cbs.on_listen && ctx->cbs.on_shutdown); + + return 0; +} diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 0000000..cc26d3c --- /dev/null +++ b/src/protocol.h @@ -0,0 +1,37 @@ +#ifndef POTD_PROTOCOL_H +#define POTD_PROTOCOL_H 1 + +#include "forward.h" + +#define PROTO_NAMELEN 16 + +struct protocol_ctx; + +typedef int (*proto_init_cb) (struct protocol_ctx *ctx); +typedef int (*proto_listen_cb) (struct protocol_ctx *ctx); +typedef int (*proto_shutdown_cb) (struct protocol_ctx *ctx); + +typedef struct protocol_cbs { + proto_listen_cb on_listen; + proto_shutdown_cb on_shutdown; +} protocol_cbs; + +typedef struct protocol_ctx { + const char name[PROTO_NAMELEN]; + forward_ctx src; + forward_ctx dst; + protocol_cbs cbs; +} protocol_ctx; + + +int proto_init_ctx(protocol_ctx **ctx, proto_init_cb init_fn); + +int proto_setup(protocol_ctx *ctx, const char *listen_addr, + const char *listen_port, const char *jail_host, + const char *jail_port); + +int proto_listen(protocol_ctx *ctx); + +int proto_validate_ctx(const protocol_ctx *ctx); + +#endif diff --git a/src/protocol_ssh.c b/src/protocol_ssh.c new file mode 100644 index 0000000..dfab000 --- /dev/null +++ b/src/protocol_ssh.c @@ -0,0 +1,596 @@ +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <poll.h> +#include <libssh/callbacks.h> +#include <libssh/server.h> + +#include "protocol_ssh.h" +#include "protocol.h" +#include "pseccomp.h" +#include "utils.h" +#include "log.h" + +#if LIBSSH_VERSION_MAJOR != 0 || LIBSSH_VERSION_MINOR < 7 || \ + LIBSSH_VERSION_MICRO < 3 +#pragma message "Unsupported libssh version < 0.7.3" +#endif + +static int version_logged = 0; + +typedef struct ssh_data { + ssh_bind sshbind; + protocol_ctx *ctx; +} ssh_data; + +typedef struct ssh_client { + ssh_channel chan; + forward_ctx dst; +} ssh_client; + +struct protocol_cbs potd_ssh_callbacks = { + .on_listen = ssh_on_listen, + .on_shutdown = ssh_on_shutdown +}; + +static int set_default_keys(ssh_bind sshbind, int rsa_already_set, + int dsa_already_set, int ecdsa_already_set); +static int gen_default_keys(void); +static int gen_export_sshkey(enum ssh_keytypes_e type, int length, const char *path); +static void ssh_log_cb(int priority, const char *function, const char *buffer, void *userdata); +static void ssh_mainloop(ssh_data *arg) + __attribute__((noreturn)); +static int authenticate(ssh_session session); +static int auth_password(const char *user, const char *password); +static int client_mainloop(ssh_client *arg); +static int copy_fd_to_chan(socket_t fd, int revents, void *userdata); +static int copy_chan_to_fd(ssh_session session, ssh_channel channel, void *data, + uint32_t len, int is_stderr, void *userdata); +static void chan_close(ssh_session session, ssh_channel channel, void *userdata); + +struct ssh_channel_callbacks_struct ssh_channel_cb = { + .channel_data_function = copy_chan_to_fd, + .channel_eof_function = chan_close, + .channel_close_function = chan_close, + .userdata = NULL +}; + + +int ssh_init_cb(protocol_ctx *ctx) +{ + if (!version_logged) { + N("libssh version: %s", ssh_version(0)); + if (ssh_version(SSH_VERSION_INT(LIBSSH_VERSION_MAJOR, + LIBSSH_VERSION_MINOR, + LIBSSH_VERSION_MICRO)) == NULL) + { + W("This software was compiled/linked for libssh %d.%d.%d," + " which you aren't currently using.", + LIBSSH_VERSION_MAJOR, LIBSSH_VERSION_MINOR, LIBSSH_VERSION_MICRO); + } + if (ssh_version(SSH_VERSION_INT(0,7,3)) == NULL) + { + W("%s", "Unsupported libssh version < 0.7.3"); + } + if (ssh_version(SSH_VERSION_INT(0,7,4)) != NULL || + ssh_version(SSH_VERSION_INT(0,7,90)) != NULL) + { + W("%s", + "libssh versions > 0.7.3 may suffer " + "from problems with the pki key generation/export"); + } + version_logged = 1; + } + + ctx->cbs = potd_ssh_callbacks; + + if (ssh_init()) + return 1; + + ssh_data *d = (ssh_data *) calloc(1, sizeof(*d)); + assert(d); + d->sshbind = ssh_bind_new(); + d->ctx = ctx; + ctx->src.data = d; + + ssh_set_log_callback(ssh_log_cb); + ssh_set_log_level(SSH_LOG_FUNCTIONS); + + if (!d->sshbind) + return 1; + if (ssh_bind_options_set(d->sshbind, SSH_BIND_OPTIONS_BANNER, + "OpenSSH_7.4p1")) + { + return 1; + } + if (gen_default_keys()) + return 1; + if (set_default_keys(d->sshbind, 0, 0, 0)) + return 1; + + return 0; +} + +int ssh_on_listen(protocol_ctx *ctx) +{ + pid_t p; + int s; + ssh_data *d = (ssh_data *) ctx->src.data; + pseccomp_ctx *psc = NULL; + + if (ssh_bind_options_set(d->sshbind, SSH_BIND_OPTIONS_BINDADDR, + ctx->src.host_buf)) + return 1; + if (ssh_bind_options_set(d->sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, + ctx->src.service_buf)) + return 1; + + s = ssh_bind_listen(d->sshbind); + if (s < 0) { + E_STRERR("Error listening to SSH socket: %s", ssh_get_error(d->sshbind)); + return s; + } + N("SSH bind and listen on %s:%s fd %d", ctx->src.host_buf, + ctx->src.service_buf, ssh_bind_get_fd(d->sshbind)); + + socket_close(&ctx->src.sock); + + p = fork(); + switch (p) { + case -1: + E_STRERR("SSH protocol daemonize on %s:%s fd %d", + ctx->src.host_buf, ctx->src.service_buf, + ssh_bind_get_fd(d->sshbind)); + return 1; + case 0: + pseccomp_set_immutable(); + pseccomp_init(&psc, PS_ALLOW|PS_MINIMUM); + s = pseccomp_protocol_rules(psc); + pseccomp_free(&psc); + if (s) { + E_STRERR("%s", "Could not add seccomp rules"); + return -1; + } + if (change_default_user_group()) { + E_STRERR("%s", "Change user/group"); + return -1; + } + ssh_mainloop(d); + break; + } + D2("SSH protocol pid: %d", p); + ssh_on_shutdown(ctx); + + return s != 0; +} + +int ssh_on_shutdown(protocol_ctx *ctx) +{ + ssh_data *d; + + assert(ctx->src.data); + + d = (ssh_data *) ctx->src.data; + ssh_bind_free(d->sshbind); + free(d); + ctx->src.data = NULL; + ssh_finalize(); + + return 0; +} + +static int set_default_keys(ssh_bind sshbind, int rsa_already_set, + int dsa_already_set, int ecdsa_already_set) +{ + const char rsa_key[] = "./ssh_host_rsa_key"; + const char dsa_key[] = "./ssh_host_dsa_key"; + const char ecdsa_key[] = "./ssh_host_ecdsa_key"; + + if (!rsa_already_set) { + if (access(rsa_key, R_OK)) { + E_STRERR("RSA key '%s' inaccessible", rsa_key); + return 1; + } + if (ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, + "./ssh_host_rsa_key")) { + E2("Faled to set RSA key: %s", ssh_get_error(sshbind)); + return 1; + } + } + if (!dsa_already_set) { + if (access(dsa_key, R_OK)) { + W_STRERR("Access DSA key '%s'", dsa_key); + } else + if (ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, + "./ssh_host_dsa_key")) + { + E2("Failed to set DSA key: %s", ssh_get_error(sshbind)); + return 1; + } + } + if (!ecdsa_already_set) { + if (access(ecdsa_key, R_OK)) { + W_STRERR("Access ECDSA key '%s'", ecdsa_key); + } else + if (ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, + "./ssh_host_ecdsa_key")) + { + E2("Failed to set ECDSA key: %s", ssh_get_error(sshbind)); + return 1; + } + } + return 0; +} + +static int gen_default_keys(void) +{ + int s = 0; + + if (gen_export_sshkey(SSH_KEYTYPE_RSA, 1024, "./ssh_host_rsa_key")) { + W("libssh %s key generation failed, using fallback ssh-keygen", "RSA"); + remove("./ssh_host_rsa_key"); + s |= system("ssh-keygen -t rsa -b 1024 -f ./ssh_host_rsa_key -N '' >/dev/null 2>/dev/null"); + } + if (gen_export_sshkey(SSH_KEYTYPE_DSS, 1024, "./ssh_host_dsa_key")) { + W("libssh %s key generation failed, using fallback ssh-keygen", "DSA"); + remove("./ssh_host_dsa_key"); + s |= system("ssh-keygen -t dsa -b 1024 -f ./ssh_host_dsa_key -N '' >/dev/null 2>/dev/null"); + } + if (gen_export_sshkey(SSH_KEYTYPE_ECDSA, 1024, "./ssh_host_ecdsa_key")) { + W("libssh %s key generation failed, using fallback ssh-keygen", "ECDSA"); + remove("./ssh_host_ecdsa_key"); + s |= system("ssh-keygen -t ecdsa -b 256 -f ./ssh_host_ecdsa_key -N '' >/dev/null 2>/dev/null"); + } + + return s != 0; +} + +static int gen_export_sshkey(enum ssh_keytypes_e type, int length, const char *path) +{ + ssh_key priv_key; + const char *type_str = NULL; + int s; + + assert(path); + assert(length == 1024 || length == 2048 || + length == 4096); + + switch (type) { + case SSH_KEYTYPE_DSS: + type_str = "DSS"; + break; + case SSH_KEYTYPE_RSA: + type_str = "RSA"; + break; + case SSH_KEYTYPE_ECDSA: + type_str = "ECDSA"; + break; + default: + W2("Unknown SSH key type: %d", type); + return 1; + } + D2("Generating %s key with length %d bits and save it on disk: %s", + type_str, length, path); + s = ssh_pki_generate(type, length, &priv_key); + if (s != SSH_OK) { + W2("Generating %s key failed: %d", type_str, s); + return 1; + } + s = ssh_pki_export_privkey_file(priv_key, "", NULL, + NULL, path); + ssh_key_free(priv_key); + + if (s != SSH_OK) { + W2("SSH private key export to file failed: %d", s); + return 1; + } + + return 0; +} + +static void ssh_log_cb(int priority, const char *function, + const char *buffer, void *userdata) +{ + switch (priority) { + case 0: + W("libssh: %s", buffer); + break; + case 1: + N("libssh: %s", buffer); + break; + default: + D("libssh: %s", buffer); + break; + } +} + +static void ssh_mainloop(ssh_data *arg) +{ + int s, auth = 0, shell = 0, is_child; + ssh_session ses; + ssh_message message; + ssh_channel chan = NULL; + ssh_client data; + + assert(arg); + set_procname("[potd] ssh"); + assert( set_child_sighandler() == 0 ); + + while (1) { + ses = ssh_new(); + assert(ses); + + s = ssh_bind_accept(arg->sshbind, ses); + if (s == SSH_ERROR) { + W("SSH error while accepting a connection: %s", + ssh_get_error(ses)); + goto failed; + } + + switch (fork()) { + case -1: + is_child = 0; + W_STRERR("%s", "Fork for SSH Client"); + break; + case 0: + set_procname("[potd] ssh-client"); + assert( set_child_sighandler() == 0 ); + is_child = 1; + break; + default: + ssh_free(ses); + is_child = 0; + break; + } + if (!is_child) + continue; + + if (ssh_handle_key_exchange(ses)) { + W("SSH key exchange failed: %s", ssh_get_error(ses)); + goto failed; + } + + /* proceed to authentication */ + auth = authenticate(ses); + if (!auth) { + W("SSH authentication error: %s", ssh_get_error(ses)); + goto failed; + } + + /* wait for a channel session */ + do { + message = ssh_message_get(ses); + if (message) { + if (ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(message) == SSH_CHANNEL_SESSION) + { + chan = ssh_message_channel_request_open_reply_accept(message); + ssh_message_free(message); + break; + } else { + ssh_message_reply_default(message); + ssh_message_free(message); + } + } else { + break; + } + } while (!chan); + + if (!chan) { + W("SSH client did not ask for a channel session: %s", + ssh_get_error(ses)); + goto failed; + } + + /* wait for a shell */ + do { + message = ssh_message_get(ses); + if (message != NULL) { + if (ssh_message_type(message) == SSH_REQUEST_CHANNEL) { + if (ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_SHELL) { + shell = 1; + ssh_message_channel_request_reply_success(message); + ssh_message_free(message); + break; + } else if (ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_PTY) { + ssh_message_channel_request_reply_success(message); + ssh_message_free(message); + continue; + } + } + ssh_message_reply_default(message); + ssh_message_free(message); + } else { + break; + } + } while (!shell); + + if (!shell) { + W("SSH client had no shell requested: %s", ssh_get_error(ses)); + goto failed; + } + + N("%s", "Dropping user into shell"); + + data.chan = chan; + data.dst = arg->ctx->dst; + if (client_mainloop(&data)) + W2("Client mainloop for fd %d failed", + ssh_bind_get_fd(arg->sshbind)); + +failed: + ssh_disconnect(ses); + ssh_free(ses); + exit(EXIT_SUCCESS); + } +} + +static int authenticate(ssh_session session) +{ + ssh_message message; + + do { + message = ssh_message_get(session); + if (!message) + break; + + switch (ssh_message_type(message)) { + + case SSH_REQUEST_AUTH: + switch (ssh_message_subtype(message)) { + case SSH_AUTH_METHOD_PASSWORD: + N("SSH: user '%s' wants to auth with pass '%s'", + ssh_message_auth_user(message), + ssh_message_auth_password(message)); + if (auth_password(ssh_message_auth_user(message), + ssh_message_auth_password(message))) + { + ssh_message_auth_reply_success(message,0); + ssh_message_free(message); + return 1; + } + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + /* not authenticated, send default message */ + ssh_message_reply_default(message); + break; + + case SSH_AUTH_METHOD_NONE: + default: + N("SSH: User '%s' wants to auth with unknown auth '%d'", + ssh_message_auth_user(message), + ssh_message_subtype(message)); + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + break; + } + break; + + default: + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + } + ssh_message_free(message); + } while (1); + + return 0; +} + +static int auth_password(const char *user, const char *password) +{ +/* + if(strcmp(user, SSHD_USER)) + return 0; + if(strcmp(password, SSHD_PASSWORD)) + return 0; +*/ + return 1; /* authenticated */ +} + +static int client_mainloop(ssh_client *data) +{ + ssh_channel chan = data->chan; + ssh_session session = ssh_channel_get_session(chan); + ssh_event event; + short events; + forward_ctx *ctx = &data->dst; + + if (fwd_connect_sock(ctx, NULL)) { + E_STRERR("Connection to %s:%s", + ctx->host_buf, ctx->service_buf); + ssh_channel_close(chan); + return 1; + } + + ssh_channel_cb.userdata = &ctx->sock.fd; + ssh_callbacks_init(&ssh_channel_cb); + ssh_set_channel_callbacks(chan, &ssh_channel_cb); + + events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL; + event = ssh_event_new(); + + if (event == NULL) { + E2("%s", "Couldn't get a event"); + return 1; + } + if (ssh_event_add_fd(event, ctx->sock.fd, events, copy_fd_to_chan, chan) != SSH_OK) { + E2("Couldn't add fd %d to the event queue", ctx->sock.fd); + return 1; + } + if (ssh_event_add_session(event, session) != SSH_OK) { + E2("%s", "Couldn't add the session to the event"); + return 1; + } + + do { + ssh_event_dopoll(event, 1000); + } while (!ssh_channel_is_closed(chan)); + + ssh_disconnect(session); + ssh_event_remove_fd(event, ctx->sock.fd); + ssh_event_remove_session(event, session); + ssh_event_free(event); + return 0; +} + +static int copy_fd_to_chan(socket_t fd, int revents, void *userdata) +{ + ssh_channel chan = (ssh_channel)userdata; + char buf[BUFSIZ]; + int sz = 0; + + if(!chan) { + close(fd); + return -1; + } + if(revents & POLLIN) { + sz = read(fd, buf, BUFSIZ); + if(sz > 0) { + ssh_channel_write(chan, buf, sz); + } + } + if(revents & POLLHUP || sz <= 0) { + ssh_channel_close(chan); + sz = -1; + } + + return sz; +} + +static int copy_chan_to_fd(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata) +{ + int fd = *(int*)userdata; + int sz; + (void)session; + (void)channel; + (void)is_stderr; + + sz = write(fd, data, len); + if (sz <= 0) + ssh_channel_close(channel); + + return sz; +} + +static void chan_close(ssh_session session, ssh_channel channel, + void *userdata) +{ + int fd = *(int*)userdata; + (void)session; + (void)channel; + + close(fd); +} diff --git a/src/protocol_ssh.h b/src/protocol_ssh.h new file mode 100644 index 0000000..b5edfe0 --- /dev/null +++ b/src/protocol_ssh.h @@ -0,0 +1,14 @@ +#ifndef POTD_SERVER_SSH_H +#define POTD_SERVER_SSH_H 1 + +#include <libssh/server.h> + +#include "protocol.h" + +int ssh_init_cb(protocol_ctx *ctx); + +int ssh_on_listen(protocol_ctx *ctx); + +int ssh_on_shutdown(protocol_ctx *ctx); + +#endif diff --git a/src/pseccomp.c b/src/pseccomp.c new file mode 100644 index 0000000..a08bc11 --- /dev/null +++ b/src/pseccomp.c @@ -0,0 +1,223 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <sys/prctl.h> +#ifdef HAVE_VALGRIND +#include <valgrind.h> +#endif + +#include "pseccomp.h" +#include "log.h" +#include "utils.h" + +static int pseccomp_using_valgrind(void); + +static const int minimum_disabled_syscalls[] = { + SCMP_SYS(reboot), + SCMP_SYS(mount), + SCMP_SYS(umount), SCMP_SYS(umount2), + SCMP_SYS(ptrace), + SCMP_SYS(kexec_load), + SCMP_SYS(kexec_file_load), + SCMP_SYS(open_by_handle_at), + SCMP_SYS(create_module), + SCMP_SYS(init_module), + SCMP_SYS(finit_module), + SCMP_SYS(delete_module), + SCMP_SYS(iopl), + SCMP_SYS(swapon), + SCMP_SYS(swapoff), + SCMP_SYS(syslog), + SCMP_SYS(nice), + SCMP_SYS(kcmp), + SCMP_SYS(unshare), + SCMP_SYS(setns), + SCMP_SYS(pivot_root), + SCMP_SYS(chroot), + SCMP_SYS(fchdir), + SCMP_SYS(capset), + SCMP_SYS(mknod), + SCMP_SYS(mknodat) +}; + +static const int default_allowed_syscalls[] = { + SCMP_SYS(restart_syscall), + SCMP_SYS(signalfd), SCMP_SYS(signalfd4), + SCMP_SYS(rt_sigreturn), SCMP_SYS(rt_sigprocmask), + SCMP_SYS(rt_sigaction), SCMP_SYS(time), SCMP_SYS(nanosleep), + SCMP_SYS(clock_gettime), SCMP_SYS(set_tid_address), + SCMP_SYS(exit), SCMP_SYS(exit_group), + SCMP_SYS(read), SCMP_SYS(readv), SCMP_SYS(write), SCMP_SYS(writev), + SCMP_SYS(fcntl), SCMP_SYS(fcntl64), + SCMP_SYS(close), SCMP_SYS(wait4), + SCMP_SYS(sigprocmask), SCMP_SYS(tgkill), SCMP_SYS(gettid), SCMP_SYS(set_tls), + SCMP_SYS(fork), SCMP_SYS(clone), SCMP_SYS(execve), + SCMP_SYS(socket), SCMP_SYS(bind), SCMP_SYS(setsockopt), SCMP_SYS(shutdown), + SCMP_SYS(listen), SCMP_SYS(connect), SCMP_SYS(getsockname), + SCMP_SYS(accept), SCMP_SYS(sendto), SCMP_SYS(recvmsg), SCMP_SYS(recvfrom), + SCMP_SYS(epoll_create1), SCMP_SYS(epoll_ctl), SCMP_SYS(epoll_pwait), + SCMP_SYS(poll), SCMP_SYS(pipe), SCMP_SYS(pipe2), + SCMP_SYS(set_robust_list), SCMP_SYS(getrlimit), + SCMP_SYS(seccomp), SCMP_SYS(getrusage), + SCMP_SYS(prlimit64), + SCMP_SYS(prctl), SCMP_SYS(mmap), SCMP_SYS(mmap2), SCMP_SYS(brk), SCMP_SYS(madvise), + SCMP_SYS(mlock), SCMP_SYS(getrandom), + SCMP_SYS(mprotect), SCMP_SYS(munmap), SCMP_SYS(futex), + /* operations on files */ + SCMP_SYS(open), SCMP_SYS(openat), + SCMP_SYS(unlink), SCMP_SYS(fstat), SCMP_SYS(fstat64), SCMP_SYS(access), + SCMP_SYS(_llseek), SCMP_SYS(lseek), SCMP_SYS(stat), SCMP_SYS(stat64), + SCMP_SYS(readlink), SCMP_SYS(getcwd), + SCMP_SYS(lstat), SCMP_SYS(sysinfo), + /* operations on user/group */ + SCMP_SYS(setuid), SCMP_SYS(setuid32), SCMP_SYS(setgid), SCMP_SYS(setgid32), + SCMP_SYS(setresuid), SCMP_SYS(setresuid32), SCMP_SYS(setresgid), SCMP_SYS(setresgid32), + SCMP_SYS(getuid), SCMP_SYS(getuid32), SCMP_SYS(geteuid), SCMP_SYS(geteuid32), + SCMP_SYS(getgid), SCMP_SYS(getgid32), SCMP_SYS(getegid), SCMP_SYS(getegid), + SCMP_SYS(getgroups), SCMP_SYS(getdents), + /* operations on processes */ + SCMP_SYS(getpgrp), SCMP_SYS(setpgid), SCMP_SYS(getpid), SCMP_SYS(getppid), + SCMP_SYS(kill), + /* other */ + SCMP_SYS(unshare), SCMP_SYS(setns), + SCMP_SYS(chroot), SCMP_SYS(chdir), SCMP_SYS(mount), SCMP_SYS(umount2), + SCMP_SYS(mknod), SCMP_SYS(mkdir), SCMP_SYS(rmdir), + SCMP_SYS(statfs), SCMP_SYS(ioctl), + SCMP_SYS(umask), SCMP_SYS(chown), SCMP_SYS(chmod), SCMP_SYS(setsid), + SCMP_SYS(dup), SCMP_SYS(dup2), SCMP_SYS(dup3), + SCMP_SYS(sethostname), SCMP_SYS(uname), SCMP_SYS(arch_prctl) +}; + +static const int protocol_disabled_syscalls[] = { + SCMP_SYS(execve), SCMP_SYS(execveat) +}; + +static const int jail_allowed_syscalls[] = { + SCMP_SYS(restart_syscall), + SCMP_SYS(signalfd), SCMP_SYS(signalfd4), + SCMP_SYS(rt_sigreturn), SCMP_SYS(rt_sigprocmask), + SCMP_SYS(rt_sigaction), SCMP_SYS(time), SCMP_SYS(nanosleep), + SCMP_SYS(clock_gettime), SCMP_SYS(set_tid_address), + SCMP_SYS(exit), SCMP_SYS(exit_group), + SCMP_SYS(read), SCMP_SYS(write), SCMP_SYS(writev), + SCMP_SYS(fcntl), SCMP_SYS(fcntl64), + SCMP_SYS(close), SCMP_SYS(wait4), + SCMP_SYS(sigprocmask), SCMP_SYS(tgkill), SCMP_SYS(gettid), SCMP_SYS(set_tls), + SCMP_SYS(fork), SCMP_SYS(clone), SCMP_SYS(execve), + SCMP_SYS(mmap), SCMP_SYS(mmap2), SCMP_SYS(brk), SCMP_SYS(madvise), + SCMP_SYS(mprotect), SCMP_SYS(munmap), SCMP_SYS(futex), + SCMP_SYS(open), SCMP_SYS(openat), SCMP_SYS(fstat), SCMP_SYS(fstat64), SCMP_SYS(access), + SCMP_SYS(poll), SCMP_SYS(pipe), SCMP_SYS(pipe2), + SCMP_SYS(lseek), SCMP_SYS(stat), SCMP_SYS(stat64), SCMP_SYS(readlink), SCMP_SYS(getcwd), + SCMP_SYS(lstat), SCMP_SYS(sysinfo), + SCMP_SYS(setuid), SCMP_SYS(setgid), + SCMP_SYS(setresuid), SCMP_SYS(setresgid), + SCMP_SYS(getuid), SCMP_SYS(geteuid), SCMP_SYS(getgid), SCMP_SYS(getegid), + SCMP_SYS(getgroups), SCMP_SYS(getdents), + SCMP_SYS(getpgrp), SCMP_SYS(setpgid), SCMP_SYS(getpid), SCMP_SYS(getppid), + SCMP_SYS(kill), + SCMP_SYS(chdir), SCMP_SYS(mount), + SCMP_SYS(umount2), + SCMP_SYS(ioctl), + SCMP_SYS(dup), SCMP_SYS(dup2), SCMP_SYS(dup3), + SCMP_SYS(sethostname), SCMP_SYS(uname), SCMP_SYS(arch_prctl) +}; + + +static int pseccomp_using_valgrind(void) +{ +#ifdef HAVE_VALGRIND + if (RUNNING_ON_VALGRIND) { + W("%s", "SECCOMP: running on valgrind, disabled"); + return 1; + } +#endif + return 0; +} + +int pseccomp_init(pseccomp_ctx **ctx, unsigned flags) +{ + assert(ctx); + + if (!*ctx) + *ctx = (pseccomp_ctx *) malloc(sizeof(**ctx)); + assert(*ctx); + + memset(*ctx, 0, sizeof(**ctx)); + (*ctx)->sfilter = seccomp_init( + (flags & PS_ALLOW || flags & PS_MINIMUM ? + SCMP_ACT_ALLOW : SCMP_ACT_ERRNO(EINVAL)) + ); + + return 0; +} + +void pseccomp_free(pseccomp_ctx **ctx) +{ + assert(ctx && *ctx); + + seccomp_release((*ctx)->sfilter); + free(*ctx); + (*ctx) = NULL; +} + +int pseccomp_set_immutable(void) +{ + if (prctl(PR_SET_DUMPABLE, 0) && + prctl(PR_SET_NO_NEW_PRIVS, 1)) + { + FATAL("%s", "PR_SET_NO_NEW_PRIVS, PR_SET_DUMPABLE"); + } + + return 0; +} + +int pseccomp_default_rules(pseccomp_ctx *ctx) +{ + size_t i; + + if (pseccomp_using_valgrind()) + return 0; + + if (ctx->flags & PS_MINIMUM) { + for (i = 0; i < SIZEOF(minimum_disabled_syscalls); ++i) + seccomp_rule_add(ctx->sfilter, SCMP_ACT_ERRNO(EINVAL), + minimum_disabled_syscalls[i], 0); + } else { + for (i = 0; i < SIZEOF(default_allowed_syscalls); ++i) + seccomp_rule_add(ctx->sfilter, SCMP_ACT_ALLOW, + default_allowed_syscalls[i], 0); + } + + return seccomp_load(ctx->sfilter); +} + +int pseccomp_protocol_rules(pseccomp_ctx *ctx) +{ + size_t i; + + if (pseccomp_using_valgrind()) + return 0; + + for (i = 0; i < SIZEOF(protocol_disabled_syscalls); ++i) + seccomp_rule_add(ctx->sfilter, SCMP_ACT_ERRNO(EINVAL), + protocol_disabled_syscalls[i], 0); + + return seccomp_load(ctx->sfilter); +} + +int pseccomp_jail_rules(pseccomp_ctx *ctx) +{ + size_t i; + + if (pseccomp_using_valgrind()) + return 0; + + for (i = 0; i < SIZEOF(jail_allowed_syscalls); ++i) + seccomp_rule_add(ctx->sfilter, SCMP_ACT_ALLOW, + jail_allowed_syscalls[i], 0); + + return seccomp_load(ctx->sfilter); +} diff --git a/src/pseccomp.h b/src/pseccomp.h new file mode 100644 index 0000000..70fe3de --- /dev/null +++ b/src/pseccomp.h @@ -0,0 +1,27 @@ +#ifndef POTD_SECCOMP_H +#define POTD_SECCOMP_H 1 + +#include <seccomp.h> + +#define PS_ALLOW 0x1 +#define PS_MINIMUM 0x2 + +typedef struct pseccomp_ctx { + unsigned flags; + scmp_filter_ctx sfilter; +} pseccomp_ctx; + + +int pseccomp_init(pseccomp_ctx **ctx, unsigned flags); + +void pseccomp_free(pseccomp_ctx **ctx); + +int pseccomp_set_immutable(void); + +int pseccomp_default_rules(pseccomp_ctx *ctx); + +int pseccomp_protocol_rules(pseccomp_ctx *ctx); + +int pseccomp_jail_rules(pseccomp_ctx *ctx); + +#endif diff --git a/src/redirector.c b/src/redirector.c new file mode 100644 index 0000000..b4d7a78 --- /dev/null +++ b/src/redirector.c @@ -0,0 +1,418 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <pthread.h> +#include <time.h> +#include <assert.h> + +#include "redirector.h" +#include "socket.h" +#include "utils.h" +#include "log.h" + +typedef struct client_thread { + pthread_t self; + psocket client_sock; + char host_buf[NI_MAXHOST], service_buf[NI_MAXSERV]; + redirector_ctx *rdr_ctx; +} client_thread; + +typedef struct server_event { + redirector_ctx **rdr_ctx; + const size_t siz; + size_t last_accept_count; + time_t last_accept_stamp; +} server_event; + +typedef struct client_event { + const psocket *fwd_sock; + const client_thread *client_args; +} client_event; + +static forward_state +fwd_state_string(const forward_state c_state, const client_thread *args, + const psocket *fwd); +static int +redirector_mainloop(event_ctx **ev_ctx, redirector_ctx *rdr_ctx[], size_t siz) + __attribute__((noreturn)); +static int redirector_accept_client(event_ctx *ev_ctx, int fd, void *user_data); +static void * +client_mainloop(void *arg); +static int client_io(event_ctx *ev_ctx, int src_fd, void *user_data); + + +int redirector_init_ctx(redirector_ctx **ctx) +{ + forward_ctx *fwd; + + assert(ctx); + if (!*ctx) + *ctx = (redirector_ctx *) malloc(sizeof(**ctx)); + assert(*ctx); + + memset(*ctx, 0, sizeof(**ctx)); + fwd = &(*ctx)->fwd_ctx; + if (fwd_init_ctx(&fwd)) + return 1; + + return 0; +} + +void redirector_free_ctx(redirector_ctx **rdr_ctx) +{ + assert(rdr_ctx && *rdr_ctx); + + socket_close(&(*rdr_ctx)->fwd_ctx.sock); + socket_close(&(*rdr_ctx)->sock); + free(*rdr_ctx); + (*rdr_ctx) = NULL; +} + +int redirector_setup(redirector_ctx *ctx, + const char *listen_addr, const char *listen_port, + const char *host, const char *port) +{ + int s; + struct addrinfo *srv_addr = NULL; + + assert(ctx); + assert(listen_port); + + if (!listen_addr) + listen_addr = "0.0.0.0"; + D2("Try to listen on %s:%s and forward to %s:%s", + (listen_addr ? listen_addr : "*"), listen_port, + host, port); + s = socket_init_in(listen_addr, listen_port, &srv_addr); + if (s) { + E_GAIERR(s, "Could not initialise server socket"); + return 1; + } + if (socket_bind_in(&ctx->sock, &srv_addr)) { + E_STRERR("Could not bind server socket to %s:%s", + listen_addr, listen_port); + return 1; + } + if (socket_listen_in(&ctx->sock)) { + E_STRERR("Could not listen on server socket on %s:%s", + listen_addr, listen_port); + return 1; + } + + if (fwd_setup_client(&ctx->fwd_ctx, host, port)) + return 1; + if (fwd_validate_ctx(&ctx->fwd_ctx)) + return 1; + + return 0; +} + +int redirector_validate_ctx(const redirector_ctx *ctx) +{ + assert(ctx); + assert(ctx->sock.fd >= 0 && ctx->sock.addr_len > 0); + + return 0; +} + +int redirector_setup_event(redirector_ctx *rdr_ctx[], size_t siz, event_ctx **ev_ctx) +{ + int s; + + assert(rdr_ctx && ev_ctx); + assert(siz > 0 && siz < POTD_MAXFD); + + event_init(ev_ctx); + if (event_setup(*ev_ctx)) + return 1; + + for (size_t i = 0; i < siz; ++i) { + if (event_add_sock(*ev_ctx, &rdr_ctx[i]->sock)) { + return 1; + } + + s = socket_addrtostr_in(&rdr_ctx[i]->sock, + rdr_ctx[i]->host_buf, rdr_ctx[i]->service_buf); + if (s) { + E_GAIERR(s, "Convert socket address to string"); + return 1; + } + N("Redirector service listening on %s:%s", + rdr_ctx[i]->host_buf, rdr_ctx[i]->service_buf); + } + + return 0; +} + +pid_t redirector_daemonize(event_ctx **ev_ctx, redirector_ctx *rdr_ctx[], size_t siz) +{ + pid_t p; + int s; + size_t i; + + assert(rdr_ctx && ev_ctx); + assert(siz > 0 && siz < POTD_MAXFD); + + for (i = 0; i < siz; ++i) { + assert(rdr_ctx[i]); + s = socket_addrtostr_in(&rdr_ctx[i]->sock, + rdr_ctx[i]->host_buf, + rdr_ctx[i]->service_buf); + if (s) { + E_GAIERR(s, "Could not initialise server daemon socket"); + return 1; + } + } + + p = fork(); + switch (p) { + case -1: + W_STRERR("%s", "Server daemonize"); + return -1; + case 0: + if (change_default_user_group()) { + E_STRERR("%s", "Change user/group"); + return -1; + } + N("%s", "Server daemon mainloop"); + redirector_mainloop(ev_ctx, rdr_ctx, siz); + } + D2("Server daemon pid: %d", p); + + event_free(ev_ctx); + for (i = 0; i < siz; ++i) + redirector_free_ctx(&rdr_ctx[i]); + + return p; +} + +static forward_state +fwd_state_string(const forward_state c_state, const client_thread *args, + const psocket *fwd) +{ + switch (c_state) { + case CON_OK: + break; + case CON_IN_ERROR: + N("Lost connection to %s:%s: %d", + args->host_buf, args->service_buf, + args->client_sock.fd); + break; + case CON_IN_TERMINATED: + N("Connection terminated: %s:%s: %d", + args->host_buf, args->service_buf, + args->client_sock.fd); + break; + case CON_OUT_ERROR: + N("Lost forward connection to %s:%s: %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, + fwd->fd); + break; + case CON_OUT_TERMINATED: + N("Forward connection terminated: %s:%s: %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, + fwd->fd); + break; + } + + return c_state; +} + +static int redirector_mainloop(event_ctx **ev_ctx, redirector_ctx *rdr_ctx[], size_t siz) +{ + int rc; + server_event ev_srv = { rdr_ctx, siz, 0, 0 }; + + set_procname("[potd] redirector"); + assert( set_child_sighandler() == 0 ); + + ev_srv.last_accept_stamp = time(NULL); + rc = event_loop(*ev_ctx, redirector_accept_client, &ev_srv); + event_free(ev_ctx); + + exit(rc); +} + +static int redirector_accept_client(event_ctx *ev_ctx, int fd, void *user_data) +{ + size_t i; + double d; + int s; + time_t t; + server_event *ev_srv = (server_event *) user_data; + client_thread *args; + redirector_ctx *rdr_ctx; + + (void) ev_ctx; + assert(ev_srv); + + for (i = 0; i < ev_srv->siz; ++i) { + rdr_ctx = ev_srv->rdr_ctx[i]; + if (rdr_ctx->sock.fd == fd) { + args = (client_thread *) calloc(1, sizeof(*args)); + assert(args); + + if (socket_accept_in(&rdr_ctx->sock, + &args->client_sock)) + { + E_STRERR("Could not accept client connection on fd %d", + rdr_ctx->sock.fd); + goto error; + } + + ev_srv->last_accept_count++; + t = time(NULL); + d = difftime(t, ev_srv->last_accept_stamp); + if (d == 0.0f) + d = 1.0f; + if (d > 5.0f) { + ev_srv->last_accept_stamp = t; + ev_srv->last_accept_count = 0; + } + if (ev_srv->last_accept_count / (size_t)d > 3) { + W2("DoS protection: Got %zu accepts/s", + ev_srv->last_accept_count / (size_t)d); + goto error; + } + + args->rdr_ctx = rdr_ctx; + s = socket_addrtostr_in(&args->client_sock, + args->host_buf, args->service_buf); + if (s) { + E_GAIERR(s, "Convert socket address to string"); + goto error; + } + N2("New connection from %s:%s to %s:%s: %d", + args->host_buf, args->service_buf, + rdr_ctx->host_buf, rdr_ctx->service_buf, + args->client_sock.fd); + + if (pthread_create(&args->self, NULL, + client_mainloop, args)) + { + E_STRERR("Thread creation for %s:%s on fd %d", + args->host_buf, args->service_buf, + args->client_sock.fd); + goto error; + } + + return 1; +error: + socket_close(&args->client_sock); + free(args); + return 0; + } + } + + return 0; +} + +static void * +client_mainloop(void *arg) +{ + client_thread *args; + client_event ev_cli; + int s; + event_ctx *ev_ctx = NULL; + psocket fwd; + + assert(arg); + args = (client_thread *) arg; + pthread_detach(args->self); + + event_init(&ev_ctx); + if (event_setup(ev_ctx)) { + E_STRERR("Client event context creation for server fd %d", + args->rdr_ctx->sock.fd); + goto finish; + } + + if (fwd_connect_sock(&args->rdr_ctx->fwd_ctx, &fwd)) { + E_STRERR("Forward connection to %s:%s server fd %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, + args->rdr_ctx->sock.fd); + goto finish; + } + N("Forwarding connection from %s:%s to %s:%s forward fd %d", + args->host_buf, args->service_buf, + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, fwd.fd); + + if (event_add_sock(ev_ctx, &fwd)) { + E_STRERR("Forward event context add to %s:%s forward fd %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, fwd.fd); + goto finish; + } + + /* + * We got the client socket from our main thread, so fd flags like + * O_NONBLOCK are not inherited! + */ + s = socket_nonblock(&args->client_sock); + if (s) { + E_STRERR("Socket non blocking mode to %s:%s forward fd %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, fwd.fd); + goto finish; + } + if (event_add_sock(ev_ctx, &args->client_sock)) { + E_STRERR("Forward event context add to %s:%s forward fd %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, fwd.fd); + goto finish; + } + + ev_cli.client_args = args; + ev_cli.fwd_sock = &fwd; + if (event_loop(ev_ctx, client_io, &ev_cli) && ev_ctx->has_error) + E_STRERR("Forward connection data from %s:%s to %s:%s", + args->host_buf, args->service_buf, + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf); + +finish: + event_free(&ev_ctx); + socket_close(&fwd); + socket_close(&args->client_sock); + free(args); + return NULL; +} + +static int +client_io(event_ctx *ev_ctx, int src_fd, void *user_data) +{ + int dest_fd; + client_event *ev_cli = (client_event *) user_data; + const psocket *client_sock = &ev_cli->client_args->client_sock; + forward_state fwd_state; + + if (src_fd == ev_cli->fwd_sock->fd) { + dest_fd = client_sock->fd; + } else if (src_fd == client_sock->fd) { + dest_fd = ev_cli->fwd_sock->fd; + } else return 0; + + fwd_state = event_forward_connection(ev_ctx, dest_fd, NULL, NULL); + + switch (fwd_state_string(fwd_state, ev_cli->client_args, + ev_cli->fwd_sock)) + { + case CON_IN_TERMINATED: + case CON_OUT_TERMINATED: + ev_ctx->active = 0; + case CON_OK: + return 1; + case CON_IN_ERROR: + case CON_OUT_ERROR: + ev_ctx->active = 0; + return 0; + } + + return 1; +} diff --git a/src/redirector.h b/src/redirector.h new file mode 100644 index 0000000..c9170cb --- /dev/null +++ b/src/redirector.h @@ -0,0 +1,31 @@ +#ifndef POTD_SERVER_H +#define POTD_SERVER_H 1 + +#include "socket.h" +#include "forward.h" +#include "pevent.h" + + +typedef struct redirector_ctx { + forward_ctx fwd_ctx; + psocket sock; + char host_buf[NI_MAXHOST], service_buf[NI_MAXSERV]; +} redirector_ctx; + + +int redirector_init_ctx(redirector_ctx **rdr_ctx); + +void redirector_free_ctx(redirector_ctx **rdr_ctx); + +int redirector_setup(redirector_ctx *rdr_ctx, + const char *listen_addr, const char *listen_port, + const char *host, const char *port); + +int redirector_validate_ctx(const redirector_ctx *rdr_ctx); + +int redirector_setup_event(redirector_ctx *rdr_ctx[], size_t siz, + event_ctx **ev_ctx); + +pid_t redirector_daemonize(event_ctx **ev_ctx, redirector_ctx *rdr_ctx[], size_t siz); + +#endif diff --git a/src/server.c b/src/server.c deleted file mode 100644 index 61664c5..0000000 --- a/src/server.c +++ /dev/null @@ -1,38 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include <assert.h> - -#include "server.h" - - -server_ctx * -server_init_ctx(server_ctx *ctx, init_cb init_fn) -{ - if (!ctx) - ctx = (server_ctx *) malloc(sizeof(*ctx)); - assert(ctx); - - memset(ctx, 0, sizeof(*ctx)); - if (!init_fn(ctx)) - return NULL; - - return ctx; -} - -int server_validate_ctx(server_ctx *ctx) -{ - assert(ctx); - assert(ctx->server_cbs.on_connect && ctx->server_cbs.on_disconnect - && ctx->server_cbs.mainloop); - assert(ctx->server_cbs.on_free && ctx->server_cbs.on_listen - && ctx->server_cbs.on_shutdown); - return 0; -} - -int server_mainloop(server_ctx *ctx) -{ - while (1) { - sleep(1); - } - return 0; -} diff --git a/src/server.h b/src/server.h deleted file mode 100644 index 69c3ad7..0000000 --- a/src/server.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef POTD_SERVER_H -#define POTD_SERVER_H 1 - -#include "socket.h" - -typedef struct server_data { - void *data; -} server_data; - -typedef struct server_session { - void *data; -} server_session; - -typedef int (*on_connect_cb) (struct server_data *data, struct server_session *ses); -typedef int (*on_disconnect_cb) (struct server_data *data, struct server_session *ses); -typedef int (*on_data_cb) (struct server_data *data, struct server_session *ses); -typedef int (*on_free_cb) (struct server_data *data); -typedef int (*on_listen_cb) (struct server_data *data); -typedef int (*on_shutdown_cb) (struct server_data *data); - -typedef struct server_callbacks { - on_connect_cb on_connect; - on_disconnect_cb on_disconnect; - on_data_cb mainloop; - on_free_cb on_free; - on_listen_cb on_listen; - on_shutdown_cb on_shutdown; -} server_callbacks; - -typedef struct server_ctx { - server_callbacks server_cbs; - server_data server_dat; - psocket sock; -} server_ctx; - -typedef int (*init_cb) (struct server_ctx *ctx); - - -server_ctx * -server_init_ctx(server_ctx *ctx, init_cb init_fn); - -int server_validate_ctx(server_ctx *ctx); - -int server_mainloop(server_ctx *ctx); - -#endif diff --git a/src/server_ssh.c b/src/server_ssh.c deleted file mode 100644 index 92d9454..0000000 --- a/src/server_ssh.c +++ /dev/null @@ -1,77 +0,0 @@ -#include <stdlib.h> -#include <libssh/callbacks.h> -#include <libssh/server.h> - -#include "server_ssh.h" -#include "server.h" - -static void set_default_keys(ssh_bind sshbind, int rsa_already_set, - int dsa_already_set, int ecdsa_already_set); - - -int ssh_on_connect(struct server_data *data, struct server_session *ses) -{ - return 0; -} - -int ssh_on_disconnect(struct server_data *data, struct server_session *ses) -{ - return 0; -} - -int ssh_mainloop_cb(struct server_data *data, struct server_session *ses) -{ - return 0; -} - -int ssh_init_cb(struct server_ctx *ctx) -{ - ctx->server_cbs.on_connect = ssh_on_connect; - ctx->server_cbs.on_disconnect = ssh_on_disconnect; - ctx->server_cbs.mainloop = ssh_mainloop_cb; - ctx->server_cbs.on_free = ssh_free_cb; - ctx->server_cbs.on_listen = ssh_listen_cb; - ctx->server_cbs.on_shutdown = ssh_shutdown_cb; - - ssh_init(); - ssh_data *d = (ssh_data *) calloc(1, sizeof(*d)); - d->sshbind = ssh_bind_new(); - ctx->server_dat.data = d; - if (!d->sshbind) - return 1; - - set_default_keys(d->sshbind, 0, 0, 0); - return 0; -} - -int ssh_free_cb(struct server_data *data) -{ - return 0; -} - -int ssh_listen_cb(struct server_data *data) -{ - return 0; -} - -int ssh_shutdown_cb(struct server_data *data) -{ - return 0; -} - -static void set_default_keys(ssh_bind sshbind, int rsa_already_set, - int dsa_already_set, int ecdsa_already_set) -{ - if (!rsa_already_set) { - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, - "./ssh_host_rsa_key"); - } - if (!dsa_already_set) { - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, - "./ssh_host_dsa_key"); - } - if (!ecdsa_already_set) { - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, - "./ssh_host_ecdsa_key"); - } -} diff --git a/src/server_ssh.h b/src/server_ssh.h deleted file mode 100644 index dc80645..0000000 --- a/src/server_ssh.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef POTD_SERVER_SSH_H -#define POTD_SERVER_SSH_H 1 - -#include <libssh/server.h> - -#include "server.h" - -typedef struct ssh_data { - ssh_bind sshbind; -} ssh_data; - - -int ssh_on_connect(struct server_data *data, struct server_session *ses); - -int ssh_on_disconnect(struct server_data *data, struct server_session *ses); - -int ssh_mainloop_cb(struct server_data *_data, struct server_session *ses); - -int ssh_init_cb(struct server_ctx *ctx); - -int ssh_free_cb(struct server_data *data); - -int ssh_listen_cb(struct server_data *data); - -int ssh_shutdown_cb(struct server_data *data); - -#endif diff --git a/src/socket.c b/src/socket.c index 99e4477..06abc93 100644 --- a/src/socket.c +++ b/src/socket.c @@ -1,30 +1,320 @@ +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> +#include <sys/ioctl.h> +#include <net/if.h> +#include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include "socket.h" +#include "utils.h" +static int socket_setopts(int sockfd); +static inline void socket_freeaddr(struct addrinfo **results); -int socket_init_in(psocket *psocket, const char *listen_addr, unsigned int listen_port) + +static int socket_setopts(int sockfd) { - struct in_addr addr = {0}; + int s, enable = 1; + + s = fcntl(sockfd, F_GETFL, 0); + if (s < 0) + return 1; + s |= O_CLOEXEC; + if (fcntl(sockfd, F_SETFL, s) == -1) + return 1; - assert(psocket); - if (!inet_aton(listen_addr, &addr)) + s = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if (s) return 1; - psocket->sock.sin_family = AF_INET; - psocket->sock.sin_addr = addr; - psocket->sock.sin_port = htons(listen_port); - psocket->fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); - return psocket->fd < 0; + return 0; +} + +static inline void socket_freeaddr(struct addrinfo **results) +{ + if (*results) { + freeaddrinfo(*results); + *results = NULL; + } +} + +int socket_nonblock(const psocket *psock) +{ + return set_fd_nonblock(psock->fd); +} + +int socket_init_in(const char *addr, + const char *port, struct addrinfo **results) +{ + int s; + struct addrinfo hints; + + assert(addr || port); /* getaddrinfo wants either node or service */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* IPV4 && IPV6 */ + hints.ai_socktype = SOCK_STREAM; /* TCP */ + hints.ai_flags = AI_PASSIVE; /* all interfaces */ + + s = getaddrinfo(addr, port, &hints, results); + if (s) + socket_freeaddr(results); + + return s; } -int socket_bind_listen(psocket *psocket) +int socket_bind_in(psocket *psock, struct addrinfo **results) { - assert(psocket); - if (bind(psocket->fd, &psocket->sock, sizeof(psocket->sock)) < 0) + int s = 1, fd = -1, rv; + struct addrinfo *rp = NULL; + + assert(psock && results && *results); + psock->fd = -1; + + for (rp = *results; rp != NULL; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd < 0) + continue; + rv = bind(fd, rp->ai_addr, rp->ai_addrlen); + if (!rv) + break; + close(fd); + } + + if (!rp) + goto finalise; + + s = socket_setopts(fd); + if (s) + goto finalise; + + psock->fd = fd; + psock->addr_len = rp->ai_addrlen; + psock->addr = *rp->ai_addr; + psock->family = rp->ai_family; + psock->socktype = rp->ai_socktype; + psock->protocol = rp->ai_protocol; + s = socket_nonblock(psock); + +finalise: + socket_freeaddr(results); + + return s; +} + +int socket_listen_in(psocket *psock) +{ + assert(psock); + + return listen(psock->fd, POTD_BACKLOG) != 0; +} + +int socket_accept_in(const psocket *psock, psocket *client_psock) +{ + int fd; + + assert(psock && client_psock); + + *client_psock = *psock; + fd = accept(psock->fd, &client_psock->addr, + &client_psock->addr_len); + if (fd < 0) + return 1; + if (socket_setopts(fd)) + { + close(fd); + return 1; + } + + client_psock->fd = fd; + if (socket_nonblock(client_psock)) { + socket_close(client_psock); return 1; - return listen(psocket->fd, POTD_BACKLOG) < 0; + } + + return 0; +} + +int socket_connect_in(psocket *psock, struct addrinfo **results) +{ + int s = 1, fd = -1, rv; + struct addrinfo *rp = NULL; + + assert(psock && results && *results); + psock->fd = -1; + + for (rp = *results; rp != NULL; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd < 0) + continue; + rv = connect(fd, rp->ai_addr, rp->ai_addrlen); + if (!rv) + break; + close(fd); + } + + if (!rp) + goto finalise; + + s = socket_setopts(fd); + if (s) + goto finalise; + + psock->fd = fd; + psock->addr_len = rp->ai_addrlen; + psock->addr = *(rp->ai_addr); + psock->family = rp->ai_family; + psock->socktype = rp->ai_socktype; + psock->protocol = rp->ai_protocol; + s = socket_nonblock(psock); + +finalise: + socket_freeaddr(results); + + return s; +} + +int socket_connectaddr_in(psocket *psock, struct addrinfo **results, + char host_buf[NI_MAXHOST], + char service_buf[NI_MAXSERV]) +{ + if (socket_connect_in(psock, results)) + return -1; + return socket_addrtostr_in(psock, host_buf, service_buf); +} + +int socket_addrtostr_in(const psocket *psock, + char hbuf[NI_MAXHOST], char sbuf[NI_MAXSERV]) +{ + int s; + + assert(psock); + s = getnameinfo(&psock->addr, + psock->addr_len, + &hbuf[0], NI_MAXHOST, + &sbuf[0], NI_MAXSERV, + NI_NUMERICHOST | NI_NUMERICSERV); + + return s; +} + +int socket_reconnect_in(psocket *psock) +{ + int rv; + + assert(psock); + if (psock->fd >= 0) + return 1; + + psock->fd = socket(psock->family, psock->socktype, psock->protocol); + if (psock->fd < 0) + return 1; + rv = connect(psock->fd, &psock->addr, psock->addr_len); + if (rv) { + socket_close(psock); + return 1; + } + + if (socket_setopts(psock->fd)) { + socket_close(psock); + return 1; + } + + return socket_nonblock(psock); +} + +int socket_close(psocket *psock) +{ + int rv; + + assert(psock); + if (psock->fd < 0) + return 0; + rv = close(psock->fd); + psock->fd = -1; + + return rv; +} + +void socket_clone(const psocket *src, psocket *dst) +{ + assert(src && dst); + + memcpy(dst, src, sizeof(*dst)); + dst->fd = -1; +} + +ssize_t socket_get_ifnames(const psocket *test_sock, char name[][IFNAMSIZ], + size_t siz, int loopback_only) +{ + struct ifreq ifr; + struct ifreq *it, *end; + struct ifconf ifc; + char buf[1024]; + int sock; + size_t rc = 0; + + assert(test_sock); + sock = socket(test_sock->family, test_sock->socktype, + test_sock->protocol); + if (sock <= 0) + return -1; + + ifc.ifc_len = sizeof buf; + ifc.ifc_buf = buf; + if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) + return -1; + it = ifc.ifc_req; + end = it + (ifc.ifc_len / sizeof(struct ifreq)); + + for (; it != end; ++it) { + strncpy(ifr.ifr_name, it->ifr_name, IFNAMSIZ); + + if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) { + if (loopback_only && !(ifr.ifr_flags & IFF_LOOPBACK)) + continue; + if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) { + strncpy(name[rc++], it->ifr_name, IFNAMSIZ); + if (siz == rc) + break; + } + } + } + + close(sock); + + return rc; +} + +int socket_set_ifaddr(const psocket *test_sock, + const char *ifname, const char *addr, const char *mask) +{ + struct ifreq ifr; + int sock; + + assert(test_sock); + sock = socket(test_sock->family, test_sock->socktype, + test_sock->protocol); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + + ifr.ifr_addr.sa_family = AF_INET; + inet_pton(AF_INET, addr, ifr.ifr_addr.sa_data + 2); + ioctl(sock, SIOCSIFADDR, &ifr); + + inet_pton(AF_INET, mask, ifr.ifr_addr.sa_data + 2); + ioctl(sock, SIOCSIFNETMASK, &ifr); + + ioctl(sock, SIOCGIFFLAGS, &ifr); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_flags |= (IFF_UP | IFF_RUNNING); + + ioctl(sock, SIOCSIFFLAGS, &ifr); + close(sock); + + return 0; } diff --git a/src/socket.h b/src/socket.h index 1b1a762..faa7fa3 100644 --- a/src/socket.h +++ b/src/socket.h @@ -1,19 +1,51 @@ #ifndef POTD_SOCKET_H #define POTD_SOCKET_H 1 -#include <netinet/in.h> +#include <netdb.h> +#include <net/if.h> -#define POTD_BACKLOG 3 +#define POTD_BACKLOG 1 typedef struct psocket { int fd; - struct sockaddr_in sock; + socklen_t addr_len; + struct sockaddr addr; + int family; + int socktype; + int protocol; } psocket; -int socket_init_in(psocket *psocket, const char *listen_addr, - unsigned int listen_port); +int socket_nonblock(const psocket *psock); -int socket_bind_listen(psocket *psocket); +int socket_init_in(const char *addr, + const char *port, struct addrinfo **results); + +int socket_bind_in(psocket *psock, struct addrinfo **results); + +int socket_listen_in(psocket *psock); + +int socket_accept_in(const psocket *psock, psocket *client_psock); + +int socket_connect_in(psocket *psock, struct addrinfo **results); + +int socket_connectaddr_in(psocket *psock, struct addrinfo **results, + char host_buf[NI_MAXHOST], + char service_buf[NI_MAXSERV]); + +int socket_addrtostr_in(const psocket *psock, + char hbuf[NI_MAXHOST], char sbuf[NI_MAXSERV]); + +int socket_reconnect_in(psocket *psock); + +int socket_close(psocket *psock); + +void socket_clone(const psocket *src, psocket *dst); + +ssize_t socket_get_ifnames(const psocket *test_sock, char name[][IFNAMSIZ], + size_t siz, int loopback_only); + +int socket_set_ifaddr(const psocket *test_sock, + const char *ifname, const char *addr, const char *mask); #endif diff --git a/src/utils.c b/src/utils.c index 5c86017..3de57a2 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,74 +1,884 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <ctype.h> +#include <stdarg.h> +#include <fcntl.h> #include <signal.h> +#include <pwd.h> +#include <grp.h> +#include <sched.h> #include <sys/types.h> +#include <sys/sysmacros.h> #include <sys/stat.h> -#include <syslog.h> +#include <sys/wait.h> +#include <sys/prctl.h> +#include <sys/mount.h> +#include <linux/limits.h> +#include <assert.h> #include "utils.h" +#include "log.h" +#include "options.h" #define _POSIX_PATH_MAX 256 +char *arg0 = NULL; +static int null_fd = -1; +static const char cgmem[] = "/sys/fs/cgroup/memory/potd"; +static const char cgcpu[] = "/sys/fs/cgroup/cpu/potd"; +static const char cgpid[] = "/sys/fs/cgroup/pids/potd"; +static const char cgdef[] = "/sys/fs/cgroup/potd"; +static const char *_cgmem = NULL; +static const char *_cgcpu = NULL; +static const char *_cgpid = NULL; + +static char * +sig_to_str(int signo, char *buf, size_t siz); +static void sighandler_child(int signo); +static void sighandler_master(int signo); +static int cgroups_write_file(const char *cdir, const char *csub, + const char *value, size_t siz); +static inline void bin2hex_char(unsigned char c, char hexc[5]); + + +int set_fd_nonblock(int fd) +{ + int flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + return 1; + flags |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) == -1) + return 1; + + return 0; +} + +static char * +sig_to_str(int signo, char *buf, size_t siz) +{ + switch (signo) { + case SIGCHLD: + strncpy(buf, "SIGCHLD", siz-1); break; + case SIGPIPE: + strncpy(buf, "SIGPIPE", siz-1); break; + case SIGABRT: + strncpy(buf, "SIGABRT", siz-1); break; + case SIGSEGV: + strncpy(buf, "SIGSEGV", siz-1); break; + case SIGTERM: + strncpy(buf, "SIGTERM", siz-1); break; + case SIGINT: + strncpy(buf, "SIGINT", siz-1); break; + case SIGHUP: + strncpy(buf, "SIGHUP", siz-1); break; + default: + strncpy(buf, "UNKNOWN", siz-1); break; + } + buf[siz - 1] = 0; + + return buf; +} + +static void sighandler_child(int signo) +{ + char buf[16] = {0}; + + W("Got signal[%d]: %s", signo, sig_to_str(signo, &buf[0], sizeof buf)); + switch (signo) { + case SIGABRT: + exit(EXIT_FAILURE); + case SIGHUP: + if (getppid() == 1) { + N("Master process %d died, exiting", getpgrp()); + exit(EXIT_SUCCESS); + } + break; + case SIGSEGV: +#ifdef HAVE_CONFIG_H + E("Segmentation fault .. please report to <%s>", PACKAGE_BUGREPORT); +#else + E("%s", "Segmentation fault .."); +#endif + exit(EXIT_FAILURE); + } +} + +int set_child_sighandler(void) +{ + if (prctl(PR_SET_PDEATHSIG, SIGHUP) != 0) + return 1; + assert( signal(SIGCHLD, SIG_IGN) != SIG_ERR ); + assert( signal(SIGPIPE, SIG_IGN) != SIG_ERR ); + assert( signal(SIGABRT, sighandler_child) != SIG_ERR ); + assert( signal(SIGSEGV, sighandler_child) != SIG_ERR ); + + return signal(SIGHUP, sighandler_child) == SIG_ERR; +} + +static void sighandler_master(int signo) +{ + static int exiting = 0; + char buf[16] = {0}; + + W("Got signal[%d]: %s", signo, sig_to_str(signo, &buf[0], sizeof buf)); + switch (signo) { + case SIGSEGV: + case SIGINT: + case SIGTERM: + case SIGABRT: + if (exiting) + break; + exiting = 1; + kill(0, SIGTERM); + exit(EXIT_FAILURE); + } +} -void set_procname(char *arg0, const char *newname) +int set_master_sighandler(void) { + int s = 0; + + s |= signal(SIGSEGV, sighandler_master) == SIG_ERR; + s |= signal(SIGINT, sighandler_master) == SIG_ERR; + s |= signal(SIGTERM, sighandler_master) == SIG_ERR; + s |= signal(SIGABRT, sighandler_master) == SIG_ERR; + + return s; +} + +void set_procname(const char *new_arg0) +{ + assert(arg0); memset(arg0, 0, _POSIX_PATH_MAX); - strncpy(arg0, newname, _POSIX_PATH_MAX); + strncpy(arg0, new_arg0, _POSIX_PATH_MAX); } -int daemonize(void) +pid_t daemonize(int stay_foreground) { + int status = -1; pid_t pid; /* Fork off the parent process */ pid = fork(); /* An error occurred */ - if (pid < 0) - exit(EXIT_FAILURE); + if (pid < 0) { + E_STRERR("%s", "fork"); + return pid; + } /* Success: Let the parent terminate */ - if (pid > 0) + if (pid > 0) { + if (!stay_foreground) + exit(EXIT_SUCCESS); + waitpid(-1, &status, 0); exit(EXIT_SUCCESS); + } /* On success: The child process becomes session leader */ - if (setsid() < 0) + if (!stay_foreground && setsid() < 0) { + E_STRERR("%s", "setsid"); exit(EXIT_FAILURE); - - /* Catch, ignore and handle signals */ - //TODO: Implement a working signal handler */ - signal(SIGCHLD, SIG_IGN); - signal(SIGHUP, SIG_IGN); + } /* Fork off for the second time*/ - pid = fork(); + if (!stay_foreground) { + pid = fork(); - /* An error occurred */ - if (pid < 0) - exit(EXIT_FAILURE); + /* An error occurred */ + if (pid < 0) + exit(EXIT_FAILURE); - /* Success: Let the parent terminate */ - if (pid > 0) - exit(EXIT_SUCCESS); + /* Success: Let the parent terminate */ + if (pid > 0) + exit(EXIT_SUCCESS); + } + + if (!stay_foreground && setpgrp()) { + E_STRERR("%s", "setpgrp"); + exit(EXIT_FAILURE); + } /* Set new file permissions */ umask(0); - /* Change the working directory to the root directory */ - /* or another appropriated directory */ - chdir("/"); + if (!stay_foreground) { + /* Change the working directory to the root directory */ + /* or another appropriated directory */ + chdir("/"); + /* Close all open file descriptors */ + assert( close_fds_except(-1) == 0 ); + assert( redirect_devnull_to(0, 1, 2, -1) == 0 ); + } else { + assert( close_fds_except(0, 1, 2, -1) == 0 ); + } - /* Close all open file descriptors */ - int x; - for (x = sysconf(_SC_OPEN_MAX); x>=0; x--) + if (log_open()) + return -1; + + return pid; +} + +int close_fds_except(int fds, ...) +{ + int fd; + long max_fd; + size_t i, except_count, found; + va_list ap; + + max_fd = sysconf(_SC_OPEN_MAX) - 1; + if (max_fd <= 0) + max_fd = 1024; + + va_start(ap, fds); { - close (x); + int *all_fds = (int *) malloc((max_fd+1) * sizeof(*all_fds)); + assert(all_fds); + memset(all_fds, -1, max_fd * sizeof(*all_fds)); + + except_count = 0; + while ( (fd = va_arg(ap, int)) >= 0 ) { + all_fds[except_count++] = fd; + } + all_fds[except_count++] = fds; + + for (fd = max_fd; fd >= 0; --fd) { + found = 0; + for (i = 0; i < except_count && fds >= 0; ++i) { + if (fd == all_fds[i]) + found++; + } + if (!found) { + close(fd); + } + } + + free(all_fds); } + va_end(ap); - /* Open the log file */ - openlog ("firstdaemon", LOG_PID, LOG_DAEMON); + return 0; +} + +int redirect_devnull_to(int fds, ...) +{ + int fd, rc = 0; + va_list ap; + + if (null_fd < 0) + null_fd = open("/dev/null", O_RDWR); + if (null_fd < 0) + return -1; + if (fds < -1) + return -1; + + va_start(ap, fds); + { + while ( (fd = va_arg(ap, int)) >= 0 ) { + if ( dup2(null_fd, fd) < 0 ) + rc++; + } + } + va_end(ap); + + return rc; +} + +int change_user_group(const char *user, const char *group) +{ + struct passwd *pwd = NULL; + struct group *grp = NULL; + gid_t gid; + + pwd = getpwnam(user); + if (!pwd) + return 1; + + if (!group) { + gid = pwd->pw_gid; + } else { + grp = getgrnam(group); + if (!grp) + return 1; + gid = grp->gr_gid; + } + + if (setresgid(gid, gid, gid)) + return 1; + if (setresuid(pwd->pw_uid, pwd->pw_uid, pwd->pw_uid)) + return 1; + + return 0; +} + +int change_default_user_group(void) +{ + return change_user_group("nobody", NULL); +} + +int safe_chroot(const char *newroot) +{ + int s; + + s = chdir(newroot); + if (s) { + E_STRERR("Change directory to '%s'", newroot); + return 1; + } + + s = chroot("."); + if (s) { + E_STRERR("Change root directory to '%s'", "."); + return 1; + } + + s = chdir("/"); + if (s) { + E_STRERR("Change directory inside new root to '%s'", "/"); + return 1; + } + + return 0; +} + +int dir_is_mountpoint(const char *path) +{ + struct stat current = {0}, parent = {0}; + size_t plen = strnlen(path, PATH_MAX); + char parent_path[plen + 4]; + + if (stat(path, ¤t)) + goto error; + strncpy(parent_path, path, plen); + parent_path[plen] = '/'; + parent_path[plen+1] = '.'; + parent_path[plen+2] = '.'; + parent_path[plen+3] = 0; + + if (stat(parent_path, &parent)) + goto error; + + return current.st_dev != parent.st_dev; +error: + W_STRERR("Mountpoint check for '%s'", path); + return -1; +} + +void chk_chroot(void) +{ + struct stat s = {0}; + + if (stat("/", &s) == 0) { + if (s.st_ino != 2) + return; + } + + W2("%s", "Can not mount filesystem as slave/private"); +} + +void mount_root(void) +{ + int s; + s = mount("", "/", "none", MS_SLAVE|MS_REC, NULL); + if (s) + s = mount("", "/", "none", MS_PRIVATE|MS_REC, NULL); + if (s) + chk_chroot(); +} + +int mount_dev(const char *mount_path) +{ + int s; + + s = mount("tmpfs", mount_path, "tmpfs", + MS_NOSUID|MS_STRICTATIME| + MS_NOEXEC|MS_REC, + "size=4k,mode=755,gid=0"); + if (s) { + E_STRERR("Mount devtmpfs filesystem to %s", mount_path); + return 1; + } return 0; } + +int mount_pts(const char *mount_path) +{ + int s; + + s = mount("devpts", mount_path, "devpts", + MS_MGC_VAL, + "newinstance,gid=5,mode=620,ptmxmode=0666"); + + if (s) { + E_STRERR("Mount devpts filesystem to %s", mount_path); + return 1; + } + + return 0; +} + +int mount_proc(const char *mount_path) +{ + int s; + + umount(mount_path); + s = mount("proc", mount_path, "proc", + MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REC, NULL); + if (s) { + E_STRERR("Mount proc filesystem to %s", mount_path); + return 1; + } + + return 0; +} + +int setup_network_namespace(const char *name) +{ + int fd; + char netns_path[PATH_MAX]; + int made_netns_run_dir_mount = 0; + + snprintf(netns_path, sizeof netns_path, "%s/%s", + getopt_str(OPT_NETNS_RUN_DIR), name); + D2("Network Namespace path '%s'", netns_path); + + if (mkdir(getopt_str(OPT_NETNS_RUN_DIR), + S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) + { + if (errno != EEXIST) { + E_STRERR("Create netns directory '%s'", + getopt_str(OPT_NETNS_RUN_DIR)); + return 1; + } + } + + while (mount("", getopt_str(OPT_NETNS_RUN_DIR), "none", + MS_SHARED|MS_REC, NULL)) + { + /* Fail unless we need to make the mount point */ + if (errno != EINVAL || made_netns_run_dir_mount) { + E_STRERR("Mount netns directory '%s' as shared", + getopt_str(OPT_NETNS_RUN_DIR)); + return 1; + } + + /* Upgrade NETNS_RUN_DIR to a mount point */ + if (mount(getopt_str(OPT_NETNS_RUN_DIR), getopt_str(OPT_NETNS_RUN_DIR), + "none", MS_BIND | MS_REC, NULL)) + { + E_STRERR("Bind mount netns directory '%s'", + getopt_str(OPT_NETNS_RUN_DIR)); + return 1; + } + made_netns_run_dir_mount = 1; + } + + /* Create the filesystem state */ + fd = open(netns_path, O_RDONLY|O_CREAT|O_EXCL, 0); + if (fd < 0 && errno != EEXIST) { + E_STRERR("Create namespace file '%s'", netns_path); + return 1; + } + if (fd >= 0) + close(fd); + + if (unshare(CLONE_NEWNET) < 0) { + E_STRERR("Create network namespace '%s'", name); + goto error; + } + + /* Bind the netns last so I can watch for it */ + if (mount("/proc/self/ns/net", netns_path, "none", MS_BIND, NULL) < 0) { + E_STRERR("Bind /proc/self/ns/net to '%s'", netns_path); + goto error; + } + + return 0; +error: + /* cleanup netns? */ + return 1; +} + +int switch_network_namespace(const char *name) +{ + char net_path[PATH_MAX]; + int netns; + + snprintf(net_path, sizeof(net_path), "%s/%s", + getopt_str(OPT_NETNS_RUN_DIR), name); + netns = open(net_path, O_RDONLY | O_CLOEXEC); + if (netns < 0) { + E_STRERR("Cannot open network namespace '%s'", name); + return 1; + } + + if (setns(netns, CLONE_NEWNET) < 0) { + E_STRERR("Setting the network namespace '%s'", name); + close(netns); + return 1; + } + close(netns); + + return 0; +} + +int create_device_file_checked(const char *mount_path, const char *device_file, + mode_t mode, int add_mode, dev_t dev) +{ + int s; + mode_t defmode = S_IRUSR|S_IWUSR| + S_IRGRP|S_IWGRP| + S_IROTH; + size_t plen = strnlen(mount_path, PATH_MAX); + size_t dlen = strnlen(device_file, PATH_MAX); + struct stat devbuf = {0}; + char devpath[plen+dlen+2]; + + snprintf(devpath, plen+dlen+2, "%s/%s", mount_path, device_file); + s = stat(devpath, &devbuf); + + if (s && errno != EEXIST && errno != ENOENT) { + return 1; + } + if (errno == EEXIST) { + if (unlink(devpath)) + return 1; + } + + D2("Create device file: %s", devpath); + if (!add_mode) + defmode = 0; + s = mknod(devpath, defmode|mode, dev); + if (s) { + E_STRERR("Device creation '%s'", devpath); + return 1; + } + + return 0; +} + +int create_device_files(const char *mount_path) +{ + int s = 0; + + s |= create_device_file_checked(mount_path, "ptmx", S_IFCHR, 1, makedev(5,2)); + s |= create_device_file_checked(mount_path, "tty", S_IFCHR, 1, makedev(5,0)); + + return s; +} + +static int cgroups_write_file(const char *cdir, const char *csub, + const char *value, size_t siz) +{ + int fd, s = 0; + char buf[BUFSIZ] = {0}; + + assert(cdir && csub && value); + + D2("Write '%s' to '%s/%s'", value, cdir, csub); + if (snprintf(buf, sizeof buf, "%s/%s", cdir, csub) > 0) { + if ((fd = open(buf, O_WRONLY)) < 0 || + write(fd, value, siz) <= 0) + { + W_STRERR("Write '%s' to '%s/%s'", + value, cdir, csub); + s = 1; + } + close(fd); + } + + return s; +} + +int cgroups_set(void) +{ + int s = 0, fail = 0; + + const char maxmem[] = "memory.limit_in_bytes"; + const char maxmem_soft[] = "memory.soft_limit_in_bytes"; + const char kmem[] = "memory.kmem.limit_in_bytes"; + const char kmem_tcp[] = "memory.kmem.tcp.limit_in_bytes"; + const char maxmem_limit[] = "8388608"; /* 8*1024*1024 = 8MB */ + const char maxmem_soft_limit[] = "7340032"; /* 7*1024*1024 = 8MB */ + const char cpu_shares[] = "cpu.shares"; + const char cpu_shares_limit[] = "32"; + const char cfs_period[] = "cpu.cfs_period_us"; + const char cfs_period_limit[] = "50000"; + const char cfs_quota[] = "cpu.cfs_quota_us"; + const char cfs_quota_limit[] = "10000"; + const char pid_max[] = "pids.max"; + const char pid_max_limit[] = "10"; + const char rt_period[] = "cpu.rt_period_us"; + const char *rt_period_limit = cfs_period_limit; + const char rt_runtime[] = "cpu.rt_runtime_us"; + const char *rt_runtime_limit = cfs_quota_limit; + const char ccpus[] = "cpuset.cpus"; + const char cmems[] = "cpuset.mems"; + + if (remove(cgmem) && errno != ENOENT) + return 1; + errno = 0; + s |= mkdir(cgmem, + S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); + if (errno) + fail++; + + if (remove(cgcpu) && errno != ENOENT) + return 1; + errno = 0; + s |= mkdir(cgcpu, + S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); + if (errno) + fail++; + + if (remove(cgpid) && errno != ENOENT) + return 1; + errno = 0; + s |= mkdir(cgpid, + S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); + if (errno) + fail++; + + if (fail == 3) { + if (remove(cgdef) && errno != ENOENT) + return 1; + s = mkdir(cgdef, + S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); + if (s) + return 1; + + s |= cgroups_write_file(cgdef, ccpus, "0", 1); + s |= cgroups_write_file(cgdef, cmems, "0", 1); + + _cgmem = cgdef; + _cgcpu = cgdef; + _cgpid = cgdef; + } else { + _cgmem = cgmem; + _cgcpu = cgcpu; + _cgpid = cgpid; + } + + s |= cgroups_write_file(_cgmem, maxmem, maxmem_limit, sizeof maxmem_limit); + s |= cgroups_write_file(_cgmem, maxmem_soft, maxmem_soft_limit, + sizeof maxmem_limit); + s |= cgroups_write_file(_cgmem, kmem_tcp, maxmem_limit, sizeof maxmem_limit); + s |= cgroups_write_file(_cgmem, kmem, maxmem_limit, sizeof maxmem_limit); + s |= cgroups_write_file(_cgcpu, cpu_shares, cpu_shares_limit, + sizeof cpu_shares_limit); + + errno = 0; + cgroups_write_file(_cgcpu, cfs_period, cfs_period_limit, + sizeof cfs_period_limit); + if (errno) { + s |= cgroups_write_file(_cgcpu, rt_period, rt_period_limit, + sizeof cfs_period_limit); + } + + errno = 0; + cgroups_write_file(_cgcpu, cfs_quota, cfs_quota_limit, + sizeof cfs_quota_limit); + if (errno) { + s |= cgroups_write_file(_cgcpu, rt_runtime, rt_runtime_limit, + sizeof cfs_quota_limit); + } + + s |= cgroups_write_file(_cgpid, pid_max, pid_max_limit, + sizeof pid_max_limit); + + return s; +} + +int cgroups_activate(void) +{ + pid_t p = getpid(); + int s; + char buf[32] = {0}; + const char tasks[] = "tasks"; + + s = snprintf(buf, sizeof buf, "%d", p); + if (s <= 0) + return 1; + s = cgroups_write_file(_cgmem, tasks, buf, s); + + s = snprintf(buf, sizeof buf, "%d", p); + if (s <= 0) + return 1; + s = cgroups_write_file(_cgcpu, tasks, buf, s); + + s = snprintf(buf, sizeof buf, "%d", p); + if (s <= 0) + return 1; + s = cgroups_write_file(_cgpid, tasks, buf, s); + + return s; +} + +#if 0 +int update_guid_map(pid_t pid, unsigned int map[3], int update_uidmap) +{ + int s, fd; + ssize_t written; + const char path_pid[] = "/proc/%d/%s"; + const char path_self[] = "/proc/self/%s"; + char buf[64]; + + if (pid < 0) { + s = snprintf(buf, sizeof buf, path_self, + (update_uidmap ? "uid_map" : "gid_map")); + } else { + s = snprintf(buf, sizeof buf, path_pid, pid, + (update_uidmap ? "uid_map" : "gid_map")); + } + if (s <= 0) + return 1; + + fd = open(buf, O_WRONLY); + if (fd < 0) + return 1; + + s = snprintf(buf, sizeof buf, "%u %u %u\n", map[0], map[1], map[2]); + written = write(fd, buf, s); + if (written <= 0) + return 1; + + return 0; +} + +int update_setgroups_self(int allow) +{ + int fd; + ssize_t written; + const char path_self[] = "/proc/self/setgroups"; + const char str_allow[] = "allow"; + const char str_deny[] = "deny"; + + fd = open(path_self, O_WRONLY); + if (fd < 0) + return 1; + + if (allow) { + written = write(fd, str_allow, sizeof(str_allow) - 1); + } else { + written = write(fd, str_deny, sizeof(str_deny) - 1); + } + if (written <= 0) + return 1; + + return 0; +} +#endif + +static inline void bin2hex_char(unsigned char c, char hexc[5]) +{ + static const char hexalnum[] = "0123456789ABCDEF"; + + hexc[0] = '\\'; + hexc[1] = 'x'; + hexc[2] = hexalnum[ (c >> 4)%16 ]; + hexc[3] = hexalnum[ (c & 0x0F)%16 ]; + hexc[4] = 0; +} + +void escape_ascii_string(const char ascii[], size_t siz, char **dest, size_t *newsiz) +{ + char hexbyte[5]; + const size_t binsiz = 4; + size_t i, j, ns; + + assert(ascii && dest && newsiz); + + ns = 0; + for (i = 0; i < siz; ++i) { + if (isprint(ascii[i])) + ns++; + else + ns += binsiz; + } + + if (ns > *newsiz) { + if (*dest) + free(*dest); + *dest = (char *) malloc(sizeof(char) * (ns+1)); + assert(*dest); + (*newsiz) = ns; + } + + for (i = 0, j = 0; i < siz && j < ns; ++i) { + if (isprint(ascii[i])) { + (*dest)[j] = ascii[i]; + j++; + } else { + bin2hex_char(ascii[i], hexbyte); + snprintf((*dest)+j, binsiz+1, "%s", hexbyte); + j += binsiz; + } + } + + (*dest)[ns] = 0; +} + +size_t parse_hostport(const char *str, const char *result[2], + size_t siz[2]) +{ + size_t i; + const char *hostend = strchr(str, ':'); + const char *portend; + const char sep[] = ": \t\n\0"; + + result[0] = NULL; + result[1] = NULL; + siz[0] = 0; + siz[1] = 0; + + if (!hostend) + return 0; + hostend++; + for (i = 0; i < SIZEOF(sep); ++i) { + portend = strchr(hostend, sep[i]); + if (portend) + break; + } + if (!portend) + return 0; + + result[0] = str; + result[1] = hostend; + siz[0] = hostend - str - 1; + siz[1] = portend - hostend; + + return siz[0] + siz[1] + 1 + (*portend != 0 ? 1 : 0); +} + +size_t parse_hostport_str(const char *str, char hbuf[NI_MAXHOST], + char sbuf[NI_MAXSERV]) +{ + const char *hostport[2]; + size_t hostport_siz[2]; + size_t siz; + + siz = parse_hostport(str, hostport, hostport_siz); + if (!siz) + return 0; + if (snprintf(hbuf, NI_MAXHOST, "%.*s", (int) hostport_siz[0], + hostport[0]) <= 0) + { + return 0; + } + if (snprintf(sbuf, NI_MAXSERV, "%.*s", (int) hostport_siz[1], + hostport[1]) <= 0) + { + return 0; + } + + return siz; +} diff --git a/src/utils.h b/src/utils.h index 587bf25..f72255b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,8 +1,77 @@ #ifndef POTD_UTILS_H #define POTD_UTILS_H 1 -void set_procname(char *arg0, const char *newname); +#include <stdlib.h> +#include <sys/types.h> +#include <netdb.h> -int daemonize(void); +#ifndef SIZEOF +#define SIZEOF(arr) (sizeof(arr)/sizeof(arr[0])) +#endif + +#define MIN(x, y) (x > y ? y : x) + +extern char *arg0; + + +int set_fd_nonblock(int fd); + +int set_child_sighandler(void); + +int set_master_sighandler(void); + +void set_procname(const char *new_arg0); + +pid_t daemonize(int stay_foreground); + +int close_fds_except(int fd, ...); + +int redirect_devnull_to(int fds, ...); + +int change_user_group(const char *user, const char *group); + +int change_default_user_group(void); + +int safe_chroot(const char *newroot); + +int dir_is_mountpoint(const char *path); + +void chk_chroot(void); + +void mount_root(void); + +int mount_dev(const char *mount_path); + +int mount_pts(const char *mount_path); + +int mount_proc(const char *mount_path); + +int setup_network_namespace(const char *name); + +int switch_network_namespace(const char *name); + +int create_device_file_checked(const char *mount_path, const char *device_file, + mode_t mode, int add_mode, dev_t dev); + +int create_device_files(const char *mount_path); + +int cgroups_set(void); + +int cgroups_activate(void); + +#if 0 +int update_guid_map(pid_t pid, unsigned int uid_map[3], int update_uidmap); + +int update_setgroups_self(int allow); +#endif + +void escape_ascii_string(const char ascii[], size_t siz, + char **dest, size_t *newsiz); + +size_t parse_hostport(const char *str, const char *result[2], + size_t siz[2]); + +size_t parse_hostport_str(const char *str, char hbuf[NI_MAXHOST], + char sbuf[NI_MAXSERV]); #endif |