[devel] [PATCH hasher-priv v1 1/3] *literacy*

Arseny Maslennikov arseny на altlinux.org
Чт Сен 17 16:10:28 MSK 2020


On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> From: Alexey Gladkov <legion на altlinux.org>
> 
> All privileged operations moved to the daemon. Commands to the server

Suggested replacement:
"All privileged operations are moved to the daemon. Commands to the server"

> are sent through the unix domain socket. The credentials which the sender
> specifies are checked by the kernel. The hasher-priv no longer SUID.

Suggested replacement for the last sentence:
"Neither hasher-priv nor its daemon are installed with SUID on"

> 
> For each user server creates a separate session process that executes

Suggested replacement:
s/server/the server/

> commands only from the user who created it. The session process ends
> after a certain period of inactivity.
> 
> Signed-off-by: Alexey Gladkov <legion на altlinux.org>
> ---
>  hasher-priv/.gitignore      |   1 +
>  hasher-priv/DESIGN          | 281 ++++++++++++++++----------
>  hasher-priv/Makefile        |  28 ++-
>  hasher-priv/caller.c        |  81 ++++----
>  hasher-priv/caller_server.c | 373 ++++++++++++++++++++++++++++++++++
>  hasher-priv/caller_task.c   | 214 ++++++++++++++++++++
>  hasher-priv/cmdline.c       |  27 ++-
>  hasher-priv/communication.c | 392 ++++++++++++++++++++++++++++++++++++
>  hasher-priv/communication.h |  77 +++++++
>  hasher-priv/config.c        | 143 ++++++++++++-
>  hasher-priv/epoll.c         |  39 ++++
>  hasher-priv/epoll.h         |  18 ++
>  hasher-priv/hasher-priv.c   |  78 +++++++
>  hasher-priv/hasher-privd.c  | 375 ++++++++++++++++++++++++++++++++++
>  hasher-priv/io_log.c        |   2 +-
>  hasher-priv/io_x11.c        |   2 +-
>  hasher-priv/killuid.c       |   2 +-
>  hasher-priv/logging.c       |  64 ++++++
>  hasher-priv/logging.h       |  55 +++++
>  hasher-priv/main.c          |  75 -------
>  hasher-priv/pass.c          | 117 ++++++++++-
>  hasher-priv/pidfile.c       | 128 ++++++++++++
>  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, 2632 insertions(+), 246 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

This file desperately needs lots of fixes throughout, to be fair.

> index 1470ce7..310667d 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 a hasher-priv (euid=user,egid=hashman,gid!=egid) control flow:

Suggested replacement:
s/a hasher-priv/the client's/

A reword of all the header phrases would also suffice.

>  + 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 a hasher-privd (euid=root,egid=hashman,gid==egid) control flow:

Same as above.

> ++ 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 incomming 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..82aa385 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 = /var/run
>  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..8182c69
> --- /dev/null
> +++ b/hasher-priv/caller_server.c
> @@ -0,0 +1,373 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion на altlinux.org>
> +
> +  The part of the hasher-privd program.

Which part?

> +
> +  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->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 permissions to send commands"

Suggested replacement:
"has no permissions" -> "has no permission"

> +		    " 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(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),
> +		"hasher-priv-%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/hasher-priv-%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 client that caller server is ready */

Suggested replacement:
"client" -> "the client"

> +	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");

Suggested error message:
"unable to read signal info from signalfd"
The form present in the patch is only obvious to a developer.

> +					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;
> +	}
> +
> +	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..d8f2dd5
> --- /dev/null
> +++ b/hasher-priv/caller_task.c
> @@ -0,0 +1,214 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion на altlinux.org>
> +
> +  The part of the hasher-privd program.

Which part?
The same anonymous part as caller_server.c? =)

> +
> +  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;
> +
> +	if ((pid = fork()) != 0) {
> +		if (pid < 0) {
> +			err("fork: %m");
> +			return -1;
> +		}
> +		return pid;
> +	}
> +
> +	if ((rc = reopen_iostreams(task->stdin, task->stdout, task->stderr)) < 0)
> +		exit(rc);
> +
> +	/* cleanup environment to avoid side effects. */

s/cleanup/clean up/

> +	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);
> +
> +	if (chroot_path && *chroot_path != '/')
> +		fatal("%s: invalid chroot path", chroot_path);
> +
> +	/* Fourth, parse environment for config options. */

This reminds me of Michael Zadornov and his numbered pigs.

> +	parse_env();
> +
> +	/* We don't need environment variables any longer. */

Suggested replacement:
"We don't need environment variables any longer."

> +	if (clearenv() != 0)
> +		fatal("clearenv: %m");
> +
> +	/* Finally, execute choosen task. */

s/choosen/chosen/; even further: is it actually chosen in this function?
It's actually passed in an argument by pointer.

> +	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 of all user-space buffered data */

Suggested replacement:
"Write down all the user-space buffered data"
This is the closest option to the original suggestion.

