aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlns <matzeton@googlemail.com>2018-04-11 14:28:18 +0200
committerToni Uhlig <matzeton@googlemail.com>2018-06-13 18:23:43 +0200
commitf2f11e477a489ac25a4c4be064eddc26fc9d677c (patch)
treed4f679146a61b28056e772e30570c53fb4721b80
parentebabaa69c0a3ba992895c7a66729e81e0923d5f1 (diff)
POTD skeleton.
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
-rw-r--r--.gitignore1
-rw-r--r--AUTHORS1
-rw-r--r--COPYING3
-rw-r--r--README20
-rw-r--r--configure.ac229
-rw-r--r--src/Makefile.am6
-rw-r--r--src/capabilities.c344
-rw-r--r--src/capabilities.h25
-rw-r--r--src/forward.c172
-rw-r--r--src/forward.h35
-rw-r--r--src/jail.c553
-rw-r--r--src/jail.h34
-rw-r--r--src/log.c2
-rw-r--r--src/log.h42
-rw-r--r--src/log_colored.c95
-rw-r--r--src/log_colored.h10
-rw-r--r--src/log_file.c171
-rw-r--r--src/log_file.h24
-rw-r--r--src/main.c383
-rw-r--r--src/options.c405
-rw-r--r--src/options.h33
-rw-r--r--src/pevent.c232
-rw-r--r--src/pevent.h46
-rw-r--r--src/protocol.c54
-rw-r--r--src/protocol.h37
-rw-r--r--src/protocol_ssh.c596
-rw-r--r--src/protocol_ssh.h14
-rw-r--r--src/pseccomp.c223
-rw-r--r--src/pseccomp.h27
-rw-r--r--src/redirector.c418
-rw-r--r--src/redirector.h31
-rw-r--r--src/server.c38
-rw-r--r--src/server.h46
-rw-r--r--src/server_ssh.c77
-rw-r--r--src/server_ssh.h27
-rw-r--r--src/socket.c316
-rw-r--r--src/socket.h44
-rw-r--r--src/utils.c868
-rw-r--r--src/utils.h73
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
diff --git a/AUTHORS b/AUTHORS
index e69de29..d562d9b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -0,0 +1 @@
+Toni Uhlig <matzeton@googlemail.com>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..28383e0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,3 @@
+Non-free proprietary license!
+
+(C) 2018 Toni Uhlig <matzeton@googlemail.com>
diff --git a/README b/README
index e69de29..597c817 100644
--- a/README
+++ b/README
@@ -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
diff --git a/src/log.c b/src/log.c
index 40ef5cb..0cc4a76 100644
--- a/src/log.c
+++ b/src/log.c
@@ -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;
diff --git a/src/log.h b/src/log.h
index 3363395..00042f9 100644
--- a/src/log.h
+++ b/src/log.h
@@ -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
diff --git a/src/main.c b/src/main.c
index 7ccc404..0e54fe0 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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, &current))
+ 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