[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