[devel] [PATCH v2 1/6] Turn hasher-priv into a daemon
Arseny Maslennikov
arseny на altlinux.org
Чт Окт 22 14:43:38 MSK 2020
From: Alexey Gladkov <legion на altlinux.org>
From: Alexey Gladkov <legion на altlinux.org>
All the privileged operations are now performed by a daemonised system
service. The daemon accepts IPC commands via a Unix domain socket,
fulfills the request and sends back a response.
The kernel provides a reliable way to obtain the sender's credentials,
which are then verified.
This allows to install hasher-privd without SUID bit to reduce attack
surface and facilitate NO_NEW_PRIVS environments.
When contacted by a client of user X, the daemon creates a separate
session process that handles requests only from user X. This session
process ends after a certain period of inactivity.
Signed-off-by: Alexey Gladkov <legion на altlinux.org>
Co-authored-by: Arseny Maslennikov <arseny на altlinux.org>
Signed-off-by: Arseny Maslennikov <arseny на altlinux.org>
---
hasher-priv/.gitignore | 1 +
hasher-priv/DESIGN | 281 ++++++++++++++++---------
hasher-priv/Makefile | 28 ++-
hasher-priv/caller.c | 81 ++++----
hasher-priv/caller_server.c | 382 ++++++++++++++++++++++++++++++++++
hasher-priv/caller_task.c | 217 ++++++++++++++++++++
hasher-priv/cmdline.c | 27 ++-
hasher-priv/communication.c | 394 ++++++++++++++++++++++++++++++++++++
hasher-priv/communication.h | 79 ++++++++
hasher-priv/config.c | 145 ++++++++++++-
hasher-priv/epoll.c | 39 ++++
hasher-priv/epoll.h | 18 ++
hasher-priv/hasher-priv.c | 78 +++++++
hasher-priv/hasher-privd.c | 381 ++++++++++++++++++++++++++++++++++
hasher-priv/io_log.c | 2 +-
hasher-priv/io_x11.c | 2 +-
hasher-priv/killuid.c | 2 +-
hasher-priv/logging.c | 71 +++++++
hasher-priv/logging.h | 55 +++++
hasher-priv/main.c | 75 -------
hasher-priv/pass.c | 117 ++++++++++-
hasher-priv/pidfile.c | 129 ++++++++++++
hasher-priv/pidfile.h | 44 ++++
hasher-priv/priv.h | 33 +--
hasher-priv/server.conf | 13 ++
hasher-priv/sockets.c | 183 +++++++++++++++++
hasher-priv/sockets.h | 32 +++
hasher-priv/x11.c | 1 +
28 files changed, 2663 insertions(+), 247 deletions(-)
create mode 100644 hasher-priv/caller_server.c
create mode 100644 hasher-priv/caller_task.c
create mode 100644 hasher-priv/communication.c
create mode 100644 hasher-priv/communication.h
create mode 100644 hasher-priv/epoll.c
create mode 100644 hasher-priv/epoll.h
create mode 100644 hasher-priv/hasher-priv.c
create mode 100644 hasher-priv/hasher-privd.c
create mode 100644 hasher-priv/logging.c
create mode 100644 hasher-priv/logging.h
delete mode 100644 hasher-priv/main.c
create mode 100644 hasher-priv/pidfile.c
create mode 100644 hasher-priv/pidfile.h
create mode 100644 hasher-priv/server.conf
create mode 100644 hasher-priv/sockets.c
create mode 100644 hasher-priv/sockets.h
diff --git a/hasher-priv/.gitignore b/hasher-priv/.gitignore
index 5ca24b0..8f0f658 100644
--- a/hasher-priv/.gitignore
+++ b/hasher-priv/.gitignore
@@ -4,6 +4,7 @@ getconf.sh
getugid1.sh
getugid2.sh
hasher-priv
+hasher-privd
hasher-priv.8
hasher-priv.conf.5
hasher-useradd
diff --git a/hasher-priv/DESIGN b/hasher-priv/DESIGN
index 1470ce7..8a57d2d 100644
--- a/hasher-priv/DESIGN
+++ b/hasher-priv/DESIGN
@@ -1,35 +1,63 @@
-Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow:
+Here is the control flow of hasher-priv (euid=user,egid=hashman,gid!=egid):
+ sanitize file descriptors
+ set safe umask
+ ensure 0,1,2 are valid file descriptors
+ close all the rest
+ parse command line arguments
- + check for non-zero argument list
- + parse -h, --help and -number options
- + caller_num initialized here
- + parse arguments, abort if wrong
- + chroot_path and chroot_argv are initialized here
- + valid arguments are:
- getconf
- killuid
- getugid1
- chrootuid1 <chroot path> <program> [program args]
- getugid2
- chrootuid2 <chroot path> <program> [program args]
++ connect to /var/run/hasher-priv socket
+ + wait for the creation of a session server
+ + close socket
++ connect to session server by /var/run/hasher-priv-UID socket
++ send command to start new task
+ + receive a result code from the server
++ send current stdout and stderr to server
+ + receive a result code from the server
++ send task arguments
+ + receive a result code from the server
++ send environment variables
+ + receive a result code from the server
++ send command to run task
+ + receive a task result code from the server
+
+Here is the control flow of hasher-privd (euid=root,egid=hashman,gid==egid):
++ parse command line arguments
+ + parse -h and --help options
++ set safe umask
++ check pidfile, abort if server already running
++ daemonize
+ + change current working directory to "/"
+ + close all file descriptors
++ initialize logger
++ create pidfile
++ examine and change blocked signals
++ I/O event notification and add file descriptors
+ + create a file descriptor for accepting signals
+ + create and listen server socket
++ wait for incoming caller connections
+ + handle signal if signal is received
+ + close caller's session
+ + handle connection if the caller opened a new connection
+ + get connection credentials
+ + fork new process for caller if don't have any
+ + notify the client if the session server already running
+ + close caller connection
++ close all descriptors in notification poll
++ close and remove pidfile
+
+Here is control flow for session server:
+ initialize data related to caller
- + caller_uid initialized here from getuid()
+ + caller_uid initialized here by uid received via the socket
+ caller_uid must be valid uid
- + caller_gid initialized here from getgid()
+ + caller_gid initialized here by uid received via the socket
+ caller_gid must be valid gid
- + caller_user initialized here from getpwnam(LOGNAME)->pw_name or
- getpwuid(caller_uid)->pw_name
+ + caller_user initialized here from getpwuid(caller_uid)->pw_name
+ must be true: caller_uid == pw->pw_uid
+ must be true: caller_gid == pw->pw_gid
+ caller_home initialized here
+ caller_user's home directory must exist
-+ read work limit hints from environment variables
-+ drop all environment variables
++ set safe umask
++ open a caller-specific socket
+ load configuration
+ safe chdir to /etc/hasher-priv
+ safe load "system" file
@@ -40,7 +68,7 @@ Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow:
prefix
umask
nice
- allowed_devices
+ allow_ttydev
allowed_mountpoints
rlimit_(hard|soft)_*
wlimit_(time_elapsed|time_idle|bytes_written)
@@ -53,85 +81,136 @@ Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow:
+ change_user1 and change_user2 should be initialized here
+ change_uid1 and change_gid1 initialized from change_user1
+ change_uid2 and change_gid2 initialized from change_user2
-+ execute choosen task
- + getconf: print config file /etc/hasher-priv/user.d/caller_user[:caller_num]
- + getugid1: print change_uid1:change_gid1 pair
- + getugid2: print change_uid2:change_gid2 pair
- + killuid
- + check for valid uids specified
- + drop dumpable flag (just in case - not required)
- + setuid to specified uid pair
- + kill (-1, SIGKILL)
- + purge all SYSV IPC objects belonging to specified uid pair
- + chrootuid1/chrootuid2
- + check for valid uid specified
- + setup mounts and devices
- + unshare mount namespace
- + safe chdir to chroot_path
- + safe chdir to dev
- + mount dev
- + create devices available to all users: null, zero, full, random, urandom
- + if makedev_console environment variable is true,
- create devices available to root only: console, tty0, fb0
- + mount /dev/shm
- + mount all mountpoints specified by requested_mountpoints environment variable
- + if /dev/pts was mounted, create devices: tty, ptmx
- + safe chdir to chroot_path
- + sanitize file descriptors again
- + if use_pty is disabled, create pipe to handle child's stdout and stderr
- + if X11 forwarding is requested, create socketpair and
- open /tmp/.X11-unix directory readonly for later use with fchdir()
- + unless share_ipc is enabled, isolate System V IPC namespace
- + unless share_uts is enabled, unshare UTS namespace
- + if X11 forwarding to a tcp address was not requested,
- unless share_network is enabled, unshare network
- + clear supplementary group access list
- + create pty:
- + temporarily switch to called_uid:caller_gid
- + open /dev/ptmx
- + fetch pts number
- + unlock pts pair
- + open pts slave
- + switch uid:gid back
- + chroot to "."
- + create another pty if possible:
- + temporarily switch to called_uid:caller_gid
- + safely chdir to "dev"
- + if "pts/ptmx" is available with right permissions:
- + replace "ptmx" with a symlink to "pts/ptmx"
- + open "ptmx"
- + fetch pts number
- + unlock pts pair
- + open pts slave
- + chdir back to "/"
- + switch uid:gid back
- + if another pty was crated, close pts pair that was opened earlier
- + set rlimits
- + set close-on-exec flag on all non-standard descriptors
- + fork
- + in parent:
- + setgid/setuid to caller user
- + install CHLD signal handler
- + unblock master pty and pipe descriptors
- + if use_pty is enabled, initialize tty and install WINCH signal handler
- + listen to "/dev/log"
- + while work limits are not exceeded, handle child input/output
- + close master pty descriptor, thus sending HUP to child session
- + wait for child process termination
- + remove CHLD signal handler
- + return child proccess exit code
- + in child:
- + if X11 forwarding to a tcp address was requested,
++ I/O event notification and add file descriptors
+ + create a file descriptor for accepting signals
+ + create and listen the session socket
++ notify the client that the session server is ready
++ wait for incomming caller connections
+ + handle signal if signal is received
+ + handle connection if the caller opened a new connection
+ + task handler
+ + reset timeout timer
+ + finish the server if the task doesn't arrive from the caller
+ for more than a minute.
+
+Here is control flow for the task handler:
++ receive task header
+ + receive client's stdin, stdout and stderr
+ + check number of arguments
++ receive task arguments if we expect them
++ receive environment variables if client want to send them
++ fork process to handle task
+ + in parent:
+ + wait exit status
+ + in child:
+ + replace stdin, stdout and stderr with those that were received
+ + drop all environment variables
+ + apply the client's environment variables
+ + sanitize file descriptors
+ + set safe umask
+ + ensure 0,1,2 are valid file descriptors
+ + close all the rest
+ + parse task arguments
+ + check for non-zero argument list
+ + parse -h, --help and -number options
+ + caller_num initialized here
+ + parse arguments, abort if wrong
+ + chroot_path and chroot_argv are initialized here
+ + valid arguments are:
+ getconf
+ killuid
+ getugid1
+ chrootuid1 <chroot path> <program> [program args]
+ getugid2
+ chrootuid2 <chroot path> <program> [program args]
+ + caller_num initialized from task
+ + chroot_path and chroot_argv are initialized here
+ + read work limit hints from environment variables
+ + drop all environment variables
+ + execute choosen task
+ + getconf: print config file /etc/hasher-priv/user.d/caller_user[:caller_num]
+ + getugid1: print change_uid1:change_gid1 pair
+ + getugid2: print change_uid2:change_gid2 pair
+ + killuid
+ + check for valid uids specified
+ + drop dumpable flag (just in case - not required)
+ + setuid to specified uid pair
+ + kill (-1, SIGKILL)
+ + purge all SYSV IPC objects belonging to specified uid pair
+ + chrootuid1/chrootuid2
+ + fork to kill the processes that were left from the previous chrootuid call
+ + in parent:
+ + wait exit status
+ + in child:
+ + killuid
+ + check for valid uid specified
+ + setup mounts and devices
+ + unshare mount namespace
+ + safe chdir to chroot_path
+ + safe chdir to dev
+ + mount dev
+ + create devices available to all users: null, zero, full, random, urandom
+ + if makedev_console environment variable is true,
+ create devices available to root only: console, tty0, fb0
+ + mount /dev/shm
+ + mount all mountpoints specified by requested_mountpoints environment variable
+ + if /dev/pts was mounted, create devices: tty, ptmx
+ + safe chdir to chroot_path
+ + sanitize file descriptors again
+ + if use_pty is disabled, create pipe to handle child's stdout and stderr
+ + if X11 forwarding is requested, create socketpair and
+ open /tmp/.X11-unix directory readonly for later use with fchdir()
+ + unless share_ipc is enabled, isolate System V IPC namespace
+ + unless share_uts is enabled, unshare UTS namespace
+ + if X11 forwarding to a tcp address was not requested,
unless share_network is enabled, unshare network
- + setgid/setuid to specified user
- + setsid
- + change controlling terminal to pty
- + redirect stdin if required, either to null or to pty
- + redirect stdout and stderr either to pipe or to pty
- + set nice
- + if X11 forwarding is requested,
- + add X11 auth entry using xauth utility
- + create and bind unix socket for X11 forwarding
- + send listening descriptor and fake auth data to the parent
- + set umask
- + execute specified program
+ + clear supplementary group access list
+ + create pty:
+ + temporarily switch to called_uid:caller_gid
+ + open /dev/ptmx
+ + fetch pts number
+ + unlock pts pair
+ + open pts slave
+ + switch uid:gid back
+ + chroot to "."
+ + create another pty if possible:
+ + temporarily switch to called_uid:caller_gid
+ + safely chdir to "dev"
+ + if "pts/ptmx" is available with right permissions:
+ + replace "ptmx" with a symlink to "pts/ptmx"
+ + open "ptmx"
+ + fetch pts number
+ + unlock pts pair
+ + open pts slave
+ + chdir back to "/"
+ + switch uid:gid back
+ + if another pty was crated, close pts pair that was opened earlier
+ + set rlimits
+ + set close-on-exec flag on all non-standard descriptors
+ + fork
+ + in parent:
+ + setgid/setuid to caller user
+ + install CHLD signal handler
+ + unblock master pty and pipe descriptors
+ + if use_pty is enabled, initialize tty and install WINCH signal handler
+ + listen to "/dev/log"
+ + while work limits are not exceeded, handle child input/output
+ + close master pty descriptor, thus sending HUP to child session
+ + wait for child process termination
+ + remove CHLD signal handler
+ + return child proccess exit code
+ + in child:
+ + if X11 forwarding to a tcp address was requested,
+ unless share_network is enabled, unshare network
+ + setgid/setuid to specified user
+ + setsid
+ + change controlling terminal to pty
+ + redirect stdin if required, either to null or to pty
+ + redirect stdout and stderr either to pipe or to pty
+ + set nice
+ + if X11 forwarding is requested,
+ + add X11 auth entry using xauth utility
+ + create and bind unix socket for X11 forwarding
+ + send listening descriptor and fake auth data to the parent
+ + set umask
+ + execute specified program
diff --git a/hasher-priv/Makefile b/hasher-priv/Makefile
index a815e9e..ce55274 100644
--- a/hasher-priv/Makefile
+++ b/hasher-priv/Makefile
@@ -11,7 +11,7 @@ VERSION = $(shell sed '/^Version: */!d;s///;q' hasher-priv.spec)
HELPERS = getconf.sh getugid1.sh chrootuid1.sh getugid2.sh chrootuid2.sh
MAN5PAGES = $(PROJECT).conf.5
MAN8PAGES = $(PROJECT).8 hasher-useradd.8
-TARGETS = $(PROJECT) hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
+TARGETS = $(PROJECT) hasher-privd hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
sysconfdir = /etc
libexecdir = /usr/lib
@@ -21,6 +21,7 @@ man5dir = $(mandir)/man5
man8dir = $(mandir)/man8
configdir = $(sysconfdir)/$(PROJECT)
helperdir = $(libexecdir)/$(PROJECT)
+socketdir = /run/hasher-priv
DESTDIR =
MKDIR_P = mkdir -p
@@ -33,17 +34,25 @@ WARNINGS = -Wall -W -Wshadow -Wpointer-arith -Wwrite-strings \
-Wmissing-prototypes -Wmissing-declarations -Wmissing-noreturn \
-Wmissing-format-attribute -Wredundant-decls -Wdisabled-optimization
CPPFLAGS = -std=gnu99 -D_GNU_SOURCE $(CHDIRUID_FLAGS) \
- $(LFS_CFLAGS) -DPROJECT_VERSION=\"$(VERSION)\"
+ $(LFS_CFLAGS) -DPROJECT_VERSION=\"$(VERSION)\" \
+ -DSOCKETDIR=\"$(socketdir)\" -DPROJECT=\"$(PROJECT)\"
CFLAGS = -pipe -O2
override CFLAGS += $(WARNINGS)
LDLIBS =
-SRC = caller.c chdir.c chdiruid.c chid.c child.c chrootuid.c cmdline.c \
+SRC = hasher-priv.c cmdline.c fds.c sockets.c logging.c communication.c xmalloc.c pass.c
+OBJ = $(SRC:.c=.o)
+
+server_SRC = hasher-privd.c \
+ communication.c epoll.c pidfile.c logging.c sockets.c \
+ caller.c caller_server.c caller_task.c \
+ chdir.c chdiruid.c chid.c child.c chrootuid.c cmdline.c \
config.c fds.c getconf.c getugid.c ipc.c killuid.c io_log.c io_x11.c \
- main.c makedev.c mount.c net.c parent.c pass.c pty.c signal.c tty.c \
+ makedev.c mount.c net.c parent.c pass.c pty.c signal.c tty.c \
unshare.c xmalloc.c x11.c
-OBJ = $(SRC:.c=.o)
-DEP = $(SRC:.c=.d)
+server_OBJ = $(server_SRC:.c=.o)
+
+DEP = $(SRC:.c=.d) $(server_SRC:.c=.d)
.PHONY: all install clean indent
@@ -52,14 +61,19 @@ all: $(TARGETS)
$(PROJECT): $(OBJ)
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
+hasher-privd: $(server_OBJ)
+ $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
+
install: all
$(MKDIR_P) -m710 $(DESTDIR)$(configdir)/user.d
$(INSTALL) -p -m640 fstab $(DESTDIR)$(configdir)/fstab
$(INSTALL) -p -m640 system.conf $(DESTDIR)$(configdir)/system
+ $(INSTALL) -p -m640 server.conf $(DESTDIR)$(configdir)/server
$(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
$(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
$(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
$(MKDIR_P) -m755 $(DESTDIR)$(sbindir)
+ $(INSTALL) -p -m755 hasher-privd $(DESTDIR)$(sbindir)/
$(INSTALL) -p -m755 hasher-useradd $(DESTDIR)$(sbindir)/
$(MKDIR_P) -m755 $(DESTDIR)$(man5dir)
$(INSTALL) -p -m644 $(MAN5PAGES) $(DESTDIR)$(man5dir)/
@@ -67,7 +81,7 @@ install: all
$(INSTALL) -p -m644 $(MAN8PAGES) $(DESTDIR)$(man8dir)/
clean:
- $(RM) $(TARGETS) $(DEP) $(OBJ) core *~
+ $(RM) $(TARGETS) $(DEP) $(OBJ) $(server_OBJ) core *~
indent:
indent *.h *.c
diff --git a/hasher-priv/caller.c b/hasher-priv/caller.c
index e83084a..031ddef 100644
--- a/hasher-priv/caller.c
+++ b/hasher-priv/caller.c
@@ -19,62 +19,67 @@
#include "priv.h"
#include "xmalloc.h"
+#include "logging.h"
-const char *caller_user, *caller_home;
-uid_t caller_uid;
-gid_t caller_gid;
+char *caller_user = NULL;
+char *caller_home = NULL;
+uid_t caller_uid;
+gid_t caller_gid;
/*
* Initialize caller_user, caller_uid, caller_gid and caller_home.
*/
-void
-init_caller_data(void)
+int
+init_caller_data(uid_t uid, gid_t gid)
{
- const char *logname;
struct passwd *pw = 0;
- caller_uid = getuid();
- if (caller_uid < MIN_CHANGE_UID)
- error(EXIT_FAILURE, 0, "caller has invalid uid: %u",
- caller_uid);
-
- caller_gid = getgid();
- if (caller_gid < MIN_CHANGE_GID)
- error(EXIT_FAILURE, 0, "caller has invalid gid: %u",
- caller_gid);
-
- if ((logname = getenv("LOGNAME")))
- if (!*logname || strchr(logname, ':'))
- logname = 0;
-
- if (logname)
- {
- pw = getpwnam(logname);
- if (caller_uid != pw->pw_uid || caller_gid != pw->pw_gid)
- pw = 0;
+ caller_uid = uid;
+ if (caller_uid < MIN_CHANGE_UID) {
+ err("caller has invalid uid: %u", caller_uid);
+ return -1;
+ }
+
+ caller_gid = gid;
+ if (caller_gid < MIN_CHANGE_GID) {
+ err("caller has invalid gid: %u", caller_gid);
+ return -1;
}
- if (!pw)
- pw = getpwuid(caller_uid);
+ pw = getpwuid(caller_uid);
- if (!pw || !pw->pw_name)
- error(EXIT_FAILURE, 0, "caller lookup failure");
+ if (!pw || !pw->pw_name) {
+ err("caller lookup failure");
+ return -1;
+ }
caller_user = xstrdup(pw->pw_name);
- if (caller_uid != pw->pw_uid)
- error(EXIT_FAILURE, 0, "caller %s: uid mismatch",
- caller_user);
+ if (caller_uid != pw->pw_uid) {
+ err("caller %s: uid mismatch", caller_user);
+ return -1;
+ }
- if (caller_gid != pw->pw_gid)
- error(EXIT_FAILURE, 0, "caller %s: gid mismatch",
- caller_user);
+ if (caller_gid != pw->pw_gid) {
+ err("caller %s: gid mismatch", caller_user);
+ return -1;
+ }
errno = 0;
if (pw->pw_dir && *pw->pw_dir)
caller_home = canonicalize_file_name(pw->pw_dir);
- if (!caller_home || !*caller_home)
- error(EXIT_FAILURE, errno, "caller %s: invalid home",
- caller_user);
+ if (!caller_home || !*caller_home) {
+ err("caller %s: invalid home: %m", caller_user);
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+free_caller_data(void)
+{
+ free(caller_user);
+ free(caller_home);
}
diff --git a/hasher-priv/caller_server.c b/hasher-priv/caller_server.c
new file mode 100644
index 0000000..abc2746
--- /dev/null
+++ b/hasher-priv/caller_server.c
@@ -0,0 +1,382 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
+
+ The per-calling-user server part of the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <linux/un.h>
+
+#include <sys/socket.h> /* SOCK_CLOEXEC */
+#include <sys/prctl.h>
+#include <sys/signalfd.h>
+#include <sys/wait.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <grp.h>
+
+#include "priv.h"
+#include "xmalloc.h"
+#include "sockets.h"
+#include "logging.h"
+#include "epoll.h"
+#include "communication.h"
+
+static char socketpath[UNIX_PATH_MAX];
+
+static int
+validate_arguments(task_t task, char **argv)
+{
+ int required_args = 0, more_args = 0;
+ int rc = -1;
+ int argc = 0;
+
+ while (argv && argv[argc])
+ argc++;
+
+ switch (task) {
+ case TASK_GETCONF:
+ case TASK_KILLUID:
+ case TASK_GETUGID1:
+ case TASK_GETUGID2:
+ required_args = 0;
+ break;
+ case TASK_CHROOTUID1:
+ case TASK_CHROOTUID2:
+ more_args = 1;
+ required_args = 2;
+ break;
+ default:
+ err("unknown task type: %u", task);
+ return rc;
+ }
+
+ if (argc < 0)
+ err("number of arguments must be a positive but got %d",
+ argc);
+
+ else if (argc < required_args)
+ err("%s task requires at least %d arguments but got %d",
+ task2str(task), required_args, argc);
+
+ else if (argc > required_args && !more_args)
+ err("%s task requires exactly %d arguments but got %d",
+ task2str(task), required_args, argc);
+
+ else
+ rc = 0;
+
+ return rc;
+}
+
+static void
+free_task(struct task *task)
+{
+ if (task->stdin > 0)
+ close(task->stdin);
+ if (task->stdout > 0)
+ close(task->stdout);
+ if (task->stderr > 0)
+ close(task->stderr);
+
+ if (task->env) {
+ free(task->env[0]);
+ free(task->env);
+ }
+
+ if (task->argv) {
+ free(task->argv[0]);
+ free(task->argv);
+ }
+}
+
+static int
+cancel_task(int conn, struct task *task)
+{
+ free_task(task);
+ send_command_response(conn, CMD_STATUS_FAILED, "command failed");
+ return 0;
+}
+
+static int
+process_task(int conn)
+{
+ uid_t uid;
+ gid_t gid;
+
+ if (get_peercred(conn, NULL, &uid, &gid) < 0)
+ return -1;
+
+ if (caller_uid != uid || caller_gid != gid) {
+ err("user (uid=%d) has no permission to send commands"
+ " to the session of another user (uid=%d)",
+ uid, caller_uid);
+ return -1;
+ }
+
+ struct task task = { 0 };
+ int fds[3];
+
+ while (1) {
+ task_t type = TASK_NONE;
+ struct cmd hdr = { 0 };
+
+ if (xrecvmsg(conn, &hdr, sizeof(hdr)) < 0)
+ return cancel_task(conn, &task);
+
+ switch (hdr.type) {
+ case CMD_TASK_BEGIN:
+ if (hdr.datalen != sizeof(type))
+ return cancel_task(conn, &task);
+
+ if (xrecvmsg(conn, &type, hdr.datalen) < 0)
+ return cancel_task(conn, &task);
+
+ task.type = type;
+
+ break;
+
+ case CMD_TASK_FDS:
+ if (hdr.datalen != sizeof(int) * 3)
+ return cancel_task(conn, &task);
+
+ if (task.stdin)
+ close(task.stdin);
+
+ if (task.stdout)
+ close(task.stdout);
+
+ if (task.stderr)
+ close(task.stderr);
+
+ if (fds_recv(conn, fds, 3) < 0)
+ return cancel_task(conn, &task);
+
+ task.stdin = fds[0];
+ task.stdout = fds[1];
+ task.stderr = fds[2];
+
+ break;
+
+ case CMD_TASK_ARGUMENTS:
+ if (task.argv) {
+ free(task.argv[0]);
+ free(task.argv);
+ }
+
+ if (recv_list(conn, &task.argv, hdr.datalen) < 0)
+ return cancel_task(conn, &task);
+
+ if (validate_arguments(task.type, task.argv) < 0)
+ return cancel_task(conn, &task);
+
+ break;
+
+ case CMD_TASK_ENVIRON:
+ if (task.env) {
+ free(task.env[0]);
+ free(task.env);
+ }
+
+ if (recv_list(conn, &task.env, hdr.datalen) < 0)
+ return cancel_task(conn, &task);
+
+ break;
+
+ case CMD_TASK_RUN:
+ /* process_caller_task() sends a response by itself. */
+ process_caller_task(conn, &task);
+ free_task(&task);
+ return 0;
+
+ default:
+ err("unsupported command: %d", hdr.type);
+ }
+
+ send_command_response(conn, CMD_STATUS_DONE, NULL);
+ }
+
+ return 0;
+}
+
+static int
+caller_server(int cl_conn)
+{
+ int ret = 0;
+
+ umask(077);
+
+ snprintf(socketpath, sizeof(socketpath),
+ "%d:%u",
+ caller_uid, caller_num);
+
+ int fd_conn = -1;
+ int fd_signal = -1;
+ int fd_ep = -1;
+
+ if ((fd_conn = srv_listen(SOCKETDIR, socketpath)) < 0)
+ return -1;
+
+ snprintf(socketpath, sizeof(socketpath),
+ "%s/%d:%u",
+ SOCKETDIR, caller_uid, caller_num);
+
+ if (chown(socketpath, caller_uid, caller_gid)) {
+ err("fchown: %s: %m", socketpath);
+ ret = -1;
+ goto fail;
+ }
+
+ /* Load config according to caller information. */
+ configure();
+
+ sigset_t mask;
+
+ sigfillset(&mask);
+ sigprocmask(SIG_SETMASK, &mask, NULL);
+
+ if ((fd_signal = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)) < 0) {
+ err("signalfd: %m");
+ ret = -1;
+ goto fail;
+ }
+
+ if ((fd_ep = epoll_create1(EPOLL_CLOEXEC)) < 0) {
+ err("epoll_create1: %m");
+ ret = -1;
+ goto fail;
+ }
+
+ if (epollin_add(fd_ep, fd_signal) < 0 || epollin_add(fd_ep, fd_conn) < 0) {
+ err("epollin_add: failed");
+ ret = -1;
+ goto fail;
+ }
+
+ /* Tell the client that caller server is ready */
+ send_command_response(cl_conn, CMD_STATUS_DONE, NULL);
+ close(cl_conn);
+
+ unsigned long nsec = 0;
+ int finish_server = 0;
+
+ while (!finish_server) {
+ struct epoll_event ev[42];
+ int fdcount;
+
+ errno = 0;
+ if ((fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), 1000)) < 0) {
+ if (errno == EINTR)
+ continue;
+ err("epoll_wait: %m");
+ break;
+ }
+
+ if (fdcount == 0) {
+ nsec++;
+
+ if (nsec >= server_session_timeout)
+ break;
+
+ } else for (int i = 0; i < fdcount; i++) {
+ if (!(ev[i].events & EPOLLIN)) {
+ continue;
+
+ } else if (ev[i].data.fd == fd_signal) {
+ struct signalfd_siginfo fdsi;
+ ssize_t size;
+
+ size = read_retry(fd_signal, &fdsi, sizeof(fdsi));
+
+ if (size != sizeof(fdsi)) {
+ err("unable to read signal info from signalfd");
+ continue;
+ }
+
+ int status;
+
+ switch (fdsi.ssi_signo) {
+ case SIGINT:
+ case SIGTERM:
+ finish_server = 1;
+ break;
+ case SIGCHLD:
+ if (waitpid(-1, &status, 0) < 0)
+ err("waitpid: %m");
+ break;
+ }
+
+ } else if (ev[i].data.fd == fd_conn) {
+ int conn;
+
+ if ((conn = accept4(fd_conn, NULL, 0, SOCK_CLOEXEC)) < 0) {
+ err("accept4: %m");
+ continue;
+ }
+
+ if (set_recv_timeout(conn, 3) < 0) {
+ close(conn);
+ continue;
+ }
+
+ if (!process_task(conn)) {
+ /* reset timer */
+ nsec = 0;
+ }
+
+ close(conn);
+ }
+ }
+ }
+fail:
+ epollin_remove(fd_ep, fd_signal);
+ epollin_remove(fd_ep, fd_conn);
+
+ if (fd_ep >= 0)
+ close(fd_ep);
+
+ unlink(socketpath);
+
+ return ret;
+}
+
+pid_t
+fork_server(int cl_conn, uid_t uid, gid_t gid, unsigned num)
+{
+ pid_t pid = fork();
+
+ if (pid != 0) {
+ if (pid < 0) {
+ err("fork: %m");
+ return -1;
+ }
+ return pid;
+ }
+
+ /* From now on the child sends its own failure responses. */
+ caller_num = num;
+
+ int ret = init_caller_data(uid, gid);
+
+ if (ret >= 0) {
+ info("%s(%d) num=%u: start session server", caller_user, caller_uid, caller_num);
+ ret = caller_server(cl_conn);
+ info("%s(%d): finish session server", caller_user, caller_uid);
+ }
+
+ if (ret < 0) {
+ send_command_response(cl_conn, CMD_STATUS_FAILED, NULL);
+ ret = EXIT_FAILURE;
+ } else {
+ ret = EXIT_SUCCESS;
+ }
+
+ free_caller_data();
+ close(cl_conn);
+
+ exit(ret);
+}
diff --git a/hasher-priv/caller_task.c b/hasher-priv/caller_task.c
new file mode 100644
index 0000000..8965196
--- /dev/null
+++ b/hasher-priv/caller_task.c
@@ -0,0 +1,217 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
+
+ The task handling part of the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#include "communication.h"
+#include "xmalloc.h"
+#include "logging.h"
+#include "sockets.h"
+#include "priv.h"
+
+static int
+killprevious(void)
+{
+ int rc = 0;
+ pid_t pid = fork();
+
+ if (pid < 0) {
+ err("fork: %m");
+ return -1;
+ }
+
+ if (!pid)
+ exit(do_killuid());
+
+ while (1) {
+ int wstatus;
+
+ if (waitpid(pid, &wstatus, WUNTRACED | WCONTINUED) < 0) {
+ err("waitpid: %m");
+ rc = -1;
+ break;
+ }
+
+ if (WIFEXITED(wstatus)) {
+ rc = WEXITSTATUS(wstatus);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+static int
+reopen_fd(int oldfd, int newfd)
+{
+ if (oldfd < 0)
+ return 0;
+
+ close(newfd);
+
+ if (dup2(oldfd, newfd) < 0) {
+ err("dup2: %m");
+ return -1;
+ }
+
+ close(oldfd);
+
+ return 0;
+}
+
+static int
+reopen_iostreams(int stdin, int stdout, int stderr)
+{
+ return (reopen_fd(stdin, STDIN_FILENO) < 0 ||
+ reopen_fd(stdout, STDOUT_FILENO) < 0 ||
+ reopen_fd(stderr, STDERR_FILENO) < 0) ? -1 : 0;
+}
+
+static int
+caller_task(struct task *task)
+{
+ int rc = EXIT_FAILURE;
+ int i = 0;
+ pid_t pid;
+
+ pid = fork();
+ if (pid < 0) {
+ err("fork: %m");
+ return -1;
+ }
+ if (pid != 0) {
+ return pid;
+ }
+
+ if ((rc = reopen_iostreams(task->stdin, task->stdout, task->stderr)) < 0)
+ exit(rc);
+
+ /* clean up environment to avoid side effects. */
+ if (clearenv() != 0)
+ fatal("clearenv: %m");
+
+ while (task->env && task->env[i]) {
+ if (putenv(task->env[i++]) != 0)
+ fatal("putenv: %m");
+ }
+
+ /* First, check and sanitize file descriptors. */
+ sanitize_fds();
+
+ /* Second, parse task arguments. */
+ parse_task_args(task->type, (const char **) task->argv);
+
+ /* Third, ensure correctness of chroot path. */
+ if (chroot_path && *chroot_path != '/')
+ fatal("%s: invalid chroot path", chroot_path);
+
+ /* Fourth, parse environment for config options. */
+ parse_env();
+
+ /* We won't need environment variables any longer. */
+ if (clearenv() != 0)
+ fatal("clearenv: %m");
+
+ /* Finally, execute the requested task. */
+ switch (task->type) {
+ case TASK_GETCONF:
+ rc = do_getconf();
+ break;
+ case TASK_KILLUID:
+ rc = do_killuid();
+ break;
+ case TASK_GETUGID1:
+ rc = do_getugid1();
+ break;
+ case TASK_CHROOTUID1:
+ rc = !killprevious()
+ ? do_chrootuid1()
+ : EXIT_FAILURE;
+ break;
+ case TASK_GETUGID2:
+ rc = do_getugid2();
+ break;
+ case TASK_CHROOTUID2:
+ rc = !killprevious()
+ ? do_chrootuid2()
+ : EXIT_FAILURE;
+ break;
+ default:
+ fatal("unknown task %d", task->type);
+ }
+
+ /* Write out all the user-space-buffered data */
+ fflush(stdout);
+ fflush(stderr);
+
+ exit(rc);
+}
+
+int
+process_caller_task(int conn, struct task *task)
+{
+ int rc = EXIT_FAILURE;
+ pid_t pid, cpid;
+
+ pid = fork();
+ if (pid < 0) {
+ err("fork: %m");
+ return -1;
+ }
+ if (pid != 0) {
+ return pid;
+ }
+
+ if ((cpid = caller_task(task)) > 0) {
+ while (1) {
+ pid_t w;
+ int wstatus;
+
+ if ((w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED)) < 0) {
+ err("waitpid: %m");
+ break;
+ }
+
+ if (WIFEXITED(wstatus)) {
+ rc = WEXITSTATUS(wstatus);
+ info("%s: process %d exited, status=%d", task2str(task->type), cpid, WEXITSTATUS(wstatus));
+ break;
+ }
+
+ if (WIFSIGNALED(wstatus)) {
+ info("%s: process %d killed by signal %d", task2str(task->type), cpid, WTERMSIG(wstatus));
+ break;
+ }
+ }
+ }
+
+ if (task->env) {
+ free(task->env[0]);
+ free(task->env);
+ }
+
+ if (task->argv) {
+ free(task->argv[0]);
+ free(task->argv);
+ }
+
+ /* Notify the client about the results. */
+ (rc == EXIT_FAILURE)
+ ? send_command_response(conn, CMD_STATUS_FAILED, "command failed")
+ : send_command_response(conn, CMD_STATUS_DONE, NULL);
+
+ exit(rc);
+}
diff --git a/hasher-priv/cmdline.c b/hasher-priv/cmdline.c
index 1cdfe23..aba802f 100644
--- a/hasher-priv/cmdline.c
+++ b/hasher-priv/cmdline.c
@@ -80,6 +80,8 @@ const char *chroot_path;
const char **chroot_argv;
unsigned caller_num;
+const char **task_args;
+
static unsigned
get_caller_num(const char *str)
{
@@ -126,40 +128,57 @@ parse_cmdline(int argc, const char *argv[])
if (ac < 1)
show_usage("insufficient arguments");
+ task_args = NULL;
+
if (!strcmp("getconf", av[0]))
{
if (ac != 1)
show_usage("%s: invalid usage", av[0]);
+ task_args = av + 1;
return TASK_GETCONF;
} else if (!strcmp("killuid", av[0]))
{
if (ac != 1)
show_usage("%s: invalid usage", av[0]);
+ task_args = av + 1;
return TASK_KILLUID;
} else if (!strcmp("getugid1", av[0]))
{
if (ac != 1)
show_usage("%s: invalid usage", av[0]);
+ task_args = av + 1;
return TASK_GETUGID1;
} else if (!strcmp("chrootuid1", av[0]))
{
if (ac < 3)
show_usage("%s: invalid usage", av[0]);
- chroot_path = av[1];
- chroot_argv = av + 2;
+ task_args = av + 1;
return TASK_CHROOTUID1;
} else if (!strcmp("getugid2", av[0]))
{
if (ac != 1)
show_usage("%s: invalid usage", av[0]);
+ task_args = av + 1;
return TASK_GETUGID2;
} else if (!strcmp("chrootuid2", av[0]))
{
if (ac < 3)
show_usage("%s: invalid usage", av[0]);
- chroot_path = av[1];
- chroot_argv = av + 2;
+ task_args = av + 1;
return TASK_CHROOTUID2;
} else
show_usage("%s: invalid argument", av[0]);
}
+
+void
+parse_task_args(task_t task, const char *argv[])
+{
+ switch (task) {
+ case TASK_CHROOTUID1:
+ case TASK_CHROOTUID2:
+ chroot_path = argv[0];
+ chroot_argv = (const char **)argv + 1;
+ default:
+ break;
+ }
+}
diff --git a/hasher-priv/communication.c b/hasher-priv/communication.c
new file mode 100644
index 0000000..2e3d442
--- /dev/null
+++ b/hasher-priv/communication.c
@@ -0,0 +1,394 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
+
+ This file offers some helper functions for client-server communication.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "priv.h"
+#include "logging.h"
+#include "xmalloc.h"
+#include "sockets.h"
+#include "communication.h"
+
+struct cmd_resp {
+ cmd_status_t status;
+ size_t msglen;
+};
+
+#define taskbuflen 20
+static char taskbuf[taskbuflen];
+
+static const struct cm {
+ const char *name;
+ task_t value;
+} taskmap[] = {
+ { "none", TASK_NONE },
+ { "getconf", TASK_GETCONF },
+ { "killuid", TASK_KILLUID },
+ { "getugid1", TASK_GETUGID1 },
+ { "getugid2", TASK_GETUGID2 },
+ { "chrootuid1", TASK_CHROOTUID1 },
+ { "chrootuid2", TASK_CHROOTUID2 }
+};
+
+static const size_t taskmap_size = ARRAY_SIZE(taskmap);
+
+const char* main_socket_base_name = "daemon";
+
+char *
+task2str(task_t type)
+{
+ size_t i;
+
+ for (i = 0; i < taskmap_size; i++) {
+ if (taskmap[i].value == type)
+ return strncpy(taskbuf, taskmap[i].name, taskbuflen - 1);
+ }
+ return NULL;
+}
+
+task_t
+str2task(char *s)
+{
+ size_t i;
+
+ for (i = 0; i < taskmap_size; i++) {
+ if (!strcmp(taskmap[i].name, s))
+ return taskmap[i].value;
+ }
+ return TASK_NONE;
+}
+
+static int
+send_list(int conn, cmd_t cmd, const char **argv)
+{
+ int i = 0;
+ struct cmd hdr = { .type = cmd };
+
+ while (argv && argv[i])
+ hdr.datalen += strlen(argv[i++]) + 1;
+
+ if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
+ return -1;
+
+ i = 0;
+ while (argv && argv[i]) {
+ if (xsendmsg(conn, (char *) argv[i], strlen(argv[i]) + 1) < 0)
+ return -1;
+ i++;
+ }
+
+ return 0;
+}
+
+int
+recv_list(int conn, char ***argv, size_t datalen)
+{
+ char *args = calloc(1UL, datalen);
+ if (!args) {
+ err("calloc: %m");
+ return -1;
+ }
+
+ if (datalen > 0 && xrecvmsg(conn, args, datalen) < 0) {
+ free(args);
+ return -1;
+ }
+
+ if (!argv) {
+ free(args);
+ return 0;
+ }
+
+ size_t i = 0, n = 0;
+
+ while (args && n < datalen) {
+ i++;
+ n += strnlen(args + n, datalen - n) + 1;
+ }
+
+ char **av = malloc(sizeof(char *) * (i + 1));
+ if (!av) {
+ err("malloc: %m");
+ return -1;
+ }
+ av[i] = NULL;
+
+ i = n = 0;
+
+ while (args && n < datalen) {
+ av[i++] = args + n;
+ n += strnlen(args + n, datalen - n) + 1;
+ }
+
+ *argv = av;
+
+ return 0;
+}
+
+long int
+send_command_response(int conn, cmd_status_t retcode, const char *fmt, ...)
+{
+ va_list ap;
+
+ struct cmd_resp rs = { 0 };
+ struct iovec iov = { 0 };
+ struct msghdr msg = { 0 };
+
+ rs.status = retcode;
+ rs.msglen = 0;
+
+ if (fmt && *fmt) {
+ int len;
+
+ va_start(ap, fmt);
+ len = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+
+ if (len < 0) {
+ err("unable to calculate message size");
+ return -1;
+ }
+
+ rs.msglen = (size_t) len + 1;
+ }
+
+ iov.iov_base = &rs;
+ iov.iov_len = sizeof(rs);
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ errno = 0;
+ if (sendmsg_retry(conn, &msg, MSG_NOSIGNAL) < 0) {
+ /* The client left without waiting for an answer */
+ if (errno == EPIPE)
+ return 0;
+
+ err("sendmsg: %m");
+ return -1;
+ }
+
+ if (!rs.msglen)
+ return 0;
+
+ msg.msg_iov[0].iov_base = malloc(rs.msglen);
+
+ if (!msg.msg_iov[0].iov_base) {
+ err("malloc: %m");
+ return -1;
+ }
+
+ msg.msg_iov[0].iov_len = rs.msglen;
+
+ va_start(ap, fmt);
+ vsnprintf(msg.msg_iov[0].iov_base, msg.msg_iov[0].iov_len, fmt, ap);
+ va_end(ap);
+
+ long int rc = 0;
+ errno = 0;
+ if (sendmsg_retry(conn, &msg, MSG_NOSIGNAL) < 0 && errno != EPIPE) {
+ err("sendmsg: %m");
+ rc = -1;
+ }
+
+ free(msg.msg_iov[0].iov_base);
+
+ return rc;
+}
+
+static int
+recv_command_response(int conn, cmd_status_t *retcode, char **m)
+{
+ struct cmd_resp rs = { 0 };
+
+ if (xrecvmsg(conn, &rs, sizeof(rs)) < 0)
+ return -1;
+
+ if (retcode)
+ *retcode = rs.status;
+
+ if (!m || !rs.msglen)
+ return 0;
+
+ char *x = xcalloc(1UL, rs.msglen);
+ if (xrecvmsg(conn, x, rs.msglen) < 0)
+ return -1;
+
+ *m = x;
+ return 0;
+}
+
+int
+server_command(int conn, cmd_t cmd, const char **args)
+{
+ cmd_status_t status;
+ char *msg = NULL;
+
+ if (send_list(conn, cmd, args) < 0)
+ return -1;
+
+ if (recv_command_response(conn, &status, &msg) < 0) {
+ free(msg);
+ return -1;
+ }
+
+ if (msg && *msg) {
+ err("%s", msg);
+ free(msg);
+ }
+
+ return status == CMD_STATUS_FAILED ? -1 : 0;
+}
+
+int
+server_open_session(const char *dir_name, const char *file_name, unsigned num)
+{
+ int conn = srv_connect(dir_name, file_name);
+
+ if (conn < 0)
+ return -1;
+
+ struct cmd hdr = {
+ .type = CMD_OPEN_SESSION,
+ .datalen = sizeof(num),
+ };
+
+ if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
+ return -1;
+
+ if (xsendmsg(conn, &num, sizeof(num)) < 0)
+ return -1;
+
+ cmd_status_t status;
+ char *msg = NULL;
+
+ if (recv_command_response(conn, &status, &msg) < 0) {
+ free(msg);
+ return -1;
+ }
+
+ close(conn);
+
+ if (msg && *msg) {
+ err("%s", msg);
+ free(msg);
+ }
+
+ return status == CMD_STATUS_FAILED ? -1 : 0;
+}
+
+int
+server_close_session(const char *dir_name, const char *file_name, unsigned num)
+{
+ int conn = srv_connect(dir_name, file_name);
+
+ if (conn < 0)
+ return -1;
+
+ struct cmd hdr = {
+ .type = CMD_CLOSE_SESSION,
+ .datalen = sizeof(num),
+ };
+
+ if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
+ return -1;
+
+ if (xsendmsg(conn, &num, sizeof(num)) < 0)
+ return -1;
+
+ cmd_status_t status;
+ char *msg = NULL;
+
+ if (recv_command_response(conn, &status, &msg) < 0) {
+ free(msg);
+ return -1;
+ }
+
+ close(conn);
+
+ if (msg && *msg) {
+ err("%s", msg);
+ free(msg);
+ }
+
+ return status == CMD_STATUS_FAILED ? -1 : 0;
+}
+
+int
+server_task(int conn, task_t type)
+{
+ struct cmd hdr = {
+ .type = CMD_TASK_BEGIN,
+ .datalen = sizeof(type),
+ };
+
+ if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
+ return -1;
+
+ if (xsendmsg(conn, &type, hdr.datalen) < 0)
+ return -1;
+
+ cmd_status_t status;
+ char *msg = NULL;
+
+ if (recv_command_response(conn, &status, &msg) < 0) {
+ free(msg);
+ return -1;
+ }
+
+ if (msg && *msg) {
+ err("%s", msg);
+ free(msg);
+ }
+
+ return status == CMD_STATUS_FAILED ? -1 : 0;
+}
+
+int
+server_task_fds(int conn)
+{
+ cmd_status_t status;
+ char *msg = NULL;
+
+ int myfds[3] = {
+ STDIN_FILENO,
+ STDOUT_FILENO,
+ STDERR_FILENO,
+ };
+
+ struct cmd hdr = {
+ .type = CMD_TASK_FDS,
+ .datalen = sizeof(myfds),
+ };
+
+ if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
+ return -1;
+
+ if (fds_send(conn, myfds, 3) < 0)
+ return -1;
+
+ if (recv_command_response(conn, &status, &msg) < 0) {
+ free(msg);
+ return -1;
+ }
+
+ if (msg && *msg) {
+ err("%s", msg);
+ free(msg);
+ }
+
+ return status == CMD_STATUS_FAILED ? -1 : 0;
+}
diff --git a/hasher-priv/communication.h b/hasher-priv/communication.h
new file mode 100644
index 0000000..48a4e2d
--- /dev/null
+++ b/hasher-priv/communication.h
@@ -0,0 +1,79 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
+
+ This file offers some helper functions for client-server communication.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef _COMMUNICATION_H_
+#define _COMMUNICATION_H_
+
+#include <stdint.h>
+
+const char* main_socket_base_name;
+
+typedef enum {
+ CMD_NONE = 0,
+
+ /* Master commands */
+ CMD_OPEN_SESSION,
+ CMD_CLOSE_SESSION,
+
+ /* Session commands */
+ CMD_TASK_BEGIN,
+ CMD_TASK_FDS,
+ CMD_TASK_ARGUMENTS,
+ CMD_TASK_ENVIRON,
+ CMD_TASK_RUN,
+
+} cmd_t;
+
+typedef enum {
+ CMD_STATUS_DONE = 0,
+ CMD_STATUS_FAILED,
+} cmd_status_t;
+
+struct cmd {
+ cmd_t type;
+ size_t datalen;
+};
+
+typedef enum {
+ TASK_NONE = 0,
+ TASK_GETCONF,
+ TASK_KILLUID,
+ TASK_GETUGID1,
+ TASK_CHROOTUID1,
+ TASK_GETUGID2,
+ TASK_CHROOTUID2
+} task_t;
+
+struct task {
+ int type;
+ unsigned num;
+ int stdin;
+ int stdout;
+ int stderr;
+ char **argv;
+ char **env;
+};
+
+char *task2str(task_t type);
+task_t str2task(char *s) __attribute__((nonnull(1)));
+
+int recv_list(int conn, char ***argv, size_t datalen);
+
+long int send_command_response(int conn, cmd_status_t retcode, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
+
+int server_command(int conn, cmd_t cmd, const char **args);
+int server_open_session(const char *dir_name, const char *file_name, unsigned caller_num) __attribute__((nonnull(1, 2)));
+int server_close_session(const char *dir_name, const char *file_name, unsigned caller_num) __attribute__((nonnull(1, 2)));
+
+int server_task(int conn, task_t task);
+int server_task_fds(int conn);
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+#endif /* _COMMUNICATION_H_ */
diff --git a/hasher-priv/config.c b/hasher-priv/config.c
index e3fedcd..ce83e15 100644
--- a/hasher-priv/config.c
+++ b/hasher-priv/config.c
@@ -1,8 +1,9 @@
/*
Copyright (C) 2003-2019 Dmitry V. Levin <ldv на altlinux.org>
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
- Configuration support module for the hasher-priv program.
+ Configuration support module for the hasher-privd.
SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -19,13 +20,17 @@
#include <unistd.h>
#include <limits.h>
#include <pwd.h>
+#include <grp.h>
#include "priv.h"
#include "xmalloc.h"
+#include "logging.h"
const char *const *chroot_prefix_list;
const char *chroot_prefix_path;
const char *change_user1, *change_user2;
+char *server_access_group = NULL;
+char *server_pidfile = NULL;
const char *term;
const char *x11_display, *x11_key;
str_list_t allowed_devices;
@@ -33,6 +38,8 @@ str_list_t allowed_mountpoints;
str_list_t requested_mountpoints;
uid_t change_uid1, change_uid2;
gid_t change_gid1, change_gid2;
+gid_t server_gid;
+unsigned long server_session_timeout = 0;
mode_t change_umask = 022;
int change_nice = 8;
int makedev_console;
@@ -42,6 +49,7 @@ int share_caller_network = 0;
int share_ipc = -1;
int share_network = -1;
int share_uts = -1;
+int server_log_priority = -1;
change_rlimit_t change_rlimit[] = {
/* Per-process CPU limit, in seconds. */
@@ -209,7 +217,7 @@ parse_rlim(const char *name, const char *value, const char *optname,
}
static unsigned long
-str2wlim(const char *name, const char *value, const char *filename)
+str2ul(const char *name, const char *value, const char *filename)
{
char *p = 0;
unsigned long long n;
@@ -229,7 +237,7 @@ static void
modify_wlim(unsigned long *pval, const char *value,
const char *optname, const char *filename, int is_system)
{
- unsigned long val = str2wlim(optname, value, filename);
+ unsigned long val = str2ul(optname, value, filename);
if (is_system || *pval == 0 || (val > 0 && val < *pval))
*pval = val;
@@ -633,3 +641,134 @@ parse_env(void)
if ((e = getenv("requested_mountpoints")))
parse_str_list(e, &requested_mountpoints);
}
+
+static void
+check_server_access_group(void)
+{
+ struct group *gr;
+
+ if (!server_access_group || !*server_access_group)
+ error(EXIT_FAILURE, 0, "config: undefined option: access_group");
+
+ gr = getgrnam(server_access_group);
+
+ if (!gr || !gr->gr_name)
+ error(EXIT_FAILURE, 0, "config: access_group: %s lookup failure", server_access_group);
+
+ server_gid = gr->gr_gid;
+}
+
+static void
+set_server_config(const char *name, const char *value, const char *filename)
+{
+ if (!strcasecmp("priority", name)) {
+ server_log_priority = logging_level(value);
+ } else if (!strcasecmp("session_timeout", name)) {
+ server_session_timeout = str2ul(name, value, filename);
+ } else if (!strcasecmp("pidfile", name)) {
+ free(server_pidfile);
+ server_pidfile = xstrdup(value);
+ } else if (!strcasecmp("access_group", name)) {
+ free(server_access_group);
+ server_access_group = xstrdup(value);
+ } else {
+ bad_option_name(name, filename);
+ }
+}
+
+static void
+read_server_config(int fd, const char *name)
+{
+ FILE *fp = fdopen(fd, "r");
+ char buf[BUFSIZ];
+ unsigned line;
+
+ if (!fp)
+ error(EXIT_FAILURE, errno, "fdopen: %s", name);
+
+ for (line = 1; fgets(buf, BUFSIZ, fp); ++line) {
+ const char *start, *left;
+ char *eq, *right, *end;
+
+ for (start = buf; *start && isspace(*start); ++start)
+ ;
+
+ if (!*start || '#' == *start)
+ continue;
+
+ if (!(eq = strchr(start, '=')))
+ error(EXIT_FAILURE, 0, "%s: syntax error at line %u",
+ name, line);
+
+ left = start;
+ right = eq + 1;
+
+ for (; eq > left; --eq)
+ if (!isspace(eq[-1]))
+ break;
+
+ if (left == eq)
+ error(EXIT_FAILURE, 0, "%s: syntax error at line %u",
+ name, line);
+
+ *eq = '\0';
+ end = right + strlen(right);
+
+ for (; right < end; ++right)
+ if (!isspace(*right))
+ break;
+
+ for (; end > right; --end)
+ if (!isspace(end[-1]))
+ break;
+
+ *end = '\0';
+ set_server_config(left, right, name);
+ }
+
+ if (ferror(fp))
+ error(EXIT_FAILURE, errno, "fgets: %s", name);
+
+ if (fclose(fp))
+ error(EXIT_FAILURE, errno, "fclose: %s", name);
+}
+
+static void
+load_server_config(const char *name)
+{
+ struct stat st;
+ int fd = open(name, O_RDONLY | O_NOFOLLOW | O_NOCTTY);
+
+ if (fd < 0)
+ error(EXIT_FAILURE, errno, "open: %s", name);
+
+ if (fstat(fd, &st) < 0)
+ error(EXIT_FAILURE, errno, "fstat: %s", name);
+
+ stat_root_ok_validator(&st, name);
+
+ if (!S_ISREG(st.st_mode))
+ error(EXIT_FAILURE, 0, "%s: not a regular file", name);
+
+ if (st.st_size > MAX_CONFIG_SIZE)
+ error(EXIT_FAILURE, 0, "%s: file too large: %lu",
+ name, (unsigned long) st.st_size);
+
+ read_server_config(fd, name);
+}
+
+void
+configure_server(void)
+{
+ safe_chdir("/", stat_root_ok_validator);
+ safe_chdir("etc/hasher-priv", stat_root_ok_validator);
+ load_server_config("server");
+ check_server_access_group();
+}
+
+void
+free_server_configuration(void)
+{
+ free(server_pidfile);
+ free(server_access_group);
+}
diff --git a/hasher-priv/epoll.c b/hasher-priv/epoll.c
new file mode 100644
index 0000000..6eecd71
--- /dev/null
+++ b/hasher-priv/epoll.c
@@ -0,0 +1,39 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
+
+ The helpers for epoll API for the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <sys/epoll.h>
+
+#include <string.h>
+#include <unistd.h>
+
+#include "epoll.h"
+#include "logging.h"
+
+int epollin_add(int fd_ep, int fd)
+{
+ struct epoll_event ev = {
+ .events = EPOLLIN,
+ .data.fd = fd,
+ };
+
+ if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd, &ev) < 0) {
+ err("epoll_ctl: %m");
+ return -1;
+ }
+
+ return 0;
+}
+
+void epollin_remove(int fd_ep, int fd)
+{
+ if (fd < 0)
+ return;
+ epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd, NULL);
+ close(fd);
+}
diff --git a/hasher-priv/epoll.h b/hasher-priv/epoll.h
new file mode 100644
index 0000000..dc1e567
--- /dev/null
+++ b/hasher-priv/epoll.h
@@ -0,0 +1,18 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
+
+ The helpers for epoll API for the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef _EPOLL_H_
+#define _EPOLL_H_
+
+#include <sys/epoll.h>
+
+int epollin_add(int fd_ep, int fd);
+void epollin_remove(int fd_ep, int fd);
+
+#endif /* _EPOLL_H_ */
diff --git a/hasher-priv/hasher-priv.c b/hasher-priv/hasher-priv.c
new file mode 100644
index 0000000..f0c8fa8
--- /dev/null
+++ b/hasher-priv/hasher-priv.c
@@ -0,0 +1,78 @@
+
+/*
+ Copyright (C) 2003-2019 Dmitry V. Levin <ldv на altlinux.org>
+
+ The entry function for the hasher-priv program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+/* Code in this file may be executed with root privileges. */
+
+#include <linux/un.h>
+
+#include <errno.h>
+#include <error.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "priv.h"
+#include "logging.h"
+#include "sockets.h"
+#include "communication.h"
+
+int log_fd = -1;
+
+static void
+my_error_print_progname(void)
+{
+ fprintf(stderr, "%s: ", program_invocation_short_name);
+}
+
+int
+main(int ac, const char *av[], const char *ev[])
+{
+ int conn;
+ task_t task;
+ char socketname[UNIX_PATH_MAX];
+
+ error_print_progname = my_error_print_progname;
+
+ /* First, check and sanitize file descriptors. */
+ sanitize_fds();
+
+ /* Second, parse command line arguments. */
+ task = parse_cmdline(ac, av);
+
+ /* Connect to remote server and open session. */
+ if (server_open_session(SOCKETDIR, main_socket_base_name, caller_num) < 0)
+ return EXIT_FAILURE;
+
+ /* Open user session */
+ snprintf(socketname, sizeof(socketname), "%d:%u", geteuid(), caller_num);
+
+ if ((conn = srv_connect(SOCKETDIR, socketname)) < 0)
+ return EXIT_FAILURE;
+
+ if (server_task(conn, task) < 0)
+ return EXIT_FAILURE;
+
+ if (server_task_fds(conn) < 0)
+ return EXIT_FAILURE;
+
+ if (server_command(conn, CMD_TASK_ARGUMENTS, task_args) < 0)
+ return EXIT_FAILURE;
+
+ if (server_command(conn, CMD_TASK_ENVIRON, ev) < 0)
+ return EXIT_FAILURE;
+
+ if (server_command(conn, CMD_TASK_RUN, NULL) < 0)
+ return EXIT_FAILURE;
+
+ /* Close session socket. */
+ close(conn);
+
+ return EXIT_SUCCESS;
+}
diff --git a/hasher-priv/hasher-privd.c b/hasher-priv/hasher-privd.c
new file mode 100644
index 0000000..270804e
--- /dev/null
+++ b/hasher-priv/hasher-privd.c
@@ -0,0 +1,381 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
+
+ The entry function for the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <linux/un.h>
+
+#include <sys/types.h>
+#include <sys/epoll.h>
+#include <sys/signalfd.h>
+#include <sys/stat.h> /* umask */
+#include <sys/wait.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <error.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "epoll.h"
+#include "logging.h"
+#include "pidfile.h"
+#include "sockets.h"
+#include "communication.h"
+#include "priv.h"
+
+struct session {
+ struct session *next;
+
+ uid_t caller_uid;
+ gid_t caller_gid;
+
+ unsigned caller_num;
+
+ pid_t server_pid;
+};
+
+static struct session *pool = NULL;
+unsigned caller_num;
+
+static int
+start_session(int conn, unsigned num)
+{
+ uid_t uid;
+ gid_t gid;
+ pid_t server_pid;
+ struct session **a = &pool;
+
+ if (get_peercred(conn, NULL, &uid, &gid) < 0)
+ return -1;
+
+ while (a && *a) {
+ if ((*a)->caller_uid == uid && (*a)->caller_num == num) {
+ /* Session exists and will be reused. */
+ send_command_response(conn, CMD_STATUS_DONE, NULL);
+ return 0;
+ }
+ a = &(*a)->next;
+ }
+
+ info("start session for %d:%u user", uid, num);
+
+ if ((server_pid = fork_server(conn, uid, gid, num)) < 0)
+ return -1;
+
+ *a = calloc(1L, sizeof(struct session));
+ if (!*a) {
+ err("calloc: %m");
+ return -1;
+ }
+
+ (*a)->caller_uid = uid;
+ (*a)->caller_gid = gid;
+ (*a)->caller_num = num;
+ (*a)->server_pid = server_pid;
+
+ return 0;
+}
+
+static int
+close_session(int conn, unsigned num)
+{
+ uid_t uid;
+ gid_t gid;
+ struct session *e = pool;
+
+ if (get_peercred(conn, NULL, &uid, &gid) < 0)
+ return -1;
+
+ while (e) {
+ if (e->caller_uid == uid && e->caller_num == num) {
+ info("close session for %d:%u user by request", uid, num);
+ if (kill(e->server_pid, SIGTERM) < 0) {
+ err("kill: %m");
+ return -1;
+ }
+ break;
+ }
+ e = e->next;
+ }
+
+ return 0;
+}
+
+static int
+process_request(int conn)
+{
+ int rc;
+ struct cmd hdr = { 0 };
+ unsigned num = 0;
+
+ if (xrecvmsg(conn, &hdr, sizeof(hdr)) < 0)
+ return -1;
+
+ if (hdr.datalen != sizeof(num)) {
+ err("bad command");
+ send_command_response(conn, CMD_STATUS_FAILED, "bad command");
+ return -1;
+ }
+
+ if (xrecvmsg(conn, &num, sizeof(num)) < 0)
+ return -1;
+
+ switch (hdr.type) {
+ case CMD_OPEN_SESSION:
+ rc = start_session(conn, num);
+ /* The successful response is delayed until the child
+ * server is ready or an error happens.
+ */
+ if (rc < 0)
+ send_command_response(conn, CMD_STATUS_FAILED, "command failed");
+ break;
+ case CMD_CLOSE_SESSION:
+ rc = close_session(conn, num);
+ (rc < 0)
+ ? send_command_response(conn, CMD_STATUS_FAILED, "command failed")
+ : send_command_response(conn, CMD_STATUS_DONE, NULL);
+ break;
+ default:
+ err("unknown command");
+ send_command_response(conn, CMD_STATUS_FAILED, "unknown command");
+ rc = -1;
+ }
+
+ return rc;
+}
+
+static void
+finish_sessions(int sig)
+{
+ struct session *e = pool;
+
+ while (e) {
+ if (kill(e->server_pid, sig) < 0)
+ err("kill: %m");
+ e = e->next;
+ }
+}
+
+static void
+clean_session(pid_t pid)
+{
+ struct session *x, **a = &pool;
+
+ while (a && *a) {
+ if ((*a)->server_pid == pid) {
+ x = *a;
+ *a = (*a)->next;
+ free(x);
+ }
+ a = &(*a)->next;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ int loglevel = -1;
+
+ const char *pidfile = NULL;
+ int daemonize = 1;
+
+ char socketpath[UNIX_PATH_MAX];
+
+ struct option long_options[] = {
+ { "help", no_argument, 0, 'h' },
+ { "version", no_argument, 0, 'V' },
+ { "foreground", no_argument, 0, 'f' },
+ { "loglevel", required_argument, 0, 'l' },
+ { "pidfile", required_argument, 0, 'p' },
+ { 0, 0, 0, 0 }
+ };
+
+ while ((i = getopt_long(argc, argv, "hVfl:p:", long_options, NULL)) != -1) {
+ switch (i) {
+ case 'p':
+ pidfile = optarg;
+ break;
+ case 'l':
+ loglevel = logging_level(optarg);
+ break;
+ case 'f':
+ daemonize = 0;
+ break;
+ case 'V':
+ printf("%s %s\n", program_invocation_short_name, PROJECT_VERSION);
+ return EXIT_SUCCESS;
+ default:
+ case 'h':
+ printf("Usage: %s [options]\n"
+ " -p, --pidfile=FILE pid file location;\n"
+ " -l, --loglevel=LVL set logging level;\n"
+ " -f, --foreground stay in the foreground;\n"
+ " -V, --version print program version and exit;\n"
+ " -h, --help show this text and exit.\n"
+ "\n",
+ program_invocation_short_name);
+ return EXIT_SUCCESS;
+ }
+ }
+
+ configure_server();
+
+ if (daemonize && !pidfile && server_pidfile && *server_pidfile)
+ pidfile = server_pidfile;
+
+ if (loglevel < 0)
+ loglevel = (server_log_priority >= 0)
+ ? server_log_priority
+ : logging_level("info");
+
+ umask(022);
+
+ if (pidfile && check_pid(pidfile))
+ error(EXIT_FAILURE, 0, "%s: already running",
+ program_invocation_short_name);
+
+ if (daemonize && daemon(0, 0) < 0)
+ error(EXIT_FAILURE, errno, "daemon");
+
+ logging_init(loglevel, !daemonize);
+
+ if (pidfile && write_pid(pidfile) == 0)
+ return EXIT_FAILURE;
+
+ sigset_t mask;
+
+ sigfillset(&mask);
+ sigprocmask(SIG_SETMASK, &mask, NULL);
+
+ sigdelset(&mask, SIGABRT);
+ sigdelset(&mask, SIGSEGV);
+
+ int fd_ep = -1;
+
+ if ((fd_ep = epoll_create1(EPOLL_CLOEXEC)) < 0)
+ fatal("epoll_create1: %m");
+
+ int fd_signal = -1;
+
+ if ((fd_signal = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)) < 0)
+ fatal("signalfd: %m");
+
+ mode_t m = umask(017);
+
+ int fd_conn = -1;
+
+ if ((fd_conn = srv_listen(SOCKETDIR, main_socket_base_name)) < 0)
+ return EXIT_FAILURE;
+
+ umask(m);
+
+ snprintf(socketpath, sizeof(socketpath), "%s/%s", SOCKETDIR, main_socket_base_name);
+
+ if (chown(socketpath, 0, server_gid))
+ fatal("fchown: %s: %m", socketpath);
+
+ if (epollin_add(fd_ep, fd_signal) < 0 || epollin_add(fd_ep, fd_conn) < 0)
+ return EXIT_FAILURE;
+
+ int ep_timeout = -1;
+ int finish_server = 0;
+
+ while (1) {
+ struct epoll_event ev[42];
+ int fdcount;
+
+ errno = 0;
+ if ((fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), ep_timeout)) < 0) {
+ if (errno == EINTR)
+ continue;
+ err("epoll_wait: %m");
+ break;
+ }
+
+ for (i = 0; i < fdcount; i++) {
+ if (!(ev[i].events & EPOLLIN)) {
+ continue;
+
+ } else if (ev[i].data.fd == fd_signal) {
+ struct signalfd_siginfo fdsi;
+ ssize_t size;
+
+ size = read_retry(fd_signal, &fdsi, sizeof(fdsi));
+
+ if (size != sizeof(fdsi)) {
+ err("unable to read signal info");
+ continue;
+ }
+
+ pid_t pid;
+ int status;
+
+ switch (fdsi.ssi_signo) {
+ case SIGINT:
+ case SIGTERM:
+ finish_server = 1;
+ break;
+ case SIGCHLD:
+ if ((pid = waitpid(-1, &status, 0)) < 0)
+ err("waitpid: %m");
+
+ clean_session(pid);
+ break;
+ }
+
+ } else if (ev[i].data.fd == fd_conn) {
+ int conn;
+
+ if ((conn = accept4(fd_conn, NULL, 0, SOCK_CLOEXEC)) < 0) {
+ err("accept4: %m");
+ continue;
+ }
+
+ if (set_recv_timeout(conn, 3) < 0) {
+ close(conn);
+ continue;
+ }
+
+ process_request(conn);
+ close(conn);
+ }
+ }
+
+ if (finish_server) {
+ if (!pool)
+ break;
+
+ if (fd_conn >= 0) {
+ epollin_remove(fd_ep, fd_conn);
+ fd_conn = -1;
+ ep_timeout = 3000;
+ }
+
+ finish_sessions(SIGTERM);
+ }
+ }
+
+ epollin_remove(fd_ep, fd_signal);
+ epollin_remove(fd_ep, fd_conn);
+
+ if (fd_ep >= 0)
+ close(fd_ep);
+
+ if (pidfile)
+ remove_pid(pidfile);
+
+ logging_close();
+ free_server_configuration();
+
+ return EXIT_SUCCESS;
+}
diff --git a/hasher-priv/io_log.c b/hasher-priv/io_log.c
index f5aea59..2689ff0 100644
--- a/hasher-priv/io_log.c
+++ b/hasher-priv/io_log.c
@@ -2,7 +2,7 @@
/*
Copyright (C) 2008-2019 Dmitry V. Levin <ldv на altlinux.org>
- The chrootuid parent log I/O handler for the hasher-priv program.
+ The chrootuid parent log I/O handler for the hasher-privd program.
SPDX-License-Identifier: GPL-2.0-or-later
*/
diff --git a/hasher-priv/io_x11.c b/hasher-priv/io_x11.c
index 93e3add..f193c56 100644
--- a/hasher-priv/io_x11.c
+++ b/hasher-priv/io_x11.c
@@ -2,7 +2,7 @@
/*
Copyright (C) 2005-2019 Dmitry V. Levin <ldv на altlinux.org>
- The chrootuid parent X11 I/O handler for the hasher-priv program.
+ The chrootuid parent X11 I/O handler for the hasher-privd program.
SPDX-License-Identifier: GPL-2.0-or-later
*/
diff --git a/hasher-priv/killuid.c b/hasher-priv/killuid.c
index f0bee84..71e52e1 100644
--- a/hasher-priv/killuid.c
+++ b/hasher-priv/killuid.c
@@ -2,7 +2,7 @@
/*
Copyright (C) 2003-2019 Dmitry V. Levin <ldv на altlinux.org>
- The killuid actions for the hasher-priv program.
+ The killuid actions for the hasher-privd program.
SPDX-License-Identifier: GPL-2.0-or-later
*/
diff --git a/hasher-priv/logging.c b/hasher-priv/logging.c
new file mode 100644
index 0000000..cd693e5
--- /dev/null
+++ b/hasher-priv/logging.c
@@ -0,0 +1,71 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
+
+ Logging functions for the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <strings.h>
+#include <errno.h>
+#include <syslog.h>
+
+#include "logging.h"
+
+int log_priority = -1;
+int log_to_stderr = 0;
+
+int logging_level(const char *name)
+{
+ if (!strcasecmp(name, "debug"))
+ return LOG_DEBUG;
+
+ if (!strcasecmp(name, "info"))
+ return LOG_INFO;
+
+ if (!strcasecmp(name, "warning"))
+ return LOG_WARNING;
+
+ if (!strcasecmp(name, "error"))
+ return LOG_ERR;
+
+ return 0;
+}
+
+void logging_init(int loglevel, int is_foreground)
+{
+ int options = LOG_PID;
+ log_priority = loglevel;
+ log_to_stderr = !!is_foreground;
+ if (!is_foreground)
+ openlog(program_invocation_short_name, options, LOG_DAEMON);
+}
+
+void logging_close(void)
+{
+ if (!log_to_stderr)
+ closelog();
+}
+
+void
+message(int priority, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (priority <= log_priority)
+ {
+ if (log_to_stderr)
+ {
+ fprintf(stderr, "<%d>", priority);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ }
+ else
+ vsyslog(priority, fmt, ap);
+ }
+ va_end(ap);
+}
diff --git a/hasher-priv/logging.h b/hasher-priv/logging.h
new file mode 100644
index 0000000..bc6d0da
--- /dev/null
+++ b/hasher-priv/logging.h
@@ -0,0 +1,55 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
+
+ Logging functions for the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef _LOGGING_H_
+#define _LOGGING_H_
+
+#include <syslog.h>
+#include <stdlib.h>
+
+void logging_init(int, int);
+void logging_close(void);
+int logging_level(const char *lvl) __attribute__((nonnull(1)));
+
+void message(int priority, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
+
+#define fatal(format, arg...) \
+ do { \
+ message(LOG_CRIT, \
+ "%s(%d): %s: " format, \
+ __FILE__, __LINE__, __FUNCTION__, \
+ ##arg); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+#define err(format, arg...) \
+ do { \
+ message(LOG_ERR, \
+ "%s(%d): %s: " format, \
+ __FILE__, __LINE__, __FUNCTION__, \
+ ##arg); \
+ } while (0)
+
+#define info(format, arg...) \
+ do { \
+ message(LOG_INFO, \
+ "%s(%d): %s: " format, \
+ __FILE__, __LINE__, __FUNCTION__, \
+ ##arg); \
+ } while (0)
+
+#define dbg(format, arg...) \
+ do { \
+ message(LOG_DEBUG, \
+ "%s(%d): %s: " format, \
+ __FILE__, __LINE__, __FUNCTION__, \
+ ##arg); \
+ } while (0)
+
+#endif /* _LOGGING_H_ */
diff --git a/hasher-priv/main.c b/hasher-priv/main.c
deleted file mode 100644
index 310fd5d..0000000
--- a/hasher-priv/main.c
+++ /dev/null
@@ -1,75 +0,0 @@
-
-/*
- Copyright (C) 2003-2019 Dmitry V. Levin <ldv на altlinux.org>
-
- The entry function for the hasher-priv program.
-
- SPDX-License-Identifier: GPL-2.0-or-later
-*/
-
-/* Code in this file may be executed with root privileges. */
-
-#include <errno.h>
-#include <error.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "priv.h"
-
-static void
-my_error_print_progname(void)
-{
- fprintf(stderr, "%s: ", program_invocation_short_name);
-}
-
-int
-main(int ac, const char *av[])
-{
- task_t task;
-
- error_print_progname = my_error_print_progname;
-
- /* First, check and sanitize file descriptors. */
- sanitize_fds();
-
- /* Second, parse command line arguments. */
- task = parse_cmdline(ac, av);
-
- if (chroot_path && *chroot_path != '/')
- error(EXIT_FAILURE, 0, "%s: invalid chroot path",
- chroot_path);
-
- /* Third, initialize data related to caller. */
- init_caller_data();
-
- /* 4th, parse environment for config options. */
- parse_env();
-
- /* We don't need environment variables any longer. */
- if (clearenv() != 0)
- error(EXIT_FAILURE, errno, "clearenv");
-
- /* Load config according to caller information. */
- configure();
-
- /* Finally, execute choosen task. */
- switch (task)
- {
- case TASK_GETCONF:
- return do_getconf();
- case TASK_KILLUID:
- return do_killuid();
- case TASK_GETUGID1:
- return do_getugid1();
- case TASK_CHROOTUID1:
- return do_chrootuid1();
- case TASK_GETUGID2:
- return do_getugid2();
- case TASK_CHROOTUID2:
- return do_chrootuid2();
- default:
- error(EXIT_FAILURE, 0, "unknown task %d", task);
- }
-
- return EXIT_FAILURE;
-}
diff --git a/hasher-priv/pass.c b/hasher-priv/pass.c
index b52c87e..03a07ec 100644
--- a/hasher-priv/pass.c
+++ b/hasher-priv/pass.c
@@ -17,6 +17,8 @@
#include <unistd.h>
#include <sys/socket.h>
+#include "logging.h"
+#include "sockets.h"
#include "priv.h"
union cmsg_data_u
@@ -55,8 +57,7 @@ fd_send(int ctl, int pass, const char *data, size_t data_len)
ssize_t rc;
- if ((rc = TEMP_FAILURE_RETRY(sendmsg(ctl, &msg, 0))) !=
- (ssize_t) data_len)
+ if ((rc = sendmsg_retry(ctl, &msg, 0)) != (ssize_t) data_len)
{
if (rc < 0)
{
@@ -96,8 +97,7 @@ fd_recv(int ctl, char *data, size_t data_len)
ssize_t rc;
- if ((rc = TEMP_FAILURE_RETRY(recvmsg(ctl, &msg, 0))) !=
- (ssize_t) data_len)
+ if ((rc = recvmsg_retry(ctl, &msg, 0)) != (ssize_t) data_len)
{
if (rc < 0)
{
@@ -132,3 +132,112 @@ fd_recv(int ctl, char *data, size_t data_len)
cmsg_data_p.c = CMSG_DATA(cmsg);
return *cmsg_data_p.i;
}
+
+int
+fds_send(int conn, int *fds, size_t fds_len)
+{
+ ssize_t rc;
+ size_t len = sizeof(int) * fds_len;
+
+ union {
+ char buf[CMSG_SPACE(len)];
+ struct cmsghdr align;
+ } u;
+
+ /*
+ * We must send at least 1 byte of real data in
+ * order to send ancillary data
+ */
+ char dummy;
+
+ struct iovec iov = { 0 };
+
+ iov.iov_base = &dummy;
+ iov.iov_len = sizeof(dummy);
+
+ struct msghdr msg = { 0 };
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = u.buf;
+ msg.msg_controllen = sizeof(u.buf);
+
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(len);
+
+ memcpy(CMSG_DATA(cmsg), fds, len);
+
+ if ((rc = sendmsg_retry(conn, &msg, 0)) != sizeof(dummy)) {
+ if (rc < 0)
+ err("sendmsg: %m");
+ else if (rc)
+ err("sendmsg: expected size %lu, got %lu", sizeof(dummy), rc);
+ else
+ err("sendmsg: unexpected EOF");
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+fds_recv(int conn, int *fds, size_t n_fds)
+{
+ char dummy;
+ size_t datalen = sizeof(int) * n_fds;
+
+ union {
+ struct cmsghdr cmh;
+ char control[CMSG_SPACE(datalen)];
+ } u;
+
+ u.cmh.cmsg_len = CMSG_LEN(datalen);
+ u.cmh.cmsg_level = SOL_SOCKET;
+ u.cmh.cmsg_type = SCM_RIGHTS;
+
+ struct iovec iov = { 0 };
+
+ iov.iov_base = &dummy;
+ iov.iov_len = sizeof(dummy);
+
+ struct msghdr msg = { 0 };
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = u.control;
+ msg.msg_controllen = sizeof(u.control);
+
+ ssize_t n;
+ if ((n = recvmsg_retry(conn, &msg, 0)) != sizeof(dummy)) {
+ if (n < 0)
+ err("recvmsg: %m");
+ else if (n)
+ err("recvmsg: expected size %lu, got %lu", sizeof(dummy), n);
+ else
+ err("recvmsg: unexpected EOF");
+ return -1;
+ }
+
+ if (!msg.msg_controllen) {
+ err("ancillary data not specified");
+ return -1;
+ }
+
+ for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS)
+ continue;
+
+ if (cmsg->cmsg_len - CMSG_LEN(0) != datalen) {
+ err("expected fd payload");
+ return -1;
+ }
+
+ memcpy(fds, CMSG_DATA(cmsg), datalen);
+ break;
+ }
+
+ return 0;
+}
diff --git a/hasher-priv/pidfile.c b/hasher-priv/pidfile.c
new file mode 100644
index 0000000..3da63dd
--- /dev/null
+++ b/hasher-priv/pidfile.c
@@ -0,0 +1,129 @@
+
+/*
+ pidfile.c - interact with pidfiles
+ Copyright (c) 1995 Martin Schulze <Martin.Schulze на Linux.DE>
+
+ This file was originally part of the sysklogd package, a kernel and system
+ log daemon.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+/*
+ * Sat Aug 19 13:24:33 MET DST 1995: Martin Schulze
+ * First version (v0.2) released
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "logging.h"
+#include "pidfile.h"
+
+/* read_pid
+ *
+ * Reads the specified pidfile and returns the read pid.
+ * 0 is returned if either there's no pidfile, it's empty
+ * or no pid can be read.
+ */
+int read_pid(const char *pidfile)
+{
+ FILE *f;
+ int pid;
+
+ if (!(f = fopen(pidfile, "r")))
+ return 0;
+ if (fscanf(f, "%d", &pid) != 1)
+ pid = 0;
+ fclose(f);
+ return pid;
+}
+
+/* check_pid
+ *
+ * Reads the pid using read_pid and looks up the pid in the process
+ * table (using /proc) to determine if the process already exists. If
+ * so 1 is returned, otherwise 0.
+ */
+int check_pid(const char *pidfile)
+{
+ int pid = read_pid(pidfile);
+
+ /* Amazing ! _I_ am already holding the pid file... */
+ if ((!pid) || (pid == getpid()))
+ return 0;
+
+ /*
+ * The 'standard' method of doing this is to try and do a 'fake' kill
+ * of the process. If an ESRCH error is returned the process cannot
+ * be found -- GW
+ */
+ /* But... errno is usually changed only on error.. */
+ if (kill(pid, 0) && errno == ESRCH)
+ return (0);
+
+ return pid;
+}
+
+/* write_pid
+ *
+ * Writes the pid to the specified file. If that fails 0 is
+ * returned, otherwise the pid.
+ */
+int write_pid(const char *pidfile)
+{
+ FILE *f;
+ int fd;
+ int pid;
+
+ if (((fd = open(pidfile, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)) == -1) ||
+ ((f = fdopen(fd, "r+")) == NULL)) {
+ err("Can't open or create %s", pidfile);
+ return 0;
+ }
+
+ if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
+ if (fscanf(f, "%d", &pid) != 1)
+ pid = 0;
+ fclose(f);
+ err("Can't lock, lock is held by pid %d", pid);
+ return 0;
+ }
+
+ pid = getpid();
+ if (!fprintf(f, "%d\n", pid)) {
+ err("Can't write pid: %m");
+ close(fd);
+ return 0;
+ }
+ fflush(f);
+
+ if (flock(fd, LOCK_UN) == -1) {
+ err("flock: %s: %m", pidfile);
+ close(fd);
+ return 0;
+ }
+ close(fd);
+ fclose(f);
+
+ return pid;
+}
+
+/* remove_pid
+ *
+ * Remove the the specified file. The result from unlink(2)
+ * is returned
+ */
+int remove_pid(const char *pidfile)
+{
+ if (unlink(pidfile) == -1) {
+ err("unlink: %s: %m", pidfile);
+ return -1;
+ }
+ return 0;
+}
diff --git a/hasher-priv/pidfile.h b/hasher-priv/pidfile.h
new file mode 100644
index 0000000..e152538
--- /dev/null
+++ b/hasher-priv/pidfile.h
@@ -0,0 +1,44 @@
+
+/*
+ pidfile.h - interact with pidfiles
+ Copyright (c) 1995 Martin Schulze <Martin.Schulze на Linux.DE>
+
+ This file is part of the sysklogd package, a kernel and system log daemon.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef _PIDFILE_H_
+#define _PIDFILE_H_
+
+/* read_pid
+ *
+ * Reads the specified pidfile and returns the read pid.
+ * 0 is returned if either there's no pidfile, it's empty
+ * or no pid can be read.
+ */
+int read_pid(const char *pidfile) __attribute__((nonnull(1)));
+
+/* check_pid
+ *
+ * Reads the pid using read_pid and looks up the pid in the process
+ * table (using /proc) to determine if the process already exists. If
+ * so 1 is returned, otherwise 0.
+ */
+int check_pid(const char *pidfile) __attribute__((nonnull(1)));
+
+/* write_pid
+ *
+ * Writes the pid to the specified file. If that fails 0 is
+ * returned, otherwise the pid.
+ */
+int write_pid(const char *pidfile) __attribute__((nonnull(1)));
+
+/* remove_pid
+ *
+ * Remove the the specified file. The result from unlink(2)
+ * is returned
+ */
+int remove_pid(const char *pidfile) __attribute__((nonnull(1)));
+
+#endif /* _PIDFILE_H_ */
diff --git a/hasher-priv/priv.h b/hasher-priv/priv.h
index 87e72fb..7d5a5a5 100644
--- a/hasher-priv/priv.h
+++ b/hasher-priv/priv.h
@@ -18,16 +18,7 @@
#define MIN_CHANGE_GID 34
#define MAX_CONFIG_SIZE 16384
-typedef enum
-{
- TASK_NONE = 0,
- TASK_GETCONF,
- TASK_KILLUID,
- TASK_GETUGID1,
- TASK_CHROOTUID1,
- TASK_GETUGID2,
- TASK_CHROOTUID2,
-} task_t;
+#include "communication.h"
typedef struct
{
@@ -68,9 +59,13 @@ void restore_tty(void);
int tty_copy_winsize(int master_fd, int slave_fd);
int open_pty(int *slave_fd, int chrooted, int verbose_error);
task_t parse_cmdline(int ac, const char *av[]);
-void init_caller_data(void);
+void parse_task_args(task_t task, const char *argv[]);
+int init_caller_data(uid_t uid, gid_t gid);
+void free_caller_data(void);
void parse_env(void);
void configure(void);
+void configure_server(void);
+void free_server_configuration(void);
void ch_uid(uid_t uid, uid_t *save);
void ch_gid(gid_t gid, gid_t *save);
void chdiruid(const char *path, VALIDATE_FPTR validator);
@@ -85,6 +80,9 @@ void stat_root_ok_validator(struct stat *st, const char *name);
void stat_any_ok_validator(struct stat *st, const char *name);
void fd_send(int ctl, int pass, const char *data, size_t len);
int fd_recv(int ctl, char *data, size_t data_len);
+int fds_send(int conn, int *fds, size_t fds_len) __attribute__((nonnull(2)));
+int fds_recv(int conn, int *fds, size_t datalen) __attribute__((nonnull(2)));
+
int unix_accept(int fd);
int log_listen(void);
void x11_drop_display(void);
@@ -120,9 +118,14 @@ int do_chrootuid1(void);
int do_getugid2(void);
int do_chrootuid2(void);
+int process_caller_task(int, struct task *);
+pid_t fork_server(int, uid_t, gid_t, unsigned);
+
extern const char *chroot_path;
extern const char **chroot_argv;
+extern const char **task_args;
+
extern str_list_t allowed_devices;
extern str_list_t allowed_mountpoints;
extern str_list_t requested_mountpoints;
@@ -143,7 +146,7 @@ extern int log_fd;
extern const char *const *chroot_prefix_list;
extern const char *chroot_prefix_path;
-extern const char *caller_user, *caller_home;
+extern char *caller_user, *caller_home;
extern uid_t caller_uid;
extern gid_t caller_gid;
extern unsigned caller_num;
@@ -156,4 +159,10 @@ extern int change_nice;
extern change_rlimit_t change_rlimit[];
extern work_limit_t wlimit;
+extern int server_log_priority;
+extern unsigned long server_session_timeout;
+extern char *server_access_group;
+extern char *server_pidfile;
+extern gid_t server_gid;
+
#endif /* PKG_BUILD_PRIV_H */
diff --git a/hasher-priv/server.conf b/hasher-priv/server.conf
new file mode 100644
index 0000000..2d740a6
--- /dev/null
+++ b/hasher-priv/server.conf
@@ -0,0 +1,13 @@
+# Server configuration
+
+# Set the default logging priority. (can override with command line arguments)
+priority=info
+
+# Write a pid file. (can override with command line arguments)
+pidfile=/var/run/hasher-privd.pid
+
+# Stop user's session server after {session_timeout} seconds of inactivity.
+session_timeout=3600
+
+# Allow users of this group to interact with hasher-privd via the control socket.
+access_group=hashman
diff --git a/hasher-priv/sockets.c b/hasher-priv/sockets.c
new file mode 100644
index 0000000..5ba3511
--- /dev/null
+++ b/hasher-priv/sockets.c
@@ -0,0 +1,183 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
+
+ A collection of helpers to simplify the use of sockets.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "logging.h"
+#include "sockets.h"
+
+/* This function may be executed with caller or child privileges. */
+
+int srv_listen(const char *dir_name, const char *file_name)
+{
+ struct sockaddr_un sun = { .sun_family = AF_UNIX };
+
+ snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dir_name, file_name);
+
+ if (unlink(sun.sun_path) && errno != ENOENT) {
+ err("unlink: %s: %m", sun.sun_path);
+ return -1;
+ }
+
+ if (mkdir(dir_name, 0700) && errno != EEXIST) {
+ err("mkdir: %s: %m", dir_name);
+ return -1;
+ }
+
+ int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+
+ if (fd < 0) {
+ err("socket AF_UNIX: %m");
+ return -1;
+ }
+
+ if (bind(fd, (struct sockaddr *)&sun, (socklen_t) sizeof(sun))) {
+ err("bind: %s: %m", sun.sun_path);
+ (void)close(fd);
+ return -1;
+ }
+
+ if (listen(fd, 16) < 0) {
+ err("listen: %s: %m", sun.sun_path);
+ (void)close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+int srv_connect(const char *dir_name, const char *file_name)
+{
+ int conn = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+
+ if (conn < 0) {
+ err("socket AF_UNIX: %m");
+ return -1;
+ }
+
+ struct sockaddr_un sun = { .sun_family = AF_UNIX };
+
+ snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dir_name, file_name);
+
+ if (connect(conn, (const struct sockaddr *)&sun, sizeof(sun)) < 0) {
+ err("connect: %s: %m", sun.sun_path);
+ return -1;
+ }
+
+ return conn;
+}
+
+int get_peercred(int fd, pid_t *pid, uid_t *uid, gid_t *gid)
+{
+ struct ucred uc;
+ socklen_t len = sizeof(struct ucred);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) < 0) {
+ err("getsockopt(SO_PEERCRED): %m");
+ return -1;
+ }
+
+ if (pid)
+ *pid = uc.pid;
+
+ if (uid)
+ *uid = uc.uid;
+
+ if (gid)
+ *gid = uc.gid;
+
+ return 0;
+}
+
+int
+set_recv_timeout(int fd, int secs)
+{
+ struct timeval tv = { .tv_sec = secs };
+
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
+ err("setsockopt(SO_RCVTIMEO): %m");
+ return -1;
+ }
+ return 0;
+}
+
+ssize_t
+sendmsg_retry(int sockfd, const struct msghdr *msg, int flags)
+{
+ return TEMP_FAILURE_RETRY(sendmsg(sockfd, msg, flags));
+}
+
+ssize_t
+recvmsg_retry(int sockfd, struct msghdr *msg, int flags)
+{
+ return TEMP_FAILURE_RETRY(recvmsg(sockfd, msg, flags));
+}
+
+int
+xsendmsg(int conn, void *data, size_t len)
+{
+ struct iovec iov = { 0 };
+ struct msghdr msg = { 0 };
+
+ iov.iov_base = data;
+ iov.iov_len = len;
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ ssize_t n = sendmsg_retry(conn, &msg, 0);
+
+ if (n != (ssize_t) len) {
+ if (n < 0)
+ err("recvmsg: %m");
+ else if (n)
+ err("recvmsg: expected size %lu, got %lu", len, n);
+ else
+ err("recvmsg: unexpected EOF");
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+xrecvmsg(int conn, void *data, size_t len)
+{
+ struct iovec iov = { 0 };
+ struct msghdr msg = { 0 };
+
+ iov.iov_base = data;
+ iov.iov_len = len;
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ ssize_t n = recvmsg_retry(conn, &msg, MSG_WAITALL);
+
+ if (n != (ssize_t) len) {
+ if (n < 0)
+ err("recvmsg: %m");
+ else if (n)
+ err("recvmsg: expected size %lu, got %lu", len, n);
+ else
+ err("recvmsg: unexpected EOF");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/hasher-priv/sockets.h b/hasher-priv/sockets.h
new file mode 100644
index 0000000..1ee37a2
--- /dev/null
+++ b/hasher-priv/sockets.h
@@ -0,0 +1,32 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion на altlinux.org>
+
+ A collection of helpers to simplify the use of sockets.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef _SOCKETS_H_
+#define _SOCKETS_H_
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+ssize_t sendmsg_retry(int sockfd, const struct msghdr *msg, int flags) __attribute__((nonnull(2)));
+ssize_t recvmsg_retry(int sockfd, struct msghdr *msg, int flags) __attribute__((nonnull(2)));
+
+#include <unistd.h>
+
+int srv_listen(const char *, const char *) __attribute__((nonnull(1, 2)));
+int srv_connect(const char *, const char *) __attribute__((nonnull(1, 2)));
+
+int get_peercred(int, pid_t *, uid_t *, gid_t *);
+int set_recv_timeout(int fd, int secs);
+
+#include <stdint.h>
+
+int xsendmsg(int conn, void *data, size_t len) __attribute__((nonnull(2)));
+int xrecvmsg(int conn, void *data, size_t len) __attribute__((nonnull(2)));
+
+#endif /* _SOCKETS_H_ */
diff --git a/hasher-priv/x11.c b/hasher-priv/x11.c
index 854c453..0a6b4fb 100644
--- a/hasher-priv/x11.c
+++ b/hasher-priv/x11.c
@@ -22,6 +22,7 @@
#include "priv.h"
#include "xmalloc.h"
+#include "sockets.h"
#define X11_UNIX_DIR "/tmp/.X11-unix"
--
2.25.4
Подробная информация о списке рассылки Devel