> +	fflush(stdout);
> +	fflush(stderr);
> +
> +	exit(rc);
> +}
> +
> +int
> +process_caller_task(int conn, struct task *task)
> +{
> +	int rc = EXIT_FAILURE;
> +	pid_t pid, cpid;
> +
> +	if ((pid = fork()) != 0) {
> +		if (pid < 0) {
> +			err("fork: %m");
> +			return -1;
> +		}
> +		return 0;
> +	}
> +
> +	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 client about result */

Suggested closest replacement:
"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..77f93e1
> --- /dev/null
> +++ b/hasher-priv/communication.c
> @@ -0,0 +1,392 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion на altlinux.org>
> +
> +  Functions for communication between the hasher-priv and the hasher-privd.

The proposed phrase is a bit clunky but OK.
I'll still give a suggested replacement:
"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);
> +
> +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..e6f1530
> --- /dev/null
> +++ b/hasher-priv/communication.h
> @@ -0,0 +1,77 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion на altlinux.org>
> +
> +  Functions for communication between the hasher-priv and the hasher-privd.

Same as communication.c.

> +
> +  SPDX-License-Identifier: GPL-2.0-or-later
> +*/
> +
> +#ifndef _COMMUNICATION_H_
> +#define _COMMUNICATION_H_
> +
> +#include <stdint.h>
> +
> +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..6b6bdb1 100644
> --- a/hasher-priv/config.c
> +++ b/hasher-priv/config.c
> @@ -1,6 +1,7 @@
>  
>  /*
>    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.

Priv or privd?

>  
> @@ -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_control_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_control_group(void)
> +{
> +	struct group *gr;
> +
> +	if (!server_control_group || !*server_control_group)
> +		error(EXIT_FAILURE, 0, "config: undefined: control_group");
> +
> +	gr = getgrnam(server_control_group);
> +
> +	if (!gr || !gr->gr_name)
> +		error(EXIT_FAILURE, 0, "config: control_group: %s lookup failure", server_control_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("control_group", name)) {
> +		free(server_control_group);
> +		server_control_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_control_group();
> +}
> +
> +void
> +free_server_configuration(void)
> +{
> +	free(server_pidfile);
> +	free(server_control_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..9eb598c
> --- /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, PROJECT, caller_num) < 0)
> +		return EXIT_FAILURE;
> +
> +	/* Open user session */
> +	snprintf(socketname, sizeof(socketname), "hasher-priv-%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..5c56033
> --- /dev/null
> +++ b/hasher-priv/hasher-privd.c
> @@ -0,0 +1,375 @@
> +
> +/*
> +  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) {
> +			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);
> +			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 (!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, PROJECT)) < 0)
> +		return EXIT_FAILURE;
> +
> +	umask(m);
> +
> +	snprintf(socketpath, sizeof(socketpath), "%s/%s", SOCKETDIR, PROJECT);
> +
> +	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..9adac47
> --- /dev/null
> +++ b/hasher-priv/logging.c
> @@ -0,0 +1,64 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion на altlinux.org>
> +
> +  A logging functions for the hasher-privd program.

"A functions"? Either an incorrect article or an unfitting plural.

> +
> +  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 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 stderr)
> +{
> +	int options = LOG_PID;
> +	if (stderr)
> +		options |= LOG_PERROR;
> +	log_priority = loglevel;
> +	openlog(program_invocation_short_name, options, LOG_DAEMON);
> +}
> +
> +void logging_close(void)
> +{
> +	closelog();
> +}
> +
> +void
> +message(int priority, const char *fmt, ...)
> +{
> +	va_list ap;
> +
> +	va_start(ap, fmt);
> +	if (priority <= log_priority)
> +		vsyslog(priority, fmt, ap);
> +	else if (log_priority < 0) {
> +		vfprintf(stderr, fmt, ap);
> +		fprintf(stderr, "\n");
> +	}
> +	va_end(ap);
> +}
> diff --git a/hasher-priv/logging.h b/hasher-priv/logging.h
> new file mode 100644
> index 0000000..9d28fc8
> --- /dev/null
> +++ b/hasher-priv/logging.h
> @@ -0,0 +1,55 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion на altlinux.org>
> +
> +  A logging functions for the hasher-privd program.

Same as above.

> +
> +  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..9e23e74
> --- /dev/null
> +++ b/hasher-priv/pidfile.c
> @@ -0,0 +1,128 @@
> +
> +/*
> +    pidfile.c - 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.

Not anymore, that file is now (at patch acceptance) part of hasher-priv.
An equivalent file is part of the sysklogd package, sure, but that's
rather irrelevant.

More like this file was pulled from and possibly originally written for
the sysklogd package; I agree the message should definitely state that
to preserve attribution.

> +
> +    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.

Same as above.

> +
> +    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..f0eb9f9 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_control_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..53ea5c3
> --- /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.
> +control_group=hashman
> diff --git a/hasher-priv/sockets.c b/hasher-priv/sockets.c
> new file mode 100644
> index 0000000..42618a7
> --- /dev/null
> +++ b/hasher-priv/sockets.c
> @@ -0,0 +1,183 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion на altlinux.org>
> +
> +  A сollection of helpers to simplify work with sockets.

This one's nice; however, "the use of" instead of "work with" looks even
nicer to me.

> +
> +  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..5acdb49
> --- /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 work with 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.24.0
> 
----------- следующая часть -----------
Было удалено вложение не в текстовом формате...
Имя     : signature.asc
Тип     : application/pgp-signature
Размер  : 833 байтов
Описание: отсутствует
Url     : <http://lists.altlinux.org/pipermail/devel/attachments/20200917/d7ba6f9f/attachment-0001.bin>


Подробная информация о списке рассылки Devel