summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.in818
-rw-r--r--src/conversation.c191
-rw-r--r--src/copy_file.c168
-rw-r--r--src/edit_open.c456
-rw-r--r--src/env_hooks.c291
-rw-r--r--src/exec.c463
-rw-r--r--src/exec_common.c213
-rw-r--r--src/exec_monitor.c723
-rw-r--r--src/exec_nopty.c579
-rw-r--r--src/exec_pty.c1897
-rw-r--r--src/get_pty.c192
-rw-r--r--src/hooks.c245
-rw-r--r--src/limits.c385
-rw-r--r--src/load_plugins.c549
-rw-r--r--src/net_ifs.c373
-rw-r--r--src/openbsd.c36
-rw-r--r--src/parse_args.c863
-rw-r--r--src/preload.c77
-rw-r--r--src/preserve_fds.c216
-rw-r--r--src/regress/noexec/check_noexec.c200
-rw-r--r--src/regress/ttyname/check_ttyname.c99
-rw-r--r--src/selinux.c512
-rw-r--r--src/sesh.c424
-rw-r--r--src/signal.c184
-rw-r--r--src/solaris.c117
-rw-r--r--src/sudo.c2188
-rw-r--r--src/sudo.h299
-rw-r--r--src/sudo_edit.c780
-rw-r--r--src/sudo_edit.h55
-rw-r--r--src/sudo_exec.h115
-rw-r--r--src/sudo_noexec.c258
-rw-r--r--src/sudo_plugin_int.h133
-rw-r--r--src/sudo_usage.h.in38
-rw-r--r--src/tcsetpgrp_nobg.c71
-rw-r--r--src/tgetpass.c450
-rw-r--r--src/ttyname.c338
-rw-r--r--src/utmp.c389
37 files changed, 15385 insertions, 0 deletions
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..fb49d67
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,818 @@
+#
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2010-2021
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+# @configure_input@
+#
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+abs_srcdir = @abs_srcdir@
+top_srcdir = @top_srcdir@
+abs_top_srcdir = @abs_top_srcdir@
+top_builddir = @top_builddir@
+abs_top_builddir = @abs_top_builddir@
+devdir = @devdir@
+scriptdir = $(top_srcdir)/scripts
+incdir = $(top_srcdir)/include
+rundir = @rundir@
+cross_compiling = @CROSS_COMPILING@
+
+# Compiler & tools to use
+CC = @CC@
+LIBTOOL = @LIBTOOL@
+SED = @SED@
+
+# Our install program supports extra flags...
+INSTALL = $(SHELL) $(top_srcdir)/install-sh -c
+INSTALL_OWNER = -o $(install_uid) -g $(install_gid)
+INSTALL_BACKUP = @INSTALL_BACKUP@
+
+# Libraries
+LT_LIBS = $(top_builddir)/lib/util/libsudo_util.la
+LIBS = @LIBS@ @SUDO_LIBS@ @GETGROUPS_LIB@ @NET_LIBS@ $(LT_LIBS)
+
+# C preprocessor defines
+CPPDEFS = -D_PATH_SUDO_CONF=\"$(sysconfdir)/sudo.conf\" \
+ -DLOCALEDIR=\"$(localedir)\"
+
+# C preprocessor flags
+CPPFLAGS = -I$(incdir) -I$(top_builddir) -I. -I$(srcdir) $(CPPDEFS) @CPPFLAGS@
+
+# Usually -O and/or -g
+CFLAGS = @CFLAGS@
+
+# Flags to pass to the link stage
+LDFLAGS = @LDFLAGS@
+SUDO_LDFLAGS = $(LDFLAGS) @SUDO_LDFLAGS@
+LT_LDFLAGS = @LT_LDFLAGS@
+
+# Flags to pass to libtool
+LTFLAGS = --tag=disable-static
+
+# Address sanitizer flags
+ASAN_CFLAGS = @ASAN_CFLAGS@
+ASAN_LDFLAGS = @ASAN_LDFLAGS@
+
+# PIE flags
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+
+# Stack smashing protection flags
+SSP_CFLAGS = @SSP_CFLAGS@
+SSP_LDFLAGS = @SSP_LDFLAGS@
+
+# cppcheck options, usually set in the top-level Makefile
+CPPCHECK_OPTS = -q --enable=warning,performance,portability --suppress=constStatement --suppress=compareBoolExpressionWithInt --error-exitcode=1 --inline-suppr -Dva_copy=va_copy -U__cplusplus -UQUAD_MAX -UQUAD_MIN -UUQUAD_MAX -U_POSIX_HOST_NAME_MAX -U_POSIX_PATH_MAX -U__NBBY -DNSIG=64
+
+# splint options, usually set in the top-level Makefile
+SPLINT_OPTS = -D__restrict= -checks
+
+# PVS-studio options
+PVS_CFG = $(top_srcdir)/PVS-Studio.cfg
+PVS_IGNORE = 'V707,V011,V002,V536'
+PVS_LOG_OPTS = -a 'GA:1,2' -e -t errorfile -d $(PVS_IGNORE)
+
+# Where to install things...
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+sbindir = @sbindir@
+sysconfdir = @sysconfdir@
+libexecdir = @libexecdir@
+datarootdir = @datarootdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+noexecfile = @NOEXECFILE@
+noexecdir = @NOEXECDIR@
+tmpfiles_d = @TMPFILES_D@
+
+# User and group ids the installed files should be "owned" by
+install_uid = 0
+install_gid = 0
+
+# File mode to use for shared libraries
+shlib_mode = @SHLIB_MODE@
+
+# Optional init script and rc.d link
+INIT_DIR=@INIT_DIR@
+INIT_SCRIPT=@INIT_SCRIPT@
+RC_LINK=@RC_LINK@
+
+TEST_PROGS = check_ttyname @CHECK_NOEXEC@
+TEST_LIBS = @LIBS@ $(LT_LIBS)
+TEST_LDFLAGS = @LDFLAGS@
+
+#### End of system configuration section. ####
+
+SHELL = @SHELL@
+
+PROGS = @PROGS@
+
+OBJS = conversation.o copy_file.o edit_open.o env_hooks.o exec.o \
+ exec_common.o exec_monitor.o exec_nopty.o exec_pty.o get_pty.o hooks.o \
+ limits.o load_plugins.o net_ifs.o parse_args.o preserve_fds.o \
+ signal.o sudo.o sudo_edit.o tcsetpgrp_nobg.o tgetpass.o \
+ ttyname.o utmp.o @SUDO_OBJS@
+
+IOBJS = $(OBJS:.o=.i) sesh.i
+
+POBJS = $(IOBJS:.i=.plog)
+
+SESH_OBJS = copy_file.o edit_open.o exec_common.o sesh.o
+
+CHECK_NOEXEC_OBJS = check_noexec.o exec_common.o
+
+CHECK_TTYNAME_OBJS = check_ttyname.o ttyname.o
+
+LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/
+
+VERSION = @PACKAGE_VERSION@
+
+all: $(PROGS)
+
+depend:
+ $(scriptdir)/mkdep.pl --srcdir=$(abs_top_srcdir) \
+ --builddir=$(abs_top_builddir) src/Makefile.in
+ cd $(top_builddir) && ./config.status --file src/Makefile
+
+Makefile: $(srcdir)/Makefile.in
+ cd $(top_builddir) && ./config.status --file src/Makefile
+
+./sudo_usage.h: $(srcdir)/sudo_usage.h.in
+ cd $(top_builddir) && ./config.status --file src/sudo_usage.h
+
+.SUFFIXES: .c .h .i .lo .o .plog
+
+.c.o:
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $<
+
+.c.lo:
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $<
+
+.c.i:
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+
+.i.plog:
+ ifile=$<; rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $${ifile%i}c --i-file $< --output-file $@
+
+sudo: $(OBJS) $(LT_LIBS) @STATIC_SUDOERS@
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(OBJS) $(SUDO_LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) @STATIC_SUDOERS@
+
+# We can't use -module here since you cannot preload a module on Darwin
+libsudo_noexec.la: sudo_noexec.lo
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LDFLAGS) $(LT_LDFLAGS) $(SSP_LDFLAGS) @LIBDL@ -o $@ sudo_noexec.lo -avoid-version -rpath $(noexecdir) -shrext .so
+
+# Some hackery is required to install this as sudo_noexec, not libsudo_noexec
+sudo_noexec.la: libsudo_noexec.la
+ sed 's/libsudo_noexec/sudo_noexec/g' libsudo_noexec.la > sudo_noexec.la
+ if test -f .libs/libsudo_noexec.lai; then sed 's/libsudo_noexec/sudo_noexec/g' .libs/libsudo_noexec.lai > .libs/sudo_noexec.lai; fi
+ cp -p .libs/libsudo_noexec.so .libs/sudo_noexec.so
+
+sesh: $(SESH_OBJS) $(LT_LIBS)
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(SESH_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS)
+
+check_noexec: $(CHECK_NOEXEC_OBJS) $(top_builddir)/lib/util/libsudo_util.la sudo_noexec.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_NOEXEC_OBJS) $(TEST_LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LIBS)
+
+check_ttyname: $(CHECK_TTYNAME_OBJS) $(top_builddir)/lib/util/libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_TTYNAME_OBJS) $(TEST_LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LIBS)
+
+pre-install:
+
+install: install-binaries install-rc @INSTALL_NOEXEC@
+
+install-dirs:
+ # We only create the rc.d dir when installing to the actual system dir
+ $(SHELL) $(scriptdir)/mkinstalldirs $(DESTDIR)$(bindir) \
+ $(DESTDIR)$(libexecdir)/sudo $(DESTDIR)$(noexecdir)
+ if test -n "$(INIT_SCRIPT)"; then \
+ $(SHELL) $(scriptdir)/mkinstalldirs $(DESTDIR)$(INIT_DIR); \
+ if test -z "$(DESTDIR)"; then \
+ $(SHELL) $(scriptdir)/mkinstalldirs \
+ `echo $(RC_LINK) | $(SED) 's,/[^/]*$$,,'`; \
+ fi; \
+ elif test -n "$(tmpfiles_d)"; then \
+ $(SHELL) $(scriptdir)/mkinstalldirs $(DESTDIR)$(tmpfiles_d); \
+ fi
+
+install-rc: install-dirs
+ # We only create the rc.d link when installing to the actual system dir
+ if [ -n "$(INIT_SCRIPT)" ]; then \
+ $(INSTALL) $(INSTALL_OWNER) -m 0755 $(top_builddir)/etc/init.d/$(INIT_SCRIPT) $(DESTDIR)$(INIT_DIR)/sudo; \
+ if test -z "$(DESTDIR)"; then \
+ rm -f $(RC_LINK); \
+ ln -s $(INIT_DIR)/sudo $(RC_LINK); \
+ fi; \
+ elif test -n "$(tmpfiles_d)"; then \
+ $(INSTALL) $(INSTALL_OWNER) -m 0644 $(top_builddir)/etc/init.d/sudo.conf $(DESTDIR)$(tmpfiles_d)/sudo.conf; \
+ fi
+
+install-binaries: install-dirs $(PROGS)
+ INSTALL_BACKUP='$(INSTALL_BACKUP)' $(LIBTOOL) $(LTFLAGS) --mode=install $(INSTALL) $(INSTALL_OWNER) -m 04755 sudo $(DESTDIR)$(bindir)/sudo
+ rm -f $(DESTDIR)$(bindir)/sudoedit
+ ln -s sudo $(DESTDIR)$(bindir)/sudoedit
+ if [ -f sesh ]; then \
+ INSTALL_BACKUP='$(INSTALL_BACKUP)' $(LIBTOOL) $(LTFLAGS) --mode=install $(INSTALL) $(INSTALL_OWNER) -m 0755 sesh $(DESTDIR)$(libexecdir)/sudo/sesh; \
+ fi
+
+install-doc:
+
+install-includes:
+
+install-noexec: install-dirs sudo_noexec.la
+ INSTALL_BACKUP='$(INSTALL_BACKUP)' $(LIBTOOL) $(LTFLAGS) --mode=install $(INSTALL) $(INSTALL_OWNER) -m $(shlib_mode) sudo_noexec.la $(DESTDIR)$(noexecdir)
+
+install-plugin:
+
+uninstall:
+ -$(LIBTOOL) $(LTFLAGS) --mode=uninstall rm -f $(DESTDIR)$(noexecdir)/sudo_noexec.la
+ -rm -f $(DESTDIR)$(bindir)/sudo \
+ $(DESTDIR)$(bindir)/sudoedit \
+ $(DESTDIR)$(libexecdir)/sudo/sesh \
+ $(DESTDIR)/usr/lib/tmpfiles.d/sudo.conf
+ -test -z "$(INSTALL_BACKUP)" || \
+ rm -f $(DESTDIR)$(bindir)/sudo$(INSTALL_BACKUP) \
+ $(DESTDIR)$(libexecdir)/sudo/sesh$(INSTALL_BACKUP) \
+ $(DESTDIR)$(noexecdir)/sudo_noexec.so$(INSTALL_BACKUP)
+ -test -z "$(INIT_SCRIPT)" || \
+ rm -f $(DESTDIR)$(RC_LINK) $(DESTDIR)$(INIT_DIR)/sudo
+
+splint:
+ splint $(SPLINT_OPTS) -I$(incdir) -I$(top_builddir) -I. -I$(srcdir) $(srcdir)/*.c
+
+cppcheck:
+ cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) -I. -I$(srcdir) $(srcdir)/*.c
+
+pvs-log-files: $(POBJS)
+
+pvs-studio: $(POBJS)
+ plog-converter $(PVS_LOG_OPTS) $(POBJS)
+
+check: $(TEST_PROGS)
+ @if test X"$(cross_compiling)" != X"yes"; then \
+ MALLOC_OPTIONS=S; export MALLOC_OPTIONS; \
+ MALLOC_CONF="abort:true,junk:true"; export MALLOC_CONF; \
+ ./check_ttyname; \
+ if test X"@CHECK_NOEXEC@" != X""; then \
+ ./check_noexec .libs/$(noexecfile); \
+ fi; \
+ fi
+
+clean:
+ -$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f $(PROGS) $(TEST_PROGS) \
+ *.lo *.o *.la
+ -rm -f *.i *.plog stamp-* core *.core core.*
+
+mostlyclean: clean
+
+distclean: clean
+ -rm -rf Makefile .libs sudo_usage.h
+
+clobber: distclean
+
+realclean: distclean
+ rm -f TAGS tags
+
+cleandir: realclean
+
+# *Not* auto-generated to avoid building with ASAN
+sudo_noexec.lo: $(srcdir)/sudo_noexec.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/sudo_noexec.c
+
+# Autogenerated dependencies, do not modify
+check_noexec.o: $(srcdir)/regress/noexec/check_noexec.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo_exec.h \
+ $(top_builddir)/config.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/noexec/check_noexec.c
+check_noexec.i: $(srcdir)/regress/noexec/check_noexec.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo_exec.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+check_noexec.plog: check_noexec.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/noexec/check_noexec.c --i-file $< --output-file $@
+check_ttyname.o: $(srcdir)/regress/ttyname/check_ttyname.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/ttyname/check_ttyname.c
+check_ttyname.i: $(srcdir)/regress/ttyname/check_ttyname.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+check_ttyname.plog: check_ttyname.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/ttyname/check_ttyname.c --i-file $< --output-file $@
+conversation.o: $(srcdir)/conversation.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/conversation.c
+conversation.i: $(srcdir)/conversation.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+conversation.plog: conversation.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/conversation.c --i-file $< --output-file $@
+copy_file.o: $(srcdir)/copy_file.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/copy_file.c
+copy_file.i: $(srcdir)/copy_file.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+copy_file.plog: copy_file.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/copy_file.c --i-file $< --output-file $@
+edit_open.o: $(srcdir)/edit_open.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_edit.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/edit_open.c
+edit_open.i: $(srcdir)/edit_open.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_edit.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+edit_open.plog: edit_open.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/edit_open.c --i-file $< --output-file $@
+env_hooks.o: $(srcdir)/env_hooks.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_dso.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/env_hooks.c
+env_hooks.i: $(srcdir)/env_hooks.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_dso.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+env_hooks.plog: env_hooks.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/env_hooks.c --i-file $< --output-file $@
+exec.o: $(srcdir)/exec.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/sudo.h $(srcdir)/sudo_exec.h $(srcdir)/sudo_plugin_int.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/exec.c
+exec.i: $(srcdir)/exec.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/sudo.h $(srcdir)/sudo_exec.h $(srcdir)/sudo_plugin_int.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+exec.plog: exec.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/exec.c --i-file $< --output-file $@
+exec_common.o: $(srcdir)/exec_common.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/exec_common.c
+exec_common.i: $(srcdir)/exec_common.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+exec_common.plog: exec_common.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/exec_common.c --i-file $< --output-file $@
+exec_monitor.o: $(srcdir)/exec_monitor.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/exec_monitor.c
+exec_monitor.i: $(srcdir)/exec_monitor.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+exec_monitor.plog: exec_monitor.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/exec_monitor.c --i-file $< --output-file $@
+exec_nopty.o: $(srcdir)/exec_nopty.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/exec_nopty.c
+exec_nopty.i: $(srcdir)/exec_nopty.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+exec_nopty.plog: exec_nopty.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/exec_nopty.c --i-file $< --output-file $@
+exec_pty.o: $(srcdir)/exec_pty.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/exec_pty.c
+exec_pty.i: $(srcdir)/exec_pty.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+exec_pty.plog: exec_pty.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/exec_pty.c --i-file $< --output-file $@
+get_pty.o: $(srcdir)/get_pty.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/get_pty.c
+get_pty.i: $(srcdir)/get_pty.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+get_pty.plog: get_pty.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/get_pty.c --i-file $< --output-file $@
+hooks.o: $(srcdir)/hooks.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/sudo.h $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/hooks.c
+hooks.i: $(srcdir)/hooks.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/sudo.h $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+hooks.plog: hooks.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/hooks.c --i-file $< --output-file $@
+limits.o: $(srcdir)/limits.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/limits.c
+limits.i: $(srcdir)/limits.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+limits.plog: limits.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/limits.c --i-file $< --output-file $@
+load_plugins.o: $(srcdir)/load_plugins.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_dso.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/load_plugins.c
+load_plugins.i: $(srcdir)/load_plugins.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_dso.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+load_plugins.plog: load_plugins.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/load_plugins.c --i-file $< --output-file $@
+net_ifs.o: $(srcdir)/net_ifs.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(top_builddir)/config.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/net_ifs.c
+net_ifs.i: $(srcdir)/net_ifs.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+net_ifs.plog: net_ifs.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/net_ifs.c --i-file $< --output-file $@
+openbsd.o: $(srcdir)/openbsd.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/openbsd.c
+openbsd.i: $(srcdir)/openbsd.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+openbsd.plog: openbsd.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/openbsd.c --i-file $< --output-file $@
+parse_args.o: $(srcdir)/parse_args.c $(incdir)/compat/getopt.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_lbuf.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h ./sudo_usage.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/parse_args.c
+parse_args.i: $(srcdir)/parse_args.c $(incdir)/compat/getopt.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_lbuf.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h ./sudo_usage.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+parse_args.plog: parse_args.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/parse_args.c --i-file $< --output-file $@
+preload.o: $(srcdir)/preload.c $(incdir)/sudo_compat.h $(incdir)/sudo_dso.h \
+ $(incdir)/sudo_plugin.h $(top_builddir)/config.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/preload.c
+preload.i: $(srcdir)/preload.c $(incdir)/sudo_compat.h $(incdir)/sudo_dso.h \
+ $(incdir)/sudo_plugin.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+preload.plog: preload.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/preload.c --i-file $< --output-file $@
+preserve_fds.o: $(srcdir)/preserve_fds.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/preserve_fds.c
+preserve_fds.i: $(srcdir)/preserve_fds.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+preserve_fds.plog: preserve_fds.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/preserve_fds.c --i-file $< --output-file $@
+selinux.o: $(srcdir)/selinux.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/selinux.c
+selinux.i: $(srcdir)/selinux.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+selinux.plog: selinux.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/selinux.c --i-file $< --output-file $@
+sesh.o: $(srcdir)/sesh.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/sudo.h $(srcdir)/sudo_edit.h $(srcdir)/sudo_exec.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/sesh.c
+sesh.i: $(srcdir)/sesh.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/sudo.h $(srcdir)/sudo_edit.h $(srcdir)/sudo_exec.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+sesh.plog: sesh.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sesh.c --i-file $< --output-file $@
+signal.o: $(srcdir)/signal.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(srcdir)/sudo_exec.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/signal.c
+signal.i: $(srcdir)/signal.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(srcdir)/sudo_exec.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+signal.plog: signal.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/signal.c --i-file $< --output-file $@
+solaris.o: $(srcdir)/solaris.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_dso.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/solaris.c
+solaris.i: $(srcdir)/solaris.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_dso.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+solaris.plog: solaris.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/solaris.c --i-file $< --output-file $@
+sudo.o: $(srcdir)/sudo.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/sudo.h $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h ./sudo_usage.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/sudo.c
+sudo.i: $(srcdir)/sudo.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/sudo.h $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h ./sudo_usage.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+sudo.plog: sudo.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sudo.c --i-file $< --output-file $@
+sudo_edit.o: $(srcdir)/sudo_edit.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_edit.h \
+ $(srcdir)/sudo_exec.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/sudo_edit.c
+sudo_edit.i: $(srcdir)/sudo_edit.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_edit.h \
+ $(srcdir)/sudo_exec.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+sudo_edit.plog: sudo_edit.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sudo_edit.c --i-file $< --output-file $@
+tcsetpgrp_nobg.o: $(srcdir)/tcsetpgrp_nobg.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/tcsetpgrp_nobg.c
+tcsetpgrp_nobg.i: $(srcdir)/tcsetpgrp_nobg.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+tcsetpgrp_nobg.plog: tcsetpgrp_nobg.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/tcsetpgrp_nobg.c --i-file $< --output-file $@
+tgetpass.o: $(srcdir)/tgetpass.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/tgetpass.c
+tgetpass.i: $(srcdir)/tgetpass.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+tgetpass.plog: tgetpass.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/tgetpass.c --i-file $< --output-file $@
+ttyname.o: $(srcdir)/ttyname.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/ttyname.c
+ttyname.i: $(srcdir)/ttyname.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+ttyname.plog: ttyname.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/ttyname.c --i-file $< --output-file $@
+utmp.o: $(srcdir)/utmp.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/sudo.h $(srcdir)/sudo_exec.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/utmp.c
+utmp.i: $(srcdir)/utmp.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/sudo.h $(srcdir)/sudo_exec.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+utmp.plog: utmp.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/utmp.c --i-file $< --output-file $@
diff --git a/src/conversation.c b/src/conversation.c
new file mode 100644
index 0000000..95ecadd
--- /dev/null
+++ b/src/conversation.c
@@ -0,0 +1,191 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1999-2005, 2007-2012 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+extern int tgetpass_flags; /* XXX */
+
+/*
+ * Sudo conversation function.
+ */
+int
+sudo_conversation(int num_msgs, const struct sudo_conv_message msgs[],
+ struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
+{
+ char *pass;
+ int n;
+ const int conv_debug_instance = sudo_debug_get_active_instance();
+
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+ for (n = 0; n < num_msgs; n++) {
+ const struct sudo_conv_message *msg = &msgs[n];
+ int flags = tgetpass_flags;
+ FILE *fp = stdout;
+
+ switch (msg->msg_type & 0xff) {
+ case SUDO_CONV_PROMPT_ECHO_ON:
+ SET(flags, TGP_ECHO);
+ goto read_pass;
+ case SUDO_CONV_PROMPT_MASK:
+ SET(flags, TGP_MASK);
+ FALLTHROUGH;
+ case SUDO_CONV_PROMPT_ECHO_OFF:
+ if (ISSET(msg->msg_type, SUDO_CONV_PROMPT_ECHO_OK))
+ SET(flags, TGP_NOECHO_TRY);
+ read_pass:
+ /* Read the password unless interrupted. */
+ if (replies == NULL)
+ goto err;
+ pass = tgetpass(msg->msg, msg->timeout, flags, callback);
+ if (pass == NULL)
+ goto err;
+ replies[n].reply = strdup(pass);
+ if (replies[n].reply == NULL) {
+ sudo_fatalx_nodebug(U_("%s: %s"), "sudo_conversation",
+ U_("unable to allocate memory"));
+ }
+ explicit_bzero(pass, strlen(pass));
+ break;
+ case SUDO_CONV_ERROR_MSG:
+ fp = stderr;
+ FALLTHROUGH;
+ case SUDO_CONV_INFO_MSG:
+ if (msg->msg != NULL) {
+ size_t len = strlen(msg->msg);
+ const char *crnl = NULL;
+ bool written = false;
+ int ttyfd = -1;
+
+ if (ISSET(msg->msg_type, SUDO_CONV_PREFER_TTY) &&
+ !ISSET(tgetpass_flags, TGP_STDIN))
+ ttyfd = open(_PATH_TTY, O_WRONLY);
+ if (len != 0 && (ttyfd != -1 || isatty(fileno(fp)))) {
+ /* Convert nl -> cr nl in case tty is in raw mode. */
+ if (msg->msg[len - 1] == '\n') {
+ if (len == 1 || msg->msg[len - 2] != '\r') {
+ len--;
+ crnl = "\r\n";
+ }
+ }
+ }
+ if (ttyfd != -1) {
+ /* Try writing to tty but fall back to fp on error. */
+ if ((len == 0 || write(ttyfd, msg->msg, len) != -1) &&
+ (crnl == NULL || write(ttyfd, crnl, 2) != -1)) {
+ written = true;
+ }
+ close(ttyfd);
+ }
+ if (!written) {
+ if (len != 0 && fwrite(msg->msg, 1, len, fp) == 0)
+ goto err;
+ if (crnl != NULL && fwrite(crnl, 1, 2, fp) == 0)
+ goto err;
+ }
+ }
+ break;
+ default:
+ goto err;
+ }
+ }
+
+ sudo_debug_set_active_instance(conv_debug_instance);
+ return 0;
+
+err:
+ /* Zero and free allocated memory and return an error. */
+ if (replies != NULL) {
+ do {
+ struct sudo_conv_reply *repl = &replies[n];
+ if (repl->reply == NULL)
+ continue;
+ freezero(repl->reply, strlen(repl->reply));
+ repl->reply = NULL;
+ } while (n--);
+ }
+
+ sudo_debug_set_active_instance(conv_debug_instance);
+ return -1;
+}
+
+int
+sudo_conversation_1_7(int num_msgs, const struct sudo_conv_message msgs[],
+ struct sudo_conv_reply replies[])
+{
+ return sudo_conversation(num_msgs, msgs, replies, NULL);
+}
+
+int
+sudo_conversation_printf(int msg_type, const char *fmt, ...)
+{
+ FILE *fp = stdout;
+ FILE *ttyfp = NULL;
+ va_list ap;
+ int len;
+ const int conv_debug_instance = sudo_debug_get_active_instance();
+
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+ if (ISSET(msg_type, SUDO_CONV_PREFER_TTY) &&
+ !ISSET(tgetpass_flags, TGP_STDIN)) {
+ /* Try writing to /dev/tty first. */
+ ttyfp = fopen(_PATH_TTY, "w");
+ }
+
+ switch (msg_type & 0xff) {
+ case SUDO_CONV_ERROR_MSG:
+ fp = stderr;
+ FALLTHROUGH;
+ case SUDO_CONV_INFO_MSG:
+ va_start(ap, fmt);
+ len = vfprintf(ttyfp ? ttyfp : fp, fmt, ap);
+ va_end(ap);
+ break;
+ default:
+ len = -1;
+ errno = EINVAL;
+ break;
+ }
+
+ if (ttyfp != NULL)
+ fclose(ttyfp);
+
+ sudo_debug_set_active_instance(conv_debug_instance);
+ return len;
+}
diff --git a/src/copy_file.c b/src/copy_file.c
new file mode 100644
index 0000000..9404408
--- /dev/null
+++ b/src/copy_file.c
@@ -0,0 +1,168 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "sudo.h"
+
+/*
+ * Extend the given fd to the specified size in bytes.
+ * We do this to allocate disk space up-front before overwriting
+ * the original file with the temporary. Otherwise, we could
+ * we run out of disk space after truncating the original file.
+ */
+static int
+sudo_extend_file(int fd, const char *name, off_t new_size)
+{
+ off_t old_size, size;
+ ssize_t nwritten;
+ char zeroes[BUFSIZ] = { '\0' };
+ debug_decl(sudo_extend_file, SUDO_DEBUG_UTIL);
+
+ if ((old_size = lseek(fd, 0, SEEK_END)) == -1) {
+ sudo_warn("lseek");
+ debug_return_int(-1);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: extending %s from %lld to %lld",
+ __func__, name, (long long)old_size, (long long)new_size);
+
+ for (size = old_size; size < new_size; size += nwritten) {
+ size_t len = new_size - size;
+ if (len > sizeof(zeroes))
+ len = sizeof(zeroes);
+ nwritten = write(fd, zeroes, len);
+ if (nwritten == -1) {
+ int serrno = errno;
+ if (ftruncate(fd, old_size) == -1) {
+ sudo_debug_printf(
+ SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to truncate %s to %lld", name, (long long)old_size);
+ }
+ errno = serrno;
+ debug_return_int(-1);
+ }
+ }
+ if (lseek(fd, 0, SEEK_SET) == -1) {
+ sudo_warn("lseek");
+ debug_return_int(-1);
+ }
+
+ debug_return_int(0);
+}
+
+/*
+ * Copy the contents of src_fd into dst_fd.
+ * Returns 0 on success or -1 on error.
+ */
+int
+sudo_copy_file(const char *src, int src_fd, off_t src_len, const char *dst,
+ int dst_fd, off_t dst_len)
+{
+ char buf[BUFSIZ];
+ ssize_t nwritten, nread;
+ debug_decl(sudo_copy_file, SUDO_DEBUG_UTIL);
+
+ /* Prompt the user before zeroing out an existing file. */
+ if (dst_len > 0 && src_len == 0) {
+ fprintf(stderr, U_("%s: truncate %s to zero bytes? (y/n) [n] "),
+ getprogname(), dst);
+ if (fgets(buf, sizeof(buf), stdin) == NULL ||
+ (buf[0] != 'y' && buf[0] != 'Y')) {
+ sudo_warnx(U_("not overwriting %s"), dst);
+ debug_return_int(0);
+ }
+ }
+
+ /* Extend the file to the new size if larger before copying. */
+ if (dst_len > 0 && src_len > dst_len) {
+ if (sudo_extend_file(dst_fd, dst, src_len) == -1)
+ goto write_error;
+ }
+
+ /* Overwrite the old file with the new contents. */
+ while ((nread = read(src_fd, buf, sizeof(buf))) > 0) {
+ ssize_t off = 0;
+ do {
+ nwritten = write(dst_fd, buf + off, nread - off);
+ if (nwritten == -1)
+ goto write_error;
+ off += nwritten;
+ } while (nread > off);
+ }
+ if (nread == -1) {
+ sudo_warn(U_("unable to read from %s"), src);
+ debug_return_int(-1);
+ }
+
+ /* Did the file shrink? */
+ if (src_len < dst_len) {
+ /* We don't open with O_TRUNC so must truncate manually. */
+ if (ftruncate(dst_fd, src_len) == -1) {
+ sudo_debug_printf(
+ SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to truncate %s to %lld", dst, (long long)src_len);
+ goto write_error;
+ }
+ }
+
+ debug_return_int(0);
+write_error:
+ sudo_warn(U_("unable to write to %s"), dst);
+ debug_return_int(-1);
+}
+
+bool
+sudo_check_temp_file(int tfd, const char *tfile, uid_t uid, struct stat *sb)
+{
+ struct stat sbuf;
+ debug_decl(sudo_check_temp_file, SUDO_DEBUG_UTIL);
+
+ if (sb == NULL)
+ sb = &sbuf;
+
+ if (fstat(tfd, sb) == -1) {
+ sudo_warn(U_("unable to stat %s"), tfile);
+ debug_return_bool(false);
+ }
+ if (!S_ISREG(sb->st_mode)) {
+ sudo_warnx(U_("%s: not a regular file"), tfile);
+ debug_return_bool(false);
+ }
+ if ((sb->st_mode & ALLPERMS) != (S_IRUSR|S_IWUSR)) {
+ sudo_warnx(U_("%s: bad file mode: 0%o"), tfile,
+ (unsigned int)(sb->st_mode & ALLPERMS));
+ debug_return_bool(false);
+ }
+ if (sb->st_uid != uid) {
+ sudo_warnx(U_("%s is owned by uid %u, should be %u"),
+ tfile, (unsigned int)sb->st_uid, (unsigned int)uid);
+ debug_return_bool(false);
+ }
+ debug_return_bool(true);
+}
diff --git a/src/edit_open.c b/src/edit_open.c
new file mode 100644
index 0000000..864dcea
--- /dev/null
+++ b/src/edit_open.c
@@ -0,0 +1,456 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2015-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <fcntl.h>
+
+#include "sudo.h"
+#include "sudo_edit.h"
+
+#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
+
+void
+switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups)
+{
+ int serrno = errno;
+ debug_decl(switch_user, SUDO_DEBUG_EDIT);
+
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "set uid:gid to %u:%u(%u)", (unsigned int)euid, (unsigned int)egid,
+ ngroups > 0 ? (unsigned int)groups[0] : (unsigned int)egid);
+
+ /* When restoring root, change euid first; otherwise change it last. */
+ if (euid == ROOT_UID) {
+ if (seteuid(ROOT_UID) != 0)
+ sudo_fatal("seteuid(ROOT_UID)");
+ }
+ if (setegid(egid) != 0)
+ sudo_fatal("setegid(%d)", (int)egid);
+ if (ngroups != -1) {
+ if (sudo_setgroups(ngroups, groups) != 0)
+ sudo_fatal("setgroups");
+ }
+ if (euid != ROOT_UID) {
+ if (seteuid(euid) != 0)
+ sudo_fatal("seteuid(%u)", (unsigned int)euid);
+ }
+ errno = serrno;
+
+ debug_return;
+}
+
+#if defined(HAVE_FACCESSAT) && defined(AT_EACCESS)
+/*
+ * Returns true if the open directory fd is owned or writable by the user.
+ */
+int
+dir_is_writable(int dfd, struct sudo_cred *user_cred, struct sudo_cred *cur_cred)
+{
+ struct stat sb;
+ int rc;
+ debug_decl(dir_is_writable, SUDO_DEBUG_EDIT);
+
+ if (fstat(dfd, &sb) == -1)
+ debug_return_int(-1);
+
+ /* If the user owns the dir we always consider it writable. */
+ if (sb.st_uid == user_cred->uid) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "user uid %u matches directory uid %u",
+ (unsigned int)user_cred->uid, (unsigned int)sb.st_uid);
+ debug_return_int(true);
+ }
+
+ /* Change uid/gid/groups to invoking user, usually needs root perms. */
+ if (cur_cred->euid != ROOT_UID) {
+ if (seteuid(ROOT_UID) != 0)
+ sudo_fatal("seteuid(ROOT_UID)");
+ }
+ switch_user(user_cred->uid, user_cred->gid, user_cred->ngroups,
+ user_cred->groups);
+
+ /* Access checks are done using the euid/egid and group vector. */
+ rc = faccessat(dfd, ".", W_OK, AT_EACCESS);
+
+ /* Restore uid/gid/groups, may need root perms. */
+ if (user_cred->uid != ROOT_UID) {
+ if (seteuid(ROOT_UID) != 0)
+ sudo_fatal("seteuid(ROOT_UID)");
+ }
+ switch_user(cur_cred->euid, cur_cred->egid, cur_cred->ngroups,
+ cur_cred->groups);
+
+ if (rc == 0)
+ debug_return_int(true);
+ if (errno == EACCES || errno == EROFS)
+ debug_return_int(false);
+ debug_return_int(-1);
+}
+#else
+static bool
+group_matches(gid_t target, struct sudo_cred *cred)
+{
+ int i;
+ debug_decl(group_matches, SUDO_DEBUG_EDIT);
+
+ if (target == cred->gid) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "user gid %u matches directory gid %u", (unsigned int)cred->gid,
+ (unsigned int)target);
+ debug_return_bool(true);
+ }
+ for (i = 0; i < cred->ngroups; i++) {
+ if (target == cred->groups[i]) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "user gid %u matches directory gid %u",
+ (unsigned int)cred->groups[i], (unsigned int)target);
+ debug_return_bool(true);
+ }
+ }
+ debug_return_bool(false);
+}
+
+/*
+ * Returns true if the open directory fd is owned or writable by the user.
+ */
+int
+dir_is_writable(int dfd, struct sudo_cred *user_cred, struct sudo_cred *cur_cred)
+{
+ struct stat sb;
+ debug_decl(dir_is_writable, SUDO_DEBUG_EDIT);
+
+ if (fstat(dfd, &sb) == -1)
+ debug_return_int(-1);
+
+ /* If the user owns the dir we always consider it writable. */
+ if (sb.st_uid == user_cred->uid) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "user uid %u matches directory uid %u",
+ (unsigned int)user_cred->uid, (unsigned int)sb.st_uid);
+ debug_return_int(true);
+ }
+
+ /* Other writable? */
+ if (ISSET(sb.st_mode, S_IWOTH)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "directory is writable by other");
+ debug_return_int(true);
+ }
+
+ /* Group writable? */
+ if (ISSET(sb.st_mode, S_IWGRP)) {
+ if (group_matches(sb.st_gid, user_cred)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "directory is writable by one of the user's groups");
+ debug_return_int(true);
+ }
+ }
+
+ errno = EACCES;
+ debug_return_int(false);
+}
+#endif /* HAVE_FACCESSAT && AT_EACCESS */
+
+#ifdef O_NOFOLLOW
+static int
+sudo_edit_openat_nofollow(int dfd, char *path, int oflags, mode_t mode)
+{
+ debug_decl(sudo_edit_openat_nofollow, SUDO_DEBUG_EDIT);
+
+ debug_return_int(openat(dfd, path, oflags|O_NOFOLLOW, mode));
+}
+#else
+/*
+ * Returns true if fd and path don't match or path is a symlink.
+ * Used on older systems without O_NOFOLLOW.
+ */
+static bool
+sudo_edit_is_symlink(int fd, char *path)
+{
+ struct stat sb1, sb2;
+ debug_decl(sudo_edit_is_symlink, SUDO_DEBUG_EDIT);
+
+ /*
+ * Treat [fl]stat() failure like there was a symlink.
+ */
+ if (fstat(fd, &sb1) == -1 || lstat(path, &sb2) == -1)
+ debug_return_bool(true);
+
+ /*
+ * Make sure we did not open a link and that what we opened
+ * matches what is currently on the file system.
+ */
+ if (S_ISLNK(sb2.st_mode) ||
+ sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
+ debug_return_bool(true);
+ }
+
+ debug_return_bool(false);
+}
+
+static int
+sudo_edit_openat_nofollow(int dfd, char *path, int oflags, mode_t mode)
+{
+ int fd = -1, odfd = -1;
+ struct stat sb;
+ debug_decl(sudo_edit_openat_nofollow, SUDO_DEBUG_EDIT);
+
+ /* Save cwd and chdir to dfd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ debug_return_int(-1);
+ if (fchdir(dfd) == -1) {
+ close(odfd);
+ debug_return_int(-1);
+ }
+
+ /*
+ * Check if path is a symlink. This is racey but we detect whether
+ * we lost the race in sudo_edit_is_symlink() after the open.
+ */
+ if (lstat(path, &sb) == -1 && errno != ENOENT)
+ goto done;
+ if (S_ISLNK(sb.st_mode)) {
+ errno = ELOOP;
+ goto done;
+ }
+
+ fd = open(path, oflags, mode);
+ if (fd == -1)
+ goto done;
+
+ /*
+ * Post-open symlink check. This will leave a zero-length file if
+ * O_CREAT was specified but it is too dangerous to try and remove it.
+ */
+ if (sudo_edit_is_symlink(fd, path)) {
+ close(fd);
+ fd = -1;
+ errno = ELOOP;
+ }
+
+done:
+ /* Restore cwd */
+ if (odfd != -1) {
+ if (fchdir(odfd) == -1)
+ sudo_fatal("%s", U_("unable to restore current working directory"));
+ close(odfd);
+ }
+
+ debug_return_int(fd);
+}
+#endif /* O_NOFOLLOW */
+
+static int
+sudo_edit_open_nonwritable(char *path, int oflags, mode_t mode,
+ struct sudo_cred *user_cred, struct sudo_cred *cur_cred)
+{
+ const int dflags = DIR_OPEN_FLAGS;
+ int dfd, fd, is_writable;
+ debug_decl(sudo_edit_open_nonwritable, SUDO_DEBUG_EDIT);
+
+ if (path[0] == '/') {
+ dfd = open("/", dflags);
+ path++;
+ } else {
+ dfd = open(".", dflags);
+ if (path[0] == '.' && path[1] == '/')
+ path += 2;
+ }
+ if (dfd == -1)
+ debug_return_int(-1);
+
+ for (;;) {
+ char *slash;
+ int subdfd;
+
+ /*
+ * Look up one component at a time, avoiding symbolic links in
+ * writable directories.
+ */
+ is_writable = dir_is_writable(dfd, user_cred, cur_cred);
+ if (is_writable == -1) {
+ close(dfd);
+ debug_return_int(-1);
+ }
+
+ path += strspn(path, "/");
+ slash = strchr(path, '/');
+ if (slash == NULL)
+ break;
+ *slash = '\0';
+ if (is_writable)
+ subdfd = sudo_edit_openat_nofollow(dfd, path, dflags, 0);
+ else
+ subdfd = openat(dfd, path, dflags, 0);
+ *slash = '/'; /* restore path */
+ close(dfd);
+ if (subdfd == -1)
+ debug_return_int(-1);
+ path = slash + 1;
+ dfd = subdfd;
+ }
+
+ if (is_writable) {
+ close(dfd);
+ errno = EISDIR;
+ debug_return_int(-1);
+ }
+
+ /*
+ * For "sudoedit /" we will receive ENOENT from openat() and sudoedit
+ * will try to create a file with an empty name. We treat an empty
+ * path as the cwd so sudoedit can give a sensible error message.
+ */
+ fd = openat(dfd, *path ? path : ".", oflags, mode);
+ close(dfd);
+ debug_return_int(fd);
+}
+
+#ifdef O_NOFOLLOW
+int
+sudo_edit_open(char *path, int oflags, mode_t mode, int sflags,
+ struct sudo_cred *user_cred, struct sudo_cred *cur_cred)
+{
+ int fd;
+ debug_decl(sudo_edit_open, SUDO_DEBUG_EDIT);
+
+ if (!ISSET(sflags, CD_SUDOEDIT_FOLLOW))
+ oflags |= O_NOFOLLOW;
+ if (ISSET(sflags, CD_SUDOEDIT_CHECKDIR) && user_cred->uid != ROOT_UID) {
+ fd = sudo_edit_open_nonwritable(path, oflags|O_NONBLOCK, mode,
+ user_cred, cur_cred);
+ } else {
+ fd = open(path, oflags|O_NONBLOCK, mode);
+ }
+ if (fd != -1 && !ISSET(oflags, O_NONBLOCK))
+ (void) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);
+ debug_return_int(fd);
+}
+#else
+int
+sudo_edit_open(char *path, int oflags, mode_t mode, int sflags,
+ struct sudo_cred *user_cred, struct sudo_cred *cur_cred)
+{
+ struct stat sb;
+ int fd;
+ debug_decl(sudo_edit_open, SUDO_DEBUG_EDIT);
+
+ /*
+ * Check if path is a symlink. This is racey but we detect whether
+ * we lost the race in sudo_edit_is_symlink() after the file is opened.
+ */
+ if (!ISSET(sflags, CD_SUDOEDIT_FOLLOW)) {
+ if (lstat(path, &sb) == -1 && errno != ENOENT)
+ debug_return_int(-1);
+ if (S_ISLNK(sb.st_mode)) {
+ errno = ELOOP;
+ debug_return_int(-1);
+ }
+ }
+
+ if (ISSET(sflags, CD_SUDOEDIT_CHECKDIR) && user_cred->uid != ROOT_UID) {
+ fd = sudo_edit_open_nonwritable(path, oflags|O_NONBLOCK, mode,
+ user_cred, cur_cred);
+ } else {
+ fd = open(path, oflags|O_NONBLOCK, mode);
+ }
+ if (fd == -1)
+ debug_return_int(-1);
+ if (!ISSET(oflags, O_NONBLOCK))
+ (void) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);
+
+ /*
+ * Post-open symlink check. This will leave a zero-length file if
+ * O_CREAT was specified but it is too dangerous to try and remove it.
+ */
+ if (!ISSET(sflags, CD_SUDOEDIT_FOLLOW) && sudo_edit_is_symlink(fd, path)) {
+ close(fd);
+ fd = -1;
+ errno = ELOOP;
+ }
+
+ debug_return_int(fd);
+}
+#endif /* O_NOFOLLOW */
+
+/*
+ * Verify that the parent dir of a new file exists and is not writable
+ * by the user. This fails early so the user knows ahead of time if the
+ * edit won't succeed. Additional checks are performed when copying the
+ * temporary file back to the origin so there are no TOCTOU issues.
+ * Does not modify the value of errno.
+ */
+bool
+sudo_edit_parent_valid(char *path, int sflags, struct sudo_cred *user_cred,
+ struct sudo_cred *cur_cred)
+{
+ const int serrno = errno;
+ struct stat sb;
+ bool ret = false;
+ char *slash;
+ int dfd;
+ debug_decl(sudo_edit_parent_valid, SUDO_DEBUG_EDIT);
+
+ /* Get dirname of path (the slash is restored later). */
+ slash = strrchr(path, '/');
+ if (slash == NULL) {
+ /* cwd */
+ path = ".";
+ } else if (slash == path) {
+ path = "/";
+ slash = NULL;
+ } else {
+ *slash = '\0';
+ }
+
+ /*
+ * The parent directory is allowed to be a symbolic link unless
+ * *its* parent is writable and CD_SUDOEDIT_CHECK is set.
+ */
+ dfd = sudo_edit_open(path, DIR_OPEN_FLAGS, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
+ sflags|CD_SUDOEDIT_FOLLOW, user_cred, cur_cred);
+ if (dfd != -1) {
+ if (fstat(dfd, &sb) == 0 && S_ISDIR(sb.st_mode))
+ ret = true;
+ close(dfd);
+ }
+ if (slash != NULL)
+ *slash = '/';
+
+ /* Restore errno. */
+ errno = serrno;
+
+ debug_return_bool(ret);
+}
+
+#endif /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */
diff --git a/src/env_hooks.c b/src/env_hooks.c
new file mode 100644
index 0000000..5f9a61c
--- /dev/null
+++ b/src/env_hooks.c
@@ -0,0 +1,291 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010, 2012-2016 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "sudo.h"
+#include "sudo_plugin.h"
+#include "sudo_dso.h"
+
+extern char **environ; /* global environment pointer */
+static char **priv_environ; /* private environment pointer */
+
+/*
+ * NOTE: we don't use dlsym() to find the libc getenv()
+ * since this may allocate memory on some systems (glibc)
+ * which leads to a hang if malloc() calls getenv (jemalloc).
+ */
+char *
+getenv_unhooked(const char *name)
+{
+ char **ep, *val = NULL;
+ size_t namelen = 0;
+
+ /* For BSD compatibility, treat '=' in name like end of string. */
+ while (name[namelen] != '\0' && name[namelen] != '=')
+ namelen++;
+ for (ep = environ; *ep != NULL; ep++) {
+ if (strncmp(*ep, name, namelen) == 0 && (*ep)[namelen] == '=') {
+ val = *ep + namelen + 1;
+ break;
+ }
+ }
+ return val;
+}
+
+sudo_dso_public char *
+getenv(const char *name)
+{
+ char *val = NULL;
+
+ switch (process_hooks_getenv(name, &val)) {
+ case SUDO_HOOK_RET_STOP:
+ return val;
+ case SUDO_HOOK_RET_ERROR:
+ return NULL;
+ default:
+ return getenv_unhooked(name);
+ }
+}
+
+static int
+rpl_putenv(PUTENV_CONST char *string)
+{
+ char **ep;
+ size_t len;
+ bool found = false;
+
+ /* Look for existing entry. */
+ len = (strchr(string, '=') - string) + 1;
+ for (ep = environ; *ep != NULL; ep++) {
+ if (strncmp(string, *ep, len) == 0) {
+ *ep = (char *)string;
+ found = true;
+ break;
+ }
+ }
+ /* Prune out duplicate variables. */
+ if (found) {
+ while (*ep != NULL) {
+ if (strncmp(string, *ep, len) == 0) {
+ char **cur = ep;
+ while ((*cur = *(cur + 1)) != NULL)
+ cur++;
+ } else {
+ ep++;
+ }
+ }
+ }
+
+ /* Append at the end if not already found. */
+ if (!found) {
+ size_t env_len = (size_t)(ep - environ);
+ char **envp = reallocarray(priv_environ, env_len + 2, sizeof(char *));
+ if (envp == NULL)
+ return -1;
+ if (environ != priv_environ)
+ memcpy(envp, environ, env_len * sizeof(char *));
+ envp[env_len++] = (char *)string;
+ envp[env_len] = NULL;
+ priv_environ = environ = envp;
+ }
+ return 0;
+}
+
+typedef int (*sudo_fn_putenv_t)(PUTENV_CONST char *);
+
+static int
+putenv_unhooked(PUTENV_CONST char *string)
+{
+ sudo_fn_putenv_t fn;
+
+ fn = (sudo_fn_putenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "putenv");
+ if (fn != NULL)
+ return fn(string);
+ return rpl_putenv(string);
+}
+
+sudo_dso_public int
+putenv(PUTENV_CONST char *string)
+{
+ switch (process_hooks_putenv((char *)string)) {
+ case SUDO_HOOK_RET_STOP:
+ return 0;
+ case SUDO_HOOK_RET_ERROR:
+ return -1;
+ default:
+ return putenv_unhooked(string);
+ }
+}
+
+static int
+rpl_setenv(const char *var, const char *val, int overwrite)
+{
+ char *envstr, *dst;
+ const char *src;
+ size_t esize;
+
+ if (!var || *var == '\0') {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ * POSIX says a var name with '=' is an error but BSD
+ * just ignores the '=' and anything after it.
+ */
+ for (src = var; *src != '\0' && *src != '='; src++)
+ continue;
+ esize = (size_t)(src - var) + 2;
+ if (val) {
+ esize += strlen(val); /* glibc treats a NULL val as "" */
+ }
+
+ /* Allocate and fill in envstr. */
+ if ((envstr = malloc(esize)) == NULL)
+ return -1;
+ for (src = var, dst = envstr; *src != '\0' && *src != '=';)
+ *dst++ = *src++;
+ *dst++ = '=';
+ if (val) {
+ for (src = val; *src != '\0';)
+ *dst++ = *src++;
+ }
+ *dst = '\0';
+
+ if (!overwrite && getenv(var) != NULL) {
+ free(envstr);
+ return 0;
+ }
+ if (rpl_putenv(envstr) == -1) {
+ free(envstr);
+ return -1;
+ }
+ return 0;
+}
+
+typedef int (*sudo_fn_setenv_t)(const char *, const char *, int);
+
+static int
+setenv_unhooked(const char *var, const char *val, int overwrite)
+{
+ sudo_fn_setenv_t fn;
+
+ fn = (sudo_fn_setenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "setenv");
+ if (fn != NULL)
+ return fn(var, val, overwrite);
+ return rpl_setenv(var, val, overwrite);
+}
+
+sudo_dso_public int
+setenv(const char *var, const char *val, int overwrite)
+{
+ switch (process_hooks_setenv(var, val, overwrite)) {
+ case SUDO_HOOK_RET_STOP:
+ return 0;
+ case SUDO_HOOK_RET_ERROR:
+ return -1;
+ default:
+ return setenv_unhooked(var, val, overwrite);
+ }
+}
+
+static int
+rpl_unsetenv(const char *var)
+{
+ char **ep = environ;
+ size_t len;
+
+ if (var == NULL || *var == '\0' || strchr(var, '=') != NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ len = strlen(var);
+ while (*ep != NULL) {
+ if (strncmp(var, *ep, len) == 0 && (*ep)[len] == '=') {
+ /* Found it; shift remainder + NULL over by one. */
+ char **cur = ep;
+ while ((*cur = *(cur + 1)) != NULL)
+ cur++;
+ /* Keep going, could be multiple instances of the var. */
+ } else {
+ ep++;
+ }
+ }
+ return 0;
+}
+
+#ifdef UNSETENV_VOID
+typedef void (*sudo_fn_unsetenv_t)(const char *);
+#else
+typedef int (*sudo_fn_unsetenv_t)(const char *);
+#endif
+
+static int
+unsetenv_unhooked(const char *var)
+{
+ int ret = 0;
+ sudo_fn_unsetenv_t fn;
+
+ fn = (sudo_fn_unsetenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "unsetenv");
+ if (fn != NULL) {
+# ifdef UNSETENV_VOID
+ fn(var);
+# else
+ ret = fn(var);
+# endif
+ } else {
+ ret = rpl_unsetenv(var);
+ }
+ return ret;
+}
+
+#ifdef UNSETENV_VOID
+sudo_dso_public void
+#else
+sudo_dso_public int
+#endif
+unsetenv(const char *var)
+{
+ int ret;
+
+ switch (process_hooks_unsetenv(var)) {
+ case SUDO_HOOK_RET_STOP:
+ ret = 0;
+ break;
+ case SUDO_HOOK_RET_ERROR:
+ ret = -1;
+ break;
+ default:
+ ret = unsetenv_unhooked(var);
+ break;
+ }
+#ifndef UNSETENV_VOID
+ return ret;
+#endif
+}
diff --git a/src/exec.c b/src/exec.c
new file mode 100644
index 0000000..ffee016
--- /dev/null
+++ b/src/exec.c
@@ -0,0 +1,463 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#ifdef HAVE_LOGIN_CAP_H
+# include <login_cap.h>
+# ifndef LOGIN_SETENV
+# define LOGIN_SETENV 0
+# endif
+#endif
+#ifdef HAVE_PROJECT_H
+# include <project.h>
+# include <sys/task.h>
+#endif
+
+#include "sudo.h"
+#include "sudo_exec.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+static void
+close_fds(struct command_details *details, int errfd)
+{
+ int fd, maxfd;
+ unsigned char *debug_fds;
+ debug_decl(close_fds, SUDO_DEBUG_EXEC);
+
+ if (details->closefrom < 0)
+ debug_return;
+
+ /* Preserve debug fds and error pipe as needed. */
+ maxfd = sudo_debug_get_fds(&debug_fds);
+ for (fd = 0; fd <= maxfd; fd++) {
+ if (sudo_isset(debug_fds, fd))
+ add_preserved_fd(&details->preserved_fds, fd);
+ }
+ if (errfd != -1)
+ add_preserved_fd(&details->preserved_fds, errfd);
+
+ /* Close all fds except those explicitly preserved. */
+ closefrom_except(details->closefrom, &details->preserved_fds);
+
+ debug_return;
+}
+
+/*
+ * Setup the execution environment immediately prior to the call to execve().
+ * Group setup is performed by policy_init_session(), called earlier.
+ * Returns true on success and false on failure.
+ */
+static bool
+exec_setup(struct command_details *details, int errfd)
+{
+ bool ret = false;
+ debug_decl(exec_setup, SUDO_DEBUG_EXEC);
+
+ if (details->pw != NULL) {
+#ifdef HAVE_PROJECT_H
+ set_project(details->pw);
+#endif
+#ifdef HAVE_PRIV_SET
+ if (details->privs != NULL) {
+ if (setppriv(PRIV_SET, PRIV_INHERITABLE, details->privs) != 0) {
+ sudo_warn("unable to set privileges");
+ goto done;
+ }
+ }
+ if (details->limitprivs != NULL) {
+ if (setppriv(PRIV_SET, PRIV_LIMIT, details->limitprivs) != 0) {
+ sudo_warn("unable to set limit privileges");
+ goto done;
+ }
+ } else if (details->privs != NULL) {
+ if (setppriv(PRIV_SET, PRIV_LIMIT, details->privs) != 0) {
+ sudo_warn("unable to set limit privileges");
+ goto done;
+ }
+ }
+#endif /* HAVE_PRIV_SET */
+
+#ifdef HAVE_GETUSERATTR
+ if (aix_prep_user(details->pw->pw_name, details->tty) != 0) {
+ /* error message displayed by aix_prep_user */
+ goto done;
+ }
+#endif
+#ifdef HAVE_LOGIN_CAP_H
+ if (details->login_class) {
+ int flags;
+ login_cap_t *lc;
+
+ /*
+ * We only use setusercontext() to set the nice value, rlimits
+ * and umask unless this is a login shell (sudo -i).
+ */
+ lc = login_getclass((char *)details->login_class);
+ if (!lc) {
+ sudo_warnx(U_("unknown login class %s"), details->login_class);
+ errno = ENOENT;
+ goto done;
+ }
+ if (ISSET(details->flags, CD_LOGIN_SHELL)) {
+ /* Set everything except user, group and login name. */
+ flags = LOGIN_SETALL;
+ CLR(flags, LOGIN_SETGROUP|LOGIN_SETLOGIN|LOGIN_SETUSER|LOGIN_SETENV|LOGIN_SETPATH);
+ } else {
+ flags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY|LOGIN_SETUMASK;
+ }
+ if (setusercontext(lc, details->pw, details->pw->pw_uid, flags)) {
+ sudo_warn("%s", U_("unable to set user context"));
+ if (details->pw->pw_uid != ROOT_UID)
+ goto done;
+ }
+ }
+#endif /* HAVE_LOGIN_CAP_H */
+ }
+
+ if (ISSET(details->flags, CD_SET_GROUPS)) {
+ /* set_user_groups() prints error message on failure. */
+ if (!set_user_groups(details))
+ goto done;
+ }
+
+ if (ISSET(details->flags, CD_SET_PRIORITY)) {
+ if (setpriority(PRIO_PROCESS, 0, details->priority) != 0) {
+ sudo_warn("%s", U_("unable to set process priority"));
+ goto done;
+ }
+ }
+
+ /* Policy may override umask in PAM or login.conf. */
+ if (ISSET(details->flags, CD_OVERRIDE_UMASK))
+ (void) umask(details->umask);
+
+ /* Close fds before chroot (need /dev) or uid change (prlimit on Linux). */
+ close_fds(details, errfd);
+
+ if (details->chroot) {
+ if (chroot(details->chroot) != 0 || chdir("/") != 0) {
+ sudo_warn(U_("unable to change root to %s"), details->chroot);
+ goto done;
+ }
+ }
+
+ /*
+ * Unlimit the number of processes since Linux's setuid() will
+ * return EAGAIN if RLIMIT_NPROC would be exceeded by the uid switch.
+ */
+ unlimit_nproc();
+
+#if defined(HAVE_SETRESUID)
+ if (setresuid(details->cred.uid, details->cred.euid, details->cred.euid) != 0) {
+ sudo_warn(U_("unable to change to runas uid (%u, %u)"),
+ (unsigned int)details->cred.uid, (unsigned int)details->cred.euid);
+ goto done;
+ }
+#elif defined(HAVE_SETREUID)
+ if (setreuid(details->cred.uid, details->cred.euid) != 0) {
+ sudo_warn(U_("unable to change to runas uid (%u, %u)"),
+ (unsigned int)details->cred.uid, (unsigned int)details->cred.euid);
+ goto done;
+ }
+#else
+ /* Cannot support real user-ID that is different from effective user-ID. */
+ if (setuid(details->cred.euid) != 0) {
+ sudo_warn(U_("unable to change to runas uid (%u, %u)"),
+ (unsigned int)details->cred.euid, (unsigned int)details->cred.euid);
+ goto done;
+ }
+#endif /* !HAVE_SETRESUID && !HAVE_SETREUID */
+
+ /* Restore previous value of RLIMIT_NPROC. */
+ restore_nproc();
+
+ /*
+ * Only change cwd if we have chroot()ed or the policy modules
+ * specifies a different cwd. Must be done after uid change.
+ */
+ if (details->cwd != NULL) {
+ if (details->chroot != NULL || user_details.cwd == NULL ||
+ strcmp(details->cwd, user_details.cwd) != 0) {
+ /* Note: cwd is relative to the new root, if any. */
+ if (chdir(details->cwd) == -1) {
+ sudo_warn(U_("unable to change directory to %s"), details->cwd);
+ if (!details->cwd_optional)
+ goto done;
+ if (details->chroot != NULL)
+ sudo_warnx(U_("starting from %s"), "/");
+ }
+ }
+ }
+
+ ret = true;
+
+done:
+ debug_return_bool(ret);
+}
+
+/*
+ * Setup the execution environment and execute the command.
+ * If SELinux is enabled, run the command via sesh, otherwise
+ * execute it directly.
+ * If the exec fails, cstat is filled in with the value of errno.
+ */
+void
+exec_cmnd(struct command_details *details, int errfd)
+{
+ debug_decl(exec_cmnd, SUDO_DEBUG_EXEC);
+
+ restore_signals();
+ if (exec_setup(details, errfd) == true) {
+ /* headed for execve() */
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_RBAC_ENABLED)) {
+ selinux_execve(details->execfd, details->command, details->argv,
+ details->envp, ISSET(details->flags, CD_NOEXEC));
+ } else
+#endif
+ {
+ sudo_execve(details->execfd, details->command, details->argv,
+ details->envp, ISSET(details->flags, CD_NOEXEC));
+ }
+ }
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to exec %s: %s",
+ details->command, strerror(errno));
+ debug_return;
+}
+
+/*
+ * Check for caught signals sent to sudo before command execution.
+ * Also suspends the process if SIGTSTP was caught.
+ * Returns true if we should terminate, else false.
+ */
+bool
+sudo_terminated(struct command_status *cstat)
+{
+ int signo;
+ bool sigtstp = false;
+ debug_decl(sudo_terminated, SUDO_DEBUG_EXEC);
+
+ for (signo = 0; signo < NSIG; signo++) {
+ if (signal_pending(signo)) {
+ switch (signo) {
+ case SIGCHLD:
+ /* Ignore. */
+ break;
+ case SIGTSTP:
+ /* Suspend below if not terminated. */
+ sigtstp = true;
+ break;
+ default:
+ /* Terminal signal, do not exec command. */
+ cstat->type = CMD_WSTATUS;
+ cstat->val = signo + 128;
+ debug_return_bool(true);
+ break;
+ }
+ }
+ }
+ if (sigtstp) {
+ struct sigaction sa;
+ sigset_t set, oset;
+
+ /* Send SIGTSTP to ourselves, unblocking it if needed. */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_DFL;
+ if (sudo_sigaction(SIGTSTP, &sa, NULL) != 0)
+ sudo_warn(U_("unable to set handler for signal %d"), SIGTSTP);
+ sigemptyset(&set);
+ sigaddset(&set, SIGTSTP);
+ sigprocmask(SIG_UNBLOCK, &set, &oset);
+ if (kill(getpid(), SIGTSTP) != 0)
+ sudo_warn("kill(%d, SIGTSTP)", (int)getpid());
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+ /* No need to restore old SIGTSTP handler. */
+ }
+ debug_return_bool(false);
+}
+
+#if SUDO_API_VERSION != SUDO_API_MKVERSION(1, 17)
+# error "Update sudo_needs_pty() after changing the plugin API"
+#endif
+static bool
+sudo_needs_pty(struct command_details *details)
+{
+ struct plugin_container *plugin;
+
+ if (ISSET(details->flags, CD_USE_PTY))
+ return true;
+
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ if (plugin->u.io->log_ttyin != NULL ||
+ plugin->u.io->log_ttyout != NULL ||
+ plugin->u.io->log_stdin != NULL ||
+ plugin->u.io->log_stdout != NULL ||
+ plugin->u.io->log_stderr != NULL ||
+ plugin->u.io->change_winsize != NULL ||
+ plugin->u.io->log_suspend != NULL)
+ return true;
+ }
+ return false;
+}
+
+/*
+ * If we are not running the command in a pty, we were not invoked as
+ * sudoedit, there is no command timeout and there is no close function,
+ * sudo can exec the command directly (and not wait).
+ */
+static bool
+direct_exec_allowed(struct command_details *details)
+{
+ struct plugin_container *plugin;
+ debug_decl(direct_exec_allowed, SUDO_DEBUG_EXEC);
+
+ /* Assumes sudo_needs_pty() was already checked. */
+ if (ISSET(details->flags, CD_RBAC_ENABLED|CD_SET_TIMEOUT|CD_SUDOEDIT) ||
+ policy_plugin.u.policy->close != NULL)
+ debug_return_bool(false);
+
+ TAILQ_FOREACH(plugin, &audit_plugins, entries) {
+ if (plugin->u.audit->close != NULL)
+ debug_return_bool(false);
+ }
+
+ debug_return_bool(true);
+}
+
+/*
+ * Execute a command, potentially in a pty with I/O logging, and
+ * wait for it to finish.
+ * This is a little bit tricky due to how POSIX job control works and
+ * we fact that we have two different controlling terminals to deal with.
+ */
+int
+sudo_execute(struct command_details *details, struct command_status *cstat)
+{
+ debug_decl(sudo_execute, SUDO_DEBUG_EXEC);
+
+ /* If running in background mode, fork and exit. */
+ if (ISSET(details->flags, CD_BACKGROUND)) {
+ switch (sudo_debug_fork()) {
+ case -1:
+ cstat->type = CMD_ERRNO;
+ cstat->val = errno;
+ debug_return_int(-1);
+ case 0:
+ /* child continues without controlling terminal */
+ (void)setpgid(0, 0);
+ break;
+ default:
+ /* parent exits (but does not flush buffers) */
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__,
+ sudo_debug_subsys, 0);
+ _exit(EXIT_SUCCESS);
+ }
+ }
+
+ /*
+ * Restore resource limits before running.
+ * We must do this *before* calling the PAM session module.
+ */
+ restore_limits();
+
+ /*
+ * Run the command in a new pty if there is an I/O plugin or the policy
+ * has requested a pty. If /dev/tty is unavailable and no I/O plugin
+ * is configured, this returns false and we run the command without a pty.
+ */
+ if (sudo_needs_pty(details)) {
+ if (exec_pty(details, cstat))
+ goto done;
+ }
+
+ /*
+ * If we are not running the command in a pty, we may be able to
+ * exec directly, depending on the plugins used.
+ */
+ if (direct_exec_allowed(details)) {
+ if (!sudo_terminated(cstat)) {
+ exec_cmnd(details, -1);
+ cstat->type = CMD_ERRNO;
+ cstat->val = errno;
+ }
+ goto done;
+ }
+
+ /*
+ * Run the command in the existing tty (if any) and wait for it to finish.
+ */
+ exec_nopty(details, cstat);
+
+done:
+ /* The caller will run any plugin close functions. */
+ debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0);
+}
+
+/*
+ * Kill command with increasing urgency.
+ */
+void
+terminate_command(pid_t pid, bool use_pgrp)
+{
+ debug_decl(terminate_command, SUDO_DEBUG_EXEC);
+
+ /* Avoid killing more than a single process or process group. */
+ if (pid <= 0)
+ debug_return;
+
+ /*
+ * Note that SIGCHLD will interrupt the sleep()
+ */
+ if (use_pgrp) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "killpg %d SIGHUP", (int)pid);
+ killpg(pid, SIGHUP);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "killpg %d SIGTERM", (int)pid);
+ killpg(pid, SIGTERM);
+ sleep(2);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "killpg %d SIGKILL", (int)pid);
+ killpg(pid, SIGKILL);
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "kill %d SIGHUP", (int)pid);
+ kill(pid, SIGHUP);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "kill %d SIGTERM", (int)pid);
+ kill(pid, SIGTERM);
+ sleep(2);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "kill %d SIGKILL", (int)pid);
+ kill(pid, SIGKILL);
+ }
+
+ debug_return;
+}
diff --git a/src/exec_common.c b/src/exec_common.c
new file mode 100644
index 0000000..32834de
--- /dev/null
+++ b/src/exec_common.c
@@ -0,0 +1,213 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2016 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef HAVE_PRIV_SET
+# include <priv.h>
+#endif
+#include <errno.h>
+
+#include "sudo.h"
+#include "sudo_exec.h"
+
+#ifdef RTLD_PRELOAD_VAR
+/*
+ * Add a DSO file to LD_PRELOAD or the system equivalent.
+ */
+static char **
+preload_dso(char *envp[], const char *dso_file)
+{
+ char *preload = NULL;
+ int env_len;
+ int preload_idx = -1;
+ bool present = false;
+# ifdef RTLD_PRELOAD_ENABLE_VAR
+ bool enabled = false;
+# else
+ const bool enabled = true;
+# endif
+ debug_decl(preload_dso, SUDO_DEBUG_UTIL);
+
+ /*
+ * Preload a DSO file. For a list of LD_PRELOAD-alikes, see
+ * http://www.fortran-2000.com/ArnaudRecipes/sharedlib.html
+ * XXX - need to support 32-bit and 64-bit variants
+ */
+
+ /* Count entries in envp, looking for LD_PRELOAD as we go. */
+ for (env_len = 0; envp[env_len] != NULL; env_len++) {
+ if (preload_idx == -1 && strncmp(envp[env_len], RTLD_PRELOAD_VAR "=",
+ sizeof(RTLD_PRELOAD_VAR)) == 0) {
+ const char *cp = envp[env_len] + sizeof(RTLD_PRELOAD_VAR);
+ const char *end = cp + strlen(cp);
+ const char *ep;
+ const size_t dso_len = strlen(dso_file);
+
+ /* Check to see if dso_file is already present. */
+ for (cp = sudo_strsplit(cp, end, RTLD_PRELOAD_DELIM, &ep);
+ cp != NULL; cp = sudo_strsplit(NULL, end, RTLD_PRELOAD_DELIM,
+ &ep)) {
+ if ((size_t)(ep - cp) == dso_len) {
+ if (memcmp(cp, dso_file, dso_len) == 0) {
+ /* already present */
+ present = true;
+ break;
+ }
+ }
+ }
+
+ /* Save index of existing LD_PRELOAD variable. */
+ preload_idx = env_len;
+ continue;
+ }
+# ifdef RTLD_PRELOAD_ENABLE_VAR
+ if (strncmp(envp[env_len], RTLD_PRELOAD_ENABLE_VAR "=", sizeof(RTLD_PRELOAD_ENABLE_VAR)) == 0) {
+ enabled = true;
+ continue;
+ }
+# endif
+ }
+
+ /*
+ * Make a new copy of envp as needed.
+ * It would be nice to realloc the old envp[] but we don't know
+ * whether it was dynamically allocated. [TODO: plugin API]
+ */
+ if (preload_idx == -1 || !enabled) {
+ const int env_size = env_len + 1 + (preload_idx == -1) + enabled; // -V547
+
+ char **nenvp = reallocarray(NULL, env_size, sizeof(*envp));
+ if (nenvp == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ memcpy(nenvp, envp, env_len * sizeof(*envp));
+ nenvp[env_len] = NULL;
+ envp = nenvp;
+ }
+
+ /* Prepend our LD_PRELOAD to existing value or add new entry at the end. */
+ if (!present) {
+ if (preload_idx == -1) {
+# ifdef RTLD_PRELOAD_DEFAULT
+ asprintf(&preload, "%s=%s%s%s", RTLD_PRELOAD_VAR, dso_file,
+ RTLD_PRELOAD_DELIM, RTLD_PRELOAD_DEFAULT);
+# else
+ preload = sudo_new_key_val(RTLD_PRELOAD_VAR, dso_file);
+# endif
+ if (preload == NULL) {
+ sudo_fatalx(U_("%s: %s"), __func__,
+ U_("unable to allocate memory"));
+ }
+ envp[env_len++] = preload;
+ envp[env_len] = NULL;
+ } else {
+ int len = asprintf(&preload, "%s=%s%s%s", RTLD_PRELOAD_VAR,
+ dso_file, RTLD_PRELOAD_DELIM, envp[preload_idx]);
+ if (len == -1) {
+ sudo_fatalx(U_("%s: %s"), __func__,
+ U_("unable to allocate memory"));
+ }
+ envp[preload_idx] = preload;
+ }
+ }
+# ifdef RTLD_PRELOAD_ENABLE_VAR
+ if (!enabled) {
+ envp[env_len++] = RTLD_PRELOAD_ENABLE_VAR "=";
+ envp[env_len] = NULL;
+ }
+# endif
+
+ debug_return_ptr(envp);
+}
+#endif /* RTLD_PRELOAD_VAR */
+
+/*
+ * Disable execution of child processes in the command we are about
+ * to run. On systems with privilege sets, we can remove the exec
+ * privilege. On other systems we use LD_PRELOAD and the like.
+ */
+char **
+disable_execute(char *envp[], const char *dso)
+{
+ debug_decl(disable_execute, SUDO_DEBUG_UTIL);
+
+#ifdef HAVE_PRIV_SET
+ /* Solaris privileges, remove PRIV_PROC_EXEC post-execve. */
+ (void)priv_set(PRIV_ON, PRIV_INHERITABLE, "PRIV_FILE_DAC_READ", NULL);
+ (void)priv_set(PRIV_ON, PRIV_INHERITABLE, "PRIV_FILE_DAC_WRITE", NULL);
+ (void)priv_set(PRIV_ON, PRIV_INHERITABLE, "PRIV_FILE_DAC_SEARCH", NULL);
+ if (priv_set(PRIV_OFF, PRIV_LIMIT, "PRIV_PROC_EXEC", NULL) == 0)
+ debug_return_ptr(envp);
+ sudo_warn("%s", U_("unable to remove PRIV_PROC_EXEC from PRIV_LIMIT"));
+#endif /* HAVE_PRIV_SET */
+
+#ifdef RTLD_PRELOAD_VAR
+ if (dso != NULL)
+ envp = preload_dso(envp, dso);
+#endif /* RTLD_PRELOAD_VAR */
+
+ debug_return_ptr(envp);
+}
+
+/*
+ * Like execve(2) but falls back to running through /bin/sh
+ * ala execvp(3) if we get ENOEXEC.
+ */
+int
+sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noexec)
+{
+ debug_decl(sudo_execve, SUDO_DEBUG_UTIL);
+
+ sudo_debug_execve(SUDO_DEBUG_INFO, path, argv, envp);
+
+ /* Modify the environment as needed to disable further execve(). */
+ if (noexec)
+ envp = disable_execute(envp, sudo_conf_noexec_path());
+
+#ifdef HAVE_FEXECVE
+ if (fd != -1)
+ fexecve(fd, argv, envp);
+ else
+#endif
+ execve(path, argv, envp);
+ if (fd == -1 && errno == ENOEXEC) {
+ int argc;
+ char **nargv;
+
+ for (argc = 0; argv[argc] != NULL; argc++)
+ continue;
+ nargv = reallocarray(NULL, argc + 2, sizeof(char *));
+ if (nargv != NULL) {
+ nargv[0] = "sh";
+ nargv[1] = (char *)path;
+ memcpy(nargv + 2, argv + 1, argc * sizeof(char *));
+ execve(_PATH_SUDO_BSHELL, nargv, envp);
+ free(nargv);
+ }
+ }
+ debug_return_int(-1);
+}
diff --git a/src/exec_monitor.c b/src/exec_monitor.c
new file mode 100644
index 0000000..bd8eee1
--- /dev/null
+++ b/src/exec_monitor.c
@@ -0,0 +1,723 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <termios.h>
+
+#include "sudo.h"
+#include "sudo_exec.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+struct monitor_closure {
+ pid_t cmnd_pid;
+ pid_t cmnd_pgrp;
+ pid_t mon_pgrp;
+ int backchannel;
+ struct command_status *cstat;
+ struct sudo_event_base *evbase;
+ struct sudo_event *errpipe_event;
+ struct sudo_event *backchannel_event;
+ struct sudo_event *sigint_event;
+ struct sudo_event *sigquit_event;
+ struct sudo_event *sigtstp_event;
+ struct sudo_event *sigterm_event;
+ struct sudo_event *sighup_event;
+ struct sudo_event *sigusr1_event;
+ struct sudo_event *sigusr2_event;
+ struct sudo_event *sigchld_event;
+};
+
+static bool tty_initialized;
+
+/*
+ * Deliver a signal to the running command.
+ * The signal was either forwarded to us by the parent sudo process
+ * or was received by the monitor itself.
+ *
+ * There are two "special" signals, SIGCONT_FG and SIGCONT_BG that
+ * also specify whether the command should have the controlling tty.
+ */
+static void
+deliver_signal(struct monitor_closure *mc, int signo, bool from_parent)
+{
+ char signame[SIG2STR_MAX];
+ debug_decl(deliver_signal, SUDO_DEBUG_EXEC);
+
+ /* Avoid killing more than a single process or process group. */
+ if (mc->cmnd_pid <= 0)
+ debug_return;
+
+ if (signo == SIGCONT_FG)
+ (void)strlcpy(signame, "CONT_FG", sizeof(signame));
+ else if (signo == SIGCONT_BG)
+ (void)strlcpy(signame, "CONT_BG", sizeof(signame));
+ else if (sig2str(signo, signame) == -1)
+ (void)snprintf(signame, sizeof(signame), "%d", signo);
+
+ /* Handle signal from parent or monitor. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "received SIG%s%s",
+ signame, from_parent ? " from parent" : "");
+ switch (signo) {
+ case SIGALRM:
+ terminate_command(mc->cmnd_pid, true);
+ break;
+ case SIGCONT_FG:
+ /* Continue in foreground, grant it controlling tty. */
+ if (tcsetpgrp(io_fds[SFD_FOLLOWER], mc->cmnd_pgrp) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to set foreground pgrp to %d (command)",
+ __func__, (int)mc->cmnd_pgrp);
+ }
+ /* Lazily initialize the pty if needed. */
+ if (!tty_initialized) {
+ if (sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_FOLLOWER]))
+ tty_initialized = true;
+ }
+ killpg(mc->cmnd_pid, SIGCONT);
+ break;
+ case SIGCONT_BG:
+ /* Continue in background, I take controlling tty. */
+ if (tcsetpgrp(io_fds[SFD_FOLLOWER], mc->mon_pgrp) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to set foreground pgrp to %d (monitor)",
+ __func__, (int)mc->mon_pgrp);
+ }
+ killpg(mc->cmnd_pid, SIGCONT);
+ break;
+ case SIGKILL:
+ _exit(EXIT_FAILURE); /* XXX */
+ /* NOTREACHED */
+ default:
+ /* Relay signal to command. */
+ killpg(mc->cmnd_pid, signo);
+ break;
+ }
+ debug_return;
+}
+
+/*
+ * Unpack rows and cols from a CMD_TTYWINCH value, set the new window
+ * size on the pty follower and inform the command of the change.
+ */
+static void
+handle_winch(struct monitor_closure *mc, unsigned int wsize_packed)
+{
+ struct winsize wsize, owsize;
+ debug_decl(handle_winch, SUDO_DEBUG_EXEC);
+
+ /* Rows and columns are stored as two shorts packed into a single int. */
+ wsize.ws_row = wsize_packed & 0xffff;
+ wsize.ws_col = (wsize_packed >> 16) & 0xffff;
+
+ if (ioctl(io_fds[SFD_FOLLOWER], TIOCGWINSZ, &owsize) == 0 &&
+ (wsize.ws_row != owsize.ws_row || wsize.ws_col != owsize.ws_col)) {
+
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "window size change %dx%d -> %dx%d",
+ owsize.ws_col, owsize.ws_row, wsize.ws_col, wsize.ws_row);
+
+ (void)ioctl(io_fds[SFD_FOLLOWER], TIOCSWINSZ, &wsize);
+ deliver_signal(mc, SIGWINCH, true);
+ }
+
+ debug_return;
+}
+
+/*
+ * Send status to parent over socketpair.
+ * Return value is the same as send(2).
+ */
+static int
+send_status(int fd, struct command_status *cstat)
+{
+ int n = -1;
+ debug_decl(send_status, SUDO_DEBUG_EXEC);
+
+ if (cstat->type != CMD_INVALID) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "sending status message to parent: [%d, %d]",
+ cstat->type, cstat->val);
+ n = send(fd, cstat, sizeof(*cstat), 0);
+ if (n != sizeof(*cstat)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to send status to parent", __func__);
+ }
+ cstat->type = CMD_INVALID; /* prevent re-sending */
+ }
+ debug_return_int(n);
+}
+
+/*
+ * Wait for command status after receiving SIGCHLD.
+ * If the command was stopped, the status is send back to the parent.
+ * Otherwise, cstat is filled in but not sent.
+ */
+static void
+mon_handle_sigchld(struct monitor_closure *mc)
+{
+ char signame[SIG2STR_MAX];
+ int status;
+ pid_t pid;
+ debug_decl(mon_handle_sigchld, SUDO_DEBUG_EXEC);
+
+ /* Read command status. */
+ do {
+ pid = waitpid(mc->cmnd_pid, &status, WUNTRACED|WCONTINUED|WNOHANG);
+ } while (pid == -1 && errno == EINTR);
+ switch (pid) {
+ case 0:
+ errno = ECHILD;
+ FALLTHROUGH;
+ case -1:
+ sudo_warn(U_("%s: %s"), __func__, "waitpid");
+ debug_return;
+ }
+
+ if (WIFCONTINUED(status)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) resumed",
+ __func__, (int)mc->cmnd_pid);
+ } else if (WIFSTOPPED(status)) {
+ if (sig2str(WSTOPSIG(status), signame) == -1)
+ (void)snprintf(signame, sizeof(signame), "%d", WSTOPSIG(status));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) stopped, SIG%s",
+ __func__, (int)mc->cmnd_pid, signame);
+ } else if (WIFSIGNALED(status)) {
+ if (sig2str(WTERMSIG(status), signame) == -1)
+ (void)snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s",
+ __func__, (int)mc->cmnd_pid, signame);
+ mc->cmnd_pid = -1;
+ } else if (WIFEXITED(status)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) exited: %d",
+ __func__, (int)mc->cmnd_pid, WEXITSTATUS(status));
+ mc->cmnd_pid = -1;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_WARN,
+ "%s: unexpected wait status %d for command (%d)",
+ __func__, status, (int)mc->cmnd_pid);
+ }
+
+ /* Don't overwrite execve() failure with child exit status. */
+ if (mc->cstat->type == CMD_INVALID) {
+ /*
+ * Store wait status in cstat and forward to parent if stopped.
+ * Parent does not expect SIGCONT so don't bother sending it.
+ */
+ if (!WIFCONTINUED(status)) {
+ mc->cstat->type = CMD_WSTATUS;
+ mc->cstat->val = status;
+ if (WIFSTOPPED(status)) {
+ /* Save the foreground pgid so we can restore it later. */
+ pid = tcgetpgrp(io_fds[SFD_FOLLOWER]);
+ if (pid != mc->mon_pgrp)
+ mc->cmnd_pgrp = pid;
+ send_status(mc->backchannel, mc->cstat);
+ }
+ }
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_WARN,
+ "%s: not overwriting command status %d,%d with %d,%d",
+ __func__, mc->cstat->type, mc->cstat->val, CMD_WSTATUS, status);
+ }
+
+ debug_return;
+}
+
+static void
+mon_signal_cb(int signo, int what, void *v)
+{
+ struct sudo_ev_siginfo_container *sc = v;
+ struct monitor_closure *mc = sc->closure;
+ debug_decl(mon_signal_cb, SUDO_DEBUG_EXEC);
+
+ /*
+ * Handle SIGCHLD specially and deliver other signals
+ * directly to the command.
+ */
+ if (signo == SIGCHLD) {
+ mon_handle_sigchld(mc);
+ if (mc->cmnd_pid == -1) {
+ /* Command exited or was killed, exit event loop. */
+ sudo_ev_loopexit(mc->evbase);
+ }
+ } else {
+ /*
+ * If the signal came from the process group of the command we ran,
+ * do not forward it as we don't want the child to indirectly kill
+ * itself. This can happen with, e.g., BSD-derived versions of
+ * reboot that call kill(-1, SIGTERM) to kill all other processes.
+ */
+ if (USER_SIGNALED(sc->siginfo) && sc->siginfo->si_pid != 0) {
+ pid_t si_pgrp = getpgid(sc->siginfo->si_pid);
+ if (si_pgrp != -1) {
+ if (si_pgrp == mc->cmnd_pgrp)
+ debug_return;
+ } else if (sc->siginfo->si_pid == mc->cmnd_pid) {
+ debug_return;
+ }
+ }
+ deliver_signal(mc, signo, false);
+ }
+ debug_return;
+}
+
+/* Note: this is basically the same as errpipe_cb() in exec_nopty.c */
+static void
+mon_errpipe_cb(int fd, int what, void *v)
+{
+ struct monitor_closure *mc = v;
+ ssize_t nread;
+ int errval;
+ debug_decl(mon_errpipe_cb, SUDO_DEBUG_EXEC);
+
+ /*
+ * Read errno from child or EOF when command is executed.
+ * Note that the error pipe is *blocking*.
+ */
+ nread = read(fd, &errval, sizeof(errval));
+ switch (nread) {
+ case -1:
+ if (errno != EAGAIN && errno != EINTR) {
+ if (mc->cstat->val == CMD_INVALID) {
+ /* XXX - need a way to distinguish non-exec error. */
+ mc->cstat->type = CMD_ERRNO;
+ mc->cstat->val = errno;
+ }
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: failed to read error pipe", __func__);
+ sudo_ev_loopbreak(mc->evbase);
+ }
+ break;
+ default:
+ if (nread == 0) {
+ /* The error pipe closes when the command is executed. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "EOF on error pipe");
+ } else {
+ /* Errno value when child is unable to execute command. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s",
+ strerror(errval));
+ mc->cstat->type = CMD_ERRNO;
+ mc->cstat->val = errval;
+ }
+ sudo_ev_del(mc->evbase, mc->errpipe_event);
+ close(fd);
+ break;
+ }
+ debug_return;
+}
+
+static void
+mon_backchannel_cb(int fd, int what, void *v)
+{
+ struct monitor_closure *mc = v;
+ struct command_status cstmp;
+ ssize_t n;
+ debug_decl(mon_backchannel_cb, SUDO_DEBUG_EXEC);
+
+ /*
+ * Read command from backchannel, should be a signal.
+ * Note that the backchannel is a *blocking* socket.
+ */
+ n = recv(fd, &cstmp, sizeof(cstmp), MSG_WAITALL);
+ if (n != sizeof(cstmp)) {
+ if (n == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ debug_return;
+ sudo_warn("%s", U_("error reading from socketpair"));
+ } else {
+ /* short read or EOF, parent process died? */
+ }
+ /* XXX - need a way to distinguish non-exec error. */
+ mc->cstat->type = CMD_ERRNO;
+ mc->cstat->val = n ? EIO : ECONNRESET;
+ sudo_ev_loopbreak(mc->evbase);
+ } else {
+ switch (cstmp.type) {
+ case CMD_TTYWINCH:
+ handle_winch(mc, cstmp.val);
+ break;
+ case CMD_SIGNO:
+ deliver_signal(mc, cstmp.val, true);
+ break;
+ default:
+ sudo_warnx(U_("unexpected reply type on backchannel: %d"), cstmp.type);
+ break;
+ }
+ }
+ debug_return;
+}
+
+/*
+ * Sets up std{in,out,err} and executes the actual command.
+ * Returns only if execve() fails.
+ */
+static void
+exec_cmnd_pty(struct command_details *details, bool foreground, int errfd)
+{
+ volatile pid_t self = getpid();
+ debug_decl(exec_cmnd_pty, SUDO_DEBUG_EXEC);
+
+ /* Register cleanup function */
+ sudo_fatal_callback_register(pty_cleanup);
+
+ /* Set command process group here too to avoid a race. */
+ setpgid(0, self);
+
+ /* Wire up standard fds, note that stdout/stderr may be pipes. */
+ if (dup3(io_fds[SFD_STDIN], STDIN_FILENO, 0) == -1)
+ sudo_fatal("dup3");
+ if (io_fds[SFD_STDIN] != io_fds[SFD_FOLLOWER])
+ close(io_fds[SFD_STDIN]);
+ if (dup3(io_fds[SFD_STDOUT], STDOUT_FILENO, 0) == -1)
+ sudo_fatal("dup3");
+ if (io_fds[SFD_STDOUT] != io_fds[SFD_FOLLOWER])
+ close(io_fds[SFD_STDOUT]);
+ if (dup3(io_fds[SFD_STDERR], STDERR_FILENO, 0) == -1)
+ sudo_fatal("dup3");
+ if (io_fds[SFD_STDERR] != io_fds[SFD_FOLLOWER])
+ close(io_fds[SFD_STDERR]);
+
+ /* Wait for parent to grant us the tty if we are foreground. */
+ if (foreground && !ISSET(details->flags, CD_EXEC_BG)) {
+ struct timespec ts = { 0, 1000 }; /* 1us */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: waiting for controlling tty",
+ __func__);
+ while (tcgetpgrp(io_fds[SFD_FOLLOWER]) != self)
+ nanosleep(&ts, NULL);
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: got controlling tty",
+ __func__);
+ }
+
+ /* Done with the pty follower, don't leak it. */
+ if (io_fds[SFD_FOLLOWER] != -1)
+ close(io_fds[SFD_FOLLOWER]);
+
+ /* Execute command; only returns on error. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "executing %s in the %s",
+ details->command, foreground ? "foreground" : "background");
+ exec_cmnd(details, errfd);
+
+ debug_return;
+}
+
+/*
+ * Fill in the monitor closure and setup initial events.
+ * Allocates read events for the signal pipe, error pipe and backchannel.
+ */
+static void
+fill_exec_closure_monitor(struct monitor_closure *mc,
+ struct command_status *cstat, int errfd, int backchannel)
+{
+ debug_decl(fill_exec_closure_monitor, SUDO_DEBUG_EXEC);
+
+ /* Fill in the non-event part of the closure. */
+ mc->cstat = cstat;
+ mc->backchannel = backchannel;
+ mc->mon_pgrp = getpgrp();
+
+ /* Setup event base and events. */
+ mc->evbase = sudo_ev_base_alloc();
+ if (mc->evbase == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ /* Event for command status via errfd. */
+ mc->errpipe_event = sudo_ev_alloc(errfd,
+ SUDO_EV_READ|SUDO_EV_PERSIST, mon_errpipe_cb, mc);
+ if (mc->errpipe_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(mc->evbase, mc->errpipe_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ /* Event for forwarded signals via backchannel. */
+ mc->backchannel_event = sudo_ev_alloc(backchannel,
+ SUDO_EV_READ|SUDO_EV_PERSIST, mon_backchannel_cb, mc);
+ if (mc->backchannel_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(mc->evbase, mc->backchannel_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ /* Events for local signals. */
+ mc->sigint_event = sudo_ev_alloc(SIGINT,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigint_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(mc->evbase, mc->sigint_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ mc->sigquit_event = sudo_ev_alloc(SIGQUIT,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigquit_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(mc->evbase, mc->sigquit_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ mc->sigtstp_event = sudo_ev_alloc(SIGTSTP,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigtstp_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(mc->evbase, mc->sigtstp_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ mc->sigterm_event = sudo_ev_alloc(SIGTERM,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigterm_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(mc->evbase, mc->sigterm_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ mc->sighup_event = sudo_ev_alloc(SIGHUP,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sighup_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(mc->evbase, mc->sighup_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ mc->sigusr1_event = sudo_ev_alloc(SIGUSR1,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigusr1_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(mc->evbase, mc->sigusr1_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ mc->sigusr2_event = sudo_ev_alloc(SIGUSR2,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigusr2_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(mc->evbase, mc->sigusr2_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ mc->sigchld_event = sudo_ev_alloc(SIGCHLD,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigchld_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(mc->evbase, mc->sigchld_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ /* Clear the default event base. */
+ sudo_ev_base_setdef(NULL);
+
+ debug_return;
+}
+
+/*
+ * Monitor process that creates a new session with the controlling tty,
+ * resets signal handlers and forks a child to call exec_cmnd_pty().
+ * Waits for status changes from the command and relays them to the
+ * parent and relays signals from the parent to the command.
+ * Must be called with signals blocked and the old signal mask in oset.
+ * Returns an error if fork(2) fails, else calls _exit(2).
+ */
+int
+exec_monitor(struct command_details *details, sigset_t *oset,
+ bool foreground, int backchannel)
+{
+ struct monitor_closure mc = { 0 };
+ struct command_status cstat;
+ struct sigaction sa;
+ int errpipe[2];
+ debug_decl(exec_monitor, SUDO_DEBUG_EXEC);
+
+ /* The pty leader is not used by the monitor. */
+ if (io_fds[SFD_LEADER] != -1)
+ close(io_fds[SFD_LEADER]);
+
+ /* Ignore any SIGTTIN or SIGTTOU we receive (shouldn't be possible). */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_IGN;
+ if (sudo_sigaction(SIGTTIN, &sa, NULL) != 0)
+ sudo_warn(U_("unable to set handler for signal %d"), SIGTTIN);
+ if (sudo_sigaction(SIGTTOU, &sa, NULL) != 0)
+ sudo_warn(U_("unable to set handler for signal %d"), SIGTTOU);
+
+ /* If we are starting in the foreground, the pty was already initialized. */
+ if (foreground)
+ tty_initialized = true;
+
+ /*
+ * Start a new session with the parent as the session leader
+ * and the follower device as the controlling terminal.
+ * This allows us to be notified when the command has been suspended.
+ */
+ if (setsid() == -1) {
+ sudo_warn("setsid");
+ goto bad;
+ }
+ if (pty_make_controlling() == -1) {
+ sudo_warn("%s", U_("unable to set controlling tty"));
+ goto bad;
+ }
+
+ /*
+ * We use a pipe to get errno if execve(2) fails in the child.
+ */
+ if (pipe2(errpipe, O_CLOEXEC) != 0)
+ sudo_fatal("%s", U_("unable to create pipe"));
+
+ /*
+ * Before forking, wait for the main sudo process to tell us to go.
+ * Avoids race conditions when the command exits quickly.
+ */
+ while (recv(backchannel, &cstat, sizeof(cstat), MSG_WAITALL) == -1) {
+ if (errno != EINTR && errno != EAGAIN)
+ sudo_fatal("%s", U_("unable to receive message from parent"));
+ }
+
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_RBAC_ENABLED)) {
+ if (selinux_setup(details->selinux_role, details->selinux_type,
+ details->tty, io_fds[SFD_FOLLOWER], true) == -1)
+ goto bad;
+ }
+#endif
+
+ mc.cmnd_pid = sudo_debug_fork();
+ switch (mc.cmnd_pid) {
+ case -1:
+ sudo_warn("%s", U_("unable to fork"));
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_RBAC_ENABLED)) {
+ if (selinux_restore_tty() != 0)
+ sudo_warnx("%s", U_("unable to restore tty label"));
+ }
+#endif
+ goto bad;
+ case 0:
+ /* child */
+ sigprocmask(SIG_SETMASK, oset, NULL);
+ close(backchannel);
+ close(errpipe[0]);
+ if (io_fds[SFD_USERTTY] != -1)
+ close(io_fds[SFD_USERTTY]);
+ restore_signals();
+
+ /* setup tty and exec command */
+ exec_cmnd_pty(details, foreground, errpipe[1]);
+ if (write(errpipe[1], &errno, sizeof(int)) == -1)
+ sudo_warn(U_("unable to execute %s"), details->command);
+ _exit(EXIT_FAILURE);
+ }
+ close(errpipe[1]);
+
+ /* No longer need execfd. */
+ if (details->execfd != -1) {
+ close(details->execfd);
+ details->execfd = -1;
+ }
+
+ /* Send the command's pid to main sudo process. */
+ cstat.type = CMD_PID;
+ cstat.val = mc.cmnd_pid;
+ send_status(backchannel, &cstat);
+
+ /*
+ * Create new event base and register read events for the
+ * signal pipe, error pipe, and backchannel.
+ */
+ fill_exec_closure_monitor(&mc, &cstat, errpipe[0], backchannel);
+
+ /* Restore signal mask now that signal handlers are setup. */
+ sigprocmask(SIG_SETMASK, oset, NULL);
+
+ /* If any of stdin/stdout/stderr are pipes, close them in parent. */
+ if (io_fds[SFD_STDIN] != io_fds[SFD_FOLLOWER])
+ close(io_fds[SFD_STDIN]);
+ if (io_fds[SFD_STDOUT] != io_fds[SFD_FOLLOWER])
+ close(io_fds[SFD_STDOUT]);
+ if (io_fds[SFD_STDERR] != io_fds[SFD_FOLLOWER])
+ close(io_fds[SFD_STDERR]);
+
+ /* Put command in its own process group. */
+ mc.cmnd_pgrp = mc.cmnd_pid;
+ setpgid(mc.cmnd_pid, mc.cmnd_pgrp);
+
+ /* Make the command the foreground process for the pty follower. */
+ if (foreground && !ISSET(details->flags, CD_EXEC_BG)) {
+ if (tcsetpgrp(io_fds[SFD_FOLLOWER], mc.cmnd_pgrp) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to set foreground pgrp to %d (command)",
+ __func__, (int)mc.cmnd_pgrp);
+ }
+ }
+
+ /*
+ * Wait for errno on pipe, signal on backchannel or for SIGCHLD.
+ * The event loop ends when the child is no longer running and
+ * the error pipe is closed.
+ */
+ cstat.type = CMD_INVALID;
+ cstat.val = 0;
+ (void) sudo_ev_dispatch(mc.evbase);
+ if (mc.cmnd_pid != -1) {
+ pid_t pid;
+
+ /* Command still running, did the parent die? */
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "Command still running after event loop exit, terminating");
+ terminate_command(mc.cmnd_pid, true);
+ do {
+ pid = waitpid(mc.cmnd_pid, NULL, 0);
+ } while (pid == -1 && errno == EINTR);
+ /* XXX - update cstat with wait status? */
+ }
+
+ /*
+ * Take the controlling tty. This prevents processes spawned by the
+ * command from receiving SIGHUP when the session leader (us) exits.
+ */
+ if (tcsetpgrp(io_fds[SFD_FOLLOWER], mc.mon_pgrp) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to set foreground pgrp to %d (monitor)",
+ __func__, (int)mc.mon_pgrp);
+ }
+
+ /* Send parent status. */
+ send_status(backchannel, &cstat);
+
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_RBAC_ENABLED)) {
+ if (selinux_restore_tty() != 0)
+ sudo_warnx("%s", U_("unable to restore tty label"));
+ }
+#endif
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
+ _exit(EXIT_FAILURE);
+
+bad:
+ debug_return_int(-1);
+}
diff --git a/src/exec_nopty.c b/src/exec_nopty.c
new file mode 100644
index 0000000..7281531
--- /dev/null
+++ b/src/exec_nopty.c
@@ -0,0 +1,579 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include "sudo.h"
+#include "sudo_exec.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+struct exec_closure_nopty {
+ pid_t cmnd_pid;
+ pid_t ppgrp;
+ struct command_status *cstat;
+ struct command_details *details;
+ struct sudo_event_base *evbase;
+ struct sudo_event *errpipe_event;
+ struct sudo_event *sigint_event;
+ struct sudo_event *sigquit_event;
+ struct sudo_event *sigtstp_event;
+ struct sudo_event *sigterm_event;
+ struct sudo_event *sighup_event;
+ struct sudo_event *sigalrm_event;
+ struct sudo_event *sigpipe_event;
+ struct sudo_event *sigusr1_event;
+ struct sudo_event *sigusr2_event;
+ struct sudo_event *sigchld_event;
+ struct sudo_event *sigcont_event;
+ struct sudo_event *siginfo_event;
+};
+
+static void handle_sigchld_nopty(struct exec_closure_nopty *ec);
+
+/* Note: this is basically the same as mon_errpipe_cb() in exec_monitor.c */
+static void
+errpipe_cb(int fd, int what, void *v)
+{
+ struct exec_closure_nopty *ec = v;
+ ssize_t nread;
+ int errval;
+ debug_decl(errpipe_cb, SUDO_DEBUG_EXEC);
+
+ /*
+ * Read errno from child or EOF when command is executed.
+ * Note that the error pipe is *blocking*.
+ */
+ nread = read(fd, &errval, sizeof(errval));
+ switch (nread) {
+ case -1:
+ if (errno != EAGAIN && errno != EINTR) {
+ if (ec->cstat->val == CMD_INVALID) {
+ /* XXX - need a way to distinguish non-exec error. */
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errno;
+ }
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: failed to read error pipe", __func__);
+ sudo_ev_loopbreak(ec->evbase);
+ }
+ break;
+ default:
+ if (nread == 0) {
+ /* The error pipe closes when the command is executed. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "EOF on error pipe");
+ } else {
+ /* Errno value when child is unable to execute command. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s",
+ strerror(errval));
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errval;
+ }
+ sudo_ev_del(ec->evbase, ec->errpipe_event);
+ close(fd);
+ break;
+ }
+ debug_return;
+}
+
+/* Signal callback */
+static void
+signal_cb_nopty(int signo, int what, void *v)
+{
+ struct sudo_ev_siginfo_container *sc = v;
+ struct exec_closure_nopty *ec = sc->closure;
+ char signame[SIG2STR_MAX];
+ debug_decl(signal_cb_nopty, SUDO_DEBUG_EXEC);
+
+ if (ec->cmnd_pid == -1)
+ debug_return;
+
+ if (sig2str(signo, signame) == -1)
+ (void)snprintf(signame, sizeof(signame), "%d", signo);
+ sudo_debug_printf(SUDO_DEBUG_DIAG,
+ "%s: evbase %p, command: %d, signo %s(%d), cstat %p",
+ __func__, ec->evbase, (int)ec->cmnd_pid, signame, signo, ec->cstat);
+
+ switch (signo) {
+ case SIGCHLD:
+ handle_sigchld_nopty(ec);
+ if (ec->cmnd_pid == -1) {
+ /* Command exited or was killed, exit event loop. */
+ sudo_ev_loopexit(ec->evbase);
+ }
+ debug_return;
+#ifdef SIGINFO
+ case SIGINFO:
+#endif
+ case SIGINT:
+ case SIGQUIT:
+ case SIGTSTP:
+ /*
+ * Only forward user-generated signals not sent by a process in
+ * the command's own process group. Signals sent by the kernel
+ * may include SIGTSTP when the user presses ^Z. Curses programs
+ * often trap ^Z and send SIGTSTP to their own pgrp, so we don't
+ * want to send an extra SIGTSTP.
+ */
+ if (!USER_SIGNALED(sc->siginfo))
+ debug_return;
+ if (sc->siginfo->si_pid != 0) {
+ pid_t si_pgrp = getpgid(sc->siginfo->si_pid);
+ if (si_pgrp != -1) {
+ if (si_pgrp == ec->ppgrp || si_pgrp == ec->cmnd_pid)
+ debug_return;
+ } else if (sc->siginfo->si_pid == ec->cmnd_pid) {
+ debug_return;
+ }
+ }
+ break;
+ default:
+ /*
+ * Do not forward signals sent by a process in the command's process
+ * group, as we don't want the command to indirectly kill itself.
+ * For example, this can happen with some versions of reboot that
+ * call kill(-1, SIGTERM) to kill all other processes.
+ */
+ if (USER_SIGNALED(sc->siginfo) && sc->siginfo->si_pid != 0) {
+ pid_t si_pgrp = getpgid(sc->siginfo->si_pid);
+ if (si_pgrp != -1) {
+ if (si_pgrp == ec->ppgrp || si_pgrp == ec->cmnd_pid)
+ debug_return;
+ } else if (sc->siginfo->si_pid == ec->cmnd_pid) {
+ debug_return;
+ }
+ }
+ break;
+ }
+
+ /* Send signal to command. */
+ if (signo == SIGALRM) {
+ terminate_command(ec->cmnd_pid, false);
+ } else if (kill(ec->cmnd_pid, signo) != 0) {
+ sudo_warn("kill(%d, SIG%s)", (int)ec->cmnd_pid, signame);
+ }
+
+ debug_return;
+}
+
+
+/*
+ * Fill in the exec closure and setup initial exec events.
+ * Allocates events for the signal pipe and error pipe.
+ */
+static void
+fill_exec_closure_nopty(struct exec_closure_nopty *ec,
+ struct command_status *cstat, struct command_details *details, int errfd)
+{
+ debug_decl(fill_exec_closure_nopty, SUDO_DEBUG_EXEC);
+
+ /* Fill in the non-event part of the closure. */
+ ec->ppgrp = getpgrp();
+ ec->cstat = cstat;
+ ec->details = details;
+
+ /* Setup event base and events. */
+ ec->evbase = details->evbase;
+ details->evbase = NULL;
+
+ /* Event for command status via errfd. */
+ ec->errpipe_event = sudo_ev_alloc(errfd,
+ SUDO_EV_READ|SUDO_EV_PERSIST, errpipe_cb, ec);
+ if (ec->errpipe_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->errpipe_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "error pipe fd %d\n", errfd);
+
+ /* Events for local signals. */
+ ec->sigint_event = sudo_ev_alloc(SIGINT,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigint_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigint_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigquit_event = sudo_ev_alloc(SIGQUIT,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigquit_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigquit_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigtstp_event = sudo_ev_alloc(SIGTSTP,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigtstp_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigtstp_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigterm_event = sudo_ev_alloc(SIGTERM,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigterm_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigterm_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sighup_event = sudo_ev_alloc(SIGHUP,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sighup_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sighup_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigalrm_event = sudo_ev_alloc(SIGALRM,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigalrm_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigalrm_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigpipe_event = sudo_ev_alloc(SIGPIPE,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigpipe_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigpipe_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigusr1_event = sudo_ev_alloc(SIGUSR1,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigusr1_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigusr1_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigusr2_event = sudo_ev_alloc(SIGUSR2,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigusr2_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigusr2_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigchld_event = sudo_ev_alloc(SIGCHLD,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigchld_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigchld_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigcont_event = sudo_ev_alloc(SIGCONT,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigcont_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigcont_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+#ifdef SIGINFO
+ ec->siginfo_event = sudo_ev_alloc(SIGINFO,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->siginfo_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->siginfo_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+#endif
+
+ /* Set the default event base. */
+ sudo_ev_base_setdef(ec->evbase);
+
+ debug_return;
+}
+
+/*
+ * Free the dynamically-allocated contents of the exec closure.
+ */
+static void
+free_exec_closure_nopty(struct exec_closure_nopty *ec)
+{
+ debug_decl(free_exec_closure_nopty, SUDO_DEBUG_EXEC);
+
+ sudo_ev_base_free(ec->evbase);
+ sudo_ev_free(ec->errpipe_event);
+ sudo_ev_free(ec->sigint_event);
+ sudo_ev_free(ec->sigquit_event);
+ sudo_ev_free(ec->sigtstp_event);
+ sudo_ev_free(ec->sigterm_event);
+ sudo_ev_free(ec->sighup_event);
+ sudo_ev_free(ec->sigalrm_event);
+ sudo_ev_free(ec->sigpipe_event);
+ sudo_ev_free(ec->sigusr1_event);
+ sudo_ev_free(ec->sigusr2_event);
+ sudo_ev_free(ec->sigchld_event);
+ sudo_ev_free(ec->sigcont_event);
+ sudo_ev_free(ec->siginfo_event);
+
+ debug_return;
+}
+
+/*
+ * Execute a command and wait for it to finish.
+ */
+void
+exec_nopty(struct command_details *details, struct command_status *cstat)
+{
+ struct exec_closure_nopty ec = { 0 };
+ sigset_t set, oset;
+ int errpipe[2];
+ debug_decl(exec_nopty, SUDO_DEBUG_EXEC);
+
+ /*
+ * The policy plugin's session init must be run before we fork
+ * or certain pam modules won't be able to track their state.
+ */
+ if (policy_init_session(details) != true)
+ sudo_fatalx("%s", U_("policy plugin failed session initialization"));
+
+ /*
+ * We use a pipe to get errno if execve(2) fails in the child.
+ */
+ if (pipe2(errpipe, O_CLOEXEC) != 0)
+ sudo_fatal("%s", U_("unable to create pipe"));
+
+ /*
+ * Block signals until we have our handlers setup in the parent so
+ * we don't miss SIGCHLD if the command exits immediately.
+ */
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oset);
+
+ /* Check for early termination or suspend signals before we fork. */
+ if (sudo_terminated(cstat)) {
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+ debug_return;
+ }
+
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_RBAC_ENABLED)) {
+ if (selinux_setup(details->selinux_role, details->selinux_type,
+ details->tty, -1, true) == -1) {
+ cstat->type = CMD_ERRNO;
+ cstat->val = errno;
+ debug_return;
+ }
+ }
+#endif
+
+ ec.cmnd_pid = sudo_debug_fork();
+ switch (ec.cmnd_pid) {
+ case -1:
+ sudo_fatal("%s", U_("unable to fork"));
+ break;
+ case 0:
+ /* child */
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+ close(errpipe[0]);
+ exec_cmnd(details, errpipe[1]);
+ while (write(errpipe[1], &errno, sizeof(int)) == -1) {
+ if (errno != EINTR)
+ break;
+ }
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
+ _exit(EXIT_FAILURE);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", details->command,
+ (int)ec.cmnd_pid);
+ close(errpipe[1]);
+
+ /* No longer need execfd. */
+ if (details->execfd != -1) {
+ close(details->execfd);
+ details->execfd = -1;
+ }
+
+ /* Set command timeout if specified. */
+ if (ISSET(details->flags, CD_SET_TIMEOUT))
+ alarm(details->timeout);
+
+ /*
+ * Fill in exec closure, allocate event base, signal events and
+ * the error pipe event.
+ */
+ fill_exec_closure_nopty(&ec, cstat, details, errpipe[0]);
+
+ /* Restore signal mask now that signal handlers are setup. */
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+
+ /*
+ * Non-pty event loop.
+ * Wait for command to exit, handles signals and the error pipe.
+ */
+ if (sudo_ev_dispatch(ec.evbase) == -1)
+ sudo_warn("%s", U_("error in event loop"));
+ if (sudo_ev_got_break(ec.evbase)) {
+ /* error from callback */
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely");
+ /* kill command */
+ terminate_command(ec.cmnd_pid, true);
+ ec.cmnd_pid = -1;
+ }
+
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_RBAC_ENABLED)) {
+ if (selinux_restore_tty() != 0)
+ sudo_warnx("%s", U_("unable to restore tty label"));
+ }
+#endif
+
+ /* Free things up. */
+ free_exec_closure_nopty(&ec);
+ debug_return;
+}
+
+/*
+ * Wait for command status after receiving SIGCHLD.
+ * If the command exits, fill in cstat and stop the event loop.
+ * If the command stops, save the tty pgrp, suspend sudo, then restore
+ * the tty pgrp when sudo resumes.
+ */
+static void
+handle_sigchld_nopty(struct exec_closure_nopty *ec)
+{
+ pid_t pid;
+ int status;
+ char signame[SIG2STR_MAX];
+ debug_decl(handle_sigchld_nopty, SUDO_DEBUG_EXEC);
+
+ /* Read command status. */
+ do {
+ pid = waitpid(ec->cmnd_pid, &status, WUNTRACED|WNOHANG);
+ } while (pid == -1 && errno == EINTR);
+ switch (pid) {
+ case 0:
+ /* waitpid() will return 0 for SIGCONT, which we don't care about */
+ debug_return;
+ case -1:
+ sudo_warn(U_("%s: %s"), __func__, "waitpid");
+ debug_return;
+ }
+
+ if (WIFSTOPPED(status)) {
+ /*
+ * Save the controlling terminal's process group so we can restore it
+ * after we resume, if needed. Most well-behaved shells change the
+ * pgrp back to its original value before suspending so we must
+ * not try to restore in that case, lest we race with the command upon
+ * resume, potentially stopping sudo with SIGTTOU while the command
+ * continues to run.
+ */
+ struct sigaction sa, osa;
+ pid_t saved_pgrp = -1;
+ int fd, signo = WSTOPSIG(status);
+
+ if (sig2str(signo, signame) == -1)
+ (void)snprintf(signame, sizeof(signame), "%d", signo);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) stopped, SIG%s",
+ __func__, (int)ec->cmnd_pid, signame);
+
+ fd = open(_PATH_TTY, O_RDWR);
+ if (fd != -1) {
+ saved_pgrp = tcgetpgrp(fd);
+ if (saved_pgrp == -1) {
+ close(fd);
+ fd = -1;
+ }
+ }
+ if (saved_pgrp != -1) {
+ /*
+ * Command was stopped trying to access the controlling terminal.
+ * If the command has a different pgrp and we own the controlling
+ * terminal, give it to the command's pgrp and let it continue.
+ */
+ if (signo == SIGTTOU || signo == SIGTTIN) {
+ if (saved_pgrp == ec->ppgrp) {
+ pid_t cmnd_pgrp = getpgid(ec->cmnd_pid);
+ if (cmnd_pgrp != ec->ppgrp) {
+ if (tcsetpgrp_nobg(fd, cmnd_pgrp) == 0) {
+ if (killpg(cmnd_pgrp, SIGCONT) != 0) {
+ sudo_warn("kill(%d, SIGCONT)",
+ (int)cmnd_pgrp);
+ }
+ close(fd);
+ goto done;
+ }
+ }
+ }
+ }
+ }
+ if (signo == SIGTSTP) {
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_DFL;
+ if (sudo_sigaction(SIGTSTP, &sa, &osa) != 0) {
+ sudo_warn(U_("unable to set handler for signal %d"),
+ SIGTSTP);
+ }
+ }
+ if (kill(getpid(), signo) != 0)
+ sudo_warn("kill(%d, SIG%s)", (int)getpid(), signame);
+ if (signo == SIGTSTP) {
+ if (sudo_sigaction(SIGTSTP, &osa, NULL) != 0) {
+ sudo_warn(U_("unable to restore handler for signal %d"),
+ SIGTSTP);
+ }
+ }
+ if (saved_pgrp != -1) {
+ /*
+ * On resume, restore foreground process group, if different.
+ * Otherwise, we cannot resume some shells (pdksh).
+ *
+ * It is possible that we are no longer the foreground process so
+ * use tcsetpgrp_nobg() to prevent sudo from receiving SIGTTOU.
+ */
+ if (saved_pgrp != ec->ppgrp)
+ tcsetpgrp_nobg(fd, saved_pgrp);
+ close(fd);
+ }
+ } else {
+ /* Command has exited or been killed, we are done. */
+ if (WIFSIGNALED(status)) {
+ if (sig2str(WTERMSIG(status), signame) == -1)
+ (void)snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s",
+ __func__, (int)ec->cmnd_pid, signame);
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) exited: %d",
+ __func__, (int)ec->cmnd_pid, WEXITSTATUS(status));
+ }
+ /* Don't overwrite execve() failure with command exit status. */
+ if (ec->cstat->type == CMD_INVALID) {
+ ec->cstat->type = CMD_WSTATUS;
+ ec->cstat->val = status;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_WARN,
+ "%s: not overwriting command status %d,%d with %d,%d",
+ __func__, ec->cstat->type, ec->cstat->val, CMD_WSTATUS, status);
+ }
+ ec->cmnd_pid = -1;
+ }
+done:
+ debug_return;
+}
diff --git a/src/exec_pty.c b/src/exec_pty.c
new file mode 100644
index 0000000..cfe4929
--- /dev/null
+++ b/src/exec_pty.c
@@ -0,0 +1,1897 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <termios.h> /* for struct winsize on HP-UX */
+
+#include "sudo.h"
+#include "sudo_exec.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+/* Evaluates to true if the event has /dev/tty as its fd. */
+#define USERTTY_EVENT(_ev) (sudo_ev_get_fd((_ev)) == io_fds[SFD_USERTTY])
+
+#define TERM_COOKED 0
+#define TERM_RAW 1
+
+/* Tail queue of messages to send to the monitor. */
+struct monitor_message {
+ TAILQ_ENTRY(monitor_message) entries;
+ struct command_status cstat;
+};
+TAILQ_HEAD(monitor_message_list, monitor_message);
+
+struct exec_closure_pty {
+ pid_t monitor_pid;
+ pid_t cmnd_pid;
+ pid_t ppgrp;
+ short rows;
+ short cols;
+ struct command_status *cstat;
+ struct command_details *details;
+ struct sudo_event_base *evbase;
+ struct sudo_event *backchannel_event;
+ struct sudo_event *fwdchannel_event;
+ struct sudo_event *sigint_event;
+ struct sudo_event *sigquit_event;
+ struct sudo_event *sigtstp_event;
+ struct sudo_event *sigterm_event;
+ struct sudo_event *sighup_event;
+ struct sudo_event *sigalrm_event;
+ struct sudo_event *sigusr1_event;
+ struct sudo_event *sigusr2_event;
+ struct sudo_event *sigchld_event;
+ struct sudo_event *sigwinch_event;
+ struct monitor_message_list monitor_messages;
+};
+
+/*
+ * I/O buffer with associated read/write events and a logging action.
+ * Used to, e.g. pass data from the pty to the user's terminal
+ * and any I/O logging plugins.
+ */
+struct io_buffer;
+typedef bool (*sudo_io_action_t)(const char *, unsigned int, struct io_buffer *);
+struct io_buffer {
+ SLIST_ENTRY(io_buffer) entries;
+ struct exec_closure_pty *ec;
+ struct sudo_event *revent;
+ struct sudo_event *wevent;
+ sudo_io_action_t action;
+ int len; /* buffer length (how much produced) */
+ int off; /* write position (how much already consumed) */
+ char buf[64 * 1024];
+};
+SLIST_HEAD(io_buffer_list, io_buffer);
+
+static char ptyname[PATH_MAX];
+int io_fds[6] = { -1, -1, -1, -1, -1, -1};
+static bool foreground, pipeline;
+static int ttymode = TERM_COOKED;
+static sigset_t ttyblock;
+static struct io_buffer_list iobufs;
+static const char *utmp_user;
+
+static void del_io_events(bool nonblocking);
+static void sync_ttysize(struct exec_closure_pty *ec);
+static int safe_close(int fd);
+static void ev_free_by_fd(struct sudo_event_base *evbase, int fd);
+static pid_t check_foreground(struct exec_closure_pty *ec);
+static void add_io_events(struct sudo_event_base *evbase);
+static void schedule_signal(struct exec_closure_pty *ec, int signo);
+
+/*
+ * Cleanup hook for sudo_fatal()/sudo_fatalx()
+ */
+void
+pty_cleanup(void)
+{
+ debug_decl(cleanup, SUDO_DEBUG_EXEC);
+
+ if (io_fds[SFD_USERTTY] != -1)
+ sudo_term_restore(io_fds[SFD_USERTTY], false);
+ if (utmp_user != NULL)
+ utmp_logout(ptyname, 0);
+
+ debug_return;
+}
+
+/*
+ * Allocate a pty if /dev/tty is a tty.
+ * Fills in io_fds[SFD_USERTTY], io_fds[SFD_LEADER], io_fds[SFD_FOLLOWER]
+ * and ptyname globals.
+ */
+static bool
+pty_setup(struct command_details *details, const char *tty)
+{
+ debug_decl(pty_setup, SUDO_DEBUG_EXEC);
+
+ io_fds[SFD_USERTTY] = open(_PATH_TTY, O_RDWR);
+ if (io_fds[SFD_USERTTY] == -1) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: no %s, not allocating a pty",
+ __func__, _PATH_TTY);
+ debug_return_bool(false);
+ }
+
+ if (!get_pty(&io_fds[SFD_LEADER], &io_fds[SFD_FOLLOWER],
+ ptyname, sizeof(ptyname), details->cred.euid))
+ sudo_fatal("%s", U_("unable to allocate pty"));
+
+ /* Update tty name in command details (used by SELinux and AIX). */
+ details->tty = ptyname;
+
+ /* Add entry to utmp/utmpx? */
+ if (ISSET(details->flags, CD_SET_UTMP)) {
+ utmp_user =
+ details->utmp_user ? details->utmp_user : user_details.username;
+ utmp_login(tty, ptyname, io_fds[SFD_FOLLOWER], utmp_user);
+ }
+
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: %s fd %d, pty leader fd %d, pty follower fd %d",
+ __func__, _PATH_TTY, io_fds[SFD_USERTTY], io_fds[SFD_LEADER],
+ io_fds[SFD_FOLLOWER]);
+
+ debug_return_bool(true);
+}
+
+/*
+ * Make the tty follower the controlling tty.
+ * This is only used by the monitor but ptyname[] is static.
+ */
+int
+pty_make_controlling(void)
+{
+ if (io_fds[SFD_FOLLOWER] != -1) {
+#ifdef TIOCSCTTY
+ if (ioctl(io_fds[SFD_FOLLOWER], TIOCSCTTY, NULL) != 0)
+ return -1;
+#else
+ /* Set controlling tty by reopening pty follower. */
+ int fd = open(ptyname, O_RDWR);
+ if (fd == -1)
+ return -1;
+ close(fd);
+#endif
+ }
+ return 0;
+}
+
+/* Call I/O plugin tty input log method. */
+static bool
+log_ttyin(const char *buf, unsigned int n, struct io_buffer *iob)
+{
+ struct plugin_container *plugin;
+ const char *errstr = NULL;
+ sigset_t omask;
+ bool ret = true;
+ debug_decl(log_ttyin, SUDO_DEBUG_EXEC);
+
+ sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ if (plugin->u.io->log_ttyin) {
+ int rc;
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ rc = plugin->u.io->log_ttyin(buf, n, &errstr);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_ttyin = NULL;
+ audit_error(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("I/O plugin error"),
+ iob->ec->details->info);
+ } else {
+ audit_reject(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("command rejected by I/O plugin"),
+ iob->ec->details->info);
+ }
+ ret = false;
+ break;
+ }
+ }
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+
+ debug_return_bool(ret);
+}
+
+/* Call I/O plugin stdin log method. */
+static bool
+log_stdin(const char *buf, unsigned int n, struct io_buffer *iob)
+{
+ struct plugin_container *plugin;
+ const char *errstr = NULL;
+ sigset_t omask;
+ bool ret = true;
+ debug_decl(log_stdin, SUDO_DEBUG_EXEC);
+
+ sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ if (plugin->u.io->log_stdin) {
+ int rc;
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ rc = plugin->u.io->log_stdin(buf, n, &errstr);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_stdin = NULL;
+ audit_error(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("I/O plugin error"),
+ iob->ec->details->info);
+ } else {
+ audit_reject(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("command rejected by I/O plugin"),
+ iob->ec->details->info);
+ }
+ ret = false;
+ break;
+ }
+ }
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+
+ debug_return_bool(ret);
+}
+
+/* Call I/O plugin tty output log method. */
+static bool
+log_ttyout(const char *buf, unsigned int n, struct io_buffer *iob)
+{
+ struct plugin_container *plugin;
+ const char *errstr = NULL;
+ sigset_t omask;
+ bool ret = true;
+ debug_decl(log_ttyout, SUDO_DEBUG_EXEC);
+
+ sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ if (plugin->u.io->log_ttyout) {
+ int rc;
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ rc = plugin->u.io->log_ttyout(buf, n, &errstr);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_ttyout = NULL;
+ audit_error(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("I/O plugin error"),
+ iob->ec->details->info);
+ } else {
+ audit_reject(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("command rejected by I/O plugin"),
+ iob->ec->details->info);
+ }
+ ret = false;
+ break;
+ }
+ }
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ if (!ret) {
+ /*
+ * I/O plugin rejected the output, delete the write event
+ * (user's tty) so we do not display the rejected output.
+ */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: deleting and freeing devtty wevent %p", __func__, iob->wevent);
+ sudo_ev_free(iob->wevent);
+ iob->wevent = NULL;
+ iob->off = iob->len = 0;
+ }
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+
+ debug_return_bool(ret);
+}
+
+/* Call I/O plugin stdout log method. */
+static bool
+log_stdout(const char *buf, unsigned int n, struct io_buffer *iob)
+{
+ struct plugin_container *plugin;
+ const char *errstr = NULL;
+ sigset_t omask;
+ bool ret = true;
+ debug_decl(log_stdout, SUDO_DEBUG_EXEC);
+
+ sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ if (plugin->u.io->log_stdout) {
+ int rc;
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ rc = plugin->u.io->log_stdout(buf, n, &errstr);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_stdout = NULL;
+ audit_error(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("I/O plugin error"),
+ iob->ec->details->info);
+ } else {
+ audit_reject(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("command rejected by I/O plugin"),
+ iob->ec->details->info);
+ }
+ ret = false;
+ break;
+ }
+ }
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ if (!ret) {
+ /*
+ * I/O plugin rejected the output, delete the write event
+ * (user's stdout) so we do not display the rejected output.
+ */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: deleting and freeing stdout wevent %p", __func__, iob->wevent);
+ sudo_ev_free(iob->wevent);
+ iob->wevent = NULL;
+ iob->off = iob->len = 0;
+ }
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+
+ debug_return_bool(ret);
+}
+
+/* Call I/O plugin stderr log method. */
+static bool
+log_stderr(const char *buf, unsigned int n, struct io_buffer *iob)
+{
+ struct plugin_container *plugin;
+ const char *errstr = NULL;
+ sigset_t omask;
+ bool ret = true;
+ debug_decl(log_stderr, SUDO_DEBUG_EXEC);
+
+ sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ if (plugin->u.io->log_stderr) {
+ int rc;
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ rc = plugin->u.io->log_stderr(buf, n, &errstr);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_stderr = NULL;
+ audit_error(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("I/O plugin error"),
+ iob->ec->details->info);
+ } else {
+ audit_reject(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("command rejected by I/O plugin"),
+ iob->ec->details->info);
+ }
+ ret = false;
+ break;
+ }
+ }
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ if (!ret) {
+ /*
+ * I/O plugin rejected the output, delete the write event
+ * (user's stderr) so we do not display the rejected output.
+ */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: deleting and freeing stderr wevent %p", __func__, iob->wevent);
+ sudo_ev_free(iob->wevent);
+ iob->wevent = NULL;
+ iob->off = iob->len = 0;
+ }
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+
+ debug_return_bool(ret);
+}
+
+/* Call I/O plugin suspend log method. */
+static void
+log_suspend(struct exec_closure_pty *ec, int signo)
+{
+ struct plugin_container *plugin;
+ const char *errstr = NULL;
+ sigset_t omask;
+ debug_decl(log_suspend, SUDO_DEBUG_EXEC);
+
+ sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ if (plugin->u.io->version < SUDO_API_MKVERSION(1, 13))
+ continue;
+ if (plugin->u.io->log_suspend) {
+ int rc;
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ rc = plugin->u.io->log_suspend(signo, &errstr);
+ if (rc <= 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_suspend = NULL;
+ audit_error(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("error logging suspend"),
+ ec->details->info);
+ break;
+ }
+ }
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+
+ debug_return;
+}
+
+/* Call I/O plugin window change log method. */
+static void
+log_winchange(struct exec_closure_pty *ec, unsigned int rows, unsigned int cols)
+{
+ struct plugin_container *plugin;
+ const char *errstr = NULL;
+ sigset_t omask;
+ debug_decl(log_winchange, SUDO_DEBUG_EXEC);
+
+ sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ if (plugin->u.io->version < SUDO_API_MKVERSION(1, 12))
+ continue;
+ if (plugin->u.io->change_winsize) {
+ int rc;
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ rc = plugin->u.io->change_winsize(rows, cols, &errstr);
+ if (rc <= 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->change_winsize = NULL;
+ audit_error(plugin->name, SUDO_IO_PLUGIN,
+ errstr ? errstr : _("error changing window size"),
+ ec->details->info);
+ break;
+ }
+ }
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+
+ debug_return;
+}
+
+/*
+ * Check whether we are running in the foregroup.
+ * Updates the foreground global and updates the window size.
+ * Returns 0 if there is no tty, the foreground process group ID
+ * on success, or -1 on failure (tty revoked).
+ */
+static pid_t
+check_foreground(struct exec_closure_pty *ec)
+{
+ int ret = 0;
+ debug_decl(check_foreground, SUDO_DEBUG_EXEC);
+
+ if (io_fds[SFD_USERTTY] != -1) {
+ if ((ret = tcgetpgrp(io_fds[SFD_USERTTY])) != -1) {
+ foreground = ret == ec->ppgrp;
+
+ /* Also check for window size changes. */
+ sync_ttysize(ec);
+ }
+ }
+ debug_return_int(ret);
+}
+
+/*
+ * Suspend sudo if the underlying command is suspended.
+ * Returns SIGCONT_FG if the command should be resumed in the
+ * foreground or SIGCONT_BG if it is a background process.
+ */
+static int
+suspend_sudo(struct exec_closure_pty *ec, int signo)
+{
+ char signame[SIG2STR_MAX];
+ struct sigaction sa, osa;
+ int ret = 0;
+ debug_decl(suspend_sudo, SUDO_DEBUG_EXEC);
+
+ switch (signo) {
+ case SIGTTOU:
+ case SIGTTIN:
+ /*
+ * If sudo is already the foreground process, just resume the command
+ * in the foreground. If not, we'll suspend sudo and resume later.
+ */
+ if (!foreground) {
+ if (check_foreground(ec) == -1) {
+ /* User's tty was revoked. */
+ break;
+ }
+ }
+ if (foreground) {
+ if (ttymode != TERM_RAW) {
+ if (sudo_term_raw(io_fds[SFD_USERTTY], 0))
+ ttymode = TERM_RAW;
+ }
+ ret = SIGCONT_FG; /* resume command in foreground */
+ break;
+ }
+ FALLTHROUGH;
+ case SIGSTOP:
+ case SIGTSTP:
+ /* Flush any remaining output and deschedule I/O events. */
+ del_io_events(true);
+
+ /* Restore original tty mode before suspending. */
+ if (ttymode != TERM_COOKED)
+ sudo_term_restore(io_fds[SFD_USERTTY], false);
+
+ /* Log the suspend event. */
+ log_suspend(ec, signo);
+
+ if (sig2str(signo, signame) == -1)
+ (void)snprintf(signame, sizeof(signame), "%d", signo);
+
+ /* Suspend self and continue command when we resume. */
+ if (signo != SIGSTOP) {
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_DFL;
+ if (sudo_sigaction(signo, &sa, &osa) != 0)
+ sudo_warn(U_("unable to set handler for signal %d"), signo);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO, "kill parent SIG%s", signame);
+ if (killpg(ec->ppgrp, signo) != 0)
+ sudo_warn("killpg(%d, SIG%s)", (int)ec->ppgrp, signame);
+
+ /* Log the resume event. */
+ log_suspend(ec, SIGCONT);
+
+ /* Check foreground/background status on resume. */
+ if (check_foreground(ec) == -1) {
+ /* User's tty was revoked. */
+ break;
+ }
+
+ /*
+ * We always resume the command in the foreground if sudo itself
+ * is the foreground process. This helps work around poorly behaved
+ * programs that catch SIGTTOU/SIGTTIN but suspend themselves with
+ * SIGSTOP. At worst, sudo will go into the background but upon
+ * resume the command will be runnable. Otherwise, we can get into
+ * a situation where the command will immediately suspend itself.
+ */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "parent is in %s, ttymode %d -> %d",
+ foreground ? "foreground" : "background", ttymode,
+ foreground ? TERM_RAW : TERM_COOKED);
+
+ if (foreground) {
+ /* Foreground process, set tty to raw mode. */
+ if (sudo_term_raw(io_fds[SFD_USERTTY], 0))
+ ttymode = TERM_RAW;
+ } else {
+ /* Background process, no access to tty. */
+ ttymode = TERM_COOKED;
+ }
+
+ if (signo != SIGSTOP) {
+ if (sudo_sigaction(signo, &osa, NULL) != 0)
+ sudo_warn(U_("unable to restore handler for signal %d"), signo);
+ }
+
+ ret = ttymode == TERM_RAW ? SIGCONT_FG : SIGCONT_BG;
+ break;
+ }
+
+ debug_return_int(ret);
+}
+
+/*
+ * SIGTTIN signal handler for read_callback that just sets a flag.
+ */
+static volatile sig_atomic_t got_sigttin;
+
+static void
+sigttin(int signo)
+{
+ got_sigttin = 1;
+}
+
+/*
+ * Read an iobuf that is ready.
+ */
+static void
+read_callback(int fd, int what, void *v)
+{
+ struct io_buffer *iob = v;
+ struct sudo_event_base *evbase = sudo_ev_get_base(iob->revent);
+ struct sigaction sa, osa;
+ int saved_errno;
+ ssize_t n;
+ debug_decl(read_callback, SUDO_DEBUG_EXEC);
+
+ /*
+ * We ignore SIGTTIN by default but we need to handle it when reading
+ * from the terminal. A signal event won't work here because the
+ * read() would be restarted, preventing the callback from running.
+ */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = sigttin;
+ got_sigttin = 0;
+ sigaction(SIGTTIN, &sa, &osa);
+ n = read(fd, iob->buf + iob->len, sizeof(iob->buf) - iob->len);
+ saved_errno = errno;
+ sigaction(SIGTTIN, &osa, NULL);
+ errno = saved_errno;
+
+ switch (n) {
+ case -1:
+ if (got_sigttin) {
+ /* Schedule SIGTTIN to be forwarded to the command. */
+ schedule_signal(iob->ec, SIGTTIN);
+ }
+ if (errno == EAGAIN || errno == EINTR)
+ break;
+ /* treat read error as fatal and close the fd */
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "error reading fd %d: %s", fd, strerror(errno));
+ FALLTHROUGH;
+ case 0:
+ /* got EOF or pty has gone away */
+ if (n == 0) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "read EOF from fd %d", fd);
+ }
+ safe_close(fd);
+ ev_free_by_fd(evbase, fd);
+ /* If writer already consumed the buffer, close it too. */
+ if (iob->wevent != NULL && iob->off == iob->len) {
+ safe_close(sudo_ev_get_fd(iob->wevent));
+ ev_free_by_fd(evbase, sudo_ev_get_fd(iob->wevent));
+ iob->off = iob->len = 0;
+ }
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "read %zd bytes from fd %d", n, fd);
+ if (!iob->action(iob->buf + iob->len, n, iob)) {
+ terminate_command(iob->ec->cmnd_pid, true);
+ iob->ec->cmnd_pid = -1;
+ }
+ iob->len += n;
+ /* Enable writer now that there is data in the buffer. */
+ if (iob->wevent != NULL) {
+ if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+ }
+ /* Re-enable reader if buffer is not full. */
+ if (iob->len != sizeof(iob->buf)) {
+ if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+ }
+ break;
+ }
+}
+
+/*
+ * SIGTTOU signal handler for write_callback that just sets a flag.
+ */
+static volatile sig_atomic_t got_sigttou;
+
+static void
+sigttou(int signo)
+{
+ got_sigttou = 1;
+}
+
+/*
+ * Write an iobuf that is ready.
+ */
+static void
+write_callback(int fd, int what, void *v)
+{
+ struct io_buffer *iob = v;
+ struct sudo_event_base *evbase = sudo_ev_get_base(iob->wevent);
+ struct sigaction sa, osa;
+ int saved_errno;
+ ssize_t n;
+ debug_decl(write_callback, SUDO_DEBUG_EXEC);
+
+ /*
+ * We ignore SIGTTOU by default but we need to handle it when writing
+ * to the terminal. A signal event won't work here because the
+ * write() would be restarted, preventing the callback from running.
+ */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = sigttou;
+ got_sigttou = 0;
+ sigaction(SIGTTOU, &sa, &osa);
+ n = write(fd, iob->buf + iob->off, iob->len - iob->off);
+ saved_errno = errno;
+ sigaction(SIGTTOU, &osa, NULL);
+ errno = saved_errno;
+
+ if (n == -1) {
+ switch (errno) {
+ case EPIPE:
+ case ENXIO:
+ case EIO:
+ case EBADF:
+ /* other end of pipe closed or pty revoked */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "unable to write %d bytes to fd %d",
+ iob->len - iob->off, fd);
+ /* Close reader if there is one. */
+ if (iob->revent != NULL) {
+ safe_close(sudo_ev_get_fd(iob->revent));
+ ev_free_by_fd(evbase, sudo_ev_get_fd(iob->revent));
+ }
+ safe_close(fd);
+ ev_free_by_fd(evbase, fd);
+ break;
+ case EINTR:
+ if (got_sigttou) {
+ /* Schedule SIGTTOU to be forwarded to the command. */
+ schedule_signal(iob->ec, SIGTTOU);
+ }
+ FALLTHROUGH;
+ case EAGAIN:
+ /* not an error */
+ break;
+ default:
+ /* XXX - need a way to distinguish non-exec error. */
+ iob->ec->cstat->type = CMD_ERRNO;
+ iob->ec->cstat->val = errno;
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "error writing fd %d: %s", fd, strerror(errno));
+ sudo_ev_loopbreak(evbase);
+ break;
+ }
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "wrote %zd bytes to fd %d", n, fd);
+ iob->off += n;
+ /* Reset buffer if fully consumed. */
+ if (iob->off == iob->len) {
+ iob->off = iob->len = 0;
+ /* Forward the EOF from reader to writer. */
+ if (iob->revent == NULL) {
+ safe_close(fd);
+ ev_free_by_fd(evbase, fd);
+ }
+ }
+ /* Re-enable writer if buffer is not empty. */
+ if (iob->len > iob->off) {
+ if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+ }
+ /* Enable reader if buffer is not full. */
+ if (iob->revent != NULL &&
+ (ttymode == TERM_RAW || !USERTTY_EVENT(iob->revent))) {
+ if (iob->len != sizeof(iob->buf)) {
+ if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+ }
+ }
+ }
+}
+
+static void
+io_buf_new(int rfd, int wfd,
+ bool (*action)(const char *, unsigned int, struct io_buffer *),
+ struct exec_closure_pty *ec, struct io_buffer_list *head)
+{
+ int n;
+ struct io_buffer *iob;
+ debug_decl(io_buf_new, SUDO_DEBUG_EXEC);
+
+ /* Set non-blocking mode. */
+ n = fcntl(rfd, F_GETFL, 0);
+ if (n != -1 && !ISSET(n, O_NONBLOCK))
+ (void) fcntl(rfd, F_SETFL, n | O_NONBLOCK);
+ n = fcntl(wfd, F_GETFL, 0);
+ if (n != -1 && !ISSET(n, O_NONBLOCK))
+ (void) fcntl(wfd, F_SETFL, n | O_NONBLOCK);
+
+ /* Allocate and add to head of list. */
+ if ((iob = malloc(sizeof(*iob))) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ iob->ec = ec;
+ iob->revent = sudo_ev_alloc(rfd, SUDO_EV_READ, read_callback, iob);
+ iob->wevent = sudo_ev_alloc(wfd, SUDO_EV_WRITE, write_callback, iob);
+ iob->len = 0;
+ iob->off = 0;
+ iob->action = action;
+ iob->buf[0] = '\0';
+ if (iob->revent == NULL || iob->wevent == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ SLIST_INSERT_HEAD(head, iob, entries);
+
+ debug_return;
+}
+
+/*
+ * We already closed the follower so reads from the leader will not block.
+ */
+static void
+pty_finish(struct command_status *cstat)
+{
+ struct io_buffer *iob;
+ int n;
+ debug_decl(pty_finish, SUDO_DEBUG_EXEC);
+
+ /* Flush any remaining output (the plugin already got it). */
+ if (io_fds[SFD_USERTTY] != -1) {
+ n = fcntl(io_fds[SFD_USERTTY], F_GETFL, 0);
+ if (n != -1 && ISSET(n, O_NONBLOCK)) {
+ CLR(n, O_NONBLOCK);
+ (void) fcntl(io_fds[SFD_USERTTY], F_SETFL, n);
+ }
+ }
+ del_io_events(false);
+
+ /* Free I/O buffers. */
+ while ((iob = SLIST_FIRST(&iobufs)) != NULL) {
+ SLIST_REMOVE_HEAD(&iobufs, entries);
+ if (iob->revent != NULL)
+ sudo_ev_free(iob->revent);
+ if (iob->wevent != NULL)
+ sudo_ev_free(iob->wevent);
+ free(iob);
+ }
+
+ /* Restore terminal settings. */
+ if (io_fds[SFD_USERTTY] != -1)
+ sudo_term_restore(io_fds[SFD_USERTTY], false);
+
+ /* Update utmp */
+ if (utmp_user != NULL)
+ utmp_logout(ptyname, cstat->type == CMD_WSTATUS ? cstat->val : 0);
+
+ debug_return;
+}
+
+/*
+ * Send command status to the monitor (signal or window size change).
+ */
+static void
+send_command_status(struct exec_closure_pty *ec, int type, int val)
+{
+ struct monitor_message *msg;
+ debug_decl(send_command, SUDO_DEBUG_EXEC);
+
+ if ((msg = calloc(1, sizeof(*msg))) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ msg->cstat.type = type;
+ msg->cstat.val = val;
+ TAILQ_INSERT_TAIL(&ec->monitor_messages, msg, entries);
+
+ if (sudo_ev_add(ec->evbase, ec->fwdchannel_event, NULL, true) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ /* Restart event loop to send the command immediately. */
+ sudo_ev_loopcontinue(ec->evbase);
+
+ debug_return;
+}
+
+/*
+ * Schedule a signal to be forwarded.
+ */
+static void
+schedule_signal(struct exec_closure_pty *ec, int signo)
+{
+ char signame[SIG2STR_MAX];
+ debug_decl(schedule_signal, SUDO_DEBUG_EXEC);
+
+ if (signo == 0)
+ debug_return;
+
+ if (signo == SIGCONT_FG)
+ strlcpy(signame, "CONT_FG", sizeof(signame));
+ else if (signo == SIGCONT_BG)
+ strlcpy(signame, "CONT_BG", sizeof(signame));
+ else if (sig2str(signo, signame) == -1)
+ (void)snprintf(signame, sizeof(signame), "%d", signo);
+ sudo_debug_printf(SUDO_DEBUG_DIAG, "scheduled SIG%s for command", signame);
+
+ send_command_status(ec, CMD_SIGNO, signo);
+
+ debug_return;
+}
+
+static void
+backchannel_cb(int fd, int what, void *v)
+{
+ struct exec_closure_pty *ec = v;
+ struct command_status cstat;
+ ssize_t nread;
+ debug_decl(backchannel_cb, SUDO_DEBUG_EXEC);
+
+ /*
+ * Read command status from the monitor.
+ * Note that the backchannel is a *blocking* socket.
+ */
+ nread = recv(fd, &cstat, sizeof(cstat), MSG_WAITALL);
+ switch (nread) {
+ case -1:
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+ /* Nothing ready. */
+ break;
+ default:
+ if (ec->cstat->val == CMD_INVALID) {
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errno;
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: failed to read command status: %s",
+ __func__, strerror(errno));
+ sudo_ev_loopbreak(ec->evbase);
+ }
+ break;
+ }
+ break;
+ case 0:
+ /* EOF, monitor exited or was killed. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "EOF on backchannel, monitor dead?");
+ if (ec->cstat->type == CMD_INVALID) {
+ /* XXX - need new CMD_ type for monitor errors. */
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = ECONNRESET;
+ }
+ sudo_ev_loopexit(ec->evbase);
+ break;
+ case sizeof(cstat):
+ /* Check command status. */
+ switch (cstat.type) {
+ case CMD_PID:
+ ec->cmnd_pid = cstat.val;
+ sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d",
+ ec->details->command, (int)ec->cmnd_pid);
+ break;
+ case CMD_WSTATUS:
+ if (WIFSTOPPED(cstat.val)) {
+ int signo;
+
+ /* Suspend parent and tell monitor how to resume on return. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "command stopped, suspending parent");
+ signo = suspend_sudo(ec, WSTOPSIG(cstat.val));
+ schedule_signal(ec, signo);
+ /* Re-enable I/O events */
+ add_io_events(ec->evbase);
+ } else {
+ /* Command exited or was killed, either way we are done. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "command exited or was killed");
+ sudo_ev_loopexit(ec->evbase);
+ }
+ *ec->cstat = cstat;
+ break;
+ case CMD_ERRNO:
+ /* Monitor was unable to execute command or broken pipe. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "errno from monitor: %s",
+ strerror(cstat.val));
+ sudo_ev_loopbreak(ec->evbase);
+ *ec->cstat = cstat;
+ break;
+ }
+ /* Keep reading command status messages until EAGAIN or EOF. */
+ break;
+ default:
+ /* Short read, should not happen. */
+ if (ec->cstat->val == CMD_INVALID) {
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = EIO;
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: failed to read command status: short read", __func__);
+ sudo_ev_loopbreak(ec->evbase);
+ }
+ break;
+ }
+ debug_return;
+}
+
+/*
+ * Handle changes to the monitors's status (SIGCHLD).
+ */
+static void
+handle_sigchld_pty(struct exec_closure_pty *ec)
+{
+ int n, status;
+ pid_t pid;
+ debug_decl(handle_sigchld_pty, SUDO_DEBUG_EXEC);
+
+ /*
+ * Monitor process was signaled; wait for it as needed.
+ */
+ do {
+ pid = waitpid(ec->monitor_pid, &status, WUNTRACED|WNOHANG);
+ } while (pid == -1 && errno == EINTR);
+ switch (pid) {
+ case 0:
+ errno = ECHILD;
+ FALLTHROUGH;
+ case -1:
+ sudo_warn(U_("%s: %s"), __func__, "waitpid");
+ debug_return;
+ }
+
+ /*
+ * If the monitor dies we get notified via backchannel_cb().
+ * If it was stopped, we should stop too (the command keeps
+ * running in its pty) and continue it when we come back.
+ */
+ if (WIFSTOPPED(status)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "monitor stopped, suspending sudo");
+ n = suspend_sudo(ec, WSTOPSIG(status));
+ kill(pid, SIGCONT);
+ schedule_signal(ec, n);
+ /* Re-enable I/O events */
+ add_io_events(ec->evbase);
+ } else if (WIFSIGNALED(status)) {
+ char signame[SIG2STR_MAX];
+ if (sig2str(WTERMSIG(status), signame) == -1)
+ (void)snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: monitor (%d) killed, SIG%s",
+ __func__, (int)ec->monitor_pid, signame);
+ ec->monitor_pid = -1;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: monitor exited, status %d", __func__, WEXITSTATUS(status));
+ ec->monitor_pid = -1;
+ }
+ debug_return;
+}
+
+/* Signal callback */
+static void
+signal_cb_pty(int signo, int what, void *v)
+{
+ struct sudo_ev_siginfo_container *sc = v;
+ struct exec_closure_pty *ec = sc->closure;
+ char signame[SIG2STR_MAX];
+ debug_decl(signal_cb_pty, SUDO_DEBUG_EXEC);
+
+ if (ec->monitor_pid == -1)
+ debug_return;
+
+ if (sig2str(signo, signame) == -1)
+ (void)snprintf(signame, sizeof(signame), "%d", signo);
+ sudo_debug_printf(SUDO_DEBUG_DIAG,
+ "%s: evbase %p, monitor: %d, signo %s(%d), cstat %p", __func__,
+ ec->evbase, (int)ec->monitor_pid, signame, signo, ec->cstat);
+
+ switch (signo) {
+ case SIGCHLD:
+ handle_sigchld_pty(ec);
+ break;
+ case SIGWINCH:
+ sync_ttysize(ec);
+ break;
+ default:
+ /*
+ * Do not forward signals sent by a process in the command's process
+ * group, as we don't want the command to indirectly kill itself.
+ * For example, this can happen with some versions of reboot that
+ * call kill(-1, SIGTERM) to kill all other processes.
+ */
+ if (USER_SIGNALED(sc->siginfo) && sc->siginfo->si_pid != 0) {
+ pid_t si_pgrp = getpgid(sc->siginfo->si_pid);
+ if (si_pgrp != -1) {
+ if (si_pgrp == ec->ppgrp || si_pgrp == ec->cmnd_pid)
+ debug_return;
+ } else if (sc->siginfo->si_pid == ec->cmnd_pid) {
+ debug_return;
+ }
+ }
+ /* Schedule signal to be forwarded to the command. */
+ schedule_signal(ec, signo);
+ break;
+ }
+
+ debug_return;
+}
+
+/*
+ * Forward signals in monitor_messages to the monitor so it can
+ * deliver them to the command.
+ */
+static void
+fwdchannel_cb(int sock, int what, void *v)
+{
+ struct exec_closure_pty *ec = v;
+ char signame[SIG2STR_MAX];
+ struct monitor_message *msg;
+ ssize_t nsent;
+ debug_decl(fwdchannel_cb, SUDO_DEBUG_EXEC);
+
+ while ((msg = TAILQ_FIRST(&ec->monitor_messages)) != NULL) {
+ switch (msg->cstat.type) {
+ case CMD_SIGNO:
+ if (msg->cstat.val == SIGCONT_FG)
+ strlcpy(signame, "CONT_FG", sizeof(signame));
+ else if (msg->cstat.val == SIGCONT_BG)
+ strlcpy(signame, "CONT_BG", sizeof(signame));
+ else if (sig2str(msg->cstat.val, signame) == -1)
+ (void)snprintf(signame, sizeof(signame), "%d", msg->cstat.val);
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "sending SIG%s to monitor over backchannel", signame);
+ break;
+ case CMD_TTYWINCH:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "sending window size change "
+ "to monitor over backchannelL %d x %d",
+ msg->cstat.val & 0xffff, (msg->cstat.val >> 16) & 0xffff);
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "sending cstat type %d, value %d to monitor over backchannel",
+ msg->cstat.type, msg->cstat.val);
+ break;
+ }
+ TAILQ_REMOVE(&ec->monitor_messages, msg, entries);
+ nsent = send(sock, &msg->cstat, sizeof(msg->cstat), 0);
+ if (nsent != sizeof(msg->cstat)) {
+ if (errno == EPIPE) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "broken pipe writing to monitor over backchannel");
+ /* Other end of socket gone, empty out monitor_messages. */
+ free(msg);
+ while ((msg = TAILQ_FIRST(&ec->monitor_messages)) != NULL) {
+ TAILQ_REMOVE(&ec->monitor_messages, msg, entries);
+ free(msg);
+ }
+ /* XXX - need new CMD_ type for monitor errors. */
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errno;
+ sudo_ev_loopbreak(ec->evbase);
+ }
+ break;
+ }
+ free(msg);
+ }
+}
+
+/*
+ * Fill in the exec closure and setup initial exec events.
+ * Allocates events for the signal pipe and backchannel.
+ * Forwarded signals on the backchannel are enabled on demand.
+ */
+static void
+fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat,
+ struct command_details *details, pid_t ppgrp, int backchannel)
+{
+ debug_decl(fill_exec_closure_pty, SUDO_DEBUG_EXEC);
+
+ /* Fill in the non-event part of the closure. */
+ ec->cmnd_pid = -1;
+ ec->ppgrp = ppgrp;
+ ec->cstat = cstat;
+ ec->details = details;
+ ec->rows = user_details.ts_rows;
+ ec->cols = user_details.ts_cols;
+ TAILQ_INIT(&ec->monitor_messages);
+
+ /* Reset cstat for running the command. */
+ cstat->type = CMD_INVALID;
+ cstat->val = 0;
+
+ /* Setup event base and events. */
+ ec->evbase = details->evbase;
+ details->evbase = NULL;
+
+ /* Event for command status via backchannel. */
+ ec->backchannel_event = sudo_ev_alloc(backchannel,
+ SUDO_EV_READ|SUDO_EV_PERSIST, backchannel_cb, ec);
+ if (ec->backchannel_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->backchannel_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "backchannel fd %d\n", backchannel);
+
+ /* Events for local signals. */
+ ec->sigint_event = sudo_ev_alloc(SIGINT,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigint_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigint_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigquit_event = sudo_ev_alloc(SIGQUIT,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigquit_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigquit_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigtstp_event = sudo_ev_alloc(SIGTSTP,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigtstp_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigtstp_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigterm_event = sudo_ev_alloc(SIGTERM,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigterm_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigterm_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sighup_event = sudo_ev_alloc(SIGHUP,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sighup_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sighup_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigalrm_event = sudo_ev_alloc(SIGALRM,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigalrm_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigalrm_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigusr1_event = sudo_ev_alloc(SIGUSR1,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigusr1_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigusr1_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigusr2_event = sudo_ev_alloc(SIGUSR2,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigusr2_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigusr2_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigchld_event = sudo_ev_alloc(SIGCHLD,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigchld_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigchld_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ ec->sigwinch_event = sudo_ev_alloc(SIGWINCH,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigwinch_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigwinch_event, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+
+ /* The signal forwarding event gets added on demand. */
+ ec->fwdchannel_event = sudo_ev_alloc(backchannel,
+ SUDO_EV_WRITE, fwdchannel_cb, ec);
+ if (ec->fwdchannel_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ /* Set the default event base. */
+ sudo_ev_base_setdef(ec->evbase);
+
+ debug_return;
+}
+
+/*
+ * Free the dynamically-allocated contents of the exec closure.
+ */
+static void
+free_exec_closure_pty(struct exec_closure_pty *ec)
+{
+ struct monitor_message *msg;
+ debug_decl(free_exec_closure_pty, SUDO_DEBUG_EXEC);
+
+ sudo_ev_base_free(ec->evbase);
+ sudo_ev_free(ec->backchannel_event);
+ sudo_ev_free(ec->fwdchannel_event);
+ sudo_ev_free(ec->sigint_event);
+ sudo_ev_free(ec->sigquit_event);
+ sudo_ev_free(ec->sigtstp_event);
+ sudo_ev_free(ec->sigterm_event);
+ sudo_ev_free(ec->sighup_event);
+ sudo_ev_free(ec->sigalrm_event);
+ sudo_ev_free(ec->sigusr1_event);
+ sudo_ev_free(ec->sigusr2_event);
+ sudo_ev_free(ec->sigchld_event);
+ sudo_ev_free(ec->sigwinch_event);
+
+ while ((msg = TAILQ_FIRST(&ec->monitor_messages)) != NULL) {
+ TAILQ_REMOVE(&ec->monitor_messages, msg, entries);
+ free(msg);
+ }
+
+ debug_return;
+}
+
+/*
+ * Execute a command in a pty, potentially with I/O logging, and
+ * wait for it to finish.
+ * This is a little bit tricky due to how POSIX job control works and
+ * we fact that we have two different controlling terminals to deal with.
+ */
+bool
+exec_pty(struct command_details *details, struct command_status *cstat)
+{
+ int io_pipe[3][2] = { { -1, -1 }, { -1, -1 }, { -1, -1 } };
+ bool interpose[3] = { false, false, false };
+ struct exec_closure_pty ec = { 0 };
+ struct plugin_container *plugin;
+ int evloop_retries = -1;
+ sigset_t set, oset;
+ struct sigaction sa;
+ struct stat sb;
+ pid_t ppgrp;
+ int sv[2];
+ debug_decl(exec_pty, SUDO_DEBUG_EXEC);
+
+ /*
+ * Allocate a pty.
+ */
+ if (!pty_setup(details, user_details.tty)) {
+ if (TAILQ_EMPTY(&io_plugins)) {
+ /* Not logging I/O and didn't allocate a pty. */
+ debug_return_bool(false);
+ }
+ }
+
+ /*
+ * We communicate with the monitor over a bi-directional pair of sockets.
+ * Parent sends signal info to monitor and monitor sends back wait status.
+ */
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1 ||
+ fcntl(sv[0], F_SETFD, FD_CLOEXEC) == -1 ||
+ fcntl(sv[1], F_SETFD, FD_CLOEXEC) == -1)
+ sudo_fatal("%s", U_("unable to create sockets"));
+
+ /*
+ * We don't want to receive SIGTTIN/SIGTTOU.
+ * XXX - this affects tcsetattr() and tcsetpgrp() too.
+ */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_IGN;
+ if (sudo_sigaction(SIGTTIN, &sa, NULL) != 0)
+ sudo_warn(U_("unable to set handler for signal %d"), SIGTTIN);
+ if (sudo_sigaction(SIGTTOU, &sa, NULL) != 0)
+ sudo_warn(U_("unable to set handler for signal %d"), SIGTTOU);
+
+ /*
+ * The policy plugin's session init must be run before we fork
+ * or certain pam modules won't be able to track their state.
+ */
+ if (policy_init_session(details) != true)
+ sudo_fatalx("%s", U_("policy plugin failed session initialization"));
+
+ /*
+ * Child will run the command in the pty, parent will pass data
+ * to and from pty.
+ */
+
+ /* So we can block tty-generated signals */
+ sigemptyset(&ttyblock);
+ sigaddset(&ttyblock, SIGINT);
+ sigaddset(&ttyblock, SIGQUIT);
+ sigaddset(&ttyblock, SIGTSTP);
+ sigaddset(&ttyblock, SIGTTIN);
+ sigaddset(&ttyblock, SIGTTOU);
+
+ ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */
+
+ /* Determine whether any of std{in,out,err} should be logged. */
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ if (plugin->u.io->log_stdin)
+ interpose[STDIN_FILENO] = true;
+ if (plugin->u.io->log_stdout)
+ interpose[STDOUT_FILENO] = true;
+ if (plugin->u.io->log_stderr)
+ interpose[STDERR_FILENO] = true;
+ }
+
+ /*
+ * Setup stdin/stdout/stderr for command, to be duped after forking.
+ * In background mode there is no stdin.
+ */
+ if (!ISSET(details->flags, CD_BACKGROUND))
+ io_fds[SFD_STDIN] = io_fds[SFD_FOLLOWER];
+ io_fds[SFD_STDOUT] = io_fds[SFD_FOLLOWER];
+ io_fds[SFD_STDERR] = io_fds[SFD_FOLLOWER];
+
+ if (io_fds[SFD_USERTTY] != -1) {
+ /* Read from /dev/tty, write to pty leader */
+ if (!ISSET(details->flags, CD_BACKGROUND)) {
+ io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_LEADER],
+ log_ttyin, &ec, &iobufs);
+ }
+
+ /* Read from pty leader, write to /dev/tty */
+ io_buf_new(io_fds[SFD_LEADER], io_fds[SFD_USERTTY],
+ log_ttyout, &ec, &iobufs);
+
+ /* Are we the foreground process? */
+ foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
+ }
+
+ /*
+ * If stdin, stdout or stderr is not a tty and logging is enabled,
+ * use a pipe to interpose ourselves instead of using the pty fd.
+ */
+ if (io_fds[SFD_STDIN] == -1 || !isatty(STDIN_FILENO)) {
+ if (!interpose[STDIN_FILENO]) {
+ /* Not logging stdin, do not interpose. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stdin not a tty, not logging");
+ if (fstat(STDIN_FILENO, &sb) == 0 && S_ISFIFO(sb.st_mode))
+ pipeline = true;
+ io_fds[SFD_STDIN] = dup(STDIN_FILENO);
+ if (io_fds[SFD_STDIN] == -1)
+ sudo_fatal("dup");
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stdin not a tty, creating a pipe");
+ pipeline = true;
+ if (pipe2(io_pipe[STDIN_FILENO], O_CLOEXEC) != 0)
+ sudo_fatal("%s", U_("unable to create pipe"));
+ io_buf_new(STDIN_FILENO, io_pipe[STDIN_FILENO][1],
+ log_stdin, &ec, &iobufs);
+ io_fds[SFD_STDIN] = io_pipe[STDIN_FILENO][0];
+ }
+ }
+ if (io_fds[SFD_STDOUT] == -1 || !isatty(STDOUT_FILENO)) {
+ if (!interpose[STDOUT_FILENO]) {
+ /* Not logging stdout, do not interpose. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stdout not a tty, not logging");
+ if (fstat(STDOUT_FILENO, &sb) == 0 && S_ISFIFO(sb.st_mode))
+ pipeline = true;
+ io_fds[SFD_STDOUT] = dup(STDOUT_FILENO);
+ if (io_fds[SFD_STDOUT] == -1)
+ sudo_fatal("dup");
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stdout not a tty, creating a pipe");
+ pipeline = true;
+ if (pipe2(io_pipe[STDOUT_FILENO], O_CLOEXEC) != 0)
+ sudo_fatal("%s", U_("unable to create pipe"));
+ io_buf_new(io_pipe[STDOUT_FILENO][0], STDOUT_FILENO,
+ log_stdout, &ec, &iobufs);
+ io_fds[SFD_STDOUT] = io_pipe[STDOUT_FILENO][1];
+ }
+ }
+ if (io_fds[SFD_STDERR] == -1 || !isatty(STDERR_FILENO)) {
+ if (!interpose[STDERR_FILENO]) {
+ /* Not logging stderr, do not interpose. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stderr not a tty, not logging");
+ if (fstat(STDERR_FILENO, &sb) == 0 && S_ISFIFO(sb.st_mode))
+ pipeline = true;
+ io_fds[SFD_STDERR] = dup(STDERR_FILENO);
+ if (io_fds[SFD_STDERR] == -1)
+ sudo_fatal("dup");
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stderr not a tty, creating a pipe");
+ if (pipe2(io_pipe[STDERR_FILENO], O_CLOEXEC) != 0)
+ sudo_fatal("%s", U_("unable to create pipe"));
+ io_buf_new(io_pipe[STDERR_FILENO][0], STDERR_FILENO,
+ log_stderr, &ec, &iobufs);
+ io_fds[SFD_STDERR] = io_pipe[STDERR_FILENO][1];
+ }
+ }
+
+ if (foreground) {
+ /* Copy terminal attrs from user tty -> pty follower. */
+ if (!sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_FOLLOWER])) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to copy terminal settings to pty", __func__);
+ foreground = false;
+ } else {
+ /* Start in raw mode unless part of a pipeline or backgrounded. */
+ if (!pipeline && !ISSET(details->flags, CD_EXEC_BG)) {
+ if (sudo_term_raw(io_fds[SFD_USERTTY], 0))
+ ttymode = TERM_RAW;
+ }
+ }
+ }
+
+ /*
+ * Block signals until we have our handlers setup in the parent so
+ * we don't miss SIGCHLD if the command exits immediately.
+ */
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oset);
+
+ /* Check for early termination or suspend signals before we fork. */
+ if (sudo_terminated(cstat)) {
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+ debug_return_int(true);
+ }
+
+ ec.monitor_pid = sudo_debug_fork();
+ switch (ec.monitor_pid) {
+ case -1:
+ sudo_fatal("%s", U_("unable to fork"));
+ break;
+ case 0:
+ /* child */
+ close(sv[0]);
+ /* Close the other end of the stdin/stdout/stderr pipes and exec. */
+ if (io_pipe[STDIN_FILENO][1] != -1)
+ close(io_pipe[STDIN_FILENO][1]);
+ if (io_pipe[STDOUT_FILENO][0] != -1)
+ close(io_pipe[STDOUT_FILENO][0]);
+ if (io_pipe[STDERR_FILENO][0] != -1)
+ close(io_pipe[STDERR_FILENO][0]);
+ /*
+ * If stdin/stdout is not a tty, start command in the background
+ * since it might be part of a pipeline that reads from /dev/tty.
+ * In this case, we rely on the command receiving SIGTTOU or SIGTTIN
+ * when it needs access to the controlling tty.
+ */
+ exec_monitor(details, &oset, foreground && !pipeline, sv[1]);
+ cstat->type = CMD_ERRNO;
+ cstat->val = errno;
+ if (send(sv[1], cstat, sizeof(*cstat), 0) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to send status to parent", __func__);
+ }
+ _exit(EXIT_FAILURE);
+ }
+
+ /*
+ * We close the pty follower so only the monitor and command have a
+ * reference to it. This ensures that we can don't block reading
+ * from the leader when the command and monitor have exited.
+ */
+ if (io_fds[SFD_FOLLOWER] != -1) {
+ close(io_fds[SFD_FOLLOWER]);
+ io_fds[SFD_FOLLOWER] = -1;
+ }
+
+ /* Tell the monitor to continue now that the follower is closed. */
+ cstat->type = CMD_SIGNO;
+ cstat->val = 0;
+ while (send(sv[0], cstat, sizeof(*cstat), 0) == -1) {
+ if (errno != EINTR && errno != EAGAIN)
+ sudo_fatal("%s", U_("unable to send message to monitor process"));
+ }
+
+ /* Close the other end of the stdin/stdout/stderr pipes and socketpair. */
+ if (io_pipe[STDIN_FILENO][0] != -1)
+ close(io_pipe[STDIN_FILENO][0]);
+ if (io_pipe[STDOUT_FILENO][1] != -1)
+ close(io_pipe[STDOUT_FILENO][1]);
+ if (io_pipe[STDERR_FILENO][1] != -1)
+ close(io_pipe[STDERR_FILENO][1]);
+ close(sv[1]);
+
+ /* No longer need execfd. */
+ if (details->execfd != -1) {
+ close(details->execfd);
+ details->execfd = -1;
+ }
+
+ /* Set command timeout if specified. */
+ if (ISSET(details->flags, CD_SET_TIMEOUT))
+ alarm(details->timeout);
+
+ /*
+ * Fill in exec closure, allocate event base, signal events and
+ * the backchannel event.
+ */
+ fill_exec_closure_pty(&ec, cstat, details, ppgrp, sv[0]);
+
+ /* Restore signal mask now that signal handlers are setup. */
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+
+ /*
+ * I/O logging must be in the C locale for floating point numbers
+ * to be logged consistently.
+ */
+ setlocale(LC_ALL, "C");
+
+ /*
+ * In the event loop we pass input from user tty to leader
+ * and pass output from leader to stdout and IO plugin.
+ * Try to recover on ENXIO, it means the tty was revoked.
+ */
+ add_io_events(ec.evbase);
+ do {
+ if (sudo_ev_dispatch(ec.evbase) == -1)
+ sudo_warn("%s", U_("error in event loop"));
+ if (sudo_ev_got_break(ec.evbase)) {
+ /* error from callback or monitor died */
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely");
+ /* XXX: no good way to know if we should terminate the command. */
+ if (cstat->val == CMD_INVALID && ec.cmnd_pid != -1) {
+ /* no status message, kill command */
+ terminate_command(ec.cmnd_pid, true);
+ ec.cmnd_pid = -1;
+ /* TODO: need way to pass an error to the sudo front end */
+ cstat->type = CMD_WSTATUS;
+ cstat->val = W_EXITCODE(1, SIGKILL);
+ }
+ } else if (!sudo_ev_got_exit(ec.evbase)) {
+ switch (errno) {
+ case ENXIO:
+ case EIO:
+ case EBADF:
+ /* /dev/tty was revoked, remove tty events and retry (once) */
+ if (evloop_retries == -1 && io_fds[SFD_USERTTY] != -1) {
+ ev_free_by_fd(ec.evbase, io_fds[SFD_USERTTY]);
+ evloop_retries = 1;
+ }
+ break;
+ }
+ }
+ } while (evloop_retries-- > 0);
+
+ /* Flush any remaining output, free I/O bufs and events, do logout. */
+ pty_finish(cstat);
+
+ /* Free things up. */
+ free_exec_closure_pty(&ec);
+
+ debug_return_bool(true);
+}
+
+/*
+ * Schedule I/O events before starting the main event loop or
+ * resuming from suspend.
+ */
+static void
+add_io_events(struct sudo_event_base *evbase)
+{
+ struct io_buffer *iob;
+ debug_decl(add_io_events, SUDO_DEBUG_EXEC);
+
+ /*
+ * Schedule all readers as long as the buffer is not full.
+ * Schedule writers that contain buffered data.
+ * Normally, write buffers are added on demand when data is read.
+ */
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ /* Don't read from /dev/tty if we are not in the foreground. */
+ if (iob->revent != NULL &&
+ (ttymode == TERM_RAW || !USERTTY_EVENT(iob->revent))) {
+ if (iob->len != sizeof(iob->buf)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "added I/O revent %p, fd %d, events %d",
+ iob->revent, iob->revent->fd, iob->revent->events);
+ if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+ }
+ }
+ if (iob->wevent != NULL) {
+ /* Enable writer if buffer is not empty. */
+ if (iob->len > iob->off) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "added I/O wevent %p, fd %d, events %d",
+ iob->wevent, iob->wevent->fd, iob->wevent->events);
+ if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+ }
+ }
+ }
+ debug_return;
+}
+
+/*
+ * Flush any output buffered in iobufs or readable from fds other
+ * than /dev/tty. Removes I/O events from the event base when done.
+ */
+static void
+del_io_events(bool nonblocking)
+{
+ struct io_buffer *iob;
+ struct sudo_event_base *evbase;
+ debug_decl(del_io_events, SUDO_DEBUG_EXEC);
+
+ /* Remove iobufs from existing event base. */
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ if (iob->revent != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "deleted I/O revent %p, fd %d, events %d",
+ iob->revent, iob->revent->fd, iob->revent->events);
+ sudo_ev_del(NULL, iob->revent);
+ }
+ if (iob->wevent != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "deleted I/O wevent %p, fd %d, events %d",
+ iob->wevent, iob->wevent->fd, iob->wevent->events);
+ sudo_ev_del(NULL, iob->wevent);
+ }
+ }
+
+ /* Create temporary event base for flushing. */
+ evbase = sudo_ev_base_alloc();
+ if (evbase == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ /* Avoid reading from /dev/tty, just flush existing data. */
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ /* Don't read from /dev/tty while flushing. */
+ if (iob->revent != NULL && !USERTTY_EVENT(iob->revent)) {
+ if (iob->len != sizeof(iob->buf)) {
+ if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+ }
+ }
+ /* Flush any write buffers with data in them. */
+ if (iob->wevent != NULL) {
+ if (iob->len > iob->off) {
+ if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+ }
+ }
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: flushing remaining I/O buffers (nonblocking)", __func__);
+ (void) sudo_ev_loop(evbase, SUDO_EVLOOP_NONBLOCK);
+
+ /*
+ * If not in non-blocking mode, make sure we flush write buffers.
+ * We don't want to read from the pty or stdin since that might block
+ * and the command is no longer running anyway.
+ */
+ if (!nonblocking) {
+ /* Clear out iobufs from event base. */
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ if (iob->revent != NULL && !USERTTY_EVENT(iob->revent))
+ sudo_ev_del(evbase, iob->revent);
+ if (iob->wevent != NULL)
+ sudo_ev_del(evbase, iob->wevent);
+ }
+
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ /* Flush any write buffers with data in them. */
+ if (iob->wevent != NULL) {
+ if (iob->len > iob->off) {
+ if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
+ sudo_fatal("%s", U_("unable to add event to queue"));
+ }
+ }
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: flushing remaining write buffers (blocking)", __func__);
+ (void) sudo_ev_dispatch(evbase);
+
+ /* We should now have flushed all write buffers. */
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ if (iob->wevent != NULL) {
+ if (iob->len > iob->off) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "unflushed data: wevent %p, fd %d, events %d",
+ iob->wevent, iob->wevent->fd, iob->wevent->events);
+ }
+ }
+ }
+ }
+
+ /* Free temporary event base, removing its events. */
+ sudo_ev_base_free(evbase);
+
+ debug_return;
+}
+
+/*
+ * Check for tty size changes.
+ * Passes the new window size to the I/O plugin and to the monitor.
+ */
+static void
+sync_ttysize(struct exec_closure_pty *ec)
+{
+ struct winsize wsize;
+ debug_decl(sync_ttysize, SUDO_DEBUG_EXEC);
+
+ if (ioctl(io_fds[SFD_USERTTY], TIOCGWINSZ, &wsize) == 0) {
+ if (wsize.ws_row != ec->rows || wsize.ws_col != ec->cols) {
+ const unsigned int wsize_packed = (wsize.ws_row & 0xffff) |
+ ((wsize.ws_col & 0xffff) << 16);
+
+ /* Log window change event. */
+ log_winchange(ec, wsize.ws_row, wsize.ws_col);
+
+ /* Send window change event to monitor process. */
+ send_command_status(ec, CMD_TTYWINCH, wsize_packed);
+
+ /* Update rows/cols. */
+ ec->rows = wsize.ws_row;
+ ec->cols = wsize.ws_col;
+ }
+ }
+
+ debug_return;
+}
+
+/*
+ * Remove and free any events associated with the specified
+ * file descriptor present in the I/O buffers list.
+ */
+static void
+ev_free_by_fd(struct sudo_event_base *evbase, int fd)
+{
+ struct io_buffer *iob;
+ debug_decl(ev_free_by_fd, SUDO_DEBUG_EXEC);
+
+ /* Deschedule any users of the fd and free up the events. */
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ if (iob->revent != NULL) {
+ if (sudo_ev_get_fd(iob->revent) == fd) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: deleting and freeing revent %p with fd %d",
+ __func__, iob->revent, fd);
+ sudo_ev_free(iob->revent);
+ iob->revent = NULL;
+ }
+ }
+ if (iob->wevent != NULL) {
+ if (sudo_ev_get_fd(iob->wevent) == fd) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: deleting and freeing wevent %p with fd %d",
+ __func__, iob->wevent, fd);
+ sudo_ev_free(iob->wevent);
+ iob->wevent = NULL;
+ }
+ }
+ }
+ debug_return;
+}
+
+/*
+ * Only close the fd if it is not /dev/tty or std{in,out,err}.
+ * Return value is the same as close(2).
+ */
+static int
+safe_close(int fd)
+{
+ debug_decl(safe_close, SUDO_DEBUG_EXEC);
+
+ /* Avoid closing /dev/tty or std{in,out,err}. */
+ if (fd < 3 || fd == io_fds[SFD_USERTTY]) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: not closing fd %d (%s)", __func__, fd, _PATH_TTY);
+ errno = EINVAL;
+ debug_return_int(-1);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: closing fd %d", __func__, fd);
+ debug_return_int(close(fd));
+}
diff --git a/src/get_pty.c b/src/get_pty.c
new file mode 100644
index 0000000..e50a9a6
--- /dev/null
+++ b/src/get_pty.c
@@ -0,0 +1,192 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2012, 2014-2016
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#ifdef HAVE_SYS_STROPTS_H
+#include <sys/stropts.h>
+#endif /* HAVE_SYS_STROPTS_H */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+
+#if defined(HAVE_OPENPTY)
+# if defined(HAVE_LIBUTIL_H)
+# include <libutil.h> /* *BSD */
+# elif defined(HAVE_UTIL_H)
+# include <util.h> /* macOS */
+# elif defined(HAVE_PTY_H)
+# include <pty.h> /* Linux */
+# else
+# include <termios.h> /* Solaris */
+# endif
+#endif
+
+#include "sudo.h"
+
+#if defined(HAVE_OPENPTY)
+bool
+get_pty(int *leader, int *follower, char *name, size_t namesz, uid_t ttyuid)
+{
+ struct group *gr;
+ gid_t ttygid = -1;
+ bool ret = false;
+ debug_decl(get_pty, SUDO_DEBUG_PTY);
+
+ if ((gr = getgrnam("tty")) != NULL)
+ ttygid = gr->gr_gid;
+
+ if (openpty(leader, follower, name, NULL, NULL) == 0) {
+ if (chown(name, ttyuid, ttygid) == 0)
+ ret = true;
+ }
+
+ debug_return_bool(ret);
+}
+
+#elif defined(HAVE__GETPTY)
+bool
+get_pty(int *leader, int *follower, char *name, size_t namesz, uid_t ttyuid)
+{
+ char *line;
+ bool ret = false;
+ debug_decl(get_pty, SUDO_DEBUG_PTY);
+
+ /* IRIX-style dynamic ptys (may fork) */
+ line = _getpty(leader, O_RDWR, S_IRUSR|S_IWUSR|S_IWGRP, 0);
+ if (line != NULL) {
+ *follower = open(line, O_RDWR|O_NOCTTY, 0);
+ if (*follower != -1) {
+ (void) chown(line, ttyuid, -1);
+ strlcpy(name, line, namesz);
+ ret = true;
+ } else {
+ close(*leader);
+ *leader = -1;
+ }
+ }
+ debug_return_bool(ret);
+}
+#elif defined(HAVE_GRANTPT)
+# ifndef HAVE_POSIX_OPENPT
+static int
+posix_openpt(int oflag)
+{
+ int fd;
+
+# ifdef _AIX
+ fd = open(_PATH_DEV "ptc", oflag);
+# else
+ fd = open(_PATH_DEV "ptmx", oflag);
+# endif
+ return fd;
+}
+# endif /* HAVE_POSIX_OPENPT */
+
+bool
+get_pty(int *leader, int *follower, char *name, size_t namesz, uid_t ttyuid)
+{
+ char *line;
+ bool ret = false;
+ debug_decl(get_pty, SUDO_DEBUG_PTY);
+
+ *leader = posix_openpt(O_RDWR|O_NOCTTY);
+ if (*leader != -1) {
+ (void) grantpt(*leader); /* may fork */
+ if (unlockpt(*leader) != 0) {
+ close(*leader);
+ goto done;
+ }
+ line = ptsname(*leader);
+ if (line == NULL) {
+ close(*leader);
+ goto done;
+ }
+ *follower = open(line, O_RDWR|O_NOCTTY, 0);
+ if (*follower == -1) {
+ close(*leader);
+ goto done;
+ }
+# if defined(I_PUSH) && !defined(_AIX)
+ ioctl(*follower, I_PUSH, "ptem"); /* pseudo tty emulation module */
+ ioctl(*follower, I_PUSH, "ldterm"); /* line discipline module */
+# endif
+ (void) chown(line, ttyuid, -1);
+ strlcpy(name, line, namesz);
+ ret = true;
+ }
+done:
+ debug_return_bool(ret);
+}
+
+#else /* Old-style BSD ptys */
+
+static char line[] = _PATH_DEV "ptyXX";
+
+bool
+get_pty(int *leader, int *follower, char *name, size_t namesz, uid_t ttyuid)
+{
+ char *bank, *cp;
+ struct group *gr;
+ gid_t ttygid = -1;
+ bool ret = false;
+ debug_decl(get_pty, SUDO_DEBUG_PTY);
+
+ if ((gr = getgrnam("tty")) != NULL)
+ ttygid = gr->gr_gid;
+
+ for (bank = "pqrs"; *bank != '\0'; bank++) {
+ line[sizeof(_PATH_DEV "ptyX") - 2] = *bank;
+ for (cp = "0123456789abcdef"; *cp != '\0'; cp++) {
+ line[sizeof(_PATH_DEV "ptyXX") - 2] = *cp;
+ *leader = open(line, O_RDWR|O_NOCTTY, 0);
+ if (*leader == -1) {
+ if (errno == ENOENT)
+ goto done; /* out of ptys */
+ continue; /* already in use */
+ }
+ line[sizeof(_PATH_DEV "p") - 2] = 't';
+ (void) chown(line, ttyuid, ttygid);
+ (void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
+# ifdef HAVE_REVOKE
+ (void) revoke(line);
+# endif
+ *follower = open(line, O_RDWR|O_NOCTTY, 0);
+ if (*follower != -1) {
+ strlcpy(name, line, namesz);
+ ret = true; /* success */
+ goto done;
+ }
+ (void) close(*leader);
+ }
+ }
+done:
+ debug_return_bool(ret);
+}
+#endif /* HAVE_OPENPTY */
diff --git a/src/hooks.c b/src/hooks.c
new file mode 100644
index 0000000..70ff02e
--- /dev/null
+++ b/src/hooks.c
@@ -0,0 +1,245 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2012-2016 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "sudo.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+/* Singly linked hook list. */
+struct sudo_hook_entry {
+ SLIST_ENTRY(sudo_hook_entry) entries;
+ union {
+ sudo_hook_fn_t generic_fn;
+ sudo_hook_fn_setenv_t setenv_fn;
+ sudo_hook_fn_unsetenv_t unsetenv_fn;
+ sudo_hook_fn_getenv_t getenv_fn;
+ sudo_hook_fn_putenv_t putenv_fn;
+ } u;
+ void *closure;
+};
+SLIST_HEAD(sudo_hook_list, sudo_hook_entry);
+
+/* Each hook type gets own hook list. */
+static struct sudo_hook_list sudo_hook_setenv_list =
+ SLIST_HEAD_INITIALIZER(sudo_hook_setenv_list);
+static struct sudo_hook_list sudo_hook_unsetenv_list =
+ SLIST_HEAD_INITIALIZER(sudo_hook_unsetenv_list);
+static struct sudo_hook_list sudo_hook_getenv_list =
+ SLIST_HEAD_INITIALIZER(sudo_hook_getenv_list);
+static struct sudo_hook_list sudo_hook_putenv_list =
+ SLIST_HEAD_INITIALIZER(sudo_hook_putenv_list);
+
+/* NOTE: must not anything that might call setenv() */
+int
+process_hooks_setenv(const char *name, const char *value, int overwrite)
+{
+ struct sudo_hook_entry *hook;
+ int rc = SUDO_HOOK_RET_NEXT;
+
+ /* First process the hooks. */
+ SLIST_FOREACH(hook, &sudo_hook_setenv_list, entries) {
+ rc = hook->u.setenv_fn(name, value, overwrite, hook->closure);
+ if (rc == SUDO_HOOK_RET_STOP || rc == SUDO_HOOK_RET_ERROR)
+ break;
+ }
+ return rc;
+}
+
+/* NOTE: must not anything that might call putenv() */
+int
+process_hooks_putenv(char *string)
+{
+ struct sudo_hook_entry *hook;
+ int rc = SUDO_HOOK_RET_NEXT;
+
+ /* First process the hooks. */
+ SLIST_FOREACH(hook, &sudo_hook_putenv_list, entries) {
+ rc = hook->u.putenv_fn(string, hook->closure);
+ if (rc == SUDO_HOOK_RET_STOP || rc == SUDO_HOOK_RET_ERROR)
+ break;
+ }
+ return rc;
+}
+
+/* NOTE: must not anything that might call getenv() */
+int
+process_hooks_getenv(const char *name, char **value)
+{
+ struct sudo_hook_entry *hook;
+ char *val = NULL;
+ int rc = SUDO_HOOK_RET_NEXT;
+
+ /* First process the hooks. */
+ SLIST_FOREACH(hook, &sudo_hook_getenv_list, entries) {
+ rc = hook->u.getenv_fn(name, &val, hook->closure);
+ if (rc == SUDO_HOOK_RET_STOP || rc == SUDO_HOOK_RET_ERROR)
+ break;
+ }
+ if (val != NULL)
+ *value = val;
+ return rc;
+}
+
+/* NOTE: must not anything that might call unsetenv() */
+int
+process_hooks_unsetenv(const char *name)
+{
+ struct sudo_hook_entry *hook;
+ int rc = SUDO_HOOK_RET_NEXT;
+
+ /* First process the hooks. */
+ SLIST_FOREACH(hook, &sudo_hook_unsetenv_list, entries) {
+ rc = hook->u.unsetenv_fn(name, hook->closure);
+ if (rc == SUDO_HOOK_RET_STOP || rc == SUDO_HOOK_RET_ERROR)
+ break;
+ }
+ return rc;
+}
+
+/* Hook registration internals. */
+static int
+register_hook_internal(struct sudo_hook_list *head,
+ int (*hook_fn)(), void *closure)
+{
+ struct sudo_hook_entry *hook;
+ debug_decl(register_hook_internal, SUDO_DEBUG_HOOKS);
+
+ if ((hook = calloc(1, sizeof(*hook))) == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to allocate memory");
+ debug_return_int(-1);
+ }
+ hook->u.generic_fn = hook_fn;
+ hook->closure = closure;
+ SLIST_INSERT_HEAD(head, hook, entries);
+
+ debug_return_int(0);
+}
+
+/* Register the specified hook. */
+int
+register_hook(struct sudo_hook *hook)
+{
+ int ret;
+ debug_decl(register_hook, SUDO_DEBUG_HOOKS);
+
+ if (SUDO_API_VERSION_GET_MAJOR(hook->hook_version) != SUDO_HOOK_VERSION_MAJOR) {
+ /* Major versions must match. */
+ errno = EINVAL;
+ ret = -1;
+ } else {
+ switch (hook->hook_type) {
+ case SUDO_HOOK_GETENV:
+ ret = register_hook_internal(&sudo_hook_getenv_list,
+ hook->hook_fn, hook->closure);
+ break;
+ case SUDO_HOOK_PUTENV:
+ ret = register_hook_internal(&sudo_hook_putenv_list,
+ hook->hook_fn, hook->closure);
+ break;
+ case SUDO_HOOK_SETENV:
+ ret = register_hook_internal(&sudo_hook_setenv_list,
+ hook->hook_fn, hook->closure);
+ break;
+ case SUDO_HOOK_UNSETENV:
+ ret = register_hook_internal(&sudo_hook_unsetenv_list,
+ hook->hook_fn, hook->closure);
+ break;
+ default:
+ /* XXX - use define for unknown value */
+ errno = ENOTSUP;
+ ret = 1;
+ break;
+ }
+ }
+
+ debug_return_int(ret);
+}
+
+/* Hook deregistration internals. */
+static void
+deregister_hook_internal(struct sudo_hook_list *head,
+ int (*hook_fn)(), void *closure)
+{
+ struct sudo_hook_entry *hook, *prev = NULL;
+ debug_decl(deregister_hook_internal, SUDO_DEBUG_HOOKS);
+
+ SLIST_FOREACH(hook, head, entries) {
+ if (hook->u.generic_fn == hook_fn && hook->closure == closure) {
+ /* Remove from list and free. */
+ if (prev == NULL)
+ SLIST_REMOVE_HEAD(head, entries);
+ else
+ SLIST_REMOVE_AFTER(prev, entries);
+ free(hook);
+ break;
+ }
+ prev = hook;
+ }
+
+ debug_return;
+}
+
+/* Deregister the specified hook. */
+int
+deregister_hook(struct sudo_hook *hook)
+{
+ int ret = 0;
+ debug_decl(deregister_hook, SUDO_DEBUG_HOOKS);
+
+ if (SUDO_API_VERSION_GET_MAJOR(hook->hook_version) != SUDO_HOOK_VERSION_MAJOR) {
+ /* Major versions must match. */
+ ret = -1;
+ } else {
+ switch (hook->hook_type) {
+ case SUDO_HOOK_GETENV:
+ deregister_hook_internal(&sudo_hook_getenv_list, hook->hook_fn,
+ hook->closure);
+ break;
+ case SUDO_HOOK_PUTENV:
+ deregister_hook_internal(&sudo_hook_putenv_list, hook->hook_fn,
+ hook->closure);
+ break;
+ case SUDO_HOOK_SETENV:
+ deregister_hook_internal(&sudo_hook_setenv_list, hook->hook_fn,
+ hook->closure);
+ break;
+ case SUDO_HOOK_UNSETENV:
+ deregister_hook_internal(&sudo_hook_unsetenv_list, hook->hook_fn,
+ hook->closure);
+ break;
+ default:
+ /* XXX - use define for unknown value */
+ ret = 1;
+ break;
+ }
+ }
+
+ debug_return_int(ret);
+}
diff --git a/src/limits.c b/src/limits.c
new file mode 100644
index 0000000..9ce475a
--- /dev/null
+++ b/src/limits.c
@@ -0,0 +1,385 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1999-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/resource.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef __linux__
+# include <sys/prctl.h>
+#endif
+#include <errno.h>
+#include <limits.h>
+
+#include "sudo.h"
+
+#if defined(OPEN_MAX) && OPEN_MAX > 256
+# define SUDO_OPEN_MAX OPEN_MAX
+#else
+# define SUDO_OPEN_MAX 256
+#endif
+
+#ifdef __LP64__
+# define SUDO_STACK_MIN (4 * 1024 * 1024)
+#else
+# define SUDO_STACK_MIN (2 * 1024 * 1024)
+#endif
+
+#ifdef HAVE_SETRLIMIT64
+# define getrlimit(a, b) getrlimit64((a), (b))
+# define setrlimit(a, b) setrlimit64((a), (b))
+# define rlimit rlimit64
+# define rlim_t rlim64_t
+# undef RLIM_INFINITY
+# define RLIM_INFINITY RLIM64_INFINITY
+#endif /* HAVE_SETRLIMIT64 */
+
+/* Older BSD systems have RLIMIT_VMEM, not RLIMIT_AS. */
+#if !defined(RLIMIT_AS) && defined(RLIMIT_VMEM)
+# define RLIMIT_AS RLIMIT_VMEM
+#endif
+
+/*
+ * macOS doesn't allow nofile soft limit to be infinite or
+ * the stack hard limit to be infinite.
+ * Linux containers have a problem with an infinite stack soft limit.
+ */
+static struct rlimit nofile_fallback = { SUDO_OPEN_MAX, RLIM_INFINITY };
+static struct rlimit stack_fallback = { SUDO_STACK_MIN, 65532 * 1024 };
+
+static struct saved_limit {
+ const char *name; /* rlimit_foo in lower case */
+ int resource; /* RLIMIT_FOO definition */
+ bool override; /* override limit while sudo executes? */
+ bool saved; /* true if we were able to get the value */
+ struct rlimit *fallback; /* fallback if we fail to set to newlimit */
+ struct rlimit newlimit; /* new limit to use if override is true */
+ struct rlimit oldlimit; /* original limit, valid if saved is true */
+} saved_limits[] = {
+#ifdef RLIMIT_AS
+ { "rlimit_as", RLIMIT_AS, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } },
+#endif
+ { "rlimit_core", RLIMIT_CORE, false },
+ { "rlimit_cpu", RLIMIT_CPU, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } },
+ { "rlimit_data", RLIMIT_DATA, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } },
+ { "rlimit_fsize", RLIMIT_FSIZE, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } },
+#ifdef RLIMIT_LOCKS
+ { "rlimit_locks", RLIMIT_LOCKS, false },
+#endif
+#ifdef RLIMIT_MEMLOCK
+ { "rlimit_memlock", RLIMIT_MEMLOCK, false },
+#endif
+ { "rlimit_nofile", RLIMIT_NOFILE, true, false, &nofile_fallback, { RLIM_INFINITY, RLIM_INFINITY } },
+#ifdef RLIMIT_NPROC
+ { "rlimit_nproc", RLIMIT_NPROC, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } },
+#endif
+#ifdef RLIMIT_RSS
+ { "rlimit_rss", RLIMIT_RSS, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } },
+#endif
+ { "rlimit_stack", RLIMIT_STACK, true, false, &stack_fallback, { SUDO_STACK_MIN, RLIM_INFINITY } }
+};
+
+static struct rlimit corelimit;
+static bool coredump_disabled;
+#ifdef __linux__
+static struct rlimit nproclimit;
+static int dumpflag;
+#endif
+
+/*
+ * Disable core dumps to avoid dropping a core with user password in it.
+ * Not all operating systems disable core dumps for setuid processes.
+ */
+void
+disable_coredump(void)
+{
+ struct rlimit rl = { 0, 0 };
+ debug_decl(disable_coredump, SUDO_DEBUG_UTIL);
+
+ if (getrlimit(RLIMIT_CORE, &corelimit) == -1)
+ sudo_warn("getrlimit(RLIMIT_CORE)");
+ sudo_debug_printf(SUDO_DEBUG_INFO, "RLIMIT_CORE [%lld, %lld] -> [0, 0]",
+ (long long)corelimit.rlim_cur, (long long)corelimit.rlim_max);
+ if (setrlimit(RLIMIT_CORE, &rl) == -1)
+ sudo_warn("setrlimit(RLIMIT_CORE)");
+#ifdef __linux__
+ /* On Linux, also set PR_SET_DUMPABLE to zero (reset by execve). */
+ if ((dumpflag = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)");
+ dumpflag = 0;
+ }
+ if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "prctl(PR_SET_DUMPABLE, %d, 0, 0, 0)", dumpflag);
+ }
+#endif /* __linux__ */
+ coredump_disabled = true;
+
+ debug_return;
+}
+
+/*
+ * Restore core resource limit before executing the command.
+ */
+static void
+restore_coredump(void)
+{
+ debug_decl(restore_coredump, SUDO_DEBUG_UTIL);
+
+ if (coredump_disabled) {
+ /*
+ * Linux containers don't allow RLIMIT_CORE to be set back to
+ * RLIM_INFINITY if we set the limit to zero, even for root.
+ */
+ if (setrlimit(RLIMIT_CORE, &corelimit) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "setrlimit(RLIMIT_CORE, [%lld, %lld])",
+ (long long)corelimit.rlim_cur, (long long)corelimit.rlim_max);
+ }
+#ifdef __linux__
+ if (prctl(PR_SET_DUMPABLE, dumpflag, 0, 0, 0) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "prctl(PR_SET_DUMPABLE, %d, 0, 0, 0)", dumpflag);
+ }
+#endif /* __linux__ */
+ }
+ debug_return;
+}
+
+/*
+ * Unlimit the number of processes since Linux's setuid() will
+ * apply resource limits when changing uid and return EAGAIN if
+ * nproc would be exceeded by the uid switch.
+ *
+ * This function is called *after* session setup and before the
+ * final setuid() call.
+ */
+void
+unlimit_nproc(void)
+{
+#ifdef __linux__
+ struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY };
+ debug_decl(unlimit_nproc, SUDO_DEBUG_UTIL);
+
+ if (getrlimit(RLIMIT_NPROC, &nproclimit) != 0)
+ sudo_warn("getrlimit(RLIMIT_NPROC)");
+ sudo_debug_printf(SUDO_DEBUG_INFO, "RLIMIT_NPROC [%lld, %lld] -> [inf, inf]",
+ (long long)nproclimit.rlim_cur, (long long)nproclimit.rlim_max);
+ if (setrlimit(RLIMIT_NPROC, &rl) == -1) {
+ rl.rlim_cur = rl.rlim_max = nproclimit.rlim_max;
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "RLIMIT_NPROC [%lld, %lld] -> [%lld, %lld]",
+ (long long)nproclimit.rlim_cur, (long long)nproclimit.rlim_max,
+ (long long)rl.rlim_cur, (long long)rl.rlim_max);
+ if (setrlimit(RLIMIT_NPROC, &rl) != 0)
+ sudo_warn("setrlimit(RLIMIT_NPROC)");
+ }
+ debug_return;
+#endif /* __linux__ */
+}
+
+/*
+ * Restore saved value of RLIMIT_NPROC before execve().
+ */
+void
+restore_nproc(void)
+{
+#ifdef __linux__
+ debug_decl(restore_nproc, SUDO_DEBUG_UTIL);
+
+ if (setrlimit(RLIMIT_NPROC, &nproclimit) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "setrlimit(RLIMIT_NPROC, [%lld, %lld])",
+ (long long)nproclimit.rlim_cur, (long long)nproclimit.rlim_max);
+ }
+
+ debug_return;
+#endif /* __linux__ */
+}
+
+/*
+ * Unlimit resource limits so sudo is not limited by, e.g.
+ * stack, data or file table sizes.
+ */
+void
+unlimit_sudo(void)
+{
+ unsigned int idx;
+ int rc;
+ debug_decl(unlimit_sudo, SUDO_DEBUG_UTIL);
+
+ /* Set resource limits to unlimited and stash the old values. */
+ for (idx = 0; idx < nitems(saved_limits); idx++) {
+ struct saved_limit *lim = &saved_limits[idx];
+ if (getrlimit(lim->resource, &lim->oldlimit) == -1)
+ continue;
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "getrlimit(lim->name) -> [%lld, %lld]",
+ (long long)lim->oldlimit.rlim_cur,
+ (long long)lim->oldlimit.rlim_max);
+
+ lim->saved = true;
+ if (!lim->override)
+ continue;
+
+ if (lim->newlimit.rlim_cur != RLIM_INFINITY) {
+ /* Don't reduce the soft resource limit. */
+ if (lim->oldlimit.rlim_cur == RLIM_INFINITY ||
+ lim->oldlimit.rlim_cur > lim->newlimit.rlim_cur)
+ lim->newlimit.rlim_cur = lim->oldlimit.rlim_cur;
+ }
+ if (lim->newlimit.rlim_max != RLIM_INFINITY) {
+ /* Don't reduce the hard resource limit. */
+ if (lim->oldlimit.rlim_max == RLIM_INFINITY ||
+ lim->oldlimit.rlim_max > lim->newlimit.rlim_max)
+ lim->newlimit.rlim_max = lim->oldlimit.rlim_max;
+ }
+ if ((rc = setrlimit(lim->resource, &lim->newlimit)) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "setrlimit(%s, [%lld, %lld])", lim->name,
+ (long long)lim->newlimit.rlim_cur,
+ (long long)lim->newlimit.rlim_max);
+ if (lim->fallback != NULL) {
+ if ((rc = setrlimit(lim->resource, lim->fallback)) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "setrlimit(%s, [%lld, %lld])", lim->name,
+ (long long)lim->fallback->rlim_cur,
+ (long long)lim->fallback->rlim_max);
+ }
+ }
+ if (rc == -1) {
+ /* Try setting new rlim_cur to old rlim_max. */
+ lim->newlimit.rlim_cur = lim->oldlimit.rlim_max;
+ lim->newlimit.rlim_max = lim->oldlimit.rlim_max;
+ if ((rc = setrlimit(lim->resource, &lim->newlimit)) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "setrlimit(%s, [%lld, %lld])", lim->name,
+ (long long)lim->newlimit.rlim_cur,
+ (long long)lim->newlimit.rlim_max);
+ }
+ }
+ if (rc == -1)
+ sudo_warn("setrlimit(%s)", lim->name);
+ }
+ }
+
+ debug_return;
+}
+
+/*
+ * Restore resource limits modified by unlimit_sudo() and disable_coredump().
+ */
+void
+restore_limits(void)
+{
+ unsigned int idx;
+ debug_decl(restore_limits, SUDO_DEBUG_UTIL);
+
+ /* Restore resource limits to saved values. */
+ for (idx = 0; idx < nitems(saved_limits); idx++) {
+ struct saved_limit *lim = &saved_limits[idx];
+ if (lim->override && lim->saved) {
+ struct rlimit rl = lim->oldlimit;
+ int i, rc;
+
+ for (i = 0; i < 10; i++) {
+ rc = setrlimit(lim->resource, &rl);
+ if (rc != -1 || errno != EINVAL)
+ break;
+
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "setrlimit(%s, [%lld, %lld])", lim->name,
+ (long long)rl.rlim_cur, (long long)rl.rlim_max);
+
+ /*
+ * Soft limit could be lower than current resource usage.
+ * This can be an issue on NetBSD with RLIMIT_STACK and ASLR.
+ */
+ if (rl.rlim_cur > LLONG_MAX / 2)
+ break;
+ rl.rlim_cur *= 2;
+ if (lim->newlimit.rlim_cur != RLIM_INFINITY &&
+ rl.rlim_cur > lim->newlimit.rlim_cur) {
+ rl.rlim_cur = lim->newlimit.rlim_cur;
+ }
+ if (rl.rlim_max != RLIM_INFINITY &&
+ rl.rlim_cur > rl.rlim_max) {
+ rl.rlim_max = rl.rlim_cur;
+ }
+ rc = setrlimit(lim->resource, &rl);
+ if (rc != -1 || errno != EINVAL)
+ break;
+ }
+ if (rc == -1)
+ sudo_warn("setrlimit(%s)", lim->name);
+ }
+ }
+ restore_coredump();
+
+ debug_return;
+}
+
+int
+serialize_limits(char **info, size_t info_max)
+{
+ char *str;
+ unsigned int idx, nstored = 0;
+ debug_decl(serialize_limits, SUDO_DEBUG_UTIL);
+
+ for (idx = 0; idx < nitems(saved_limits); idx++) {
+ const struct saved_limit *lim = &saved_limits[idx];
+ const struct rlimit *rl = &lim->oldlimit;
+ char curlim[(((sizeof(long long) * 8) + 2) / 3) + 2];
+ char maxlim[(((sizeof(long long) * 8) + 2) / 3) + 2];
+
+ if (!lim->saved)
+ continue;
+
+ if (nstored == info_max)
+ goto oom;
+
+ if (rl->rlim_cur == RLIM_INFINITY) {
+ strlcpy(curlim, "infinity", sizeof(curlim));
+ } else {
+ snprintf(curlim, sizeof(curlim), "%llu",
+ (unsigned long long)rl->rlim_cur);
+ }
+ if (rl->rlim_max == RLIM_INFINITY) {
+ strlcpy(maxlim, "infinity", sizeof(maxlim));
+ } else {
+ snprintf(maxlim, sizeof(maxlim), "%llu",
+ (unsigned long long)rl->rlim_max);
+ }
+ if (asprintf(&str, "%s=%s,%s", lim->name, curlim, maxlim) == -1)
+ goto oom;
+ info[nstored++] = str;
+ }
+ debug_return_int(nstored);
+oom:
+ while (nstored--)
+ free(info[nstored]);
+ debug_return_int(-1);
+}
diff --git a/src/load_plugins.c b/src/load_plugins.c
new file mode 100644
index 0000000..3c2973e
--- /dev/null
+++ b/src/load_plugins.c
@@ -0,0 +1,549 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "sudo.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+#include "sudo_dso.h"
+
+/* We always use the same name for the sudoers plugin, regardless of the OS */
+#define SUDOERS_PLUGIN "sudoers.so"
+
+#ifdef ENABLE_SUDO_PLUGIN_API
+static int
+sudo_stat_plugin(struct plugin_info *info, char *fullpath,
+ size_t pathsize, struct stat *sb)
+{
+ int status = -1;
+ debug_decl(sudo_stat_plugin, SUDO_DEBUG_PLUGIN);
+
+ if (info->path[0] == '/') {
+ if (strlcpy(fullpath, info->path, pathsize) >= pathsize) {
+ sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""),
+ _PATH_SUDO_CONF, info->lineno, info->symbol_name);
+ sudo_warnx(U_("%s: %s"), info->path, strerror(ENAMETOOLONG));
+ goto done;
+ }
+ status = stat(fullpath, sb);
+ } else {
+ int len;
+
+#ifdef STATIC_SUDOERS_PLUGIN
+ /* Check static symbols. */
+ if (strcmp(info->path, SUDOERS_PLUGIN) == 0) {
+ if (strlcpy(fullpath, info->path, pathsize) >= pathsize) {
+ sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""),
+ _PATH_SUDO_CONF, info->lineno, info->symbol_name);
+ sudo_warnx(U_("%s: %s"), info->path, strerror(ENAMETOOLONG));
+ goto done;
+ }
+ /* Plugin is static, fake up struct stat. */
+ memset(sb, 0, sizeof(*sb));
+ sb->st_uid = ROOT_UID;
+ sb->st_mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
+ status = 0;
+ goto done;
+ }
+#endif /* STATIC_SUDOERS_PLUGIN */
+
+ if (sudo_conf_plugin_dir_path() == NULL) {
+ errno = ENOENT;
+ goto done;
+ }
+
+ len = snprintf(fullpath, pathsize, "%s%s", sudo_conf_plugin_dir_path(),
+ info->path);
+ if (len < 0 || (size_t)len >= pathsize) {
+ sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""),
+ _PATH_SUDO_CONF, info->lineno, info->symbol_name);
+ sudo_warnx(U_("%s%s: %s"), sudo_conf_plugin_dir_path(), info->path,
+ strerror(ENAMETOOLONG));
+ goto done;
+ }
+ /* Try parent dir for compatibility with old plugindir default. */
+ if ((status = stat(fullpath, sb)) != 0) {
+ char *cp = strrchr(fullpath, '/');
+ if (cp > fullpath + 4 && cp[-5] == '/' && cp[-4] == 's' &&
+ cp[-3] == 'u' && cp[-2] == 'd' && cp[-1] == 'o') {
+ int serrno = errno;
+ strlcpy(cp - 4, info->path, pathsize - (cp - 4 - fullpath));
+ if ((status = stat(fullpath, sb)) != 0)
+ errno = serrno;
+ }
+ }
+ }
+done:
+ debug_return_int(status);
+}
+
+static bool
+sudo_check_plugin(struct plugin_info *info, char *fullpath, size_t pathsize)
+{
+ struct stat sb;
+ bool ret = false;
+ debug_decl(sudo_check_plugin, SUDO_DEBUG_PLUGIN);
+
+ if (sudo_stat_plugin(info, fullpath, pathsize, &sb) != 0) {
+ sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""),
+ _PATH_SUDO_CONF, info->lineno, info->symbol_name);
+ if (info->path[0] == '/') {
+ sudo_warn("%s", info->path);
+ } else {
+ sudo_warn("%s%s",
+ sudo_conf_plugin_dir_path() ? sudo_conf_plugin_dir_path() : "",
+ info->path);
+ }
+ goto done;
+ }
+
+ if (!sudo_conf_developer_mode()) {
+ if (sb.st_uid != ROOT_UID) {
+ sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""),
+ _PATH_SUDO_CONF, info->lineno, info->symbol_name);
+ sudo_warnx(U_("%s must be owned by uid %d"), fullpath, ROOT_UID);
+ goto done;
+ }
+ if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0) {
+ sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""),
+ _PATH_SUDO_CONF, info->lineno, info->symbol_name);
+ sudo_warnx(U_("%s must be only be writable by owner"), fullpath);
+ goto done;
+ }
+ }
+ ret = true;
+
+done:
+ debug_return_bool(ret);
+}
+#else
+static bool
+sudo_check_plugin(struct plugin_info *info, char *fullpath, size_t pathsize)
+{
+ debug_decl(sudo_check_plugin, SUDO_DEBUG_PLUGIN);
+ (void)strlcpy(fullpath, info->path, pathsize);
+ debug_return_bool(true);
+}
+#endif /* ENABLE_SUDO_PLUGIN_API */
+
+static bool
+fill_container(struct plugin_container *container, void *handle,
+ const char *path, struct generic_plugin *plugin, struct plugin_info *info)
+{
+ debug_decl(fill_container, SUDO_DEBUG_PLUGIN);
+
+ if ((container->path = strdup(path)) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_bool(false);
+ }
+ container->handle = handle;
+ container->name = info->symbol_name;
+ container->options = info->options;
+ container->debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER;
+ container->u.generic = plugin;
+ container->debug_files = sudo_conf_debug_files(path);
+
+ /* Zero out info strings that the container now owns. */
+ info->symbol_name = NULL;
+ info->options = NULL;
+
+ debug_return_bool(true);
+}
+
+static struct plugin_container *
+new_container(void *handle, const char *path, struct generic_plugin *plugin,
+ struct plugin_info *info)
+{
+ struct plugin_container *container = NULL;
+ debug_decl(new_container, SUDO_DEBUG_PLUGIN);
+
+ if ((container = calloc(1, sizeof(*container))) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto bad;
+ }
+ if (!fill_container(container, handle, path, plugin, info))
+ goto bad;
+
+ debug_return_ptr(container);
+bad:
+ free(container);
+ debug_return_ptr(NULL);
+}
+
+static bool
+plugin_exists(struct plugin_container_list *plugins, const char *symbol_name)
+{
+ struct plugin_container *container;
+ debug_decl(find_plugin, SUDO_DEBUG_PLUGIN);
+
+ TAILQ_FOREACH(container, plugins, entries) {
+ if (strcmp(container->name, symbol_name) == 0)
+ debug_return_bool(true);
+ }
+ debug_return_bool(false);
+}
+
+typedef struct generic_plugin * (plugin_clone_func)(void);
+
+struct generic_plugin *
+sudo_plugin_try_to_clone(void *so_handle, const char *symbol_name)
+{
+ debug_decl(sudo_plugin_clone, SUDO_DEBUG_PLUGIN);
+ struct generic_plugin * plugin = NULL;
+ char *clone_func_name = NULL;
+
+ if (asprintf(&clone_func_name, "%s_clone", symbol_name) < 0) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto cleanup;
+ }
+
+ plugin_clone_func *clone_func = (plugin_clone_func *)sudo_dso_findsym(so_handle, clone_func_name);
+ if (clone_func) {
+ plugin = (*clone_func)();
+ }
+
+cleanup:
+ free(clone_func_name);
+ debug_return_ptr(plugin);
+}
+
+static bool
+sudo_insert_plugin(struct plugin_container_list *plugin_list, void *handle,
+ const char *path, struct generic_plugin *plugin, struct plugin_info *info)
+{
+ struct plugin_container *container;
+ debug_decl(sudo_insert_plugin, SUDO_DEBUG_PLUGIN);
+
+ if (plugin_exists(plugin_list, info->symbol_name)) {
+ plugin = sudo_plugin_try_to_clone(handle, info->symbol_name);
+ if (plugin == NULL) {
+ sudo_warnx(U_("ignoring duplicate plugin \"%s\" in %s, line %d"),
+ info->symbol_name, _PATH_SUDO_CONF, info->lineno);
+ sudo_dso_unload(handle);
+ goto done;
+ }
+ }
+
+ if ((container = new_container(handle, path, plugin, info)) == NULL)
+ debug_return_bool(false);
+ TAILQ_INSERT_TAIL(plugin_list, container, entries);
+
+done:
+ debug_return_bool(true);
+}
+
+/*
+ * Load the plugin specified by "info".
+ */
+static bool
+sudo_load_plugin(struct plugin_info *info, bool quiet)
+{
+ struct generic_plugin *plugin;
+ char path[PATH_MAX];
+ void *handle = NULL;
+ bool ret = false;
+ debug_decl(sudo_load_plugin, SUDO_DEBUG_PLUGIN);
+
+ /* Check plugin owner/mode and fill in path */
+ if (!sudo_check_plugin(info, path, sizeof(path)))
+ goto done;
+
+ /* Open plugin and map in symbol */
+ handle = sudo_dso_load(path, SUDO_DSO_LAZY|SUDO_DSO_GLOBAL);
+ if (!handle) {
+ if (!quiet) {
+ const char *errstr = sudo_dso_strerror();
+ sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""),
+ _PATH_SUDO_CONF, info->lineno, info->symbol_name);
+ sudo_warnx(U_("unable to load %s: %s"), path,
+ errstr ? errstr : "unknown error");
+ }
+ goto done;
+ }
+ plugin = sudo_dso_findsym(handle, info->symbol_name);
+ if (!plugin) {
+ if (!quiet) {
+ sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""),
+ _PATH_SUDO_CONF, info->lineno, info->symbol_name);
+ sudo_warnx(U_("unable to find symbol \"%s\" in %s"),
+ info->symbol_name, path);
+ }
+ goto done;
+ }
+
+ if (SUDO_API_VERSION_GET_MAJOR(plugin->version) != SUDO_API_VERSION_MAJOR) {
+ if (!quiet) {
+ sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""),
+ _PATH_SUDO_CONF, info->lineno, info->symbol_name);
+ sudo_warnx(U_("incompatible plugin major version %d (expected %d) found in %s"),
+ SUDO_API_VERSION_GET_MAJOR(plugin->version),
+ SUDO_API_VERSION_MAJOR, path);
+ }
+ goto done;
+ }
+
+ switch (plugin->type) {
+ case SUDO_POLICY_PLUGIN:
+ if (policy_plugin.handle != NULL) {
+ /* Ignore duplicate entries. */
+ if (strcmp(policy_plugin.name, info->symbol_name) == 0) {
+ if (!quiet) {
+ sudo_warnx(U_("ignoring duplicate plugin \"%s\" in %s, line %d"),
+ info->symbol_name, _PATH_SUDO_CONF, info->lineno);
+ }
+ } else {
+ if (!quiet) {
+ sudo_warnx(U_("ignoring policy plugin \"%s\" in %s, line %d"),
+ info->symbol_name, _PATH_SUDO_CONF, info->lineno);
+ sudo_warnx("%s",
+ U_("only a single policy plugin may be specified"));
+ }
+ goto done;
+ }
+ ret = true;
+ goto done;
+ }
+ if (!fill_container(&policy_plugin, handle, path, plugin, info))
+ goto done;
+ break;
+ case SUDO_IO_PLUGIN:
+ if (!sudo_insert_plugin(&io_plugins, handle, path, plugin, info))
+ goto done;
+ break;
+ case SUDO_AUDIT_PLUGIN:
+ if (!sudo_insert_plugin(&audit_plugins, handle, path, plugin, info))
+ goto done;
+ break;
+ case SUDO_APPROVAL_PLUGIN:
+ if (!sudo_insert_plugin(&approval_plugins, handle, path, plugin, info))
+ goto done;
+ break;
+ default:
+ if (!quiet) {
+ sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""),
+ _PATH_SUDO_CONF, info->lineno, info->symbol_name);
+ sudo_warnx(U_("unknown plugin type %d found in %s"), plugin->type, path);
+ }
+ goto done;
+ }
+
+ /* Handle is either in use or has been closed. */
+ handle = NULL;
+
+ ret = true;
+
+done:
+ if (handle != NULL)
+ sudo_dso_unload(handle);
+ debug_return_bool(ret);
+}
+
+static void
+free_plugin_info(struct plugin_info *info)
+{
+ free(info->path);
+ free(info->symbol_name);
+ if (info->options != NULL) {
+ int i = 0;
+ while (info->options[i] != NULL)
+ free(info->options[i++]);
+ free(info->options);
+ }
+ free(info);
+}
+
+static void
+sudo_register_hooks(void)
+{
+ struct plugin_container *container;
+ debug_decl(sudo_register_hooks, SUDO_DEBUG_PLUGIN);
+
+ if (policy_plugin.u.policy->version >= SUDO_API_MKVERSION(1, 2)) {
+ if (policy_plugin.u.policy->register_hooks != NULL) {
+ sudo_debug_set_active_instance(policy_plugin.debug_instance);
+ policy_plugin.u.policy->register_hooks(SUDO_HOOK_VERSION,
+ register_hook);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+ }
+
+ TAILQ_FOREACH(container, &io_plugins, entries) {
+ if (container->u.io->version >= SUDO_API_MKVERSION(1, 2)) {
+ if (container->u.io->register_hooks != NULL) {
+ sudo_debug_set_active_instance(container->debug_instance);
+ container->u.io->register_hooks(SUDO_HOOK_VERSION,
+ register_hook);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+ }
+ }
+
+ TAILQ_FOREACH(container, &audit_plugins, entries) {
+ if (container->u.audit->register_hooks != NULL) {
+ sudo_debug_set_active_instance(container->debug_instance);
+ container->u.audit->register_hooks(SUDO_HOOK_VERSION,
+ register_hook);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+ }
+
+ debug_return;
+}
+
+static void
+sudo_init_event_alloc(void)
+{
+ struct plugin_container *container;
+ debug_decl(sudo_init_event_alloc, SUDO_DEBUG_PLUGIN);
+
+ if (policy_plugin.u.policy->version >= SUDO_API_MKVERSION(1, 15))
+ policy_plugin.u.policy->event_alloc = sudo_plugin_event_alloc;
+
+ TAILQ_FOREACH(container, &io_plugins, entries) {
+ if (container->u.io->version >= SUDO_API_MKVERSION(1, 15))
+ container->u.io->event_alloc = sudo_plugin_event_alloc;
+ }
+ TAILQ_FOREACH(container, &audit_plugins, entries) {
+ if (container->u.audit->version >= SUDO_API_MKVERSION(1, 17))
+ container->u.audit->event_alloc = sudo_plugin_event_alloc;
+ }
+
+ debug_return;
+}
+
+/*
+ * Load the specified symbol from the sudoers plugin.
+ * Used to provide a default plugin when none are specified in sudo.conf.
+ */
+bool
+sudo_load_sudoers_plugin(const char *symbol_name, bool optional)
+{
+ struct plugin_info *info;
+ bool ret = false;
+ debug_decl(sudo_load_sudoers_plugin, SUDO_DEBUG_PLUGIN);
+
+ /* Default policy plugin */
+ info = calloc(1, sizeof(*info));
+ if (info == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto done;
+ }
+ info->symbol_name = strdup(symbol_name);
+ info->path = strdup(SUDOERS_PLUGIN);
+ if (info->symbol_name == NULL || info->path == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ free_plugin_info(info);
+ goto done;
+ }
+ /* info->options = NULL; */
+ ret = sudo_load_plugin(info, optional);
+ free_plugin_info(info);
+
+done:
+ debug_return_bool(ret);
+}
+
+/*
+ * Load the plugins listed in sudo.conf.
+ */
+bool
+sudo_load_plugins(void)
+{
+ struct plugin_info_list *plugins;
+ struct plugin_info *info, *next;
+ bool ret = false;
+ debug_decl(sudo_load_plugins, SUDO_DEBUG_PLUGIN);
+
+ /* Walk the plugin list from sudo.conf, if any and free it. */
+ plugins = sudo_conf_plugins();
+ TAILQ_FOREACH_SAFE(info, plugins, entries, next) {
+ ret = sudo_load_plugin(info, false);
+ if (!ret)
+ goto done;
+ free_plugin_info(info);
+ }
+ TAILQ_INIT(plugins);
+
+ /*
+ * If no policy plugin, fall back to the default (sudoers).
+ * If there is also no I/O log plugin, use sudoers for that too.
+ */
+ if (policy_plugin.handle == NULL) {
+ /* Default policy plugin */
+ ret = sudo_load_sudoers_plugin("sudoers_policy", false);
+ if (!ret)
+ goto done;
+
+ /* Default audit plugin, optional (sudoers < 1.9.1 lack this) */
+ (void)sudo_load_sudoers_plugin("sudoers_audit", true);
+
+ /* Default I/O plugin */
+ if (TAILQ_EMPTY(&io_plugins)) {
+ ret = sudo_load_sudoers_plugin("sudoers_io", false);
+ if (!ret)
+ goto done;
+ }
+ } else if (strcmp(policy_plugin.name, "sudoers_policy") == 0) {
+ /*
+ * If policy plugin is sudoers_policy but there is no sudoers_audit
+ * loaded, load it too, if possible.
+ */
+ if (!plugin_exists(&audit_plugins, "sudoers_audit")) {
+ if (sudo_load_sudoers_plugin("sudoers_audit", true)) {
+ /*
+ * Move the plugin options from sudoers_policy to sudoers_audit
+ * since the audit module is now what actually opens sudoers.
+ */
+ if (policy_plugin.options != NULL) {
+ TAILQ_LAST(&audit_plugins, plugin_container_list)->options =
+ policy_plugin.options;
+ policy_plugin.options = NULL;
+ }
+ }
+ }
+ }
+
+ /* TODO: check all plugins for open function too */
+ if (policy_plugin.u.policy->check_policy == NULL) {
+ sudo_warnx(U_("policy plugin %s does not include a check_policy method"),
+ policy_plugin.name);
+ ret = false;
+ goto done;
+ }
+
+ /* Set event_alloc() in plugins. */
+ sudo_init_event_alloc();
+
+ /* Install hooks (XXX - later, after open). */
+ sudo_register_hooks();
+
+done:
+ debug_return_bool(ret);
+}
diff --git a/src/net_ifs.c b/src/net_ifs.c
new file mode 100644
index 0000000..273ca9a
--- /dev/null
+++ b/src/net_ifs.c
@@ -0,0 +1,373 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1996, 1998-2005, 2007-2015, 2018
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+/*
+ * Suppress a warning w/ gcc on Digital UN*X.
+ * The system headers should really do this....
+ */
+#if defined(__osf__) && !defined(__cplusplus)
+struct mbuf;
+struct rtentry;
+#endif
+
+/* Avoid a compilation problem with gcc and machine/sys/getppdp.h */
+#define _MACHINE_SYS_GETPPDP_INCLUDED
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#if defined(HAVE_SYS_SOCKIO_H) && !defined(SIOCGIFCONF)
+# include <sys/sockio.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <unistd.h>
+#include <netdb.h>
+#include <errno.h>
+#ifdef _ISC
+# include <sys/stream.h>
+# include <sys/sioctl.h>
+# include <sys/stropts.h>
+# define STRSET(cmd, param, len) {strioctl.ic_cmd=(cmd);\
+ strioctl.ic_dp=(param);\
+ strioctl.ic_timout=0;\
+ strioctl.ic_len=(len);}
+#endif /* _ISC */
+#ifdef _MIPS
+# include <net/soioctl.h>
+#endif /* _MIPS */
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#ifdef NEED_RESOLV_H
+# include <arpa/nameser.h>
+# include <resolv.h>
+#endif /* NEED_RESOLV_H */
+#include <net/if.h>
+#ifdef HAVE_GETIFADDRS
+# include <ifaddrs.h>
+#endif
+
+#define NEED_INET_NTOP /* to expose sudo_inet_ntop in sudo_compat.h */
+
+#define DEFAULT_TEXT_DOMAIN "sudo"
+
+#include "sudo_compat.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+
+/* Minix apparently lacks IFF_LOOPBACK */
+#ifndef IFF_LOOPBACK
+# define IFF_LOOPBACK 0
+#endif
+
+#ifndef INET_ADDRSTRLEN
+# define INET_ADDRSTRLEN 16
+#endif
+#ifndef INET6_ADDRSTRLEN
+# define INET6_ADDRSTRLEN 46
+#endif
+
+#ifdef HAVE_GETIFADDRS
+
+/*
+ * Fill in the interfaces string with the machine's ip addresses and netmasks
+ * and return the number of interfaces found. Returns -1 on error.
+ */
+int
+get_net_ifs(char **addrinfo)
+{
+ struct ifaddrs *ifa, *ifaddrs;
+ struct sockaddr_in *sin;
+#ifdef HAVE_STRUCT_IN6_ADDR
+ struct sockaddr_in6 *sin6;
+ char addrstr[INET6_ADDRSTRLEN], maskstr[INET6_ADDRSTRLEN];
+#else
+ char addrstr[INET_ADDRSTRLEN], maskstr[INET_ADDRSTRLEN];
+#endif
+ int len, num_interfaces = 0;
+ size_t ailen;
+ char *cp;
+ debug_decl(get_net_ifs, SUDO_DEBUG_NETIF);
+
+ if (!sudo_conf_probe_interfaces())
+ debug_return_int(0);
+
+ if (getifaddrs(&ifaddrs) == -1)
+ debug_return_int(-1);
+
+ /* Allocate space for the interfaces info string. */
+ for (ifa = ifaddrs; ifa != NULL; ifa = ifa -> ifa_next) {
+ /* Skip interfaces marked "down" and "loopback". */
+ if (ifa->ifa_addr == NULL || ifa->ifa_netmask == NULL ||
+ !ISSET(ifa->ifa_flags, IFF_UP) || ISSET(ifa->ifa_flags, IFF_LOOPBACK))
+ continue;
+
+ switch (ifa->ifa_addr->sa_family) {
+ case AF_INET:
+#ifdef HAVE_STRUCT_IN6_ADDR
+ case AF_INET6:
+#endif
+ num_interfaces++;
+ break;
+ }
+ }
+ if (num_interfaces == 0)
+ goto done;
+ ailen = num_interfaces * 2 * INET6_ADDRSTRLEN;
+ if ((cp = malloc(ailen)) == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to allocate memory");
+ num_interfaces = -1;
+ goto done;
+ }
+ *addrinfo = cp;
+
+ /* Store the IP addr/netmask pairs. */
+ for (ifa = ifaddrs; ifa != NULL; ifa = ifa -> ifa_next) {
+ /* Skip interfaces marked "down" and "loopback". */
+ if (ifa->ifa_addr == NULL || ifa->ifa_netmask == NULL ||
+ !ISSET(ifa->ifa_flags, IFF_UP) || ISSET(ifa->ifa_flags, IFF_LOOPBACK))
+ continue;
+
+ switch (ifa->ifa_addr->sa_family) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)ifa->ifa_addr;
+ if (inet_ntop(AF_INET, &sin->sin_addr, addrstr, sizeof(addrstr)) == NULL)
+ continue;
+ sin = (struct sockaddr_in *)ifa->ifa_netmask;
+ if (inet_ntop(AF_INET, &sin->sin_addr, maskstr, sizeof(maskstr)) == NULL)
+ continue;
+
+ len = snprintf(cp, ailen, "%s%s/%s",
+ cp == *addrinfo ? "" : " ", addrstr, maskstr);
+ if (len < 0 || (size_t)len >= ailen) {
+ sudo_warnx(U_("internal error, %s overflow"), __func__);
+ goto done;
+ }
+ cp += len;
+ ailen -= len;
+ break;
+#ifdef HAVE_STRUCT_IN6_ADDR
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
+ if (inet_ntop(AF_INET6, &sin6->sin6_addr, addrstr, sizeof(addrstr)) == NULL)
+ continue;
+ sin6 = (struct sockaddr_in6 *)ifa->ifa_netmask;
+ if (inet_ntop(AF_INET6, &sin6->sin6_addr, maskstr, sizeof(maskstr)) == NULL)
+ continue;
+
+ len = snprintf(cp, ailen, "%s%s/%s",
+ cp == *addrinfo ? "" : " ", addrstr, maskstr);
+ if (len < 0 || (size_t)len >= ailen) {
+ sudo_warnx(U_("internal error, %s overflow"), __func__);
+ goto done;
+ }
+ cp += len;
+ ailen -= len;
+ break;
+#endif /* HAVE_STRUCT_IN6_ADDR */
+ }
+ }
+
+done:
+#ifdef HAVE_FREEIFADDRS
+ freeifaddrs(ifaddrs);
+#else
+ free(ifaddrs);
+#endif
+ debug_return_int(num_interfaces);
+}
+
+#elif defined(SIOCGIFCONF) && !defined(STUB_LOAD_INTERFACES)
+
+/*
+ * Fill in the interfaces string with the machine's ip addresses and netmasks
+ * and return the number of interfaces found. Returns -1 on error.
+ */
+int
+get_net_ifs(char **addrinfo)
+{
+ char ifr_tmpbuf[sizeof(struct ifreq)];
+ struct ifreq *ifr, *ifr_tmp = (struct ifreq *)ifr_tmpbuf;
+ struct ifconf *ifconf;
+ struct sockaddr_in *sin;
+ int i, len, n, sock, num_interfaces = 0;
+ size_t ailen, buflen = sizeof(struct ifconf) + BUFSIZ;
+ char *cp, *previfname = "", *ifconf_buf = NULL;
+ char addrstr[INET_ADDRSTRLEN], maskstr[INET_ADDRSTRLEN];
+#ifdef _ISC
+ struct strioctl strioctl;
+#endif /* _ISC */
+ debug_decl(get_net_ifs, SUDO_DEBUG_NETIF);
+
+ if (!sudo_conf_probe_interfaces())
+ debug_return_int(0);
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ debug_return_int(-1);
+
+ /*
+ * Get interface configuration or return.
+ */
+ for (;;) {
+ if ((ifconf_buf = malloc(buflen)) == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to allocate memory");
+ num_interfaces = -1;
+ goto done;
+ }
+ ifconf = (struct ifconf *) ifconf_buf;
+ ifconf->ifc_len = buflen - sizeof(struct ifconf);
+ ifconf->ifc_buf = (caddr_t) (ifconf_buf + sizeof(struct ifconf));
+
+#ifdef _ISC
+ STRSET(SIOCGIFCONF, (caddr_t) ifconf, buflen);
+ if (ioctl(sock, I_STR, (caddr_t) &strioctl) < 0)
+#else
+ /* Note that some kernels return EINVAL if the buffer is too small */
+ if (ioctl(sock, SIOCGIFCONF, (caddr_t) ifconf) < 0 && errno != EINVAL)
+#endif /* _ISC */
+ goto done;
+
+ /* Break out of loop if we have a big enough buffer. */
+ if (ifconf->ifc_len + sizeof(struct ifreq) < buflen)
+ break;
+ buflen += BUFSIZ;
+ free(ifconf_buf);
+ }
+
+ /* Allocate space for the maximum number of interfaces that could exist. */
+ if ((n = ifconf->ifc_len / sizeof(struct ifreq)) == 0)
+ goto done;
+ ailen = n * 2 * INET6_ADDRSTRLEN;
+ if ((cp = malloc(ailen)) == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to allocate memory");
+ num_interfaces = -1;
+ goto done;
+ }
+ *addrinfo = cp;
+
+ /* For each interface, store the ip address and netmask. */
+ for (i = 0; i < ifconf->ifc_len; ) {
+ /* Get a pointer to the current interface. */
+ ifr = (struct ifreq *) &ifconf->ifc_buf[i];
+
+ /* Set i to the subscript of the next interface. */
+ i += sizeof(struct ifreq);
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ if (ifr->ifr_addr.sa_len > sizeof(ifr->ifr_addr))
+ i += ifr->ifr_addr.sa_len - sizeof(struct sockaddr);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+
+ /* Skip duplicates and interfaces with NULL addresses. */
+ sin = (struct sockaddr_in *) &ifr->ifr_addr;
+ if (sin->sin_addr.s_addr == 0 ||
+ strncmp(previfname, ifr->ifr_name, sizeof(ifr->ifr_name) - 1) == 0)
+ continue;
+
+ if (ifr->ifr_addr.sa_family != AF_INET)
+ continue;
+
+#ifdef SIOCGIFFLAGS
+ memset(ifr_tmp, 0, sizeof(*ifr_tmp));
+ memcpy(ifr_tmp->ifr_name, ifr->ifr_name, sizeof(ifr_tmp->ifr_name));
+ if (ioctl(sock, SIOCGIFFLAGS, (caddr_t) ifr_tmp) < 0)
+#endif
+ memcpy(ifr_tmp, ifr, sizeof(*ifr_tmp));
+
+ /* Skip interfaces marked "down" and "loopback". */
+ if (!ISSET(ifr_tmp->ifr_flags, IFF_UP) ||
+ ISSET(ifr_tmp->ifr_flags, IFF_LOOPBACK))
+ continue;
+
+ /* Get the netmask. */
+ memset(ifr_tmp, 0, sizeof(*ifr_tmp));
+ memcpy(ifr_tmp->ifr_name, ifr->ifr_name, sizeof(ifr_tmp->ifr_name));
+ sin = (struct sockaddr_in *) &ifr_tmp->ifr_addr;
+#ifdef _ISC
+ STRSET(SIOCGIFNETMASK, (caddr_t) ifr_tmp, sizeof(*ifr_tmp));
+ if (ioctl(sock, I_STR, (caddr_t) &strioctl) < 0)
+#else
+ if (ioctl(sock, SIOCGIFNETMASK, (caddr_t) ifr_tmp) < 0)
+#endif /* _ISC */
+ sin->sin_addr.s_addr = htonl(IN_CLASSC_NET);
+
+ /* Convert the addr and mask to string form. */
+ sin = (struct sockaddr_in *) &ifr->ifr_addr;
+ if (inet_ntop(AF_INET, &sin->sin_addr, addrstr, sizeof(addrstr)) == NULL)
+ continue;
+ sin = (struct sockaddr_in *) &ifr_tmp->ifr_addr;
+ if (inet_ntop(AF_INET, &sin->sin_addr, maskstr, sizeof(maskstr)) == NULL)
+ continue;
+
+ len = snprintf(cp, ailen, "%s%s/%s",
+ cp == *addrinfo ? "" : " ", addrstr, maskstr);
+ if (len < 0 || (size_t)len >= ailen) {
+ sudo_warnx(U_("internal error, %s overflow"), __func__);
+ goto done;
+ }
+ cp += len;
+ ailen -= len;
+
+ /* Stash the name of the interface we saved. */
+ previfname = ifr->ifr_name;
+ num_interfaces++;
+ }
+
+done:
+ free(ifconf_buf);
+ (void) close(sock);
+
+ debug_return_int(num_interfaces);
+}
+
+#else /* !SIOCGIFCONF || STUB_LOAD_INTERFACES */
+
+/*
+ * Stub function for those without SIOCGIFCONF or getifaddrs()
+ */
+int
+get_net_ifs(char **addrinfo)
+{
+ debug_decl(get_net_ifs, SUDO_DEBUG_NETIF);
+ debug_return_int(0);
+}
+
+#endif /* SIOCGIFCONF && !STUB_LOAD_INTERFACES */
diff --git a/src/openbsd.c b/src/openbsd.c
new file mode 100644
index 0000000..180a791
--- /dev/null
+++ b/src/openbsd.c
@@ -0,0 +1,36 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2012 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include "sudo.h"
+
+int
+os_init(int argc, char *argv[], char *envp[])
+{
+#ifdef SUDO_DEVEL
+ extern char *malloc_options;
+ malloc_options = "S";
+#endif
+ return os_init_common(argc, argv, envp);
+}
diff --git a/src/parse_args.c b/src/parse_args.c
new file mode 100644
index 0000000..2d326cc
--- /dev/null
+++ b/src/parse_args.c
@@ -0,0 +1,863 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1993-1996, 1998-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#ifdef HAVE_GETOPT_LONG
+# include <getopt.h>
+# else
+# include "compat/getopt.h"
+#endif /* HAVE_GETOPT_LONG */
+
+#include <sudo_usage.h>
+#include "sudo.h"
+#include "sudo_lbuf.h"
+
+int tgetpass_flags;
+
+/*
+ * Local functions.
+ */
+static void help(void) __attribute__((__noreturn__));
+static void usage_excl(void) __attribute__((__noreturn__));
+
+/*
+ * Mapping of command line flags to name/value settings.
+ */
+static struct sudo_settings sudo_settings[] = {
+#define ARG_BSDAUTH_TYPE 0
+ { "bsdauth_type" },
+#define ARG_LOGIN_CLASS 1
+ { "login_class" },
+#define ARG_PRESERVE_ENVIRONMENT 2
+ { "preserve_environment" },
+#define ARG_RUNAS_GROUP 3
+ { "runas_group" },
+#define ARG_SET_HOME 4
+ { "set_home" },
+#define ARG_USER_SHELL 5
+ { "run_shell" },
+#define ARG_LOGIN_SHELL 6
+ { "login_shell" },
+#define ARG_IGNORE_TICKET 7
+ { "ignore_ticket" },
+#define ARG_PROMPT 8
+ { "prompt" },
+#define ARG_SELINUX_ROLE 9
+ { "selinux_role" },
+#define ARG_SELINUX_TYPE 10
+ { "selinux_type" },
+#define ARG_RUNAS_USER 11
+ { "runas_user" },
+#define ARG_PROGNAME 12
+ { "progname" },
+#define ARG_IMPLIED_SHELL 13
+ { "implied_shell" },
+#define ARG_PRESERVE_GROUPS 14
+ { "preserve_groups" },
+#define ARG_NONINTERACTIVE 15
+ { "noninteractive" },
+#define ARG_SUDOEDIT 16
+ { "sudoedit" },
+#define ARG_CLOSEFROM 17
+ { "closefrom" },
+#define ARG_NET_ADDRS 18
+ { "network_addrs" },
+#define ARG_MAX_GROUPS 19
+ { "max_groups" },
+#define ARG_PLUGIN_DIR 20
+ { "plugin_dir" },
+#define ARG_REMOTE_HOST 21
+ { "remote_host" },
+#define ARG_TIMEOUT 22
+ { "timeout" },
+#define ARG_CHROOT 23
+ { "cmnd_chroot" },
+#define ARG_CWD 24
+ { "cmnd_cwd" },
+#define NUM_SETTINGS 25
+ { NULL }
+};
+
+struct environment {
+ char **envp; /* pointer to the new environment */
+ size_t env_size; /* size of new_environ in char **'s */
+ size_t env_len; /* number of slots used, not counting NULL */
+};
+
+/*
+ * Default flags allowed when running a command.
+ */
+#define DEFAULT_VALID_FLAGS (MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_PRESERVE_GROUPS|MODE_SHELL)
+#define EDIT_VALID_FLAGS MODE_NONINTERACTIVE
+#define LIST_VALID_FLAGS (MODE_NONINTERACTIVE|MODE_LONG_LIST)
+#define VALIDATE_VALID_FLAGS MODE_NONINTERACTIVE
+
+/* Option number for the --host long option due to ambiguity of the -h flag. */
+#define OPT_HOSTNAME 256
+
+/*
+ * Available command line options, both short and long.
+ * Note that we must disable arg permutation to support setting environment
+ * variables and to better support the optional arg of the -h flag.
+ */
+static const char short_opts[] = "+Aa:BbC:c:D:Eeg:Hh::iKklnPp:R:r:SsT:t:U:u:Vv";
+static struct option long_opts[] = {
+ { "askpass", no_argument, NULL, 'A' },
+ { "auth-type", required_argument, NULL, 'a' },
+ { "background", no_argument, NULL, 'b' },
+ { "bell", no_argument, NULL, 'B' },
+ { "close-from", required_argument, NULL, 'C' },
+ { "login-class", required_argument, NULL, 'c' },
+ { "chdir", required_argument, NULL, 'D' },
+ { "preserve-env", optional_argument, NULL, 'E' },
+ { "edit", no_argument, NULL, 'e' },
+ { "group", required_argument, NULL, 'g' },
+ { "set-home", no_argument, NULL, 'H' },
+ { "help", no_argument, NULL, 'h' },
+ { "host", required_argument, NULL, OPT_HOSTNAME },
+ { "login", no_argument, NULL, 'i' },
+ { "remove-timestamp", no_argument, NULL, 'K' },
+ { "reset-timestamp", no_argument, NULL, 'k' },
+ { "list", no_argument, NULL, 'l' },
+ { "non-interactive", no_argument, NULL, 'n' },
+ { "preserve-groups", no_argument, NULL, 'P' },
+ { "prompt", required_argument, NULL, 'p' },
+ { "chroot", required_argument, NULL, 'R' },
+ { "role", required_argument, NULL, 'r' },
+ { "stdin", no_argument, NULL, 'S' },
+ { "shell", no_argument, NULL, 's' },
+ { "type", required_argument, NULL, 't' },
+ { "command-timeout",required_argument, NULL, 'T' },
+ { "other-user", required_argument, NULL, 'U' },
+ { "user", required_argument, NULL, 'u' },
+ { "version", no_argument, NULL, 'V' },
+ { "validate", no_argument, NULL, 'v' },
+ { NULL, no_argument, NULL, '\0' },
+};
+
+/*
+ * Insert a key=value pair into the specified environment.
+ */
+static void
+env_insert(struct environment *e, char *pair)
+{
+ debug_decl(env_insert, SUDO_DEBUG_ARGS);
+
+ /* Make sure we have at least two slots free (one for NULL). */
+ if (e->env_len + 1 >= e->env_size) {
+ char **tmp;
+
+ if (e->env_size == 0)
+ e->env_size = 16;
+ tmp = reallocarray(e->envp, e->env_size, 2 * sizeof(char *));
+ if (tmp == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ e->envp = tmp;
+ e->env_size *= 2;
+ }
+ e->envp[e->env_len++] = pair;
+ e->envp[e->env_len] = NULL;
+
+ debug_return;
+}
+
+/*
+ * Format as var=val and insert into the specified environment.
+ */
+static void
+env_set(struct environment *e, char *var, char *val)
+{
+ char *pair;
+ debug_decl(env_set, SUDO_DEBUG_ARGS);
+
+ pair = sudo_new_key_val(var, val);
+ if (pair == NULL) {
+ sudo_fatalx(U_("%s: %s"),
+ __func__, U_("unable to allocate memory"));
+ }
+ env_insert(e, pair);
+
+ debug_return;
+}
+
+/*
+ * Parse a comma-separated list of env vars and add to the
+ * specified environment.
+ */
+static void
+parse_env_list(struct environment *e, char *list)
+{
+ char *cp, *last, *val;
+ debug_decl(parse_env_list, SUDO_DEBUG_ARGS);
+
+ for ((cp = strtok_r(list, ",", &last)); cp != NULL;
+ (cp = strtok_r(NULL, ",", &last))) {
+ if (strchr(cp, '=') != NULL) {
+ sudo_warnx(U_("invalid environment variable name: %s"), cp);
+ usage();
+ }
+ if ((val = getenv(cp)) != NULL)
+ env_set(e, cp, val);
+ }
+ debug_return;
+}
+
+/*
+ * Command line argument parsing.
+ * Sets nargc and nargv which corresponds to the argc/argv we'll use
+ * for the command to be run (if we are running one).
+ */
+int
+parse_args(int argc, char **argv, int *old_optind, int *nargc, char ***nargv,
+ struct sudo_settings **settingsp, char ***env_addp)
+{
+ struct environment extra_env;
+ int mode = 0; /* what mode is sudo to be run in? */
+ int flags = 0; /* mode flags */
+ int valid_flags = DEFAULT_VALID_FLAGS;
+ int ch, i;
+ char *cp;
+ const char *progname;
+ int proglen;
+ debug_decl(parse_args, SUDO_DEBUG_ARGS);
+
+ /* Is someone trying something funny? */
+ if (argc <= 0)
+ usage();
+
+ /* Pass progname to plugin so it can call initprogname() */
+ progname = getprogname();
+ sudo_settings[ARG_PROGNAME].value = progname;
+
+ /* First, check to see if we were invoked as "sudoedit". */
+ proglen = strlen(progname);
+ if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {
+ progname = "sudoedit";
+ mode = MODE_EDIT;
+ sudo_settings[ARG_SUDOEDIT].value = "true";
+ valid_flags = EDIT_VALID_FLAGS;
+ }
+
+ /* Load local IP addresses and masks. */
+ if (get_net_ifs(&cp) > 0)
+ sudo_settings[ARG_NET_ADDRS].value = cp;
+
+ /* Set max_groups from sudo.conf. */
+ i = sudo_conf_max_groups();
+ if (i != -1) {
+ if (asprintf(&cp, "%d", i) == -1)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ sudo_settings[ARG_MAX_GROUPS].value = cp;
+ }
+
+ /* Returns true if the last option string was "-h" */
+#define got_host_flag (optind > 1 && argv[optind - 1][0] == '-' && \
+ argv[optind - 1][1] == 'h' && argv[optind - 1][2] == '\0')
+
+ /* Returns true if the last option string was "--" */
+#define got_end_of_args (optind > 1 && argv[optind - 1][0] == '-' && \
+ argv[optind - 1][1] == '-' && argv[optind - 1][2] == '\0')
+
+ /* Returns true if next option is an environment variable */
+#define is_envar (optind < argc && argv[optind][0] != '/' && \
+ strchr(argv[optind], '=') != NULL)
+
+ /* Space for environment variables is lazy allocated. */
+ memset(&extra_env, 0, sizeof(extra_env));
+
+ /* XXX - should fill in settings at the end to avoid dupes */
+ for (;;) {
+ /*
+ * Some trickiness is required to allow environment variables
+ * to be interspersed with command line options.
+ */
+ if ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
+ switch (ch) {
+ case 'A':
+ SET(tgetpass_flags, TGP_ASKPASS);
+ break;
+#ifdef HAVE_BSD_AUTH_H
+ case 'a':
+ assert(optarg != NULL);
+ if (*optarg == '\0')
+ usage();
+ if (sudo_settings[ARG_BSDAUTH_TYPE].value != NULL)
+ usage();
+ sudo_settings[ARG_BSDAUTH_TYPE].value = optarg;
+ break;
+#endif
+ case 'b':
+ SET(flags, MODE_BACKGROUND);
+ break;
+ case 'B':
+ SET(tgetpass_flags, TGP_BELL);
+ break;
+ case 'C':
+ assert(optarg != NULL);
+ if (sudo_strtonum(optarg, 3, INT_MAX, NULL) == 0) {
+ sudo_warnx("%s",
+ U_("the argument to -C must be a number greater than or equal to 3"));
+ usage();
+ }
+ if (sudo_settings[ARG_CLOSEFROM].value != NULL)
+ usage();
+ sudo_settings[ARG_CLOSEFROM].value = optarg;
+ break;
+#ifdef HAVE_LOGIN_CAP_H
+ case 'c':
+ assert(optarg != NULL);
+ if (*optarg == '\0')
+ usage();
+ if (sudo_settings[ARG_LOGIN_CLASS].value != NULL)
+ usage();
+ sudo_settings[ARG_LOGIN_CLASS].value = optarg;
+ break;
+#endif
+ case 'D':
+ assert(optarg != NULL);
+ if (*optarg == '\0')
+ usage();
+ if (sudo_settings[ARG_CWD].value != NULL)
+ usage();
+ sudo_settings[ARG_CWD].value = optarg;
+ break;
+ case 'E':
+ /*
+ * Optional argument is a comma-separated list of
+ * environment variables to preserve.
+ * If not present, preserve everything.
+ */
+ if (optarg == NULL) {
+ sudo_settings[ARG_PRESERVE_ENVIRONMENT].value = "true";
+ SET(flags, MODE_PRESERVE_ENV);
+ } else {
+ parse_env_list(&extra_env, optarg);
+ }
+ break;
+ case 'e':
+ if (mode && mode != MODE_EDIT)
+ usage_excl();
+ mode = MODE_EDIT;
+ sudo_settings[ARG_SUDOEDIT].value = "true";
+ valid_flags = EDIT_VALID_FLAGS;
+ break;
+ case 'g':
+ assert(optarg != NULL);
+ if (*optarg == '\0')
+ usage();
+ if (sudo_settings[ARG_RUNAS_GROUP].value != NULL)
+ usage();
+ sudo_settings[ARG_RUNAS_GROUP].value = optarg;
+ break;
+ case 'H':
+ sudo_settings[ARG_SET_HOME].value = "true";
+ SET(flags, MODE_RESET_HOME);
+ break;
+ case 'h':
+ if (optarg == NULL) {
+ /*
+ * Optional args support -hhostname, not -h hostname.
+ * If we see a non-option after the -h flag, treat as
+ * remote host and bump optind to skip over it.
+ */
+ if (got_host_flag && argv[optind] != NULL &&
+ argv[optind][0] != '-' && !is_envar) {
+ if (sudo_settings[ARG_REMOTE_HOST].value != NULL)
+ usage();
+ sudo_settings[ARG_REMOTE_HOST].value = argv[optind++];
+ continue;
+ }
+ if (mode && mode != MODE_HELP) {
+ if (strcmp(progname, "sudoedit") != 0)
+ usage_excl();
+ }
+ mode = MODE_HELP;
+ valid_flags = 0;
+ break;
+ }
+ FALLTHROUGH;
+ case OPT_HOSTNAME:
+ assert(optarg != NULL);
+ if (*optarg == '\0')
+ usage();
+ if (sudo_settings[ARG_REMOTE_HOST].value != NULL)
+ usage();
+ sudo_settings[ARG_REMOTE_HOST].value = optarg;
+ break;
+ case 'i':
+ sudo_settings[ARG_LOGIN_SHELL].value = "true";
+ SET(flags, MODE_LOGIN_SHELL);
+ break;
+ case 'k':
+ sudo_settings[ARG_IGNORE_TICKET].value = "true";
+ break;
+ case 'K':
+ sudo_settings[ARG_IGNORE_TICKET].value = "true";
+ if (mode && mode != MODE_KILL)
+ usage_excl();
+ mode = MODE_KILL;
+ valid_flags = 0;
+ break;
+ case 'l':
+ if (mode) {
+ if (mode == MODE_LIST)
+ SET(flags, MODE_LONG_LIST);
+ else
+ usage_excl();
+ }
+ mode = MODE_LIST;
+ valid_flags = LIST_VALID_FLAGS;
+ break;
+ case 'n':
+ SET(flags, MODE_NONINTERACTIVE);
+ sudo_settings[ARG_NONINTERACTIVE].value = "true";
+ break;
+ case 'P':
+ sudo_settings[ARG_PRESERVE_GROUPS].value = "true";
+ SET(flags, MODE_PRESERVE_GROUPS);
+ break;
+ case 'p':
+ /* An empty prompt is allowed. */
+ assert(optarg != NULL);
+ if (sudo_settings[ARG_PROMPT].value != NULL)
+ usage();
+ sudo_settings[ARG_PROMPT].value = optarg;
+ break;
+ case 'R':
+ assert(optarg != NULL);
+ if (*optarg == '\0')
+ usage();
+ if (sudo_settings[ARG_CHROOT].value != NULL)
+ usage();
+ sudo_settings[ARG_CHROOT].value = optarg;
+ break;
+#ifdef HAVE_SELINUX
+ case 'r':
+ assert(optarg != NULL);
+ if (*optarg == '\0')
+ usage();
+ if (sudo_settings[ARG_SELINUX_ROLE].value != NULL)
+ usage();
+ sudo_settings[ARG_SELINUX_ROLE].value = optarg;
+ break;
+ case 't':
+ assert(optarg != NULL);
+ if (*optarg == '\0')
+ usage();
+ if (sudo_settings[ARG_SELINUX_TYPE].value != NULL)
+ usage();
+ sudo_settings[ARG_SELINUX_TYPE].value = optarg;
+ break;
+#endif
+ case 'T':
+ /* Plugin determines whether empty timeout is allowed. */
+ assert(optarg != NULL);
+ if (sudo_settings[ARG_TIMEOUT].value != NULL)
+ usage();
+ sudo_settings[ARG_TIMEOUT].value = optarg;
+ break;
+ case 'S':
+ SET(tgetpass_flags, TGP_STDIN);
+ break;
+ case 's':
+ sudo_settings[ARG_USER_SHELL].value = "true";
+ SET(flags, MODE_SHELL);
+ break;
+ case 'U':
+ assert(optarg != NULL);
+ if (list_user != NULL || *optarg == '\0')
+ usage();
+ list_user = optarg;
+ break;
+ case 'u':
+ assert(optarg != NULL);
+ if (*optarg == '\0')
+ usage();
+ if (sudo_settings[ARG_RUNAS_USER].value != NULL)
+ usage();
+ sudo_settings[ARG_RUNAS_USER].value = optarg;
+ break;
+ case 'v':
+ if (mode && mode != MODE_VALIDATE)
+ usage_excl();
+ mode = MODE_VALIDATE;
+ valid_flags = VALIDATE_VALID_FLAGS;
+ break;
+ case 'V':
+ if (mode && mode != MODE_VERSION)
+ usage_excl();
+ mode = MODE_VERSION;
+ valid_flags = 0;
+ break;
+ default:
+ usage();
+ }
+ } else if (!got_end_of_args && is_envar) {
+ /* Insert key=value pair, crank optind and resume getopt. */
+ env_insert(&extra_env, argv[optind]);
+ optind++;
+ } else {
+ /* Not an option or an environment variable -- we're done. */
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ *old_optind = optind;
+
+ if (!mode) {
+ /* Defer -k mode setting until we know whether it is a flag or not */
+ if (sudo_settings[ARG_IGNORE_TICKET].value != NULL) {
+ if (argc == 0 && !ISSET(flags, MODE_SHELL|MODE_LOGIN_SHELL)) {
+ mode = MODE_INVALIDATE; /* -k by itself */
+ sudo_settings[ARG_IGNORE_TICKET].value = NULL;
+ valid_flags = 0;
+ }
+ }
+ if (!mode)
+ mode = MODE_RUN; /* running a command */
+ }
+
+ if (argc > 0 && mode == MODE_LIST)
+ mode = MODE_CHECK;
+
+ if (ISSET(flags, MODE_LOGIN_SHELL)) {
+ if (ISSET(flags, MODE_SHELL)) {
+ sudo_warnx("%s",
+ U_("you may not specify both the -i and -s options"));
+ usage();
+ }
+ if (ISSET(flags, MODE_PRESERVE_ENV)) {
+ sudo_warnx("%s",
+ U_("you may not specify both the -i and -E options"));
+ usage();
+ }
+ SET(flags, MODE_SHELL);
+ }
+ if ((flags & valid_flags) != flags)
+ usage();
+ if (mode == MODE_EDIT &&
+ (ISSET(flags, MODE_PRESERVE_ENV) || extra_env.env_len != 0)) {
+ if (ISSET(mode, MODE_PRESERVE_ENV))
+ sudo_warnx("%s", U_("the -E option is not valid in edit mode"));
+ if (extra_env.env_len != 0)
+ sudo_warnx("%s",
+ U_("you may not specify environment variables in edit mode"));
+ usage();
+ }
+ if ((sudo_settings[ARG_RUNAS_USER].value != NULL ||
+ sudo_settings[ARG_RUNAS_GROUP].value != NULL) &&
+ !ISSET(mode, MODE_EDIT | MODE_RUN | MODE_CHECK | MODE_VALIDATE)) {
+ usage();
+ }
+ if (list_user != NULL && mode != MODE_LIST && mode != MODE_CHECK) {
+ sudo_warnx("%s",
+ U_("the -U option may only be used with the -l option"));
+ usage();
+ }
+ if (ISSET(tgetpass_flags, TGP_STDIN) && ISSET(tgetpass_flags, TGP_ASKPASS)) {
+ sudo_warnx("%s", U_("the -A and -S options may not be used together"));
+ usage();
+ }
+ if ((argc == 0 && mode == MODE_EDIT) ||
+ (argc > 0 && !ISSET(mode, MODE_RUN | MODE_EDIT | MODE_CHECK)))
+ usage();
+ if (argc == 0 && mode == MODE_RUN && !ISSET(flags, MODE_SHELL)) {
+ SET(flags, (MODE_IMPLIED_SHELL | MODE_SHELL));
+ sudo_settings[ARG_IMPLIED_SHELL].value = "true";
+ }
+#ifdef ENABLE_SUDO_PLUGIN_API
+ sudo_settings[ARG_PLUGIN_DIR].value = sudo_conf_plugin_dir_path();
+#endif
+
+ if (mode == MODE_HELP)
+ help();
+
+ /*
+ * For shell mode we need to rewrite argv
+ */
+ if (ISSET(flags, MODE_SHELL|MODE_LOGIN_SHELL) && ISSET(mode, MODE_RUN)) {
+ char **av, *cmnd = NULL;
+ int ac = 1;
+
+ if (argc != 0) {
+ /* shell -c "command" */
+ char *src, *dst;
+ size_t size = 0;
+
+ for (av = argv; *av != NULL; av++)
+ size += strlen(*av) + 1;
+ if (size == 0 || (cmnd = reallocarray(NULL, size, 2)) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (!gc_add(GC_PTR, cmnd))
+ exit(EXIT_FAILURE);
+
+ for (dst = cmnd, av = argv; *av != NULL; av++) {
+ for (src = *av; *src != '\0'; src++) {
+ /* quote potential meta characters */
+ if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
+ *dst++ = '\\';
+ *dst++ = *src;
+ }
+ *dst++ = ' ';
+ }
+ if (cmnd != dst)
+ dst--; /* replace last space with a NUL */
+ *dst = '\0';
+
+ ac += 2; /* -c cmnd */
+ }
+
+ av = reallocarray(NULL, ac + 1, sizeof(char *));
+ if (av == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (!gc_add(GC_PTR, av))
+ exit(EXIT_FAILURE);
+
+ av[0] = (char *)user_details.shell; /* plugin may override shell */
+ if (cmnd != NULL) {
+ av[1] = "-c";
+ av[2] = cmnd;
+ }
+ av[ac] = NULL;
+
+ argv = av;
+ argc = ac;
+ }
+
+ /*
+ * For sudoedit we need to rewrite argv
+ */
+ if (mode == MODE_EDIT) {
+#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
+ char **av;
+ int ac;
+
+ av = reallocarray(NULL, argc + 2, sizeof(char *));
+ if (av == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (!gc_add(GC_PTR, av))
+ exit(EXIT_FAILURE);
+
+ /* Must have the command in argv[0]. */
+ av[0] = "sudoedit";
+ for (ac = 0; argv[ac] != NULL; ac++) {
+ av[ac + 1] = argv[ac];
+ }
+ av[++ac] = NULL;
+
+ argv = av;
+ argc = ac;
+#else
+ sudo_fatalx("%s", U_("sudoedit is not supported on this platform"));
+#endif
+ }
+
+ *settingsp = sudo_settings;
+ *env_addp = extra_env.envp;
+ *nargc = argc;
+ *nargv = argv;
+ debug_return_int(mode | flags);
+}
+
+static int
+usage_err(const char *buf)
+{
+ return fputs(buf, stderr);
+}
+
+static int
+usage_out(const char *buf)
+{
+ return fputs(buf, stdout);
+}
+
+/*
+ * Display usage message.
+ * The actual usage strings are in sudo_usage.h for configure substitution.
+ */
+static void
+display_usage(int (*output)(const char *))
+{
+ struct sudo_lbuf lbuf;
+ char *uvec[6];
+ int i, ulen;
+
+ /*
+ * Use usage vectors appropriate to the progname.
+ */
+ if (strcmp(getprogname(), "sudoedit") == 0) {
+ uvec[0] = &SUDO_USAGE5[3];
+ uvec[1] = NULL;
+ } else {
+ uvec[0] = SUDO_USAGE1;
+ uvec[1] = SUDO_USAGE2;
+ uvec[2] = SUDO_USAGE3;
+ uvec[3] = SUDO_USAGE4;
+ uvec[4] = SUDO_USAGE5;
+ uvec[5] = NULL;
+ }
+
+ /*
+ * Print usage and wrap lines as needed, depending on the
+ * tty width.
+ */
+ ulen = (int)strlen(getprogname()) + 8;
+ sudo_lbuf_init(&lbuf, output, ulen, NULL,
+ user_details.ts_cols);
+ for (i = 0; uvec[i] != NULL; i++) {
+ sudo_lbuf_append(&lbuf, "usage: %s%s", getprogname(), uvec[i]);
+ sudo_lbuf_print(&lbuf);
+ }
+ sudo_lbuf_destroy(&lbuf);
+}
+
+/*
+ * Display usage message and exit.
+ */
+void
+usage(void)
+{
+ display_usage(usage_err);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Tell which options are mutually exclusive and exit.
+ */
+static void
+usage_excl(void)
+{
+ debug_decl(usage_excl, SUDO_DEBUG_ARGS);
+
+ sudo_warnx("%s",
+ U_("Only one of the -e, -h, -i, -K, -l, -s, -v or -V options may be specified"));
+ usage();
+}
+
+static void
+help(void)
+{
+ struct sudo_lbuf lbuf;
+ const int indent = 32;
+ const char *pname = getprogname();
+ debug_decl(help, SUDO_DEBUG_ARGS);
+
+ sudo_lbuf_init(&lbuf, usage_out, indent, NULL, user_details.ts_cols);
+ if (strcmp(pname, "sudoedit") == 0)
+ sudo_lbuf_append(&lbuf, _("%s - edit files as another user\n\n"), pname);
+ else
+ sudo_lbuf_append(&lbuf, _("%s - execute a command as another user\n\n"), pname);
+ sudo_lbuf_print(&lbuf);
+
+ display_usage(usage_out);
+
+ sudo_lbuf_append(&lbuf, "%s", _("\nOptions:\n"));
+ sudo_lbuf_append(&lbuf, " -A, --askpass %s\n",
+ _("use a helper program for password prompting"));
+#ifdef HAVE_BSD_AUTH_H
+ sudo_lbuf_append(&lbuf, " -a, --auth-type=type %s\n",
+ _("use specified BSD authentication type"));
+#endif
+ sudo_lbuf_append(&lbuf, " -b, --background %s\n",
+ _("run command in the background"));
+ sudo_lbuf_append(&lbuf, " -B, --bell %s\n",
+ _("ring bell when prompting"));
+ sudo_lbuf_append(&lbuf, " -C, --close-from=num %s\n",
+ _("close all file descriptors >= num"));
+#ifdef HAVE_LOGIN_CAP_H
+ sudo_lbuf_append(&lbuf, " -c, --login-class=class %s\n",
+ _("run command with the specified BSD login class"));
+#endif
+ sudo_lbuf_append(&lbuf, " -D, --chdir=directory %s\n",
+ _("change the working directory before running command"));
+ sudo_lbuf_append(&lbuf, " -E, --preserve-env %s\n",
+ _("preserve user environment when running command"));
+ sudo_lbuf_append(&lbuf, " --preserve-env=list %s\n",
+ _("preserve specific environment variables"));
+ sudo_lbuf_append(&lbuf, " -e, --edit %s\n",
+ _("edit files instead of running a command"));
+ sudo_lbuf_append(&lbuf, " -g, --group=group %s\n",
+ _("run command as the specified group name or ID"));
+ sudo_lbuf_append(&lbuf, " -H, --set-home %s\n",
+ _("set HOME variable to target user's home dir"));
+ sudo_lbuf_append(&lbuf, " -h, --help %s\n",
+ _("display help message and exit"));
+ sudo_lbuf_append(&lbuf, " -h, --host=host %s\n",
+ _("run command on host (if supported by plugin)"));
+ sudo_lbuf_append(&lbuf, " -i, --login %s\n",
+ _("run login shell as the target user; a command may also be specified"));
+ sudo_lbuf_append(&lbuf, " -K, --remove-timestamp %s\n",
+ _("remove timestamp file completely"));
+ sudo_lbuf_append(&lbuf, " -k, --reset-timestamp %s\n",
+ _("invalidate timestamp file"));
+ sudo_lbuf_append(&lbuf, " -l, --list %s\n",
+ _("list user's privileges or check a specific command; use twice for longer format"));
+ sudo_lbuf_append(&lbuf, " -n, --non-interactive %s\n",
+ _("non-interactive mode, no prompts are used"));
+ sudo_lbuf_append(&lbuf, " -P, --preserve-groups %s\n",
+ _("preserve group vector instead of setting to target's"));
+ sudo_lbuf_append(&lbuf, " -p, --prompt=prompt %s\n",
+ _("use the specified password prompt"));
+ sudo_lbuf_append(&lbuf, " -R, --chroot=directory %s\n",
+ _("change the root directory before running command"));
+#ifdef HAVE_SELINUX
+ sudo_lbuf_append(&lbuf, " -r, --role=role %s\n",
+ _("create SELinux security context with specified role"));
+#endif
+ sudo_lbuf_append(&lbuf, " -S, --stdin %s\n",
+ _("read password from standard input"));
+ sudo_lbuf_append(&lbuf, " -s, --shell %s\n",
+ _("run shell as the target user; a command may also be specified"));
+#ifdef HAVE_SELINUX
+ sudo_lbuf_append(&lbuf, " -t, --type=type %s\n",
+ _("create SELinux security context with specified type"));
+#endif
+ sudo_lbuf_append(&lbuf, " -T, --command-timeout=timeout %s\n",
+ _("terminate command after the specified time limit"));
+ sudo_lbuf_append(&lbuf, " -U, --other-user=user %s\n",
+ _("in list mode, display privileges for user"));
+ sudo_lbuf_append(&lbuf, " -u, --user=user %s\n",
+ _("run command (or edit file) as specified user name or ID"));
+ sudo_lbuf_append(&lbuf, " -V, --version %s\n",
+ _("display version information and exit"));
+ sudo_lbuf_append(&lbuf, " -v, --validate %s\n",
+ _("update user's timestamp without running a command"));
+ sudo_lbuf_append(&lbuf, " -- %s\n",
+ _("stop processing command line arguments"));
+ sudo_lbuf_print(&lbuf);
+ sudo_lbuf_destroy(&lbuf);
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 0);
+ exit(EXIT_SUCCESS);
+}
diff --git a/src/preload.c b/src/preload.c
new file mode 100644
index 0000000..250ee7e
--- /dev/null
+++ b/src/preload.c
@@ -0,0 +1,77 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010, 2011, 2013 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifdef HAVE_GSS_KRB5_CCACHE_NAME
+# if defined(HAVE_GSSAPI_GSSAPI_KRB5_H)
+# include <gssapi/gssapi.h>
+# include <gssapi/gssapi_krb5.h>
+# elif defined(HAVE_GSSAPI_GSSAPI_H)
+# include <gssapi/gssapi.h>
+# else
+# include <gssapi.h>
+# endif
+#endif
+
+#include "sudo_plugin.h"
+#include "sudo_compat.h"
+#include "sudo_dso.h"
+
+#ifdef STATIC_SUDOERS_PLUGIN
+
+extern struct policy_plugin sudoers_policy;
+extern struct io_plugin sudoers_io;
+extern struct io_plugin sudoers_audit;
+
+static struct sudo_preload_symbol sudo_rtld_default_symbols[] = {
+# ifdef HAVE_GSS_KRB5_CCACHE_NAME
+ { "gss_krb5_ccache_name", (void *)&gss_krb5_ccache_name},
+# endif
+ { (const char *)0, (void *)0 }
+};
+
+/* XXX - can we autogenerate these? */
+static struct sudo_preload_symbol sudo_sudoers_plugin_symbols[] = {
+ { "sudoers_policy", (void *)&sudoers_policy },
+ { "sudoers_io", (void *)&sudoers_io },
+ { "sudoers_audit", (void *)&sudoers_audit },
+ { (const char *)0, (void *)0 }
+};
+
+/*
+ * Statically compiled symbols indexed by handle.
+ */
+static struct sudo_preload_table sudo_preload_table[] = {
+ { (char *)0, SUDO_DSO_DEFAULT, sudo_rtld_default_symbols },
+ { "sudoers.so", &sudo_sudoers_plugin_symbols, sudo_sudoers_plugin_symbols },
+ { (char *)0, (void *)0, (struct sudo_preload_symbol *)0 }
+};
+
+void
+preload_static_symbols(void)
+{
+ sudo_dso_preload_table(sudo_preload_table);
+}
+
+#endif /* STATIC_SUDOERS_PLUGIN */
diff --git a/src/preserve_fds.c b/src/preserve_fds.c
new file mode 100644
index 0000000..3dd65db
--- /dev/null
+++ b/src/preserve_fds.c
@@ -0,0 +1,216 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include "sudo.h"
+
+/*
+ * Add an fd to preserve.
+ */
+int
+add_preserved_fd(struct preserved_fd_list *pfds, int fd)
+{
+ struct preserved_fd *pfd, *pfd_new;
+ debug_decl(add_preserved_fd, SUDO_DEBUG_UTIL);
+
+ pfd_new = malloc(sizeof(*pfd));
+ if (pfd_new == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ pfd_new->lowfd = fd;
+ pfd_new->highfd = fd;
+ pfd_new->flags = fcntl(fd, F_GETFD);
+ if (pfd_new->flags == -1) {
+ free(pfd_new);
+ debug_return_int(-1);
+ }
+
+ TAILQ_FOREACH(pfd, pfds, entries) {
+ if (fd == pfd->highfd) {
+ /* already preserved */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "fd %d already preserved", fd);
+ free(pfd_new);
+ pfd_new = NULL;
+ break;
+ }
+ if (fd < pfd->highfd) {
+ TAILQ_INSERT_BEFORE(pfd, pfd_new, entries);
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "preserving fd %d", fd);
+ pfd_new = NULL;
+ break;
+ }
+ }
+ if (pfd_new != NULL) {
+ TAILQ_INSERT_TAIL(pfds, pfd_new, entries);
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "preserving fd %d", fd);
+ }
+
+ debug_return_int(0);
+}
+
+/*
+ * Close all descriptors, startfd and higher except those listed
+ * in pfds.
+ */
+void
+closefrom_except(int startfd, struct preserved_fd_list *pfds)
+{
+ int fd, lastfd = -1;
+ struct preserved_fd *pfd, *pfd_next;
+ unsigned char *fdbits;
+ debug_decl(closefrom_except, SUDO_DEBUG_UTIL);
+
+ /* First, relocate preserved fds to be as contiguous as possible. */
+ TAILQ_FOREACH_REVERSE_SAFE(pfd, pfds, preserved_fd_list, entries, pfd_next) {
+ if (pfd->highfd < startfd)
+ continue;
+ fd = dup(pfd->highfd);
+ if (fd == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "dup %d", pfd->highfd);
+ if (errno == EBADF) {
+ TAILQ_REMOVE(pfds, pfd, entries);
+ continue;
+ }
+ /* NOTE: still need to adjust lastfd below with unchanged lowfd. */
+ } else if (fd < pfd->highfd) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "dup %d -> %d", pfd->highfd, pfd->lowfd);
+ sudo_debug_update_fd(pfd->highfd, pfd->lowfd);
+ pfd->lowfd = fd;
+ fd = pfd->highfd;
+ }
+ if (fd != -1)
+ (void) close(fd);
+
+ if (pfd->lowfd > lastfd)
+ lastfd = pfd->lowfd; /* highest (relocated) preserved fd */
+ }
+
+ if (lastfd == -1) {
+ /* No fds to preserve. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "closefrom(%d)", startfd);
+ closefrom(startfd);
+ debug_return;
+ }
+
+ /* Create bitmap of preserved (relocated) fds. */
+ fdbits = calloc((lastfd + NBBY) / NBBY, 1);
+ if (fdbits == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ TAILQ_FOREACH(pfd, pfds, entries) {
+ sudo_setbit(fdbits, pfd->lowfd);
+ }
+
+ /*
+ * Close any unpreserved fds [startfd,lastfd]
+ */
+ for (fd = startfd; fd <= lastfd; fd++) {
+ if (!sudo_isset(fdbits, fd)) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "closing fd %d", fd);
+#ifdef __APPLE__
+ /* Avoid potential libdispatch crash when we close its fds. */
+ (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
+#else
+ (void) close(fd);
+#endif
+ }
+ }
+ free(fdbits);
+
+ /* Let closefrom() do the rest for us. */
+ if (lastfd + 1 > startfd)
+ startfd = lastfd + 1;
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "closefrom(%d)", startfd);
+ closefrom(startfd);
+
+ /* Restore preserved fds and set flags. */
+ TAILQ_FOREACH_REVERSE(pfd, pfds, preserved_fd_list, entries) {
+ if (pfd->lowfd != pfd->highfd) {
+ if (dup2(pfd->lowfd, pfd->highfd) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "dup2(%d, %d): %s", pfd->lowfd, pfd->highfd,
+ strerror(errno));
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "dup2(%d, %d)", pfd->lowfd, pfd->highfd);
+ }
+ if (fcntl(pfd->highfd, F_SETFD, pfd->flags) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "fcntl(%d, F_SETFD, %d): %s", pfd->highfd,
+ pfd->flags, strerror(errno));
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "fcntl(%d, F_SETFD, %d)", pfd->highfd, pfd->flags);
+ }
+ sudo_debug_update_fd(pfd->lowfd, pfd->highfd);
+ (void) close(pfd->lowfd);
+ pfd->lowfd = pfd->highfd;
+ }
+ }
+ debug_return;
+}
+
+/*
+ * Parse a comma-separated list of fds and add them to preserved_fds.
+ */
+void
+parse_preserved_fds(struct preserved_fd_list *pfds, const char *fdstr)
+{
+ const char *cp = fdstr;
+ long lval;
+ char *ep;
+ debug_decl(parse_preserved_fds, SUDO_DEBUG_UTIL);
+
+ do {
+ errno = 0;
+ lval = strtol(cp, &ep, 10);
+ if (ep == cp || (*ep != ',' && *ep != '\0')) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to parse fd string %s", cp);
+ break;
+ }
+ if ((errno == ERANGE && lval == LONG_MAX) || lval < 0 || lval > INT_MAX) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "range error parsing fd string %s", cp);
+ } else {
+ add_preserved_fd(pfds, (int)lval);
+ }
+ cp = ep + 1;
+ } while (*ep != '\0');
+
+ debug_return;
+}
diff --git a/src/regress/noexec/check_noexec.c b/src/regress/noexec/check_noexec.c
new file mode 100644
index 0000000..1e4eb59
--- /dev/null
+++ b/src/regress/noexec/check_noexec.c
@@ -0,0 +1,200 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2016 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+#ifdef HAVE_WORDEXP_H
+# include <wordexp.h>
+#endif
+#include <signal.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+#include "sudo_exec.h"
+
+sudo_dso_public int main(int argc, char *argv[], char *envp[]);
+
+static bool
+report_status(int status, const char *what)
+{
+ bool ret = false;
+
+ /* system() returns -1 for exec failure. */
+ if (status == -1) {
+ printf("%s: OK (%s)\n", getprogname(), what);
+ return true;
+ }
+
+ /* check exit value, expecting 127 for failure */
+ if (WIFEXITED(status)) {
+ int exitval = WEXITSTATUS(status);
+ if (exitval == 127) {
+ printf("%s: OK (%s)\n", getprogname(), what);
+ ret = true;
+ } else {
+ printf("%s: FAIL (%s) [%d]\n", getprogname(), what, exitval);
+ }
+ } else if (WIFSIGNALED(status)) {
+ printf("%s: FAIL (%s) [signal %d]\n", getprogname(), what,
+ WTERMSIG(status));
+ } else {
+ /* should not happen */
+ printf("%s: FAIL (%s) [status %d]\n", getprogname(), what, status);
+ }
+
+ return ret;
+}
+
+static int
+try_execl(void)
+{
+ pid_t child, pid;
+ int status;
+
+ child = fork();
+ switch (child) {
+ case -1:
+ sudo_fatal_nodebug("fork");
+ case 0:
+ /* child */
+ /* Try to exec /bin/true, else exit with value 127. */
+ execl("/bin/true", "true", (char *)0);
+ _exit(127);
+ default:
+ /* parent */
+ do {
+ pid = waitpid(child, &status, 0);
+ } while (pid == -1 && errno == EINTR);
+ if (pid == -1)
+ sudo_fatal_nodebug("waitpid");
+
+ if (report_status(status, "execl"))
+ return 0;
+ return 1;
+ }
+}
+
+static int
+try_system(void)
+{
+ int status;
+
+ /* Try to run /bin/true, system() returns 127 on exec failure. */
+ status = system("/bin/true > /dev/null 2>&1");
+
+ if (report_status(status, "system"))
+ return 0;
+ return 1;
+}
+
+#ifdef HAVE_WORDEXP_H
+static int
+try_wordexp(void)
+{
+ wordexp_t we;
+ int rc, ret = 1;
+
+ /*
+ * sudo_noexec.so prevents command substitution via the WRDE_NOCMD flag
+ * where possible.
+ */
+ rc = wordexp("$(/bin/echo foo)", &we, 0);
+ switch (rc) {
+ case -1:
+ /* sudo's wordexp() wrapper returns -1 if RTLD_NEXT is not supported. */
+ case 127:
+ /* Solaris 10 wordexp() returns 127 for execve() failure. */
+#ifdef WRDE_ERRNO
+ case WRDE_ERRNO:
+ /* Solaris 11 wordexp() returns WRDE_ERRNO for execve() failure. */
+#endif
+ printf("%s: OK (wordexp) [%d]\n", getprogname(), rc);
+ ret = 0;
+ break;
+ case WRDE_SYNTAX:
+ /* FreeBSD returns WRDE_SYNTAX if it can't write to the shell process */
+ printf("%s: OK (wordexp) [WRDE_SYNTAX]\n", getprogname());
+ ret = 0;
+ break;
+ case WRDE_CMDSUB:
+ printf("%s: OK (wordexp) [WRDE_CMDSUB]\n", getprogname());
+ ret = 0;
+ break;
+ case 0:
+ /*
+ * On HP-UX 11.00 we don't seem to be able to add WRDE_NOCMD
+ * but the execve() wrapper prevents the command substitution.
+ */
+ if (we.we_wordc == 0) {
+ printf("%s: OK (wordexp) [%d]\n", getprogname(), rc);
+ wordfree(&we);
+ ret = 0;
+ break;
+ }
+ wordfree(&we);
+ FALLTHROUGH;
+ default:
+ printf("%s: FAIL (wordexp) [%d]\n", getprogname(), rc);
+ break;
+ }
+ return ret;
+}
+#endif
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+ int errors = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_noexec");
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s regress | /path/to/sudo_noexec.so\n", getprogname());
+ exit(EXIT_FAILURE);
+ }
+
+ /* Disable execution for post-exec and re-exec ourself. */
+ if (strcmp(argv[1], "rexec") != 0) {
+ const char *noexec = argv[1];
+ argv[1] = "rexec";
+ execve(argv[0], argv, disable_execute(envp, noexec));
+ sudo_fatalx_nodebug("execve");
+ }
+
+ errors += try_execl();
+ errors += try_system();
+#ifdef HAVE_WORDEXP_H
+ errors += try_wordexp();
+#endif
+
+ return errors;
+}
diff --git a/src/regress/ttyname/check_ttyname.c b/src/regress/ttyname/check_ttyname.c
new file mode 100644
index 0000000..5914f71
--- /dev/null
+++ b/src/regress/ttyname/check_ttyname.c
@@ -0,0 +1,99 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+#include "sudo_debug.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+int sudo_debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER;
+extern char *get_process_ttyname(char *name, size_t namelen);
+
+static int
+match_ttys(const char *tty1, const char *tty2)
+{
+ struct stat sb1, sb2;
+
+ if (tty1 != NULL && tty2 != NULL) {
+ if (strcmp(tty1, tty2) == 0)
+ return 0;
+ /* Could be the same device with a different name. */
+ if (stat(tty1, &sb1) == 0 && S_ISCHR(sb1.st_mode) &&
+ stat(tty2, &sb2) == 0 && S_ISCHR(sb2.st_mode)) {
+ if (sb1.st_rdev == sb2.st_rdev)
+ return 0;
+ }
+ } else if (tty1 == NULL && tty2 == NULL) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ char *tty_libc = NULL, *tty_sudo = NULL;
+ char pathbuf[PATH_MAX];
+ int ret = 1;
+
+ initprogname(argc > 0 ? argv[0] : "check_ttyname");
+
+ /* Lookup tty name using kernel info if possible. */
+ if (get_process_ttyname(pathbuf, sizeof(pathbuf)) != NULL)
+ tty_sudo = pathbuf;
+
+#if defined(HAVE_KINFO_PROC2_NETBSD) || \
+ defined(HAVE_KINFO_PROC_OPENBSD) || \
+ defined(HAVE_KINFO_PROC_FREEBSD) || \
+ defined(HAVE_KINFO_PROC_44BSD) || \
+ defined(HAVE__TTYNAME_DEV) || defined(HAVE_STRUCT_PSINFO_PR_TTYDEV) || \
+ defined(HAVE_PSTAT_GETPROC) || defined(__linux__)
+
+ /* Lookup tty name attached to stdin via libc. */
+ tty_libc = ttyname(STDIN_FILENO);
+#endif
+
+ /* Compare libc and kernel ttys. */
+ ret = match_ttys(tty_libc, tty_sudo);
+ if (ret == 0) {
+ printf("%s: OK (%s)\n", getprogname(), tty_sudo ? tty_sudo : "none");
+ } else if (tty_libc == NULL) {
+ printf("%s: SKIP (%s)\n", getprogname(), tty_sudo ? tty_sudo : "none");
+ ret = 0;
+ } else {
+ printf("%s: FAIL %s (sudo) vs. %s (libc)\n", getprogname(),
+ tty_sudo ? tty_sudo : "none", tty_libc ? tty_libc : "none");
+ }
+
+ return ret;
+}
diff --git a/src/selinux.c b/src/selinux.c
new file mode 100644
index 0000000..a2f73f8
--- /dev/null
+++ b/src/selinux.c
@@ -0,0 +1,512 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2016 Todd C. Miller <Todd.Miller@sudo.ws>
+ * Copyright (c) 2008 Dan Walsh <dwalsh@redhat.com>
+ *
+ * Borrowed heavily from newrole source code
+ * Authors:
+ * Anthony Colatrella
+ * Tim Fraser
+ * Steve Grubb <sgrubb@redhat.com>
+ * Darrel Goeddel <DGoeddel@trustedcs.com>
+ * Michael Thompson <mcthomps@us.ibm.com>
+ * Dan Walsh <dwalsh@redhat.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifdef HAVE_SELINUX
+
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include <selinux/selinux.h> /* for is_selinux_enabled() */
+#include <selinux/context.h> /* for context-mangling functions */
+#include <selinux/get_default_type.h>
+#include <selinux/get_context_list.h>
+
+#ifdef HAVE_LINUX_AUDIT
+# include <libaudit.h>
+#endif
+
+#include "sudo.h"
+#include "sudo_exec.h"
+
+static struct selinux_state {
+ security_context_t old_context;
+ security_context_t new_context;
+ security_context_t tty_con_raw;
+ security_context_t new_tty_con_raw;
+ const char *ttyn;
+ int ttyfd;
+ int enforcing;
+} se_state;
+
+#ifdef HAVE_LINUX_AUDIT
+static int
+audit_role_change(const security_context_t old_context,
+ const security_context_t new_context, const char *ttyn, int result)
+{
+ int au_fd, rc = -1;
+ char *message;
+ debug_decl(audit_role_change, SUDO_DEBUG_SELINUX);
+
+ au_fd = audit_open();
+ if (au_fd == -1) {
+ /* Kernel may not have audit support. */
+ if (errno != EINVAL && errno != EPROTONOSUPPORT && errno != EAFNOSUPPORT
+)
+ sudo_fatal("%s", U_("unable to open audit system"));
+ } else {
+ /* audit role change using the same format as newrole(1) */
+ rc = asprintf(&message, "newrole: old-context=%s new-context=%s",
+ old_context, new_context);
+ if (rc == -1)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ rc = audit_log_user_message(au_fd, AUDIT_USER_ROLE_CHANGE,
+ message, NULL, NULL, ttyn, result);
+ if (rc <= 0)
+ sudo_warn("%s", U_("unable to send audit message"));
+ free(message);
+ close(au_fd);
+ }
+
+ debug_return_int(rc);
+}
+#endif
+
+/*
+ * This function attempts to revert the relabeling done to the tty.
+ * fd - referencing the opened ttyn
+ * ttyn - name of tty to restore
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+int
+selinux_restore_tty(void)
+{
+ int ret = -1;
+ security_context_t chk_tty_con_raw = NULL;
+ debug_decl(selinux_restore_tty, SUDO_DEBUG_SELINUX);
+
+ if (se_state.ttyfd == -1 || se_state.new_tty_con_raw == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: no tty, skip relabel",
+ __func__);
+ debug_return_int(0);
+ }
+
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %s -> %s",
+ __func__, se_state.new_tty_con_raw, se_state.tty_con_raw);
+
+ /* Verify that the tty still has the context set by sudo. */
+ if (fgetfilecon_raw(se_state.ttyfd, &chk_tty_con_raw) == -1) {
+ sudo_warn(U_("unable to fgetfilecon %s"), se_state.ttyn);
+ goto skip_relabel;
+ }
+
+ if (strcmp(chk_tty_con_raw, se_state.new_tty_con_raw) != 0) {
+ sudo_warnx(U_("%s changed labels"), se_state.ttyn);
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: not restoring tty label, expected %s, have %s",
+ __func__, se_state.new_tty_con_raw, chk_tty_con_raw);
+ goto skip_relabel;
+ }
+
+ if (fsetfilecon_raw(se_state.ttyfd, se_state.tty_con_raw) == -1) {
+ sudo_warn(U_("unable to restore context for %s"), se_state.ttyn);
+ goto skip_relabel;
+ }
+
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: successfully set tty label to %s",
+ __func__, se_state.tty_con_raw);
+ ret = 0;
+
+skip_relabel:
+ if (se_state.ttyfd != -1) {
+ close(se_state.ttyfd);
+ se_state.ttyfd = -1;
+ }
+ freecon(chk_tty_con_raw);
+ debug_return_int(ret);
+}
+
+/*
+ * This function attempts to relabel the tty. If this function fails, then
+ * the contexts are free'd and -1 is returned. On success, 0 is returned
+ * and tty_con_raw and new_tty_con_raw are set.
+ *
+ * This function will not fail if it can not relabel the tty when selinux is
+ * in permissive mode.
+ */
+static int
+relabel_tty(const char *ttyn, int ptyfd)
+{
+ security_context_t tty_con = NULL;
+ security_context_t new_tty_con = NULL;
+ struct stat sb;
+ int fd;
+ debug_decl(relabel_tty, SUDO_DEBUG_SELINUX);
+
+ se_state.ttyfd = ptyfd;
+
+ /* It is perfectly legal to have no tty. */
+ if (ptyfd == -1 && ttyn == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: no tty, skip relabel",
+ __func__);
+ debug_return_int(0);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: relabeling tty %s", __func__, ttyn);
+
+ /* If sudo is not allocating a pty for the command, open current tty. */
+ if (ptyfd == -1) {
+ se_state.ttyfd = open(ttyn, O_RDWR|O_NOCTTY|O_NONBLOCK);
+ if (se_state.ttyfd == -1 || fstat(se_state.ttyfd, &sb) == -1) {
+ sudo_warn(U_("unable to open %s, not relabeling tty"), ttyn);
+ goto bad;
+ }
+ if (!S_ISCHR(sb.st_mode)) {
+ sudo_warn(U_("%s is not a character device, not relabeling tty"),
+ ttyn);
+ goto bad;
+ }
+ (void)fcntl(se_state.ttyfd, F_SETFL,
+ fcntl(se_state.ttyfd, F_GETFL, 0) & ~O_NONBLOCK);
+ }
+
+ if (fgetfilecon(se_state.ttyfd, &tty_con) == -1) {
+ sudo_warn("%s", U_("unable to get current tty context, not relabeling tty"));
+ goto bad;
+ }
+
+ if (tty_con != NULL) {
+ security_class_t tclass = string_to_security_class("chr_file");
+ if (tclass == 0) {
+ sudo_warn("%s", U_("unknown security class \"chr_file\", not relabeling tty"));
+ goto bad;
+ }
+ if (security_compute_relabel(se_state.new_context, tty_con,
+ tclass, &new_tty_con) == -1) {
+ sudo_warn("%s", U_("unable to get new tty context, not relabeling tty"));
+ goto bad;
+ }
+ }
+
+ if (new_tty_con != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: tty context %s -> %s",
+ __func__, tty_con, new_tty_con);
+ if (fsetfilecon(se_state.ttyfd, new_tty_con) == -1) {
+ sudo_warn("%s", U_("unable to set new tty context"));
+ goto bad;
+ }
+ }
+
+ if (ptyfd != -1) {
+ int oflags, flags = 0;
+
+ /* Reopen pty that was relabeled, std{in,out,err} are reset later. */
+ se_state.ttyfd = open(ttyn, O_RDWR|O_NOCTTY, 0);
+ if (se_state.ttyfd == -1 || fstat(se_state.ttyfd, &sb) == -1) {
+ sudo_warn(U_("unable to open %s"), ttyn);
+ goto bad;
+ }
+ if (!S_ISCHR(sb.st_mode)) {
+ sudo_warn(U_("%s is not a character device, not relabeling tty"),
+ ttyn);
+ goto bad;
+ }
+ /* Preserve O_NONBLOCK and the close-on-exec flags. */
+ if ((oflags = fcntl(ptyfd, F_GETFL)) == -1) {
+ sudo_warn("F_GETFL");
+ goto bad;
+ }
+ if (ISSET(oflags, O_NONBLOCK))
+ flags |= O_NONBLOCK;
+ if ((oflags = fcntl(ptyfd, F_GETFD)) == -1) {
+ sudo_warn("F_GETFD");
+ goto bad;
+ }
+ if (ISSET(oflags, FD_CLOEXEC))
+ flags |= O_CLOEXEC;
+ if (dup3(se_state.ttyfd, ptyfd, flags) == -1) {
+ sudo_warn("dup3");
+ goto bad;
+ }
+ } else {
+ /* Re-open tty to get new label and reset std{in,out,err} */
+ close(se_state.ttyfd);
+ se_state.ttyfd = open(ttyn, O_RDWR|O_NOCTTY|O_NONBLOCK);
+ if (se_state.ttyfd == -1 || fstat(se_state.ttyfd, &sb) == -1) {
+ sudo_warn(U_("unable to open %s"), ttyn);
+ goto bad;
+ }
+ if (!S_ISCHR(sb.st_mode)) {
+ sudo_warn(U_("%s is not a character device, not relabeling tty"),
+ ttyn);
+ goto bad;
+ }
+ (void)fcntl(se_state.ttyfd, F_SETFL,
+ fcntl(se_state.ttyfd, F_GETFL, 0) & ~O_NONBLOCK);
+ for (fd = STDIN_FILENO; fd <= STDERR_FILENO; fd++) {
+ if (isatty(fd) && dup2(se_state.ttyfd, fd) == -1) {
+ sudo_warn("dup2");
+ goto bad;
+ }
+ }
+ }
+ /* Retain se_state.ttyfd so we can restore label when command finishes. */
+ (void)fcntl(se_state.ttyfd, F_SETFD, FD_CLOEXEC);
+
+ se_state.ttyn = ttyn;
+ if (selinux_trans_to_raw_context(tty_con, &se_state.tty_con_raw) == -1)
+ goto bad;
+ if (selinux_trans_to_raw_context(new_tty_con, &se_state.new_tty_con_raw) == -1)
+ goto bad;
+ freecon(tty_con);
+ freecon(new_tty_con);
+ debug_return_int(0);
+
+bad:
+ if (se_state.ttyfd != -1 && se_state.ttyfd != ptyfd) {
+ close(se_state.ttyfd);
+ se_state.ttyfd = -1;
+ }
+ freecon(se_state.tty_con_raw);
+ se_state.tty_con_raw = NULL;
+ freecon(se_state.new_tty_con_raw);
+ se_state.new_tty_con_raw = NULL;
+ freecon(tty_con);
+ freecon(new_tty_con);
+ debug_return_int(se_state.enforcing ? -1 : 0);
+}
+
+/*
+ * Returns a new security context based on the old context and the
+ * specified role and type.
+ */
+security_context_t
+get_exec_context(security_context_t old_context, const char *role, const char *type)
+{
+ security_context_t new_context = NULL;
+ context_t context = NULL;
+ char *typebuf = NULL;
+ debug_decl(get_exec_context, SUDO_DEBUG_SELINUX);
+
+ /* We must have a role, the type is optional (we can use the default). */
+ if (role == NULL) {
+ sudo_warnx(U_("you must specify a role for type %s"), type);
+ errno = EINVAL;
+ goto bad;
+ }
+ if (type == NULL) {
+ if (get_default_type(role, &typebuf)) {
+ sudo_warnx(U_("unable to get default type for role %s"), role);
+ errno = EINVAL;
+ goto bad;
+ }
+ type = typebuf;
+ }
+
+ /*
+ * Expand old_context into a context_t so that we can extract and modify
+ * its components easily.
+ */
+ if ((context = context_new(old_context)) == NULL) {
+ sudo_warn("%s", U_("failed to get new context"));
+ goto bad;
+ }
+
+ /*
+ * Replace the role and type in "context" with the role and
+ * type we will be running the command as.
+ */
+ if (context_role_set(context, role)) {
+ sudo_warn(U_("failed to set new role %s"), role);
+ goto bad;
+ }
+ if (context_type_set(context, type)) {
+ sudo_warn(U_("failed to set new type %s"), type);
+ goto bad;
+ }
+
+ /*
+ * Convert "context" back into a string and verify it.
+ */
+ if ((new_context = strdup(context_str(context))) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto bad;
+ }
+ if (security_check_context(new_context) == -1) {
+ sudo_warnx(U_("%s is not a valid context"), new_context);
+ errno = EINVAL;
+ goto bad;
+ }
+
+ context_free(context);
+ debug_return_str(new_context);
+
+bad:
+ free(typebuf);
+ context_free(context);
+ freecon(new_context);
+ debug_return_str(NULL);
+}
+
+/*
+ * Determine the exec and tty contexts in preparation for fork/exec.
+ * Must run as root, before forking the child process.
+ * Sets the tty context but not the exec context (which happens later).
+ * If ptyfd is not -1, it indicates we are running
+ * in a pty and do not need to reset std{in,out,err}.
+ * Returns 0 on success and -1 on failure.
+ */
+int
+selinux_setup(const char *role, const char *type, const char *ttyn,
+ int ptyfd, bool label_tty)
+{
+ int ret = -1;
+ debug_decl(selinux_setup, SUDO_DEBUG_SELINUX);
+
+ /* Store the caller's SID in old_context. */
+ if (getprevcon(&se_state.old_context)) {
+ sudo_warn("%s", U_("failed to get old context"));
+ goto done;
+ }
+
+ se_state.enforcing = security_getenforce();
+ if (se_state.enforcing == -1) {
+ sudo_warn("%s", U_("unable to determine enforcing mode."));
+ goto done;
+ }
+
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: old context %s", __func__,
+ se_state.old_context);
+ se_state.new_context = get_exec_context(se_state.old_context, role, type);
+ if (se_state.new_context == NULL) {
+#ifdef HAVE_LINUX_AUDIT
+ audit_role_change(se_state.old_context, "?", se_state.ttyn, 0);
+#endif
+ goto done;
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: new context %s", __func__,
+ se_state.new_context);
+
+ if (label_tty && relabel_tty(ttyn, ptyfd) == -1) {
+ sudo_warn(U_("unable to set tty context to %s"), se_state.new_context);
+ goto done;
+ }
+
+#ifdef HAVE_LINUX_AUDIT
+ audit_role_change(se_state.old_context, se_state.new_context,
+ se_state.ttyn, 1);
+#endif
+
+ ret = 0;
+
+done:
+ debug_return_int(ret);
+}
+
+int
+selinux_setcon(void)
+{
+ debug_decl(selinux_setcon, SUDO_DEBUG_SELINUX);
+
+ if (setexeccon(se_state.new_context)) {
+ sudo_warn(U_("unable to set exec context to %s"), se_state.new_context);
+ if (se_state.enforcing)
+ debug_return_int(-1);
+ }
+
+#ifdef HAVE_SETKEYCREATECON
+ if (setkeycreatecon(se_state.new_context)) {
+ sudo_warn(U_("unable to set key creation context to %s"), se_state.new_context);
+ if (se_state.enforcing)
+ debug_return_int(-1);
+ }
+#endif /* HAVE_SETKEYCREATECON */
+
+ debug_return_int(0);
+}
+
+void
+selinux_execve(int fd, const char *path, char *const argv[], char *envp[],
+ bool noexec)
+{
+ char **nargv;
+ const char *sesh;
+ int argc, nargc, serrno;
+ debug_decl(selinux_execve, SUDO_DEBUG_SELINUX);
+
+ sesh = sudo_conf_sesh_path();
+ if (sesh == NULL) {
+ sudo_warnx("internal error: sesh path not set");
+ errno = EINVAL;
+ debug_return;
+ }
+
+ /* Set SELinux exec and keycreate contexts. */
+ if (selinux_setcon() == -1)
+ debug_return;
+
+ /*
+ * Build new argv with sesh as argv[0].
+ * If argv[0] ends in -noexec, sesh will disable execute
+ * for the command it runs.
+ */
+ for (argc = 0; argv[argc] != NULL; argc++)
+ continue;
+ if (argc == 0) {
+ errno = EINVAL;
+ debug_return;
+ }
+ nargv = reallocarray(NULL, argc + 3, sizeof(char *));
+ if (nargv == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return;
+ }
+ if (noexec)
+ nargv[0] = *argv[0] == '-' ? "-sesh-noexec" : "sesh-noexec";
+ else
+ nargv[0] = *argv[0] == '-' ? "-sesh" : "sesh";
+ nargc = 1;
+ if (fd != -1 && asprintf(&nargv[nargc++], "--execfd=%d", fd) == -1) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return;
+ }
+ nargv[nargc++] = (char *)path;
+ memcpy(&nargv[nargc], &argv[1], argc * sizeof(char *)); /* copies NULL */
+
+ /* sesh will handle noexec for us. */
+ sudo_execve(-1, sesh, nargv, envp, false);
+ serrno = errno;
+ free(nargv);
+ errno = serrno;
+ debug_return;
+}
+
+#endif /* HAVE_SELINUX */
diff --git a/src/sesh.c b/src/sesh.c
new file mode 100644
index 0000000..f8f4654
--- /dev/null
+++ b/src/sesh.c
@@ -0,0 +1,424 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008, 2010-2018, 2020-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+
+#include "sudo.h"
+#include "sudo_exec.h"
+#include "sudo_edit.h"
+
+sudo_dso_public int main(int argc, char *argv[], char *envp[]);
+
+static int sesh_sudoedit(int argc, char *argv[]);
+
+/*
+ * Exit codes defined in sudo_exec.h:
+ * SESH_SUCCESS (0) ... successful operation
+ * SESH_ERR_FAILURE (1) ... unspecified error
+ * SESH_ERR_INVALID (30) ... invalid -e arg value
+ * SESH_ERR_BAD_PATHS (31) ... odd number of paths
+ * SESH_ERR_NO_FILES (32) ... copy error, no files copied
+ * SESH_ERR_SOME_FILES (33) ... copy error, no files copied
+ */
+int
+main(int argc, char *argv[], char *envp[])
+{
+ int ret;
+ debug_decl(main, SUDO_DEBUG_MAIN);
+
+ initprogname(argc > 0 ? argv[0] : "sesh");
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE_NAME, LOCALEDIR);
+ textdomain(PACKAGE_NAME);
+
+ if (argc < 2)
+ sudo_fatalx("%s", U_("requires at least one argument"));
+
+ /* Read sudo.conf and initialize the debug subsystem. */
+ if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) == -1)
+ exit(EXIT_FAILURE);
+ sudo_debug_register(getprogname(), NULL, NULL,
+ sudo_conf_debug_files(getprogname()));
+
+ if (strcmp(argv[1], "-e") == 0) {
+ ret = sesh_sudoedit(argc, argv);
+ } else {
+ bool login_shell, noexec = false;
+ char *cp, *cmnd;
+ int fd = -1;
+
+ /* If the first char of argv[0] is '-', we are running a login shell. */
+ login_shell = argv[0][0] == '-';
+
+ /* If argv[0] ends in -noexec, pass the flag to sudo_execve() */
+ if ((cp = strrchr(argv[0], '-')) != NULL && cp != argv[0])
+ noexec = strcmp(cp, "-noexec") == 0;
+
+ /* If argv[1] is --execfd=%d, extract the fd to exec with. */
+ if (strncmp(argv[1], "--execfd=", 9) == 0) {
+ const char *errstr;
+
+ cp = argv[1] + 9;
+ fd = sudo_strtonum(cp, 0, INT_MAX, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("invalid file descriptor number: %s"), cp);
+ argv++;
+ argc--;
+ }
+
+ /* Shift argv and make a copy of the command to execute. */
+ argv++;
+ argc--;
+ if ((cmnd = strdup(argv[0])) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ /* If invoked as a login shell, modify argv[0] accordingly. */
+ if (login_shell) {
+ if ((cp = strrchr(argv[0], '/')) == NULL)
+ sudo_fatal(U_("unable to run %s as a login shell"), argv[0]);
+ *cp = '-';
+ argv[0] = cp;
+ }
+ sudo_execve(fd, cmnd, argv, envp, noexec);
+ sudo_warn(U_("unable to execute %s"), cmnd);
+ ret = SESH_ERR_FAILURE;
+ }
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, ret);
+ _exit(ret);
+}
+
+/*
+ * Destructively parse a string in the format:
+ * uid:gid:groups,...
+ *
+ * On success, fills in ud and returns true, else false.
+ */
+static bool
+parse_user(char *userstr, struct sudo_cred *cred)
+{
+ char *cp, *ep;
+ const char *errstr;
+ debug_decl(parse_user, SUDO_DEBUG_EDIT);
+
+ /* UID */
+ cp = userstr;
+ if ((ep = strchr(cp, ':')) == NULL) {
+ sudo_warnx(U_("%s: %s"), cp, U_("invalid value"));
+ debug_return_bool(false);
+ }
+ *ep++ = '\0';
+ cred->uid = cred->euid = sudo_strtoid(cp, &errstr);
+ if (errstr != NULL) {
+ sudo_warnx(U_("%s: %s"), cp, errstr);
+ debug_return_bool(false);
+ }
+
+ /* GID */
+ cp = ep;
+ if ((ep = strchr(cp, ':')) == NULL) {
+ sudo_warnx(U_("%s: %s"), cp, U_("invalid value"));
+ debug_return_bool(false);
+ }
+ *ep++ = '\0';
+ cred->gid = cred->egid = sudo_strtoid(cp, &errstr);
+ if (errstr != NULL) {
+ sudo_warnx(U_("%s: %s"), cp, errstr);
+ debug_return_bool(false);
+ }
+
+ /* group vector */
+ cp = ep;
+ cred->ngroups = sudo_parse_gids(cp, NULL, &cred->groups);
+ if (cred->ngroups == -1)
+ debug_return_bool(false);
+
+ debug_return_bool(true);
+}
+
+static int
+sesh_edit_create_tfiles(int edit_flags, struct sudo_cred *user_cred,
+ struct sudo_cred *run_cred, int argc, char *argv[])
+{
+ int i, fd_src = -1, fd_dst = -1;
+ struct timespec times[2];
+ struct stat sb;
+ debug_decl(sesh_edit_create_tfiles, SUDO_DEBUG_EDIT);
+
+ for (i = 0; i < argc - 1; i += 2) {
+ char *path_src = argv[i];
+ const char *path_dst = argv[i + 1];
+
+ /*
+ * Try to open the source file for reading.
+ * If it doesn't exist, we'll create an empty destination file.
+ */
+ fd_src = sudo_edit_open(path_src, O_RDONLY,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, edit_flags, user_cred, run_cred);
+ if (fd_src == -1) {
+ if (errno != ENOENT) {
+ if (errno == ELOOP) {
+ sudo_warnx(U_("%s: editing symbolic links is not "
+ "permitted"), path_src);
+ } else if (errno == EISDIR) {
+ sudo_warnx(U_("%s: editing files in a writable directory "
+ "is not permitted"), path_src);
+ } else {
+ sudo_warn("%s", path_src);
+ }
+ goto cleanup;
+ }
+ /* New file, verify parent dir exists and is not writable. */
+ if (!sudo_edit_parent_valid(path_src, edit_flags, user_cred, run_cred))
+ goto cleanup;
+ }
+ if (fd_src == -1) {
+ /* New file. */
+ memset(&sb, 0, sizeof(sb));
+ } else if (fstat(fd_src, &sb) == -1 || !S_ISREG(sb.st_mode)) {
+ sudo_warnx(U_("%s: not a regular file"), path_src);
+ goto cleanup;
+ }
+
+ /*
+ * Create temporary file using O_EXCL to ensure that temporary
+ * files are created by us and that we do not open any symlinks.
+ */
+ fd_dst = open(path_dst, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
+ if (fd_dst == -1) {
+ sudo_warn("%s", path_dst);
+ goto cleanup;
+ }
+
+ if (fd_src != -1) {
+ if (sudo_copy_file(path_src, fd_src, -1, path_dst, fd_dst, -1) == -1)
+ goto cleanup;
+ close(fd_src);
+ }
+
+ /* Make mtime on temp file match src (sb filled in above). */
+ mtim_get(&sb, times[0]);
+ times[1].tv_sec = times[0].tv_sec;
+ times[1].tv_nsec = times[0].tv_nsec;
+ if (futimens(fd_dst, times) == -1) {
+ if (utimensat(AT_FDCWD, path_dst, times, 0) == -1)
+ sudo_warn("%s", path_dst);
+ }
+ close(fd_dst);
+ fd_dst = -1;
+ }
+ debug_return_int(SESH_SUCCESS);
+
+cleanup:
+ /* Remove temporary files. */
+ for (i = 0; i < argc - 1; i += 2)
+ unlink(argv[i + 1]);
+ if (fd_src != -1)
+ close(fd_src);
+ if (fd_dst != -1)
+ close(fd_dst);
+ debug_return_int(SESH_ERR_NO_FILES);
+}
+
+static int
+sesh_edit_copy_tfiles(int edit_flags, struct sudo_cred *user_cred,
+ struct sudo_cred *run_cred, int argc, char *argv[])
+{
+ int i, ret = SESH_SUCCESS;
+ int fd_src = -1, fd_dst = -1;
+ debug_decl(sesh_edit_copy_tfiles, SUDO_DEBUG_EDIT);
+
+ for (i = 0; i < argc - 1; i += 2) {
+ const char *path_src = argv[i];
+ char *path_dst = argv[i + 1];
+ off_t len_src, len_dst;
+ struct stat sb;
+
+ /* Open temporary file for reading. */
+ if (fd_src != -1)
+ close(fd_src);
+ fd_src = open(path_src, O_RDONLY|O_NONBLOCK|O_NOFOLLOW);
+ if (fd_src == -1) {
+ sudo_warn("%s", path_src);
+ ret = SESH_ERR_SOME_FILES;
+ continue;
+ }
+ /* Make sure the temporary file is safe and has the proper owner. */
+ if (!sudo_check_temp_file(fd_src, path_src, run_cred->uid, &sb)) {
+ sudo_warnx(U_("contents of edit session left in %s"), path_src);
+ ret = SESH_ERR_SOME_FILES;
+ continue;
+ }
+ (void) fcntl(fd_src, F_SETFL, fcntl(fd_src, F_GETFL, 0) & ~O_NONBLOCK);
+
+ /* Create destination file. */
+ if (fd_dst != -1)
+ close(fd_dst);
+ fd_dst = sudo_edit_open(path_dst, O_WRONLY|O_CREAT,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, edit_flags, user_cred, run_cred);
+ if (fd_dst == -1) {
+ if (errno == ELOOP) {
+ sudo_warnx(U_("%s: editing symbolic links is not "
+ "permitted"), path_dst);
+ } else if (errno == EISDIR) {
+ sudo_warnx(U_("%s: editing files in a writable directory "
+ "is not permitted"), path_dst);
+ } else {
+ sudo_warn("%s", path_dst);
+ }
+ sudo_warnx(U_("contents of edit session left in %s"), path_src);
+ ret = SESH_ERR_SOME_FILES;
+ continue;
+ }
+
+ /* sudo_check_temp_file() filled in sb for us. */
+ len_src = sb.st_size;
+ if (fstat(fd_dst, &sb) != 0) {
+ sudo_warn("%s", path_dst);
+ sudo_warnx(U_("contents of edit session left in %s"), path_src);
+ ret = SESH_ERR_SOME_FILES;
+ continue;
+ }
+ len_dst = sb.st_size;
+
+ if (sudo_copy_file(path_src, fd_src, len_src, path_dst, fd_dst,
+ len_dst) == -1) {
+ sudo_warnx(U_("contents of edit session left in %s"), path_src);
+ ret = SESH_ERR_SOME_FILES;
+ continue;
+ }
+ unlink(path_src);
+ }
+ if (fd_src != -1)
+ close(fd_src);
+ if (fd_dst != -1)
+ close(fd_dst);
+
+ debug_return_int(ret);
+}
+
+static int
+sesh_sudoedit(int argc, char *argv[])
+{
+ int edit_flags, post, ret;
+ struct sudo_cred user_cred, run_cred;
+ debug_decl(sesh_sudoedit, SUDO_DEBUG_EDIT);
+
+ memset(&user_cred, 0, sizeof(user_cred));
+ memset(&run_cred, 0, sizeof(run_cred));
+ edit_flags = CD_SUDOEDIT_FOLLOW;
+
+ /* Check for -h flag (don't follow links). */
+ if (argv[2] != NULL && strcmp(argv[2], "-h") == 0) {
+ argv++;
+ argc--;
+ CLR(edit_flags, CD_SUDOEDIT_FOLLOW); // -V753
+ }
+
+ /* Check for -w flag (disallow directories writable by the user). */
+ if (argv[2] != NULL && strcmp(argv[2], "-w") == 0) {
+ SET(edit_flags, CD_SUDOEDIT_CHECKDIR);
+
+ /* Parse uid:gid:gid1,gid2,... */
+ if (argv[3] == NULL || !parse_user(argv[3], &user_cred))
+ debug_return_int(SESH_ERR_FAILURE);
+ argv += 2;
+ argc -= 2;
+ }
+
+ if (argc < 3)
+ debug_return_int(SESH_ERR_FAILURE);
+
+ /*
+ * We need to know whether we are performing the copy operation
+ * before or after the editing. Without this we would not know
+ * which files are temporary and which are the originals.
+ * post = 0 ... before
+ * post = 1 ... after
+ */
+ if (strcmp(argv[2], "0") == 0)
+ post = 0;
+ else if (strcmp(argv[2], "1") == 0)
+ post = 1;
+ else /* invalid value */
+ debug_return_int(SESH_ERR_INVALID);
+
+ /* Align argv & argc to the beginning of the file list. */
+ argv += 3;
+ argc -= 3;
+
+ /* no files specified, nothing to do */
+ if (argc == 0)
+ debug_return_int(SESH_SUCCESS);
+ /* odd number of paths specified */
+ if (argc & 1)
+ debug_return_int(SESH_ERR_BAD_PATHS);
+
+ /* Masquerade as sudoedit so the user gets consistent error messages. */
+ setprogname("sudoedit");
+
+ /*
+ * sudoedit runs us with the effective user-ID and group-ID of
+ * the target user as well as with the target user's group list.
+ */
+ run_cred.uid = run_cred.euid = geteuid();
+ run_cred.gid = run_cred.egid = getegid();
+ run_cred.ngroups = getgroups(0, NULL); // -V575
+ if (run_cred.ngroups > 0) {
+ run_cred.groups = reallocarray(NULL, run_cred.ngroups,
+ sizeof(GETGROUPS_T));
+ if (run_cred.groups == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__,
+ U_("unable to allocate memory"));
+ debug_return_int(SESH_ERR_FAILURE);
+ }
+ if (getgroups(run_cred.ngroups, run_cred.groups) < 0) {
+ sudo_warn("%s", U_("unable to get group list"));
+ debug_return_int(SESH_ERR_FAILURE);
+ }
+ } else {
+ run_cred.ngroups = 0;
+ run_cred.groups = NULL;
+ }
+
+ ret = post ?
+ sesh_edit_copy_tfiles(edit_flags, &user_cred, &run_cred, argc, argv) :
+ sesh_edit_create_tfiles(edit_flags, &user_cred, &run_cred, argc, argv);
+ debug_return_int(ret);
+}
diff --git a/src/signal.c b/src/signal.c
new file mode 100644
index 0000000..7f90d70
--- /dev/null
+++ b/src/signal.c
@@ -0,0 +1,184 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2016 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+
+#include "sudo.h"
+#include "sudo_exec.h"
+
+static struct signal_state {
+ int signo;
+ int restore;
+ struct sigaction sa;
+} saved_signals[] = {
+ { SIGALRM }, /* SAVED_SIGALRM */
+ { SIGCHLD }, /* SAVED_SIGCHLD */
+ { SIGCONT }, /* SAVED_SIGCONT */
+ { SIGHUP }, /* SAVED_SIGHUP */
+ { SIGINT }, /* SAVED_SIGINT */
+ { SIGPIPE }, /* SAVED_SIGPIPE */
+ { SIGQUIT }, /* SAVED_SIGQUIT */
+ { SIGTERM }, /* SAVED_SIGTERM */
+ { SIGTSTP }, /* SAVED_SIGTSTP */
+ { SIGTTIN }, /* SAVED_SIGTTIN */
+ { SIGTTOU }, /* SAVED_SIGTTOU */
+ { SIGUSR1 }, /* SAVED_SIGUSR1 */
+ { SIGUSR2 }, /* SAVED_SIGUSR2 */
+ { -1 }
+};
+
+static sig_atomic_t pending_signals[NSIG];
+
+static void
+sudo_handler(int signo)
+{
+ /* Mark signal as pending. */
+ pending_signals[signo] = 1;
+}
+
+bool
+signal_pending(int signo)
+{
+ return pending_signals[signo] == 1;
+}
+
+/*
+ * Save signal handler state so it can be restored before exec.
+ */
+void
+save_signals(void)
+{
+ struct signal_state *ss;
+ debug_decl(save_signals, SUDO_DEBUG_MAIN);
+
+ for (ss = saved_signals; ss->signo != -1; ss++) {
+ if (sigaction(ss->signo, NULL, &ss->sa) != 0)
+ sudo_warn(U_("unable to save handler for signal %d"), ss->signo);
+ }
+
+ debug_return;
+}
+
+/*
+ * Restore signal handlers to initial state for exec.
+ */
+void
+restore_signals(void)
+{
+ struct signal_state *ss;
+ debug_decl(restore_signals, SUDO_DEBUG_MAIN);
+
+ for (ss = saved_signals; ss->signo != -1; ss++) {
+ if (ss->restore) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "restoring handler for signal %d: %s", ss->signo,
+ ss->sa.sa_handler == SIG_IGN ? "SIG_IGN" :
+ ss->sa.sa_handler == SIG_DFL ? "SIG_DFL" : "???");
+ if (sigaction(ss->signo, &ss->sa, NULL) != 0) {
+ sudo_warn(U_("unable to restore handler for signal %d"),
+ ss->signo);
+ }
+ }
+ }
+
+ debug_return;
+}
+
+/*
+ * Trap tty-generated (and other) signals so we can't be killed before
+ * calling the policy close function. The signal pipe will be drained
+ * in sudo_execute() before running the command and new handlers will
+ * be installed in the parent.
+ */
+void
+init_signals(void)
+{
+ struct sigaction sa;
+ struct signal_state *ss;
+ debug_decl(init_signals, SUDO_DEBUG_MAIN);
+
+ memset(&sa, 0, sizeof(sa));
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = sudo_handler;
+
+ for (ss = saved_signals; ss->signo > 0; ss++) {
+ switch (ss->signo) {
+ case SIGCONT:
+ case SIGPIPE:
+ case SIGTTIN:
+ case SIGTTOU:
+ /* Don't install these until exec time. */
+ break;
+ default:
+ if (ss->sa.sa_handler != SIG_IGN) {
+ if (sigaction(ss->signo, &sa, NULL) != 0) {
+ sudo_warn(U_("unable to set handler for signal %d"),
+ ss->signo);
+ }
+ }
+ break;
+ }
+ }
+ /* Ignore SIGPIPE until exec. */
+ if (saved_signals[SAVED_SIGPIPE].sa.sa_handler != SIG_IGN) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "will restore signal %d on exec", SIGPIPE);
+ saved_signals[SAVED_SIGPIPE].restore = true;
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sa, NULL) != 0)
+ sudo_warn(U_("unable to set handler for signal %d"), SIGPIPE);
+ }
+
+ debug_return;
+}
+
+/*
+ * Like sigaction() but sets restore flag in saved_signals[]
+ * if needed.
+ */
+int
+sudo_sigaction(int signo, struct sigaction *sa, struct sigaction *osa)
+{
+ struct signal_state *ss;
+ int ret;
+ debug_decl(sudo_sigaction, SUDO_DEBUG_MAIN);
+
+ for (ss = saved_signals; ss->signo > 0; ss++) {
+ if (ss->signo == signo) {
+ /* If signal was or now is ignored, restore old handler on exec. */
+ if (ss->sa.sa_handler == SIG_IGN || sa->sa_handler == SIG_IGN) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "will restore signal %d on exec", signo);
+ ss->restore = true;
+ }
+ break;
+ }
+ }
+ ret = sigaction(signo, sa, osa);
+
+ debug_return_int(ret);
+}
diff --git a/src/solaris.c b/src/solaris.c
new file mode 100644
index 0000000..754664f
--- /dev/null
+++ b/src/solaris.c
@@ -0,0 +1,117 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifdef HAVE_PROJECT_H
+# include <project.h>
+# include <sys/task.h>
+# include <errno.h>
+# include <pwd.h>
+#endif
+
+#include "sudo.h"
+#include "sudo_dso.h"
+
+int
+os_init(int argc, char *argv[], char *envp[])
+{
+ /*
+ * Solaris 11 is unable to load the per-locale shared objects
+ * without this. We must keep the handle open for it to work.
+ * This bug was fixed in Solaris 11 Update 1.
+ */
+ void *handle = sudo_dso_load("/usr/lib/locale/common/methods_unicode.so.3",
+ SUDO_DSO_LAZY|SUDO_DSO_GLOBAL);
+ (void)&handle;
+
+ return os_init_common(argc, argv, envp);
+}
+
+#ifdef HAVE_PROJECT_H
+void
+set_project(struct passwd *pw)
+{
+ struct project proj;
+ char buf[PROJECT_BUFSZ];
+ int errval;
+ debug_decl(set_project, SUDO_DEBUG_UTIL);
+
+ /*
+ * Collect the default project for the user and settaskid
+ */
+ setprojent();
+ if (getdefaultproj(pw->pw_name, &proj, buf, sizeof(buf)) != NULL) {
+ errval = setproject(proj.pj_name, pw->pw_name, TASK_NORMAL);
+ switch(errval) {
+ case 0:
+ break;
+ case SETPROJ_ERR_TASK:
+ switch (errno) {
+ case EAGAIN:
+ sudo_warnx("%s", U_("resource control limit has been reached"));
+ break;
+ case ESRCH:
+ sudo_warnx(U_("user \"%s\" is not a member of project \"%s\""),
+ pw->pw_name, proj.pj_name);
+ break;
+ case EACCES:
+ sudo_warnx("%s", U_("the invoking task is final"));
+ break;
+ default:
+ sudo_warnx(U_("could not join project \"%s\""), proj.pj_name);
+ break;
+ }
+ break;
+ case SETPROJ_ERR_POOL:
+ switch (errno) {
+ case EACCES:
+ sudo_warnx(U_("no resource pool accepting default bindings "
+ "exists for project \"%s\""), proj.pj_name);
+ break;
+ case ESRCH:
+ sudo_warnx(U_("specified resource pool does not exist for "
+ "project \"%s\""), proj.pj_name);
+ break;
+ default:
+ sudo_warnx(U_("could not bind to default resource pool for "
+ "project \"%s\""), proj.pj_name);
+ break;
+ }
+ break;
+ default:
+ if (errval <= 0) {
+ sudo_warnx(U_("setproject failed for project \"%s\""), proj.pj_name);
+ } else {
+ sudo_warnx(U_("warning, resource control assignment failed for "
+ "project \"%s\""), proj.pj_name);
+ }
+ break;
+ }
+ } else {
+ sudo_warn("getdefaultproj");
+ }
+ endprojent();
+ debug_return;
+}
+#endif /* HAVE_PROJECT_H */
diff --git a/src/sudo.c b/src/sudo.c
new file mode 100644
index 0000000..4c6c60e
--- /dev/null
+++ b/src/sudo.c
@@ -0,0 +1,2188 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#ifdef __TANDEM
+# include <floss.h>
+#endif
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <grp.h>
+#include <pwd.h>
+#include <time.h>
+#ifdef HAVE_SELINUX
+# include <selinux/selinux.h> /* for is_selinux_enabled() */
+#endif
+#ifdef HAVE_SETAUTHDB
+# include <usersec.h>
+#endif /* HAVE_SETAUTHDB */
+#if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS)
+# ifdef __hpux
+# undef MAXINT
+# include <hpsecurity.h>
+# else
+# include <sys/security.h>
+# endif /* __hpux */
+# include <prot.h>
+#endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */
+
+#include <sudo_usage.h>
+#include "sudo.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+/*
+ * Local variables
+ */
+struct plugin_container policy_plugin;
+struct plugin_container_list io_plugins = TAILQ_HEAD_INITIALIZER(io_plugins);
+struct plugin_container_list audit_plugins = TAILQ_HEAD_INITIALIZER(audit_plugins);
+struct plugin_container_list approval_plugins = TAILQ_HEAD_INITIALIZER(approval_plugins);
+struct user_details user_details;
+const char *list_user; /* extern for parse_args.c */
+int sudo_debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER;
+static struct command_details command_details;
+static int sudo_mode;
+static struct sudo_event_base *sudo_event_base;
+
+struct sudo_gc_entry {
+ SLIST_ENTRY(sudo_gc_entry) entries;
+ enum sudo_gc_types type;
+ union {
+ char **vec;
+ void *ptr;
+ } u;
+};
+SLIST_HEAD(sudo_gc_list, sudo_gc_entry);
+#ifdef NO_LEAKS
+static struct sudo_gc_list sudo_gc_list = SLIST_HEAD_INITIALIZER(sudo_gc_list);
+#endif
+
+/*
+ * Local functions
+ */
+static void fix_fds(void);
+static void sudo_check_suid(const char *path);
+static char **get_user_info(struct user_details *);
+static void command_info_to_details(char * const info[],
+ struct command_details *details);
+static void gc_init(void);
+
+/* Policy plugin convenience functions. */
+static void policy_open(struct sudo_settings *settings,
+ char * const user_info[], char * const user_env[]);
+static void policy_close(int exit_status, int error);
+static int policy_show_version(int verbose);
+static void policy_check(int argc, char * const argv[], char *env_add[],
+ char **command_info[], char **argv_out[], char **user_env_out[]);
+static void policy_list(int argc, char * const argv[],
+ int verbose, const char *user, char * const envp[]);
+static void policy_validate(char * const argv[], char * const envp[]);
+static void policy_invalidate(int unlinkit);
+
+/* I/O log plugin convenience functions. */
+static void iolog_open(struct sudo_settings *settings, char * const user_info[],
+ char * const command_details[], int argc, char * const argv[],
+ char * const user_env[]);
+static void iolog_close(int exit_status, int error);
+static void iolog_show_version(int verbose, struct sudo_settings *settings,
+ char * const user_info[], int argc, char * const argv[],
+ char * const user_env[]);
+static void unlink_plugin(struct plugin_container_list *plugin_list, struct plugin_container *plugin);
+static void free_plugin_container(struct plugin_container *plugin, bool ioplugin);
+
+/* Audit plugin convenience functions. */
+static void audit_open(struct sudo_settings *settings, char * const user_info[],
+ int submit_optind, char * const submit_argv[], char * const submit_envp[]);
+static void audit_close(int exit_status, int error);
+static void audit_show_version(int verbose);
+static void audit_accept(const char *plugin_name,
+ unsigned int plugin_type, char * const command_info[],
+ char * const run_argv[], char * const run_envp[]);
+
+/* Approval plugin convenience functions. */
+static void approval_check(struct sudo_settings *settings,
+ char * const user_info[], int submit_optind, char * const submit_argv[],
+ char * const submit_envp[], char * const command_info[],
+ char * const run_argv[], char * const run_envp[]);
+static void approval_show_version(int verbose, struct sudo_settings *settings,
+ char * const user_info[], int submit_optind, char * const submit_argv[],
+ char * const submit_envp[]);
+
+sudo_dso_public int main(int argc, char *argv[], char *envp[]);
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+ int nargc, status = 0;
+ char **nargv, **env_add, **user_info;
+ char **command_info = NULL, **argv_out = NULL, **user_env_out = NULL;
+ const char * const allowed_prognames[] = { "sudo", "sudoedit", NULL };
+ struct sudo_settings *settings;
+ int submit_optind;
+ sigset_t mask;
+ debug_decl_vars(main, SUDO_DEBUG_MAIN);
+
+ /* Only allow "sudo" or "sudoedit" as the program name. */
+ initprogname2(argc > 0 ? argv[0] : "sudo", allowed_prognames);
+
+ /* Crank resource limits to unlimited. */
+ unlimit_sudo();
+
+ /* Make sure fds 0-2 are open and do OS-specific initialization. */
+ fix_fds();
+ os_init(argc, argv, envp);
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE_NAME, LOCALEDIR);
+ textdomain(PACKAGE_NAME);
+
+ (void) tzset();
+
+ /* Must be done before we do any password lookups */
+#if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS)
+ (void) set_auth_parameters(argc, argv);
+# ifdef HAVE_INITPRIVS
+ initprivs();
+# endif
+#endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */
+
+ /* Initialize the debug subsystem. */
+ if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) == -1)
+ exit(EXIT_FAILURE);
+ sudo_debug_instance = sudo_debug_register(getprogname(),
+ NULL, NULL, sudo_conf_debug_files(getprogname()));
+ if (sudo_debug_instance == SUDO_DEBUG_INSTANCE_ERROR)
+ exit(EXIT_FAILURE);
+
+ /* Make sure we are setuid root. */
+ sudo_check_suid(argc > 0 ? argv[0] : "sudo");
+
+ /* Save original signal state and setup default signal handlers. */
+ save_signals();
+ init_signals();
+
+ /* Reset signal mask to the default value (unblock). */
+ (void) sigemptyset(&mask);
+ (void) sigprocmask(SIG_SETMASK, &mask, NULL);
+
+ /* Parse the rest of sudo.conf. */
+ sudo_conf_read(NULL, SUDO_CONF_ALL & ~SUDO_CONF_DEBUG);
+
+ /* Fill in user_info with user name, uid, cwd, etc. */
+ if ((user_info = get_user_info(&user_details)) == NULL)
+ exit(EXIT_FAILURE); /* get_user_info printed error message */
+
+ /* Disable core dumps if not enabled in sudo.conf. */
+ if (sudo_conf_disable_coredump())
+ disable_coredump();
+
+ /* Parse command line arguments. */
+ sudo_mode = parse_args(argc, argv, &submit_optind, &nargc, &nargv,
+ &settings, &env_add);
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "sudo_mode %d", sudo_mode);
+
+ /* Print sudo version early, in case of plugin init failure. */
+ if (ISSET(sudo_mode, MODE_VERSION)) {
+ printf(_("Sudo version %s\n"), PACKAGE_VERSION);
+ if (user_details.cred.uid == ROOT_UID)
+ (void) printf(_("Configure options: %s\n"), CONFIGURE_ARGS);
+ }
+
+ /* Use conversation function for sudo_(warn|fatal)x? for plugins. */
+ sudo_warn_set_conversation(sudo_conversation);
+
+ /* Load plugins. */
+ if (!sudo_load_plugins())
+ sudo_fatalx("%s", U_("fatal error, unable to load plugins"));
+
+ /* Allocate event base so plugin can use it. */
+ if ((sudo_event_base = sudo_ev_base_alloc()) == NULL)
+ sudo_fatalx("%s", U_("unable to allocate memory"));
+
+ /* Open policy and audit plugins. */
+ /* XXX - audit policy_open errors */
+ audit_open(settings, user_info, submit_optind, argv, envp);
+ policy_open(settings, user_info, envp);
+
+ switch (sudo_mode & MODE_MASK) {
+ case MODE_VERSION:
+ policy_show_version(!user_details.cred.uid);
+ iolog_show_version(!user_details.cred.uid, settings, user_info,
+ nargc, nargv, envp);
+ approval_show_version(!user_details.cred.uid, settings, user_info,
+ submit_optind, argv, envp);
+ audit_show_version(!user_details.cred.uid);
+ break;
+ case MODE_VALIDATE:
+ case MODE_VALIDATE|MODE_INVALIDATE:
+ policy_validate(nargv, envp);
+ break;
+ case MODE_KILL:
+ case MODE_INVALIDATE:
+ policy_invalidate(sudo_mode == MODE_KILL);
+ break;
+ case MODE_CHECK:
+ case MODE_CHECK|MODE_INVALIDATE:
+ case MODE_LIST:
+ case MODE_LIST|MODE_INVALIDATE:
+ policy_list(nargc, nargv, ISSET(sudo_mode, MODE_LONG_LIST),
+ list_user, envp);
+ break;
+ case MODE_EDIT:
+ case MODE_RUN:
+ policy_check(nargc, nargv, env_add, &command_info, &argv_out,
+ &user_env_out);
+
+ /* Reset nargv/nargc based on argv_out. */
+ /* XXX - leaks old nargv in shell mode */
+ for (nargv = argv_out, nargc = 0; nargv[nargc] != NULL; nargc++)
+ continue;
+ if (nargc == 0)
+ sudo_fatalx("%s",
+ U_("plugin did not return a command to execute"));
+
+ /* Approval plugins run after policy plugin accepts the command. */
+ approval_check(settings, user_info, submit_optind, argv, envp,
+ command_info, nargv, user_env_out);
+
+ /* Open I/O plugin once policy and approval plugins succeed. */
+ iolog_open(settings, user_info, command_info, nargc, nargv,
+ user_env_out);
+
+ /* Audit the accept event on behalf of the sudo front-end. */
+ audit_accept("sudo", SUDO_FRONT_END, command_info,
+ nargv, user_env_out);
+
+ /* Setup command details and run command/edit. */
+ command_info_to_details(command_info, &command_details);
+ command_details.tty = user_details.tty;
+ command_details.argv = argv_out;
+ command_details.envp = user_env_out;
+ command_details.evbase = sudo_event_base;
+ if (ISSET(sudo_mode, MODE_LOGIN_SHELL))
+ SET(command_details.flags, CD_LOGIN_SHELL);
+ if (ISSET(sudo_mode, MODE_BACKGROUND))
+ SET(command_details.flags, CD_BACKGROUND);
+ if (ISSET(command_details.flags, CD_SUDOEDIT)) {
+ status = sudo_edit(&command_details);
+ } else {
+ status = run_command(&command_details);
+ }
+ /* The close method was called by sudo_edit/run_command. */
+ break;
+ default:
+ sudo_fatalx(U_("unexpected sudo mode 0x%x"), sudo_mode);
+ }
+
+ /*
+ * If the command was terminated by a signal, sudo needs to terminated
+ * the same way. Otherwise, the shell may ignore a keyboard-generated
+ * signal. However, we want to avoid having sudo dump core itself.
+ */
+ if (WIFSIGNALED(status)) {
+ struct sigaction sa;
+
+ if (WCOREDUMP(status))
+ disable_coredump();
+
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_DFL;
+ sigaction(WTERMSIG(status), &sa, NULL);
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys,
+ WTERMSIG(status) | 128);
+ kill(getpid(), WTERMSIG(status));
+ }
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys,
+ WEXITSTATUS(status));
+ exit(WEXITSTATUS(status));
+}
+
+int
+os_init_common(int argc, char *argv[], char *envp[])
+{
+#ifdef STATIC_SUDOERS_PLUGIN
+ preload_static_symbols();
+#endif
+ gc_init();
+ return 0;
+}
+
+/*
+ * Ensure that stdin, stdout and stderr are open; set to /dev/null if not.
+ * Some operating systems do this automatically in the kernel or libc.
+ */
+static void
+fix_fds(void)
+{
+ int miss[3], devnull = -1;
+ debug_decl(fix_fds, SUDO_DEBUG_UTIL);
+
+ /*
+ * stdin, stdout and stderr must be open; set them to /dev/null
+ * if they are closed.
+ */
+ miss[STDIN_FILENO] = fcntl(STDIN_FILENO, F_GETFL, 0) == -1;
+ miss[STDOUT_FILENO] = fcntl(STDOUT_FILENO, F_GETFL, 0) == -1;
+ miss[STDERR_FILENO] = fcntl(STDERR_FILENO, F_GETFL, 0) == -1;
+ if (miss[STDIN_FILENO] || miss[STDOUT_FILENO] || miss[STDERR_FILENO]) {
+ devnull = open(_PATH_DEVNULL, O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (devnull == -1)
+ sudo_fatal(U_("unable to open %s"), _PATH_DEVNULL);
+ if (miss[STDIN_FILENO] && dup2(devnull, STDIN_FILENO) == -1)
+ sudo_fatal("dup2");
+ if (miss[STDOUT_FILENO] && dup2(devnull, STDOUT_FILENO) == -1)
+ sudo_fatal("dup2");
+ if (miss[STDERR_FILENO] && dup2(devnull, STDERR_FILENO) == -1)
+ sudo_fatal("dup2");
+ if (devnull > STDERR_FILENO)
+ close(devnull);
+ }
+ debug_return;
+}
+
+/*
+ * Allocate space for groups and fill in using sudo_getgrouplist2()
+ * for when we cannot (or don't want to) use getgroups().
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+fill_group_list(const char *user, struct sudo_cred *cred)
+{
+ int ret = -1;
+ debug_decl(fill_group_list, SUDO_DEBUG_UTIL);
+
+ /*
+ * If user specified a max number of groups, use it, otherwise let
+ * sudo_getgrouplist2() allocate the group vector.
+ */
+ cred->ngroups = sudo_conf_max_groups();
+ if (cred->ngroups > 0) {
+ cred->groups = reallocarray(NULL, cred->ngroups, sizeof(GETGROUPS_T));
+ if (cred->groups != NULL) {
+ /* No error on insufficient space if user specified max_groups. */
+ (void)sudo_getgrouplist2(user, cred->gid,
+ &cred->groups, &cred->ngroups);
+ ret = 0;
+ }
+ } else {
+ cred->groups = NULL;
+ ret = sudo_getgrouplist2(user, cred->gid, &cred->groups,
+ &cred->ngroups);
+ }
+ if (ret == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: %s: unable to get groups via sudo_getgrouplist2()",
+ __func__, user);
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: %s: got %d groups via sudo_getgrouplist2()",
+ __func__, user, cred->ngroups);
+ }
+ debug_return_int(ret);
+}
+
+static char *
+get_user_groups(const char *user, struct sudo_cred *cred)
+{
+ char *cp, *gid_list = NULL;
+ size_t glsize;
+ int i, len, group_source;
+ debug_decl(get_user_groups, SUDO_DEBUG_UTIL);
+
+ cred->groups = NULL;
+ group_source = sudo_conf_group_source();
+ if (group_source != GROUP_SOURCE_DYNAMIC) {
+ int maxgroups = (int)sysconf(_SC_NGROUPS_MAX);
+ if (maxgroups < 0)
+ maxgroups = NGROUPS_MAX;
+
+ /* Note that macOS may return ngroups > NGROUPS_MAX. */
+ cred->ngroups = getgroups(0, NULL); // -V575
+ if (cred->ngroups > 0) {
+ /* Use groups from kernel if not at limit or source is static. */
+ if (cred->ngroups != maxgroups || group_source == GROUP_SOURCE_STATIC) {
+ cred->groups = reallocarray(NULL, cred->ngroups, sizeof(GETGROUPS_T));
+ if (cred->groups == NULL)
+ goto done;
+ if (getgroups(cred->ngroups, cred->groups) < 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to get %d groups via getgroups()",
+ __func__, cred->ngroups);
+ free(cred->groups);
+ cred->groups = NULL;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: got %d groups via getgroups()",
+ __func__, cred->ngroups);
+ }
+ }
+ }
+ }
+ if (cred->groups == NULL) {
+ /*
+ * Query group database if kernel list is too small or disabled.
+ * Typically, this is because NFS can only support up to 16 groups.
+ */
+ if (fill_group_list(user, cred) == -1)
+ goto done;
+ }
+
+ /*
+ * Format group list as a comma-separated string of gids.
+ */
+ glsize = sizeof("groups=") - 1 + (cred->ngroups * (MAX_UID_T_LEN + 1));
+ if ((gid_list = malloc(glsize)) == NULL)
+ goto done;
+ memcpy(gid_list, "groups=", sizeof("groups=") - 1);
+ cp = gid_list + sizeof("groups=") - 1;
+ for (i = 0; i < cred->ngroups; i++) {
+ len = snprintf(cp, glsize - (cp - gid_list), "%s%u",
+ i ? "," : "", (unsigned int)cred->groups[i]);
+ if (len < 0 || (size_t)len >= glsize - (cp - gid_list))
+ sudo_fatalx(U_("internal error, %s overflow"), __func__);
+ cp += len;
+ }
+done:
+ debug_return_str(gid_list);
+}
+
+/*
+ * Return user information as an array of name=value pairs.
+ * and fill in struct user_details (which shares the same strings).
+ */
+static char **
+get_user_info(struct user_details *ud)
+{
+ char *cp, **user_info, path[PATH_MAX];
+ size_t user_info_max = 32 + RLIM_NLIMITS;
+ unsigned int i = 0;
+ mode_t mask;
+ struct passwd *pw;
+ int fd, n;
+ debug_decl(get_user_info, SUDO_DEBUG_UTIL);
+
+ /*
+ * On BSD systems you can set a hint to keep the password and
+ * group databases open instead of having to open and close
+ * them all the time. Since sudo does a lot of password and
+ * group lookups, keeping the file open can speed things up.
+ */
+#ifdef HAVE_SETPASSENT
+ setpassent(1);
+#endif /* HAVE_SETPASSENT */
+#ifdef HAVE_SETGROUPENT
+ setgroupent(1);
+#endif /* HAVE_SETGROUPENT */
+
+ memset(ud, 0, sizeof(*ud));
+
+ /* XXX - bound check number of entries */
+ user_info = reallocarray(NULL, user_info_max, sizeof(char *));
+ if (user_info == NULL)
+ goto oom;
+
+ ud->pid = getpid();
+ ud->ppid = getppid();
+ ud->pgid = getpgid(0);
+ fd = open(_PATH_TTY, O_RDWR);
+ if (fd != -1) {
+ if ((ud->tcpgid = tcgetpgrp(fd)) == -1)
+ ud->tcpgid = 0;
+ close(fd);
+ }
+ if ((ud->sid = getsid(0)) == -1)
+ ud->sid = 0;
+
+ ud->cred.uid = getuid();
+ ud->cred.euid = geteuid();
+ ud->cred.gid = getgid();
+ ud->cred.egid = getegid();
+
+#ifdef HAVE_SETAUTHDB
+ aix_setauthdb(IDtouser(ud->cred.uid), NULL);
+#endif
+ pw = getpwuid(ud->cred.uid);
+#ifdef HAVE_SETAUTHDB
+ aix_restoreauthdb();
+#endif
+ if (pw == NULL)
+ sudo_fatalx(U_("you do not exist in the %s database"), "passwd");
+
+ user_info[i] = sudo_new_key_val("user", pw->pw_name);
+ if (user_info[i] == NULL)
+ goto oom;
+ ud->username = user_info[i] + sizeof("user=") - 1;
+
+ /* Stash user's shell for use with the -s flag; don't pass to plugin. */
+ if ((ud->shell = getenv("SHELL")) == NULL || ud->shell[0] == '\0') {
+ ud->shell = pw->pw_shell[0] ? pw->pw_shell : _PATH_SUDO_BSHELL;
+ }
+ if ((ud->shell = strdup(ud->shell)) == NULL)
+ goto oom;
+
+ if (asprintf(&user_info[++i], "pid=%d", (int)ud->pid) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "ppid=%d", (int)ud->ppid) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "pgid=%d", (int)ud->pgid) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "tcpgid=%d", (int)ud->tcpgid) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "sid=%d", (int)ud->sid) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "uid=%u", (unsigned int)ud->cred.uid) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "euid=%u", (unsigned int)ud->cred.euid) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "gid=%u", (unsigned int)ud->cred.gid) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "egid=%u", (unsigned int)ud->cred.egid) == -1)
+ goto oom;
+
+ if ((cp = get_user_groups(ud->username, &ud->cred)) == NULL)
+ goto oom;
+ user_info[++i] = cp;
+
+ mask = umask(0);
+ umask(mask);
+ if (asprintf(&user_info[++i], "umask=0%o", (unsigned int)mask) == -1)
+ goto oom;
+
+ if (getcwd(path, sizeof(path)) != NULL) {
+ user_info[++i] = sudo_new_key_val("cwd", path);
+ if (user_info[i] == NULL)
+ goto oom;
+ ud->cwd = user_info[i] + sizeof("cwd=") - 1;
+ }
+
+ if (get_process_ttyname(path, sizeof(path)) != NULL) {
+ user_info[++i] = sudo_new_key_val("tty", path);
+ if (user_info[i] == NULL)
+ goto oom;
+ ud->tty = user_info[i] + sizeof("tty=") - 1;
+ } else {
+ /* tty may not always be present */
+ if (errno != ENOENT)
+ sudo_warn("%s", U_("unable to determine tty"));
+ }
+
+ cp = sudo_gethostname();
+ user_info[++i] = sudo_new_key_val("host", cp ? cp : "localhost");
+ free(cp);
+ if (user_info[i] == NULL)
+ goto oom;
+ ud->host = user_info[i] + sizeof("host=") - 1;
+
+ sudo_get_ttysize(&ud->ts_rows, &ud->ts_cols);
+ if (asprintf(&user_info[++i], "lines=%d", ud->ts_rows) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "cols=%d", ud->ts_cols) == -1)
+ goto oom;
+
+ n = serialize_limits(&user_info[i + 1], user_info_max - (i + 1));
+ if (n == -1)
+ goto oom;
+ i += n;
+
+ user_info[++i] = NULL;
+
+ /* Add to list of vectors to be garbage collected at exit. */
+ if (!gc_add(GC_VECTOR, user_info))
+ goto bad;
+
+ debug_return_ptr(user_info);
+oom:
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+bad:
+ while (i--)
+ free(user_info[i]);
+ free(user_info);
+ debug_return_ptr(NULL);
+}
+
+/*
+ * Convert a command_info array into a command_details structure.
+ */
+static void
+command_info_to_details(char * const info[], struct command_details *details)
+{
+ int i;
+ id_t id;
+ char *cp;
+ const char *errstr;
+ debug_decl(command_info_to_details, SUDO_DEBUG_PCOMM);
+
+ memset(details, 0, sizeof(*details));
+ details->info = info;
+ details->closefrom = -1;
+ details->execfd = -1;
+ details->flags = CD_SUDOEDIT_CHECKDIR | CD_SET_GROUPS;
+ TAILQ_INIT(&details->preserved_fds);
+
+#define SET_STRING(s, n) \
+ if (strncmp(s, info[i], sizeof(s) - 1) == 0 && info[i][sizeof(s) - 1]) { \
+ details->n = info[i] + sizeof(s) - 1; \
+ break; \
+ }
+#define SET_FLAG(s, n) \
+ if (strncmp(s, info[i], sizeof(s) - 1) == 0) { \
+ switch (sudo_strtobool(info[i] + sizeof(s) - 1)) { \
+ case true: \
+ SET(details->flags, n); \
+ break; \
+ case false: \
+ CLR(details->flags, n); \
+ break; \
+ default: \
+ sudo_debug_printf(SUDO_DEBUG_ERROR, \
+ "invalid boolean value for %s", info[i]); \
+ break; \
+ } \
+ break; \
+ }
+
+ sudo_debug_printf(SUDO_DEBUG_INFO, "command info from plugin:");
+ for (i = 0; info[i] != NULL; i++) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, " %d: %s", i, info[i]);
+ switch (info[i][0]) {
+ case 'c':
+ SET_STRING("chroot=", chroot)
+ SET_STRING("command=", command)
+ SET_STRING("cwd=", cwd)
+ if (strncmp("cwd_optional=", info[i], sizeof("cwd_optional=") - 1) == 0) {
+ cp = info[i] + sizeof("cwd_optional=") - 1;
+ details->cwd_optional = sudo_strtobool(cp);
+ if (details->cwd_optional == -1) {
+ errno = EINVAL;
+ sudo_fatal("%s", info[i]);
+ }
+ break;
+ }
+ if (strncmp("closefrom=", info[i], sizeof("closefrom=") - 1) == 0) {
+ cp = info[i] + sizeof("closefrom=") - 1;
+ details->closefrom = sudo_strtonum(cp, 0, INT_MAX, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ break;
+ }
+ break;
+ case 'e':
+ SET_FLAG("exec_background=", CD_EXEC_BG)
+ if (strncmp("execfd=", info[i], sizeof("execfd=") - 1) == 0) {
+ cp = info[i] + sizeof("execfd=") - 1;
+ details->execfd = sudo_strtonum(cp, 0, INT_MAX, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+#ifdef HAVE_FEXECVE
+ /* Must keep fd open during exec. */
+ add_preserved_fd(&details->preserved_fds, details->execfd);
+#else
+ /* Plugin thinks we support fexecve() but we don't. */
+ (void)fcntl(details->execfd, F_SETFD, FD_CLOEXEC);
+ details->execfd = -1;
+#endif
+ break;
+ }
+ break;
+ case 'l':
+ SET_STRING("login_class=", login_class)
+ break;
+ case 'n':
+ if (strncmp("nice=", info[i], sizeof("nice=") - 1) == 0) {
+ cp = info[i] + sizeof("nice=") - 1;
+ details->priority = sudo_strtonum(cp, INT_MIN, INT_MAX,
+ &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ SET(details->flags, CD_SET_PRIORITY);
+ break;
+ }
+ SET_FLAG("noexec=", CD_NOEXEC)
+ break;
+ case 'p':
+ SET_FLAG("preserve_groups=", CD_PRESERVE_GROUPS)
+ if (strncmp("preserve_fds=", info[i], sizeof("preserve_fds=") - 1) == 0) {
+ parse_preserved_fds(&details->preserved_fds,
+ info[i] + sizeof("preserve_fds=") - 1);
+ break;
+ }
+ break;
+ case 'r':
+ if (strncmp("runas_egid=", info[i], sizeof("runas_egid=") - 1) == 0) {
+ cp = info[i] + sizeof("runas_egid=") - 1;
+ id = sudo_strtoid(cp, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ details->cred.egid = (gid_t)id;
+ SET(details->flags, CD_SET_EGID);
+ break;
+ }
+ if (strncmp("runas_euid=", info[i], sizeof("runas_euid=") - 1) == 0) {
+ cp = info[i] + sizeof("runas_euid=") - 1;
+ id = sudo_strtoid(cp, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ details->cred.euid = (uid_t)id;
+ SET(details->flags, CD_SET_EUID);
+ break;
+ }
+ if (strncmp("runas_gid=", info[i], sizeof("runas_gid=") - 1) == 0) {
+ cp = info[i] + sizeof("runas_gid=") - 1;
+ id = sudo_strtoid(cp, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ details->cred.gid = (gid_t)id;
+ SET(details->flags, CD_SET_GID);
+ break;
+ }
+ if (strncmp("runas_groups=", info[i], sizeof("runas_groups=") - 1) == 0) {
+ cp = info[i] + sizeof("runas_groups=") - 1;
+ details->cred.ngroups = sudo_parse_gids(cp, NULL, &details->cred.groups);
+ /* sudo_parse_gids() will print a warning on error. */
+ if (details->cred.ngroups == -1)
+ exit(EXIT_FAILURE); /* XXX */
+ break;
+ }
+ if (strncmp("runas_uid=", info[i], sizeof("runas_uid=") - 1) == 0) {
+ cp = info[i] + sizeof("runas_uid=") - 1;
+ id = sudo_strtoid(cp, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ details->cred.uid = (uid_t)id;
+ SET(details->flags, CD_SET_UID);
+ break;
+ }
+#ifdef HAVE_PRIV_SET
+ if (strncmp("runas_privs=", info[i], sizeof("runas_privs=") - 1) == 0) {
+ const char *endp;
+ cp = info[i] + sizeof("runas_privs=") - 1;
+ if (*cp != '\0') {
+ details->privs = priv_str_to_set(cp, ",", &endp);
+ if (details->privs == NULL)
+ sudo_warn("invalid runas_privs %s", endp);
+ }
+ break;
+ }
+ if (strncmp("runas_limitprivs=", info[i], sizeof("runas_limitprivs=") - 1) == 0) {
+ const char *endp;
+ cp = info[i] + sizeof("runas_limitprivs=") - 1;
+ if (*cp != '\0') {
+ details->limitprivs = priv_str_to_set(cp, ",", &endp);
+ if (details->limitprivs == NULL)
+ sudo_warn("invalid runas_limitprivs %s", endp);
+ }
+ break;
+ }
+#endif /* HAVE_PRIV_SET */
+ SET_STRING("runas_user=", runas_user)
+ break;
+ case 's':
+ SET_STRING("selinux_role=", selinux_role)
+ SET_STRING("selinux_type=", selinux_type)
+ SET_FLAG("set_utmp=", CD_SET_UTMP)
+ SET_FLAG("sudoedit=", CD_SUDOEDIT)
+ SET_FLAG("sudoedit_checkdir=", CD_SUDOEDIT_CHECKDIR)
+ SET_FLAG("sudoedit_follow=", CD_SUDOEDIT_FOLLOW)
+ break;
+ case 't':
+ if (strncmp("timeout=", info[i], sizeof("timeout=") - 1) == 0) {
+ cp = info[i] + sizeof("timeout=") - 1;
+ details->timeout = sudo_strtonum(cp, 0, INT_MAX, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ SET(details->flags, CD_SET_TIMEOUT);
+ break;
+ }
+ break;
+ case 'u':
+ if (strncmp("umask=", info[i], sizeof("umask=") - 1) == 0) {
+ cp = info[i] + sizeof("umask=") - 1;
+ details->umask = sudo_strtomode(cp, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ SET(details->flags, CD_SET_UMASK);
+ break;
+ }
+ SET_FLAG("umask_override=", CD_OVERRIDE_UMASK)
+ SET_FLAG("use_pty=", CD_USE_PTY)
+ SET_STRING("utmp_user=", utmp_user)
+ break;
+ }
+ }
+
+ if (!ISSET(details->flags, CD_SET_EUID))
+ details->cred.euid = details->cred.uid;
+ if (!ISSET(details->flags, CD_SET_EGID))
+ details->cred.egid = details->cred.gid;
+ if (!ISSET(details->flags, CD_SET_UMASK))
+ CLR(details->flags, CD_OVERRIDE_UMASK);
+
+#ifdef HAVE_SETAUTHDB
+ aix_setauthdb(IDtouser(details->cred.euid), NULL);
+#endif
+ if (details->runas_user != NULL)
+ details->pw = getpwnam(details->runas_user);
+ if (details->pw == NULL)
+ details->pw = getpwuid(details->cred.euid);
+#ifdef HAVE_SETAUTHDB
+ aix_restoreauthdb();
+#endif
+ if (details->pw != NULL && (details->pw = pw_dup(details->pw)) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+#ifdef HAVE_SELINUX
+ if (details->selinux_role != NULL && is_selinux_enabled() > 0)
+ SET(details->flags, CD_RBAC_ENABLED);
+#endif
+ debug_return;
+}
+
+static void
+sudo_check_suid(const char *sudo)
+{
+ char pathbuf[PATH_MAX];
+ struct stat sb;
+ bool qualified;
+ debug_decl(sudo_check_suid, SUDO_DEBUG_PCOMM);
+
+ if (geteuid() != ROOT_UID) {
+ /* Search for sudo binary in PATH if not fully qualified. */
+ qualified = strchr(sudo, '/') != NULL;
+ if (!qualified) {
+ char *path = getenv_unhooked("PATH");
+ if (path != NULL) {
+ const char *cp, *ep;
+ const char *pathend = path + strlen(path);
+
+ for (cp = sudo_strsplit(path, pathend, ":", &ep); cp != NULL;
+ cp = sudo_strsplit(NULL, pathend, ":", &ep)) {
+
+ int len = snprintf(pathbuf, sizeof(pathbuf), "%.*s/%s",
+ (int)(ep - cp), cp, sudo);
+ if (len < 0 || len >= ssizeof(pathbuf))
+ continue;
+ if (access(pathbuf, X_OK) == 0) {
+ sudo = pathbuf;
+ qualified = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (qualified && stat(sudo, &sb) == 0) {
+ /* Try to determine why sudo was not running as root. */
+ if (sb.st_uid != ROOT_UID || !ISSET(sb.st_mode, S_ISUID)) {
+ sudo_fatalx(
+ U_("%s must be owned by uid %d and have the setuid bit set"),
+ sudo, ROOT_UID);
+ } else {
+ sudo_fatalx(U_("effective uid is not %d, is %s on a file system "
+ "with the 'nosuid' option set or an NFS file system without"
+ " root privileges?"), ROOT_UID, sudo);
+ }
+ } else {
+ sudo_fatalx(
+ U_("effective uid is not %d, is sudo installed setuid root?"),
+ ROOT_UID);
+ }
+ }
+ debug_return;
+}
+
+bool
+set_user_groups(struct command_details *details)
+{
+ bool ret = false;
+ debug_decl(set_user_groups, SUDO_DEBUG_EXEC);
+
+ if (!ISSET(details->flags, CD_PRESERVE_GROUPS)) {
+ if (details->cred.ngroups >= 0) {
+ if (sudo_setgroups(details->cred.ngroups, details->cred.groups) < 0) {
+ sudo_warn("%s", U_("unable to set supplementary group IDs"));
+ goto done;
+ }
+ }
+ }
+#ifdef HAVE_SETEUID
+ if (ISSET(details->flags, CD_SET_EGID) && setegid(details->cred.egid)) {
+ sudo_warn(U_("unable to set effective gid to runas gid %u"),
+ (unsigned int)details->cred.egid);
+ goto done;
+ }
+#endif
+ if (ISSET(details->flags, CD_SET_GID) && setgid(details->cred.gid)) {
+ sudo_warn(U_("unable to set gid to runas gid %u"),
+ (unsigned int)details->cred.gid);
+ goto done;
+ }
+ ret = true;
+
+done:
+ CLR(details->flags, CD_SET_GROUPS);
+ debug_return_bool(ret);
+}
+
+/*
+ * Run the command and wait for it to complete.
+ * Returns wait status suitable for use with the wait(2) macros.
+ */
+int
+run_command(struct command_details *details)
+{
+ struct command_status cstat;
+ int status = W_EXITCODE(1, 0);
+ debug_decl(run_command, SUDO_DEBUG_EXEC);
+
+ cstat.type = CMD_INVALID;
+ cstat.val = 0;
+
+ sudo_execute(details, &cstat);
+
+ switch (cstat.type) {
+ case CMD_ERRNO:
+ /* exec_setup() or execve() returned an error. */
+ iolog_close(0, cstat.val);
+ policy_close(0, cstat.val);
+ audit_close(SUDO_PLUGIN_EXEC_ERROR, cstat.val);
+ break;
+ case CMD_WSTATUS:
+ /* Command ran, exited or was killed. */
+ status = cstat.val;
+ iolog_close(status, 0);
+ policy_close(status, 0);
+ audit_close(SUDO_PLUGIN_WAIT_STATUS, cstat.val);
+ break;
+ default:
+ /* TODO: handle front end error conditions. */
+ sudo_warnx(U_("unexpected child termination condition: %d"), cstat.type);
+ break;
+ }
+ debug_return_int(status);
+}
+
+/*
+ * Format struct sudo_settings as name=value pairs for the plugin
+ * to consume. Returns a NULL-terminated plugin-style array of pairs.
+ */
+static char **
+format_plugin_settings(struct plugin_container *plugin,
+ struct sudo_settings *sudo_settings)
+{
+ size_t plugin_settings_size;
+ struct sudo_debug_file *debug_file;
+ struct sudo_settings *setting;
+ char **plugin_settings;
+ unsigned int i = 0;
+ debug_decl(format_plugin_settings, SUDO_DEBUG_PCOMM);
+
+ /* Determine sudo_settings array size (including plugin_path and NULL) */
+ plugin_settings_size = 2;
+ for (setting = sudo_settings; setting->name != NULL; setting++)
+ plugin_settings_size++;
+ if (plugin->debug_files != NULL) {
+ TAILQ_FOREACH(debug_file, plugin->debug_files, entries)
+ plugin_settings_size++;
+ }
+
+ /* Allocate and fill in. */
+ plugin_settings = reallocarray(NULL, plugin_settings_size, sizeof(char *));
+ if (plugin_settings == NULL)
+ goto bad;
+ plugin_settings[i] = sudo_new_key_val("plugin_path", plugin->path);
+ if (plugin_settings[i] == NULL)
+ goto bad;
+ for (setting = sudo_settings; setting->name != NULL; setting++) {
+ if (setting->value != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "settings: %s=%s",
+ setting->name, setting->value);
+ plugin_settings[++i] =
+ sudo_new_key_val(setting->name, setting->value);
+ if (plugin_settings[i] == NULL)
+ goto bad;
+ }
+ }
+ if (plugin->debug_files != NULL) {
+ TAILQ_FOREACH(debug_file, plugin->debug_files, entries) {
+ /* XXX - quote filename? */
+ if (asprintf(&plugin_settings[++i], "debug_flags=%s %s",
+ debug_file->debug_file, debug_file->debug_flags) == -1)
+ goto bad;
+ }
+ }
+ plugin_settings[++i] = NULL;
+
+ /* Add to list of vectors to be garbage collected at exit. */
+ if (!gc_add(GC_VECTOR, plugin_settings))
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ debug_return_ptr(plugin_settings);
+bad:
+ while (i--)
+ free(plugin_settings[i]);
+ free(plugin_settings);
+ debug_return_ptr(NULL);
+}
+
+static void
+policy_open(struct sudo_settings *settings, char * const user_info[],
+ char * const user_env[])
+{
+ char **plugin_settings;
+ const char *errstr = NULL;
+ int ok;
+ debug_decl(policy_open, SUDO_DEBUG_PCOMM);
+
+ /* Convert struct sudo_settings to plugin_settings[] */
+ plugin_settings = format_plugin_settings(&policy_plugin, settings);
+ if (plugin_settings == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ /*
+ * Backward compatibility for older API versions
+ */
+ sudo_debug_set_active_instance(SUDO_DEBUG_INSTANCE_INITIALIZER);
+ switch (policy_plugin.u.generic->version) {
+ case SUDO_API_MKVERSION(1, 0):
+ case SUDO_API_MKVERSION(1, 1):
+ ok = policy_plugin.u.policy_1_0->open(policy_plugin.u.io_1_0->version,
+ sudo_conversation_1_7, sudo_conversation_printf, plugin_settings,
+ user_info, user_env);
+ break;
+ default:
+ ok = policy_plugin.u.policy->open(SUDO_API_VERSION, sudo_conversation,
+ sudo_conversation_printf, plugin_settings, user_info, user_env,
+ policy_plugin.options, &errstr);
+ }
+
+ /* Stash plugin debug instance ID if set in open() function. */
+ policy_plugin.debug_instance = sudo_debug_get_active_instance();
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+ if (ok != 1) {
+ if (ok == -2)
+ usage();
+ else {
+ /* XXX - audit */
+ sudo_fatalx("%s", U_("unable to initialize policy plugin"));
+ }
+ }
+
+ debug_return;
+}
+
+static void
+policy_close(int exit_status, int error_code)
+{
+ debug_decl(policy_close, SUDO_DEBUG_PCOMM);
+
+ if (error_code != 0) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "%s: calling policy close with errno %d",
+ policy_plugin.name, error_code);
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "%s: calling policy close with wait status %d",
+ policy_plugin.name, exit_status);
+ }
+ if (policy_plugin.u.policy->close != NULL) {
+ sudo_debug_set_active_instance(policy_plugin.debug_instance);
+ policy_plugin.u.policy->close(exit_status, error_code);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ } else if (error_code != 0) {
+ if (command_details.command != NULL) {
+ errno = error_code;
+ sudo_warn(U_("unable to execute %s"), command_details.command);
+ }
+ }
+
+ debug_return;
+}
+
+static int
+policy_show_version(int verbose)
+{
+ int ret = true;
+ debug_decl(policy_show_version, SUDO_DEBUG_PCOMM);
+
+ sudo_debug_set_active_instance(policy_plugin.debug_instance);
+ if (policy_plugin.u.policy->show_version != NULL)
+ ret = policy_plugin.u.policy->show_version(verbose);
+ if (policy_plugin.u.policy->version >= SUDO_API_MKVERSION(1, 15)) {
+ if (policy_plugin.u.policy->close != NULL)
+ policy_plugin.u.policy->close(0, 0);
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+ debug_return_int(ret);
+}
+
+static void
+policy_check(int argc, char * const argv[],
+ char *env_add[], char **command_info[], char **argv_out[],
+ char **user_env_out[])
+{
+ const char *errstr = NULL;
+ int ok;
+ debug_decl(policy_check, SUDO_DEBUG_PCOMM);
+
+ if (policy_plugin.u.policy->check_policy == NULL) {
+ sudo_fatalx(U_("policy plugin %s is missing the \"check_policy\" method"),
+ policy_plugin.name);
+ }
+ sudo_debug_set_active_instance(policy_plugin.debug_instance);
+ ok = policy_plugin.u.policy->check_policy(argc, argv, env_add,
+ command_info, argv_out, user_env_out, &errstr);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "policy plugin returns %d (%s)",
+ ok, errstr ? errstr : "");
+
+ /* On success, the close method will be called by sudo_edit/run_command. */
+ if (ok != 1) {
+ switch (ok) {
+ case 0:
+ audit_reject(policy_plugin.name, SUDO_POLICY_PLUGIN,
+ errstr ? errstr : _("command rejected by policy"),
+ *command_info);
+ break;
+ case -1:
+ audit_error(policy_plugin.name, SUDO_POLICY_PLUGIN,
+ errstr ? errstr : _("policy plugin error"),
+ *command_info);
+ break;
+ case -2:
+ usage();
+ break;
+ }
+
+ /* Policy must be closed after auditing to avoid use after free. */
+ if (policy_plugin.u.policy->version >= SUDO_API_MKVERSION(1, 15))
+ policy_close(0, 0);
+ audit_close(SUDO_PLUGIN_NO_STATUS, 0);
+ exit(EXIT_FAILURE); /* policy plugin printed error message */
+ }
+ audit_accept(policy_plugin.name, SUDO_POLICY_PLUGIN, *command_info,
+ *argv_out, *user_env_out);
+
+ debug_return;
+}
+
+static void
+policy_list(int argc, char * const argv[], int verbose,
+ const char *user, char * const envp[])
+{
+ const char *errstr = NULL;
+ /* TODO: add list_user */
+ char * const command_info[] = {
+ "command=list",
+ NULL
+ };
+ int ok;
+ debug_decl(policy_list, SUDO_DEBUG_PCOMM);
+
+ if (policy_plugin.u.policy->list == NULL) {
+ sudo_fatalx(U_("policy plugin %s does not support listing privileges"),
+ policy_plugin.name);
+ }
+ sudo_debug_set_active_instance(policy_plugin.debug_instance);
+ ok = policy_plugin.u.policy->list(argc, argv, verbose, user, &errstr);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+ switch (ok) {
+ case 1:
+ audit_accept(policy_plugin.name, SUDO_POLICY_PLUGIN,
+ command_info, argv, envp);
+ break;
+ case 0:
+ audit_reject(policy_plugin.name, SUDO_POLICY_PLUGIN,
+ errstr ? errstr : _("command rejected by policy"),
+ command_info);
+ break;
+ default:
+ audit_error(policy_plugin.name, SUDO_POLICY_PLUGIN,
+ errstr ? errstr : _("policy plugin error"),
+ command_info);
+ break;
+ }
+
+ /* Policy must be closed after auditing to avoid use after free. */
+ if (policy_plugin.u.policy->version >= SUDO_API_MKVERSION(1, 15))
+ policy_close(0, 0);
+ audit_close(SUDO_PLUGIN_NO_STATUS, 0);
+
+ exit(ok != 1);
+}
+
+static void
+policy_validate(char * const argv[], char * const envp[])
+{
+ const char *errstr = NULL;
+ char * const command_info[] = {
+ "command=validate",
+ NULL
+ };
+ int ok = 0;
+ debug_decl(policy_validate, SUDO_DEBUG_PCOMM);
+
+ if (policy_plugin.u.policy->validate == NULL) {
+ sudo_fatalx(U_("policy plugin %s does not support the -v option"),
+ policy_plugin.name);
+ }
+ sudo_debug_set_active_instance(policy_plugin.debug_instance);
+ ok = policy_plugin.u.policy->validate(&errstr);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+ switch (ok) {
+ case 1:
+ audit_accept(policy_plugin.name, SUDO_POLICY_PLUGIN, command_info,
+ argv, envp);
+ break;
+ case 0:
+ audit_reject(policy_plugin.name, SUDO_POLICY_PLUGIN,
+ errstr ? errstr : _("command rejected by policy"),
+ command_info);
+ break;
+ default:
+ audit_error(policy_plugin.name, SUDO_POLICY_PLUGIN,
+ errstr ? errstr : _("policy plugin error"),
+ command_info);
+ break;
+ }
+
+ /* Policy must be closed after auditing to avoid use after free. */
+ if (policy_plugin.u.policy->version >= SUDO_API_MKVERSION(1, 15))
+ policy_close(0, 0);
+ audit_close(SUDO_PLUGIN_NO_STATUS, 0);
+
+ exit(ok != 1);
+}
+
+static void
+policy_invalidate(int unlinkit)
+{
+ debug_decl(policy_invalidate, SUDO_DEBUG_PCOMM);
+
+ if (policy_plugin.u.policy->invalidate == NULL) {
+ sudo_fatalx(U_("policy plugin %s does not support the -k/-K options"),
+ policy_plugin.name);
+ }
+ sudo_debug_set_active_instance(policy_plugin.debug_instance);
+ policy_plugin.u.policy->invalidate(unlinkit);
+ if (policy_plugin.u.policy->version >= SUDO_API_MKVERSION(1, 15)) {
+ if (policy_plugin.u.policy->close != NULL)
+ policy_plugin.u.policy->close(0, 0);
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+ audit_close(SUDO_PLUGIN_NO_STATUS, 0);
+
+ exit(EXIT_SUCCESS);
+}
+
+int
+policy_init_session(struct command_details *details)
+{
+ const char *errstr = NULL;
+ int ret = true;
+ debug_decl(policy_init_session, SUDO_DEBUG_PCOMM);
+
+ /*
+ * We set groups, including supplementary group vector,
+ * as part of the session setup. This allows for dynamic
+ * groups to be set via pam_group(8) in pam_setcred(3).
+ */
+ if (ISSET(details->flags, CD_SET_GROUPS)) {
+ /* set_user_groups() prints error message on failure. */
+ if (!set_user_groups(details))
+ goto done;
+ }
+
+ /* Session setup may override sudoers umask so set it first. */
+ if (ISSET(details->flags, CD_SET_UMASK))
+ (void) umask(details->umask);
+
+ if (policy_plugin.u.policy->init_session) {
+ /*
+ * Backward compatibility for older API versions
+ */
+ sudo_debug_set_active_instance(policy_plugin.debug_instance);
+ switch (policy_plugin.u.generic->version) {
+ case SUDO_API_MKVERSION(1, 0):
+ case SUDO_API_MKVERSION(1, 1):
+ ret = policy_plugin.u.policy_1_0->init_session(details->pw);
+ break;
+ default:
+ ret = policy_plugin.u.policy->init_session(details->pw,
+ &details->envp, &errstr);
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ if (ret != 1) {
+ audit_error(policy_plugin.name, SUDO_POLICY_PLUGIN,
+ errstr ? errstr : _("policy plugin error"),
+ details->info);
+ }
+ }
+done:
+ debug_return_int(ret);
+}
+
+static int
+iolog_open_int(struct plugin_container *plugin, struct sudo_settings *settings,
+ char * const user_info[], char * const command_info[],
+ int argc, char * const argv[], char * const user_env[], const char **errstr)
+{
+ char **plugin_settings;
+ int ret;
+ debug_decl(iolog_open_int, SUDO_DEBUG_PCOMM);
+
+ /* Convert struct sudo_settings to plugin_settings[] */
+ plugin_settings = format_plugin_settings(plugin, settings);
+ if (plugin_settings == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_int(-1);
+ }
+
+ /*
+ * Backward compatibility for older API versions
+ */
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ switch (plugin->u.generic->version) {
+ case SUDO_API_MKVERSION(1, 0):
+ ret = plugin->u.io_1_0->open(plugin->u.io_1_0->version,
+ sudo_conversation_1_7, sudo_conversation_printf, plugin_settings,
+ user_info, argc, argv, user_env);
+ break;
+ case SUDO_API_MKVERSION(1, 1):
+ ret = plugin->u.io_1_1->open(plugin->u.io_1_1->version,
+ sudo_conversation_1_7, sudo_conversation_printf, plugin_settings,
+ user_info, command_info, argc, argv, user_env);
+ break;
+ default:
+ ret = plugin->u.io->open(SUDO_API_VERSION, sudo_conversation,
+ sudo_conversation_printf, plugin_settings, user_info, command_info,
+ argc, argv, user_env, plugin->options, errstr);
+ }
+
+ /* Stash plugin debug instance ID if set in open() function. */
+ plugin->debug_instance = sudo_debug_get_active_instance();
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+ debug_return_int(ret);
+}
+
+static void
+iolog_open(struct sudo_settings *settings, char * const user_info[],
+ char * const command_info[], int argc, char * const argv[],
+ char * const user_env[])
+{
+ struct plugin_container *plugin, *next;
+ const char *errstr = NULL;
+ debug_decl(iolog_open, SUDO_DEBUG_PCOMM);
+
+ /* XXX - iolog_open should audit errors */
+ TAILQ_FOREACH_SAFE(plugin, &io_plugins, entries, next) {
+ int ok = iolog_open_int(plugin, settings, user_info,
+ command_info, argc, argv, user_env, &errstr);
+ switch (ok) {
+ case 1:
+ break;
+ case 0:
+ /* I/O plugin asked to be disabled, remove and free. */
+ /* XXX - audit */
+ unlink_plugin(&io_plugins, plugin);
+ break;
+ case -2:
+ usage();
+ break;
+ default:
+ /* XXX - audit error */
+ sudo_fatalx(U_("error initializing I/O plugin %s"),
+ plugin->name);
+ }
+ }
+
+ debug_return;
+}
+
+static void
+iolog_close(int exit_status, int error_code)
+{
+ struct plugin_container *plugin;
+ debug_decl(iolog_close, SUDO_DEBUG_PCOMM);
+
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ if (plugin->u.io->close != NULL) {
+ if (error_code != 0) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "%s: calling I/O close with errno %d",
+ plugin->name, error_code);
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "%s: calling I/O close with wait status %d",
+ plugin->name, exit_status);
+ }
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ plugin->u.io->close(exit_status, error_code);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+ }
+
+ debug_return;
+}
+
+static void
+iolog_show_version(int verbose, struct sudo_settings *settings,
+ char * const user_info[], int argc, char * const argv[],
+ char * const user_env[])
+{
+ const char *errstr = NULL;
+ struct plugin_container *plugin;
+ debug_decl(iolog_show_version, SUDO_DEBUG_PCOMM);
+
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ int ok = iolog_open_int(plugin, settings, user_info, NULL,
+ argc, argv, user_env, &errstr);
+ if (ok != -1) {
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ if (plugin->u.io->show_version != NULL) {
+ /* Return value of show_version currently ignored. */
+ plugin->u.io->show_version(verbose);
+ }
+ if (plugin->u.io->version >= SUDO_API_MKVERSION(1, 15)) {
+ if (plugin->u.io->close != NULL)
+ plugin->u.io->close(0, 0);
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+ }
+
+ debug_return;
+}
+
+/*
+ * Remove the specified plugin from the plugins list.
+ * Deregisters any hooks before unlinking, then frees the container.
+ */
+static void
+unlink_plugin(struct plugin_container_list *plugin_list,
+ struct plugin_container *plugin)
+{
+ void (*deregister_hooks)(int , int (*)(struct sudo_hook *)) = NULL;
+ debug_decl(unlink_plugin, SUDO_DEBUG_PCOMM);
+
+ /* Deregister hooks, if any. */
+ if (plugin->u.generic->version >= SUDO_API_MKVERSION(1, 2)) {
+ switch (plugin->u.generic->type) {
+ case SUDO_IO_PLUGIN:
+ deregister_hooks = plugin->u.io->deregister_hooks;
+ break;
+ case SUDO_AUDIT_PLUGIN:
+ deregister_hooks = plugin->u.audit->deregister_hooks;
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: unsupported plugin type %d", __func__,
+ plugin->u.generic->type);
+ break;
+ }
+ }
+ if (deregister_hooks != NULL) {
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ deregister_hooks(SUDO_HOOK_VERSION, deregister_hook);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+
+ /* Remove from plugin list and free. */
+ TAILQ_REMOVE(plugin_list, plugin, entries);
+ free_plugin_container(plugin, true);
+
+ debug_return;
+}
+
+static int
+audit_open_int(struct plugin_container *plugin, struct sudo_settings *settings,
+ char * const user_info[], int submit_optind, char * const submit_argv[],
+ char * const submit_envp[], const char **errstr)
+{
+ char **plugin_settings;
+ int ret;
+ debug_decl(audit_open_int, SUDO_DEBUG_PCOMM);
+
+ /* Convert struct sudo_settings to plugin_settings[] */
+ plugin_settings = format_plugin_settings(plugin, settings);
+ if (plugin_settings == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_int(-1);
+ }
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ ret = plugin->u.audit->open(SUDO_API_VERSION, sudo_conversation,
+ sudo_conversation_printf, plugin_settings, user_info,
+ submit_optind, submit_argv, submit_envp, plugin->options, errstr);
+
+ /* Stash plugin debug instance ID if set in open() function. */
+ plugin->debug_instance = sudo_debug_get_active_instance();
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+ debug_return_int(ret);
+}
+
+static void
+audit_open(struct sudo_settings *settings, char * const user_info[],
+ int submit_optind, char * const submit_argv[], char * const submit_envp[])
+{
+ struct plugin_container *plugin, *next;
+ const char *errstr = NULL;
+ debug_decl(audit_open, SUDO_DEBUG_PCOMM);
+
+ TAILQ_FOREACH_SAFE(plugin, &audit_plugins, entries, next) {
+ int ok = audit_open_int(plugin, settings, user_info,
+ submit_optind, submit_argv, submit_envp, &errstr);
+ switch (ok) {
+ case 1:
+ break;
+ case 0:
+ /* Audit plugin asked to be disabled, remove and free. */
+ unlink_plugin(&audit_plugins, plugin);
+ break;
+ case -2:
+ usage();
+ break;
+ default:
+ /* TODO: pass error message to other audit plugins */
+ sudo_fatalx(U_("error initializing audit plugin %s"),
+ plugin->name);
+ }
+ }
+
+ debug_return;
+}
+
+static void
+audit_close(int status_type, int status)
+{
+ struct plugin_container *plugin;
+ debug_decl(audit_close, SUDO_DEBUG_PCOMM);
+
+ TAILQ_FOREACH(plugin, &audit_plugins, entries) {
+ if (plugin->u.audit->close != NULL) {
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ plugin->u.audit->close(status_type, status);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+ }
+
+ debug_return;
+}
+
+static void
+audit_show_version(int verbose)
+{
+ struct plugin_container *plugin;
+ debug_decl(audit_show_version, SUDO_DEBUG_PCOMM);
+
+ TAILQ_FOREACH(plugin, &audit_plugins, entries) {
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ if (plugin->u.audit->show_version != NULL) {
+ /* Return value of show_version currently ignored. */
+ plugin->u.audit->show_version(verbose);
+ }
+ if (plugin->u.audit->close != NULL)
+ plugin->u.audit->close(SUDO_PLUGIN_NO_STATUS, 0);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+
+ debug_return;
+}
+
+/*
+ * Error from plugin or front-end.
+ * The error will not be sent to plugin source, if specified.
+ */
+static void
+audit_error2(struct plugin_container *source, const char *plugin_name,
+ unsigned int plugin_type, const char *audit_msg, char * const command_info[])
+{
+ struct plugin_container *plugin;
+ const char *errstr = NULL;
+ int ok;
+ debug_decl(audit_error2, SUDO_DEBUG_PCOMM);
+
+ TAILQ_FOREACH(plugin, &audit_plugins, entries) {
+ if (plugin->u.audit->error == NULL)
+ continue;
+
+ /* Avoid a loop if the audit plugin itself has an error. */
+ if (plugin == source)
+ continue;
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ ok = plugin->u.audit->error(plugin_name, plugin_type,
+ audit_msg, command_info, &errstr);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ if (ok != 1) {
+ /*
+ * Don't propagate the error to other audit plugins.
+ * It is not worth the trouble to avoid potential loops.
+ */
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: plugin %s error failed, ret %d", __func__,
+ plugin->name, ok);
+ sudo_warnx(U_("%s: unable to log error event%s%s"),
+ plugin->name, errstr ? ": " : "", errstr ? errstr : "");
+ }
+ }
+
+ debug_return;
+}
+
+/*
+ * Command accepted by policy.
+ * See command_info[] for additional info.
+ * XXX - actual environment may be updated by policy_init_session().
+ */
+static void
+audit_accept(const char *plugin_name, unsigned int plugin_type,
+ char * const command_info[], char * const run_argv[],
+ char * const run_envp[])
+{
+ struct plugin_container *plugin;
+ const char *errstr = NULL;
+ int ok;
+ debug_decl(audit_accept, SUDO_DEBUG_PCOMM);
+
+ TAILQ_FOREACH(plugin, &audit_plugins, entries) {
+ if (plugin->u.audit->accept == NULL)
+ continue;
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ ok = plugin->u.audit->accept(plugin_name, plugin_type,
+ command_info, run_argv, run_envp, &errstr);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ if (ok != 1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: plugin %s accept failed, ret %d", __func__,
+ plugin->name, ok);
+ sudo_warnx(U_("%s: unable to log accept event%s%s"),
+ plugin->name, errstr ? ": " : "", errstr ? errstr : "");
+
+ /* Notify other audit plugins and exit. */
+ audit_error2(plugin, plugin->name, SUDO_AUDIT_PLUGIN,
+ errstr ? errstr : _("audit plugin error"), command_info);
+ audit_close(SUDO_PLUGIN_NO_STATUS, 0);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ debug_return;
+}
+
+/*
+ * Command rejected by policy or I/O plugin.
+ */
+void
+audit_reject(const char *plugin_name, unsigned int plugin_type,
+ const char *audit_msg, char * const command_info[])
+{
+ struct plugin_container *plugin;
+ const char *errstr = NULL;
+ int ok;
+ debug_decl(audit_reject, SUDO_DEBUG_PCOMM);
+
+ TAILQ_FOREACH(plugin, &audit_plugins, entries) {
+ if (plugin->u.audit->reject == NULL)
+ continue;
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ ok = plugin->u.audit->reject(plugin_name, plugin_type,
+ audit_msg, command_info, &errstr);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ if (ok != 1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: plugin %s reject failed, ret %d", __func__,
+ plugin->name, ok);
+ sudo_warnx(U_("%s: unable to log reject event%s%s"),
+ plugin->name, errstr ? ": " : "", errstr ? errstr : "");
+
+ /* Notify other audit plugins. */
+ audit_error2(plugin, plugin->name, SUDO_AUDIT_PLUGIN,
+ errstr ? errstr : _("audit plugin error"), command_info);
+ }
+ }
+
+ debug_return;
+}
+
+/*
+ * Error from plugin or front-end.
+ */
+void
+audit_error(const char *plugin_name, unsigned int plugin_type,
+ const char *audit_msg, char * const command_info[])
+{
+ audit_error2(NULL, plugin_name, plugin_type, audit_msg, command_info);
+}
+
+static int
+approval_open_int(struct plugin_container *plugin,
+ struct sudo_settings *settings, char * const user_info[],
+ int submit_optind, char * const submit_argv[], char * const submit_envp[])
+{
+ char **plugin_settings;
+ const char *errstr = NULL;
+ int ret;
+ debug_decl(approval_open_int, SUDO_DEBUG_PCOMM);
+
+ /* Convert struct sudo_settings to plugin_settings[] */
+ plugin_settings = format_plugin_settings(plugin, settings);
+ if (plugin_settings == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ sudo_debug_set_active_instance(SUDO_DEBUG_INSTANCE_INITIALIZER);
+ ret = plugin->u.approval->open(SUDO_API_VERSION, sudo_conversation,
+ sudo_conversation_printf, plugin_settings, user_info, submit_optind,
+ submit_argv, submit_envp, plugin->options, &errstr);
+
+ /* Stash plugin debug instance ID if set in open() function. */
+ plugin->debug_instance = sudo_debug_get_active_instance();
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+ switch (ret) {
+ case 1:
+ break;
+ case 0:
+ /* approval plugin asked to be disabled, remove and free. */
+ unlink_plugin(&approval_plugins, plugin);
+ break;
+ case -2:
+ usage();
+ break;
+ default:
+ /* XXX - audit */
+ sudo_fatalx(U_("error initializing approval plugin %s"),
+ plugin->name);
+ }
+
+ debug_return_int(ret);
+}
+
+static void
+approval_show_version(int verbose, struct sudo_settings *settings,
+ char * const user_info[], int submit_optind, char * const submit_argv[],
+ char * const submit_envp[])
+{
+ struct plugin_container *plugin, *next;
+ int ok;
+ debug_decl(approval_show_version, SUDO_DEBUG_PCOMM);
+
+ /*
+ * Approval plugin us only open for the life of the show_version() call.
+ */
+ TAILQ_FOREACH_SAFE(plugin, &approval_plugins, entries, next) {
+ if (plugin->u.approval->show_version == NULL)
+ continue;
+
+ ok = approval_open_int(plugin, settings, user_info, submit_optind,
+ submit_argv, submit_envp);
+ if (ok == 1) {
+ /* Return value of show_version currently ignored. */
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ plugin->u.approval->show_version(verbose);
+ if (plugin->u.approval->close != NULL)
+ plugin->u.approval->close();
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+ }
+
+ debug_return;
+}
+
+/*
+ * Run approval checks (there may be more than one).
+ * This is a "one-shot" plugin that has no open/close and is only
+ * called if the policy plugin accepts the command first.
+ */
+static void
+approval_check(struct sudo_settings *settings, char * const user_info[],
+ int submit_optind, char * const submit_argv[], char * const submit_envp[],
+ char * const command_info[], char * const run_argv[],
+ char * const run_envp[])
+{
+ struct plugin_container *plugin, *next;
+ const char *errstr = NULL;
+ int ok;
+ debug_decl(approval_check, SUDO_DEBUG_PCOMM);
+
+ /*
+ * Approval plugin us only open for the life of the check() call.
+ */
+ TAILQ_FOREACH_SAFE(plugin, &approval_plugins, entries, next) {
+ if (plugin->u.approval->check == NULL)
+ continue;
+
+ ok = approval_open_int(plugin, settings, user_info, submit_optind,
+ submit_argv, submit_envp);
+ if (ok != 1)
+ continue;
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ ok = plugin->u.approval->check(command_info, run_argv, run_envp,
+ &errstr);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "approval plugin %s returns %d (%s)",
+ plugin->name, ok, errstr ? errstr : "");
+
+ switch (ok) {
+ case 0:
+ audit_reject(plugin->name, SUDO_APPROVAL_PLUGIN,
+ errstr ? errstr : _("command rejected by approver"),
+ command_info);
+ break;
+ case 1:
+ audit_accept(plugin->name, SUDO_APPROVAL_PLUGIN, command_info,
+ run_argv, run_envp);
+ break;
+ case -1:
+ audit_error(plugin->name, SUDO_APPROVAL_PLUGIN,
+ errstr ? errstr : _("approval plugin error"),
+ command_info);
+ break;
+ case -2:
+ usage();
+ break;
+ }
+
+ /* Close approval plugin now that errstr has been consumed. */
+ if (plugin->u.approval->close != NULL) {
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ plugin->u.approval->close();
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+
+ /* On error, close policy and audit plugins then exit. */
+ if (ok != 1) {
+ if (policy_plugin.u.policy->version >= SUDO_API_MKVERSION(1, 15))
+ policy_close(0, EPERM);
+ audit_close(SUDO_PLUGIN_NO_STATUS, 0);
+ exit(EXIT_FAILURE); /* approval plugin printed error message */
+ }
+ }
+
+ debug_return;
+}
+
+static void
+plugin_event_callback(int fd, int what, void *v)
+{
+ struct sudo_plugin_event_int *ev_int = v;
+ int old_instance;
+ debug_decl(plugin_event_callback, SUDO_DEBUG_PCOMM);
+
+ /* Run the real callback using the plugin's debug instance. */
+ old_instance = sudo_debug_set_active_instance(ev_int->debug_instance);
+ ev_int->callback(fd, what, ev_int->closure);
+ sudo_debug_set_active_instance(old_instance);
+
+ debug_return;
+}
+
+/*
+ * Fill in a previously allocated struct sudo_plugin_event.
+ */
+static int
+plugin_event_set(struct sudo_plugin_event *pev, int fd, int events,
+ sudo_ev_callback_t callback, void *closure)
+{
+ struct sudo_plugin_event_int *ev_int;
+ debug_decl(plugin_event_set, SUDO_DEBUG_PCOMM);
+
+ ev_int = __containerof(pev, struct sudo_plugin_event_int, public);
+ if (sudo_ev_set(&ev_int->private, fd, events, plugin_event_callback, ev_int) == -1)
+ debug_return_int(-1);
+
+ /* Stash active instance so we can restore it when callback runs. */
+ ev_int->debug_instance = sudo_debug_get_active_instance();
+
+ /* Actual user-specified callback and closure. */
+ ev_int->callback = callback;
+ ev_int->closure = closure;
+
+ /* Plugin can only operate on the main event loop. */
+ ev_int->private.base = sudo_event_base;
+
+ debug_return_int(1);
+}
+
+/*
+ * Add a struct sudo_plugin_event to the main event loop.
+ */
+static int
+plugin_event_add(struct sudo_plugin_event *pev, struct timespec *timo)
+{
+ struct sudo_plugin_event_int *ev_int;
+ debug_decl(plugin_event_add, SUDO_DEBUG_PCOMM);
+
+ ev_int = __containerof(pev, struct sudo_plugin_event_int, public);
+ if (sudo_ev_add(NULL, &ev_int->private, timo, 0) == -1)
+ debug_return_int(-1);
+ debug_return_int(1);
+}
+
+/*
+ * Delete a struct sudo_plugin_event from the main event loop.
+ */
+static int
+plugin_event_del(struct sudo_plugin_event *pev)
+{
+ struct sudo_plugin_event_int *ev_int;
+ debug_decl(plugin_event_del, SUDO_DEBUG_PCOMM);
+
+ ev_int = __containerof(pev, struct sudo_plugin_event_int, public);
+ if (sudo_ev_del(NULL, &ev_int->private) == -1)
+ debug_return_int(-1);
+ debug_return_int(1);
+}
+
+/*
+ * Get the amount of time remaining in a timeout event.
+ */
+static int
+plugin_event_pending(struct sudo_plugin_event *pev, int events,
+ struct timespec *ts)
+{
+ struct sudo_plugin_event_int *ev_int;
+ debug_decl(plugin_event_pending, SUDO_DEBUG_PCOMM);
+
+ ev_int = __containerof(pev, struct sudo_plugin_event_int, public);
+ debug_return_int(sudo_ev_pending(&ev_int->private, events, ts));
+}
+
+/*
+ * Get the file descriptor associated with an event.
+ */
+static int
+plugin_event_fd(struct sudo_plugin_event *pev)
+{
+ struct sudo_plugin_event_int *ev_int;
+ debug_decl(plugin_event_fd, SUDO_DEBUG_PCOMM);
+
+ ev_int = __containerof(pev, struct sudo_plugin_event_int, public);
+ debug_return_int(sudo_ev_get_fd(&ev_int->private));
+}
+
+/*
+ * Break out of the event loop, killing the command if it is running.
+ */
+static void
+plugin_event_loopbreak(struct sudo_plugin_event *pev)
+{
+ struct sudo_plugin_event_int *ev_int;
+ debug_decl(plugin_event_loopbreak, SUDO_DEBUG_PCOMM);
+
+ ev_int = __containerof(pev, struct sudo_plugin_event_int, public);
+ sudo_ev_loopbreak(ev_int->private.base);
+ debug_return;
+}
+
+/*
+ * Reset the event base of a struct sudo_plugin_event.
+ * The event is removed from the old base (if any) first.
+ * A NULL base can be used to set the default sudo event base.
+ */
+static void
+plugin_event_setbase(struct sudo_plugin_event *pev, void *base)
+{
+ struct sudo_plugin_event_int *ev_int;
+ debug_decl(plugin_event_setbase, SUDO_DEBUG_PCOMM);
+
+ ev_int = __containerof(pev, struct sudo_plugin_event_int, public);
+ if (ev_int->private.base != NULL)
+ sudo_ev_del(ev_int->private.base, &ev_int->private);
+ ev_int->private.base = base ? base : sudo_event_base;
+ debug_return;
+}
+
+/*
+ * Free a struct sudo_plugin_event allocated by plugin_event_alloc().
+ */
+static void
+plugin_event_free(struct sudo_plugin_event *pev)
+{
+ struct sudo_plugin_event_int *ev_int;
+ debug_decl(plugin_event_free, SUDO_DEBUG_PCOMM);
+
+ /* The private field is first so sudo_ev_free() can free the struct. */
+ ev_int = __containerof(pev, struct sudo_plugin_event_int, public);
+ sudo_ev_free(&ev_int->private);
+
+ debug_return;
+}
+
+/*
+ * Allocate a struct sudo_plugin_event and fill in the public fields.
+ */
+struct sudo_plugin_event *
+sudo_plugin_event_alloc(void)
+{
+ struct sudo_plugin_event_int *ev_int;
+ debug_decl(plugin_event_alloc, SUDO_DEBUG_PCOMM);
+
+ if ((ev_int = malloc(sizeof(*ev_int))) == NULL)
+ debug_return_ptr(NULL);
+
+ /* Init public fields. */
+ ev_int->public.set = plugin_event_set;
+ ev_int->public.add = plugin_event_add;
+ ev_int->public.del = plugin_event_del;
+ ev_int->public.fd = plugin_event_fd;
+ ev_int->public.pending = plugin_event_pending;
+ ev_int->public.setbase = plugin_event_setbase;
+ ev_int->public.loopbreak = plugin_event_loopbreak;
+ ev_int->public.free = plugin_event_free;
+
+ /* Debug instance to use with the callback. */
+ ev_int->debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER;
+
+ /* Clear private portion in case caller tries to use us uninitialized. */
+ memset(&ev_int->private, 0, sizeof(ev_int->private));
+
+ debug_return_ptr(&ev_int->public);
+}
+
+static void
+free_plugin_container(struct plugin_container *plugin, bool ioplugin)
+{
+ debug_decl(free_plugin_container, SUDO_DEBUG_PLUGIN);
+
+ free(plugin->path);
+ free(plugin->name);
+ if (plugin->options != NULL) {
+ int i = 0;
+ while (plugin->options[i] != NULL)
+ free(plugin->options[i++]);
+ free(plugin->options);
+ }
+ if (ioplugin)
+ free(plugin);
+
+ debug_return;
+}
+
+bool
+gc_add(enum sudo_gc_types type, void *v)
+{
+#ifdef NO_LEAKS
+ struct sudo_gc_entry *gc;
+ debug_decl(gc_add, SUDO_DEBUG_MAIN);
+
+ if (v == NULL)
+ debug_return_bool(false);
+
+ gc = calloc(1, sizeof(*gc));
+ if (gc == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_bool(false);
+ }
+ switch (type) {
+ case GC_PTR:
+ gc->u.ptr = v;
+ break;
+ case GC_VECTOR:
+ gc->u.vec = v;
+ break;
+ default:
+ free(gc);
+ sudo_warnx("unexpected garbage type %d", type);
+ debug_return_bool(false);
+ }
+ gc->type = type;
+ SLIST_INSERT_HEAD(&sudo_gc_list, gc, entries);
+ debug_return_bool(true);
+#else
+ return true;
+#endif /* NO_LEAKS */
+}
+
+#ifdef NO_LEAKS
+static void
+gc_run(void)
+{
+ struct plugin_container *plugin;
+ struct sudo_gc_entry *gc;
+ char **cur;
+ debug_decl(gc_run, SUDO_DEBUG_MAIN);
+
+ /* Collect garbage. */
+ while ((gc = SLIST_FIRST(&sudo_gc_list))) {
+ SLIST_REMOVE_HEAD(&sudo_gc_list, entries);
+ switch (gc->type) {
+ case GC_PTR:
+ free(gc->u.ptr);
+ free(gc);
+ break;
+ case GC_VECTOR:
+ for (cur = gc->u.vec; *cur != NULL; cur++)
+ free(*cur);
+ free(gc->u.vec);
+ free(gc);
+ break;
+ default:
+ sudo_warnx("unexpected garbage type %d", gc->type);
+ }
+ }
+
+ /* Free plugin structs. */
+ free_plugin_container(&policy_plugin, false);
+ while ((plugin = TAILQ_FIRST(&io_plugins))) {
+ TAILQ_REMOVE(&io_plugins, plugin, entries);
+ free_plugin_container(plugin, true);
+ }
+
+ debug_return;
+}
+#endif /* NO_LEAKS */
+
+static void
+gc_init(void)
+{
+#ifdef NO_LEAKS
+ atexit(gc_run);
+#endif
+}
diff --git a/src/sudo.h b/src/sudo.h
new file mode 100644
index 0000000..7f48ece
--- /dev/null
+++ b/src/sudo.h
@@ -0,0 +1,299 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1993-1996, 1998-2005, 2007-2021
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+#ifndef SUDO_SUDO_H
+#define SUDO_SUDO_H
+
+#include <limits.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#ifdef HAVE_PRIV_SET
+# include <priv.h>
+#endif
+
+#include "pathnames.h"
+#include "sudo_compat.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_event.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_queue.h"
+#include "sudo_util.h"
+
+/* Enable asserts() to avoid static analyzer false positives. */
+#if !(defined(SUDO_DEVEL) || defined(__clang_analyzer__) || defined(__COVERITY__))
+# define NDEBUG
+#endif
+
+/*
+ * Various modes sudo can be in (based on arguments) in hex
+ */
+#define MODE_RUN 0x00000001
+#define MODE_EDIT 0x00000002
+#define MODE_VALIDATE 0x00000004
+#define MODE_INVALIDATE 0x00000008
+#define MODE_KILL 0x00000010
+#define MODE_VERSION 0x00000020
+#define MODE_HELP 0x00000040
+#define MODE_LIST 0x00000080
+#define MODE_CHECK 0x00000100
+#define MODE_MASK 0x0000ffff
+
+/* Mode flags */
+/* XXX - prune this */
+#define MODE_BACKGROUND 0x00010000
+#define MODE_SHELL 0x00020000
+#define MODE_LOGIN_SHELL 0x00040000
+#define MODE_IMPLIED_SHELL 0x00080000
+#define MODE_RESET_HOME 0x00100000
+#define MODE_PRESERVE_GROUPS 0x00200000
+#define MODE_PRESERVE_ENV 0x00400000
+#define MODE_NONINTERACTIVE 0x00800000
+#define MODE_LONG_LIST 0x01000000
+
+/*
+ * Flags for tgetpass()
+ */
+#define TGP_NOECHO 0x00 /* turn echo off reading pw (default) */
+#define TGP_ECHO 0x01 /* leave echo on when reading passwd */
+#define TGP_STDIN 0x02 /* read from stdin, not /dev/tty */
+#define TGP_ASKPASS 0x04 /* read from askpass helper program */
+#define TGP_MASK 0x08 /* mask user input when reading */
+#define TGP_NOECHO_TRY 0x10 /* turn off echo if possible */
+#define TGP_BELL 0x20 /* bell on password prompt */
+
+/* name/value pairs for command line settings. */
+struct sudo_settings {
+ const char *name;
+ const char *value;
+};
+
+/* Sudo user credentials */
+struct sudo_cred {
+ uid_t uid;
+ uid_t euid;
+ uid_t gid;
+ uid_t egid;
+ int ngroups;
+ GETGROUPS_T *groups;
+};
+
+struct user_details {
+ struct sudo_cred cred;
+ pid_t pid;
+ pid_t ppid;
+ pid_t pgid;
+ pid_t tcpgid;
+ pid_t sid;
+ const char *username;
+ const char *cwd;
+ const char *tty;
+ const char *host;
+ const char *shell;
+ int ts_rows;
+ int ts_cols;
+};
+
+#define CD_SET_UID 0x000001
+#define CD_SET_EUID 0x000002
+#define CD_SET_GID 0x000004
+#define CD_SET_EGID 0x000008
+#define CD_PRESERVE_GROUPS 0x000010
+#define CD_NOEXEC 0x000020
+#define CD_SET_PRIORITY 0x000040
+#define CD_SET_UMASK 0x000080
+#define CD_SET_TIMEOUT 0x000100
+#define CD_SUDOEDIT 0x000200
+#define CD_BACKGROUND 0x000400
+#define CD_RBAC_ENABLED 0x000800
+#define CD_USE_PTY 0x001000
+#define CD_SET_UTMP 0x002000
+#define CD_EXEC_BG 0x004000
+#define CD_SUDOEDIT_FOLLOW 0x008000
+#define CD_SUDOEDIT_CHECKDIR 0x010000
+#define CD_SET_GROUPS 0x020000
+#define CD_LOGIN_SHELL 0x040000
+#define CD_OVERRIDE_UMASK 0x080000
+
+struct preserved_fd {
+ TAILQ_ENTRY(preserved_fd) entries;
+ int lowfd;
+ int highfd;
+ int flags;
+};
+TAILQ_HEAD(preserved_fd_list, preserved_fd);
+
+struct command_details {
+ struct sudo_cred cred;
+ mode_t umask;
+ int priority;
+ int timeout;
+ int closefrom;
+ int flags;
+ int execfd;
+ int cwd_optional;
+ struct preserved_fd_list preserved_fds;
+ struct passwd *pw;
+ const char *command;
+ const char *runas_user;
+ const char *cwd;
+ const char *login_class;
+ const char *chroot;
+ const char *selinux_role;
+ const char *selinux_type;
+ const char *utmp_user;
+ const char *tty;
+ char **argv;
+ char **envp;
+ struct sudo_event_base *evbase;
+#ifdef HAVE_PRIV_SET
+ priv_set_t *privs;
+ priv_set_t *limitprivs;
+#endif
+ char * const *info;
+};
+
+/* Status passed between parent and child via socketpair */
+struct command_status {
+#define CMD_INVALID 0
+#define CMD_ERRNO 1
+#define CMD_WSTATUS 2
+#define CMD_SIGNO 3
+#define CMD_PID 4
+#define CMD_TTYWINCH 5
+ int type;
+ int val;
+};
+
+/* Garbage collector data types. */
+enum sudo_gc_types {
+ GC_UNKNOWN,
+ GC_VECTOR,
+ GC_PTR
+};
+
+/* For fatal() and fatalx() (XXX - needed?) */
+void cleanup(int);
+
+/* tgetpass.c */
+char *tgetpass(const char *prompt, int timeout, int flags,
+ struct sudo_conv_callback *callback);
+
+/* exec.c */
+int sudo_execute(struct command_details *details, struct command_status *cstat);
+
+/* parse_args.c */
+int parse_args(int argc, char **argv, int *old_optind, int *nargc,
+ char ***nargv, struct sudo_settings **settingsp, char ***env_addp);
+extern int tgetpass_flags;
+
+/* get_pty.c */
+bool get_pty(int *leader, int *follower, char *name, size_t namesz, uid_t uid);
+
+/* sudo.c */
+int policy_init_session(struct command_details *details);
+int run_command(struct command_details *details);
+int os_init_common(int argc, char *argv[], char *envp[]);
+bool gc_add(enum sudo_gc_types type, void *v);
+bool set_user_groups(struct command_details *details);
+struct sudo_plugin_event *sudo_plugin_event_alloc(void);
+void audit_reject(const char *plugin_name, unsigned int plugin_type,
+ const char *audit_msg, char * const command_info[]);
+void audit_error(const char *plugin_name, unsigned int plugin_type,
+ const char *audit_msg, char * const command_info[]);
+extern const char *list_user;
+extern struct user_details user_details;
+extern int sudo_debug_instance;
+
+/* sudo_edit.c */
+int sudo_edit(struct command_details *details);
+
+/* parse_args.c */
+void usage(void) __attribute__((__noreturn__));
+
+/* openbsd.c */
+int os_init_openbsd(int argc, char *argv[], char *envp[]);
+
+/* selinux.c */
+int selinux_restore_tty(void);
+int selinux_setup(const char *role, const char *type, const char *ttyn,
+ int ttyfd, bool label_tty);
+int selinux_setcon(void);
+void selinux_execve(int fd, const char *path, char *const argv[],
+ char *envp[], bool noexec);
+
+/* solaris.c */
+void set_project(struct passwd *);
+int os_init_solaris(int argc, char *argv[], char *envp[]);
+
+/* hooks.c */
+/* XXX - move to sudo_plugin_int.h? */
+struct sudo_hook;
+int register_hook(struct sudo_hook *hook);
+int deregister_hook(struct sudo_hook *hook);
+int process_hooks_getenv(const char *name, char **val);
+int process_hooks_setenv(const char *name, const char *value, int overwrite);
+int process_hooks_putenv(char *string);
+int process_hooks_unsetenv(const char *name);
+
+/* env_hooks.c */
+char *getenv_unhooked(const char *name);
+
+/* interfaces.c */
+int get_net_ifs(char **addrinfo);
+
+/* ttyname.c */
+char *get_process_ttyname(char *name, size_t namelen);
+
+/* signal.c */
+struct sigaction;
+int sudo_sigaction(int signo, struct sigaction *sa, struct sigaction *osa);
+void init_signals(void);
+void restore_signals(void);
+void save_signals(void);
+bool signal_pending(int signo);
+
+/* preload.c */
+void preload_static_symbols(void);
+
+/* preserve_fds.c */
+int add_preserved_fd(struct preserved_fd_list *pfds, int fd);
+void closefrom_except(int startfd, struct preserved_fd_list *pfds);
+void parse_preserved_fds(struct preserved_fd_list *pfds, const char *fdstr);
+
+/* setpgrp_nobg.c */
+int tcsetpgrp_nobg(int fd, pid_t pgrp_id);
+
+/* limits.c */
+void disable_coredump();
+void restore_limits(void);
+void restore_nproc(void);
+void unlimit_nproc(void);
+void unlimit_sudo(void);
+int serialize_limits(char **info, size_t info_max);
+
+#endif /* SUDO_SUDO_H */
diff --git a/src/sudo_edit.c b/src/sudo_edit.c
new file mode 100644
index 0000000..b3eea98
--- /dev/null
+++ b/src/sudo_edit.c
@@ -0,0 +1,780 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2004-2008, 2010-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "sudo.h"
+#include "sudo_edit.h"
+#include "sudo_exec.h"
+
+#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
+
+/*
+ * Editor temporary file name along with original name, mtime and size.
+ */
+struct tempfile {
+ char *tfile;
+ char *ofile;
+ off_t osize;
+ struct timespec omtim;
+};
+
+static char edit_tmpdir[MAX(sizeof(_PATH_VARTMP), sizeof(_PATH_TMP))];
+
+/*
+ * Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp
+ * Returns true on success, else false;
+ */
+static bool
+set_tmpdir(struct sudo_cred *user_cred)
+{
+ const char *tdir = NULL;
+ const char *tmpdirs[] = {
+ _PATH_VARTMP,
+#ifdef _PATH_USRTMP
+ _PATH_USRTMP,
+#endif
+ _PATH_TMP
+ };
+ struct sudo_cred saved_cred;
+ unsigned int i;
+ size_t len;
+ int dfd;
+ debug_decl(set_tmpdir, SUDO_DEBUG_EDIT);
+
+ /* Stash old credentials. */
+ saved_cred.uid = getuid();
+ saved_cred.euid = geteuid();
+ saved_cred.gid = getgid();
+ saved_cred.egid = getegid();
+ saved_cred.ngroups = getgroups(0, NULL); // -V575
+ if (saved_cred.ngroups > 0) {
+ saved_cred.groups =
+ reallocarray(NULL, saved_cred.ngroups, sizeof(GETGROUPS_T));
+ if (saved_cred.groups == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_bool(false);
+ }
+ if (getgroups(saved_cred.ngroups, saved_cred.groups) < 0) {
+ sudo_warn("%s", U_("unable to get group list"));
+ free(saved_cred.groups);
+ debug_return_bool(false);
+ }
+ } else {
+ saved_cred.ngroups = 0;
+ saved_cred.groups = NULL;
+ }
+
+ for (i = 0; tdir == NULL && i < nitems(tmpdirs); i++) {
+ if ((dfd = open(tmpdirs[i], O_RDONLY)) != -1) {
+ if (dir_is_writable(dfd, user_cred, &saved_cred) == true)
+ tdir = tmpdirs[i];
+ close(dfd);
+ }
+ }
+ free(saved_cred.groups);
+
+ if (tdir == NULL) {
+ sudo_warnx("%s", U_("no writable temporary directory found"));
+ debug_return_bool(false);
+ }
+
+ len = strlcpy(edit_tmpdir, tdir, sizeof(edit_tmpdir));
+ if (len >= sizeof(edit_tmpdir)) {
+ errno = ENAMETOOLONG;
+ sudo_warn("%s", tdir);
+ debug_return_bool(false);
+ }
+ while (len > 0 && edit_tmpdir[--len] == '/')
+ edit_tmpdir[len] = '\0';
+ debug_return_bool(true);
+}
+
+/*
+ * Construct a temporary file name for file and return an
+ * open file descriptor. The temporary file name is stored
+ * in tfile which the caller is responsible for freeing.
+ */
+static int
+sudo_edit_mktemp(const char *ofile, char **tfile)
+{
+ const char *cp, *suff;
+ int len, tfd;
+ debug_decl(sudo_edit_mktemp, SUDO_DEBUG_EDIT);
+
+ if ((cp = strrchr(ofile, '/')) != NULL)
+ cp++;
+ else
+ cp = ofile;
+ suff = strrchr(cp, '.');
+ if (suff != NULL) {
+ len = asprintf(tfile, "%s/%.*sXXXXXXXX%s", edit_tmpdir,
+ (int)(size_t)(suff - cp), cp, suff);
+ } else {
+ len = asprintf(tfile, "%s/%s.XXXXXXXX", edit_tmpdir, cp);
+ }
+ if (len == -1) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_int(-1);
+ }
+ tfd = mkstemps(*tfile, suff ? strlen(suff) : 0);
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "%s -> %s, fd %d", ofile, *tfile, tfd);
+ debug_return_int(tfd);
+}
+
+/*
+ * Create temporary copies of files[] and store the temporary path name
+ * along with the original name, size and mtime in tf.
+ * Returns the number of files copied (which may be less than nfiles)
+ * or -1 if a fatal error occurred.
+ */
+static int
+sudo_edit_create_tfiles(struct command_details *command_details,
+ struct tempfile *tf, char *files[], int nfiles)
+{
+ int i, j, tfd, ofd, rc;
+ struct timespec times[2];
+ struct stat sb;
+ debug_decl(sudo_edit_create_tfiles, SUDO_DEBUG_EDIT);
+
+ /*
+ * For each file specified by the user, make a temporary version
+ * and copy the contents of the original to it.
+ */
+ for (i = 0, j = 0; i < nfiles; i++) {
+ rc = -1;
+ switch_user(command_details->cred.euid, command_details->cred.egid,
+ command_details->cred.ngroups, command_details->cred.groups);
+ ofd = sudo_edit_open(files[i], O_RDONLY,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details->flags,
+ &user_details.cred, &command_details->cred);
+ if (ofd != -1 || errno == ENOENT) {
+ if (ofd != -1) {
+ rc = fstat(ofd, &sb);
+ } else {
+ /* New file, verify parent dir exists and is not writable. */
+ memset(&sb, 0, sizeof(sb));
+ if (sudo_edit_parent_valid(files[i], command_details->flags, &user_details.cred, &command_details->cred))
+ rc = 0;
+ }
+ }
+ switch_user(ROOT_UID, user_details.cred.egid,
+ user_details.cred.ngroups, user_details.cred.groups);
+ if (ofd != -1 && !S_ISREG(sb.st_mode)) {
+ sudo_warnx(U_("%s: not a regular file"), files[i]);
+ close(ofd);
+ continue;
+ }
+ if (rc == -1) {
+ /* open() or fstat() error. */
+ if (ofd == -1 && errno == ELOOP) {
+ sudo_warnx(U_("%s: editing symbolic links is not permitted"),
+ files[i]);
+ } else if (ofd == -1 && errno == EISDIR) {
+ sudo_warnx(U_("%s: editing files in a writable directory is not permitted"),
+ files[i]);
+ } else {
+ sudo_warn("%s", files[i]);
+ }
+ if (ofd != -1)
+ close(ofd);
+ continue;
+ }
+ tf[j].ofile = files[i];
+ tf[j].osize = sb.st_size; // -V614
+ mtim_get(&sb, tf[j].omtim);
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "seteuid(%u)", (unsigned int)user_details.cred.uid);
+ if (seteuid(user_details.cred.uid) != 0)
+ sudo_fatal("seteuid(%u)", (unsigned int)user_details.cred.uid);
+ tfd = sudo_edit_mktemp(tf[j].ofile, &tf[j].tfile);
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "seteuid(%u)", ROOT_UID);
+ if (seteuid(ROOT_UID) != 0)
+ sudo_fatal("seteuid(ROOT_UID)");
+ if (tfd == -1) {
+ sudo_warn("mkstemps");
+ if (ofd != -1)
+ close(ofd);
+ debug_return_int(-1);
+ }
+ if (ofd != -1) {
+ if (sudo_copy_file(tf[j].ofile, ofd, tf[j].osize, tf[j].tfile, tfd, -1) == -1) {
+ close(ofd);
+ close(tfd);
+ debug_return_int(-1);
+ }
+ close(ofd);
+ }
+ /*
+ * We always update the stashed mtime because the time
+ * resolution of the filesystem the temporary file is on may
+ * not match that of the filesystem where the file to be edited
+ * resides. It is OK if futimens() fails since we only use the
+ * info to determine whether or not a file has been modified.
+ */
+ times[0].tv_sec = times[1].tv_sec = tf[j].omtim.tv_sec;
+ times[0].tv_nsec = times[1].tv_nsec = tf[j].omtim.tv_nsec;
+ if (futimens(tfd, times) == -1) {
+ if (utimensat(AT_FDCWD, tf[j].tfile, times, 0) == -1)
+ sudo_warn("%s", tf[j].tfile);
+ }
+ rc = fstat(tfd, &sb);
+ if (!rc)
+ mtim_get(&sb, tf[j].omtim);
+ close(tfd);
+ j++;
+ }
+ debug_return_int(j);
+}
+
+/*
+ * Copy the temporary files specified in tf to the originals.
+ * Returns the number of copy errors or 0 if completely successful.
+ */
+static int
+sudo_edit_copy_tfiles(struct command_details *command_details,
+ struct tempfile *tf, int nfiles, struct timespec *times)
+{
+ int i, tfd, ofd, errors = 0;
+ struct timespec ts;
+ struct stat sb;
+ mode_t oldmask;
+ debug_decl(sudo_edit_copy_tfiles, SUDO_DEBUG_EDIT);
+
+ /* Copy contents of temp files to real ones. */
+ for (i = 0; i < nfiles; i++) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "seteuid(%u)", (unsigned int)user_details.cred.uid);
+ if (seteuid(user_details.cred.uid) != 0)
+ sudo_fatal("seteuid(%u)", (unsigned int)user_details.cred.uid);
+ tfd = sudo_edit_open(tf[i].tfile, O_RDONLY,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, 0, &user_details.cred, NULL);
+ if (seteuid(ROOT_UID) != 0)
+ sudo_fatal("seteuid(ROOT_UID)");
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "seteuid(%u)", ROOT_UID);
+ if (tfd == -1 || !sudo_check_temp_file(tfd, tf[i].tfile, user_details.cred.uid, &sb)) {
+ sudo_warnx(U_("%s left unmodified"), tf[i].ofile);
+ if (tfd != -1)
+ close(tfd);
+ errors++;
+ continue;
+ }
+ mtim_get(&sb, ts);
+ if (tf[i].osize == sb.st_size && sudo_timespeccmp(&tf[i].omtim, &ts, ==)) {
+ /*
+ * If mtime and size match but the user spent no measurable
+ * time in the editor we can't tell if the file was changed.
+ */
+ if (sudo_timespeccmp(&times[0], &times[1], !=)) {
+ sudo_warnx(U_("%s unchanged"), tf[i].ofile);
+ unlink(tf[i].tfile);
+ close(tfd);
+ continue;
+ }
+ }
+ switch_user(command_details->cred.euid, command_details->cred.egid,
+ command_details->cred.ngroups, command_details->cred.groups);
+ oldmask = umask(command_details->umask);
+ ofd = sudo_edit_open(tf[i].ofile, O_WRONLY|O_CREAT,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details->flags,
+ &user_details.cred, &command_details->cred);
+ umask(oldmask);
+ switch_user(ROOT_UID, user_details.cred.egid,
+ user_details.cred.ngroups, user_details.cred.groups);
+ if (ofd == -1) {
+ sudo_warn(U_("unable to write to %s"), tf[i].ofile);
+ goto bad;
+ }
+
+ /* Overwrite the old file with the new contents. */
+ if (sudo_copy_file(tf[i].tfile, tfd, sb.st_size, tf[i].ofile, ofd,
+ tf[i].osize) == 0) {
+ /* success, remove temporary file. */
+ unlink(tf[i].tfile);
+ } else {
+bad:
+ sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
+ errors++;
+ }
+
+ if (ofd != -1)
+ close(ofd);
+ close(tfd);
+ }
+ debug_return_int(errors);
+}
+
+#ifdef HAVE_SELINUX
+static int
+selinux_run_helper(uid_t uid, gid_t gid, int ngroups, GETGROUPS_T *groups,
+ char *const argv[], char *const envp[])
+{
+ int status, ret = SESH_ERR_FAILURE;
+ const char *sesh;
+ pid_t child, pid;
+ debug_decl(selinux_run_helper, SUDO_DEBUG_EDIT);
+
+ sesh = sudo_conf_sesh_path();
+ if (sesh == NULL) {
+ sudo_warnx("internal error: sesh path not set");
+ debug_return_int(-1);
+ }
+
+ child = sudo_debug_fork();
+ switch (child) {
+ case -1:
+ sudo_warn("%s", U_("unable to fork"));
+ break;
+ case 0:
+ /* child runs sesh in new context */
+ if (selinux_setcon() == 0) {
+ switch_user(uid, gid, ngroups, groups);
+ execve(sesh, argv, envp);
+ }
+ _exit(SESH_ERR_FAILURE);
+ default:
+ /* parent waits */
+ do {
+ pid = waitpid(child, &status, 0);
+ } while (pid == -1 && errno == EINTR);
+
+ ret = WIFSIGNALED(status) ? SESH_ERR_KILLED : WEXITSTATUS(status);
+ }
+
+ debug_return_int(ret);
+}
+
+static char *
+selinux_fmt_sudo_user(void)
+{
+ char *cp, *user_str;
+ size_t user_size;
+ int i, len;
+ debug_decl(selinux_fmt_sudo_user, SUDO_DEBUG_EDIT);
+
+ user_size = (MAX_UID_T_LEN + 1) * (2 + user_details.cred.ngroups);
+ if ((user_str = malloc(user_size)) == NULL)
+ debug_return_ptr(NULL);
+
+ /* UID:GID: */
+ len = snprintf(user_str, user_size, "%u:%u:",
+ (unsigned int)user_details.cred.uid, (unsigned int)user_details.cred.gid);
+ if (len < 0 || (size_t)len >= user_size)
+ sudo_fatalx(U_("internal error, %s overflow"), __func__);
+
+ /* Supplementary GIDs */
+ cp = user_str + len;
+ for (i = 0; i < user_details.cred.ngroups; i++) {
+ len = snprintf(cp, user_size - (cp - user_str), "%s%u",
+ i ? "," : "", (unsigned int)user_details.cred.groups[i]);
+ if (len < 0 || (size_t)len >= user_size - (cp - user_str))
+ sudo_fatalx(U_("internal error, %s overflow"), __func__);
+ cp += len;
+ }
+
+ debug_return_ptr(user_str);
+}
+
+static int
+selinux_edit_create_tfiles(struct command_details *command_details,
+ struct tempfile *tf, char *files[], int nfiles)
+{
+ char **sesh_args, **sesh_ap, *user_str = NULL;
+ int i, error, sesh_nargs, ret = -1;
+ struct stat sb;
+ debug_decl(selinux_edit_create_tfiles, SUDO_DEBUG_EDIT);
+
+ if (nfiles < 1)
+ debug_return_int(0);
+
+ /* Construct common args for sesh */
+ sesh_nargs = 6 + (nfiles * 2) + 1;
+ sesh_args = sesh_ap = reallocarray(NULL, sesh_nargs, sizeof(char *));
+ if (sesh_args == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto done;
+ }
+ *sesh_ap++ = "sesh";
+ *sesh_ap++ = "-e";
+ if (!ISSET(command_details->flags, CD_SUDOEDIT_FOLLOW))
+ *sesh_ap++ = "-h";
+ if (ISSET(command_details->flags, CD_SUDOEDIT_CHECKDIR)) {
+ if ((user_str = selinux_fmt_sudo_user()) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto done;
+ }
+ *sesh_ap++ = "-w";
+ *sesh_ap++ = user_str;
+ }
+ *sesh_ap++ = "0";
+
+ for (i = 0; i < nfiles; i++) {
+ char *tfile, *ofile = files[i];
+ int tfd;
+ *sesh_ap++ = ofile;
+ tf[i].ofile = ofile;
+ if (stat(ofile, &sb) == -1)
+ memset(&sb, 0, sizeof(sb)); /* new file */
+ tf[i].osize = sb.st_size;
+ mtim_get(&sb, tf[i].omtim);
+ /*
+ * The temp file must be created by the sesh helper,
+ * which uses O_EXCL | O_NOFOLLOW to make this safe.
+ */
+ tfd = sudo_edit_mktemp(ofile, &tfile);
+ if (tfd == -1) {
+ sudo_warn("mkstemps");
+ free(tfile);
+ goto done;
+ }
+ /* Helper will re-create temp file with proper security context. */
+ close(tfd);
+ unlink(tfile);
+ *sesh_ap++ = tfile;
+ tf[i].tfile = tfile;
+ }
+ *sesh_ap = NULL;
+
+ /* Run sesh -e [-h] 0 <o1> <t1> ... <on> <tn> */
+ error = selinux_run_helper(command_details->cred.uid, command_details->cred.gid,
+ command_details->cred.ngroups, command_details->cred.groups, sesh_args,
+ command_details->envp);
+ switch (error) {
+ case SESH_SUCCESS:
+ break;
+ case SESH_ERR_BAD_PATHS:
+ sudo_fatalx("%s", U_("sesh: internal error: odd number of paths"));
+ case SESH_ERR_NO_FILES:
+ sudo_fatalx("%s", U_("sesh: unable to create temporary files"));
+ case SESH_ERR_KILLED:
+ sudo_fatalx("%s", U_("sesh: killed by a signal"));
+ default:
+ sudo_warnx(U_("sesh: unknown error %d"), error);
+ goto done;
+ }
+
+ for (i = 0; i < nfiles; i++) {
+ int tfd = open(tf[i].tfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW);
+ if (tfd == -1) {
+ sudo_warn(U_("unable to open %s"), tf[i].tfile);
+ goto done;
+ }
+ if (!sudo_check_temp_file(tfd, tf[i].tfile, command_details->cred.uid, NULL)) {
+ close(tfd);
+ goto done;
+ }
+ if (fchown(tfd, user_details.cred.uid, user_details.cred.gid) != 0) {
+ sudo_warn("unable to chown(%s) to %d:%d for editing",
+ tf[i].tfile, user_details.cred.uid, user_details.cred.gid);
+ close(tfd);
+ goto done;
+ }
+ close(tfd);
+ }
+ ret = nfiles;
+
+done:
+ /* Contents of tf will be freed by caller. */
+ free(sesh_args);
+ free(user_str);
+
+ debug_return_int(ret);
+}
+
+static int
+selinux_edit_copy_tfiles(struct command_details *command_details,
+ struct tempfile *tf, int nfiles, struct timespec *times)
+{
+ char **sesh_args, **sesh_ap, *user_str = NULL;
+ int i, error, sesh_nargs, ret = 1;
+ int tfd = -1;
+ struct timespec ts;
+ struct stat sb;
+ debug_decl(selinux_edit_copy_tfiles, SUDO_DEBUG_EDIT);
+
+ if (nfiles < 1)
+ debug_return_int(0);
+
+ /* Construct common args for sesh */
+ sesh_nargs = 5 + (nfiles * 2) + 1;
+ sesh_args = sesh_ap = reallocarray(NULL, sesh_nargs, sizeof(char *));
+ if (sesh_args == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto done;
+ }
+ *sesh_ap++ = "sesh";
+ *sesh_ap++ = "-e";
+ if (ISSET(command_details->flags, CD_SUDOEDIT_CHECKDIR)) {
+ if ((user_str = selinux_fmt_sudo_user()) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto done;
+ }
+ *sesh_ap++ = "-w";
+ *sesh_ap++ = user_str;
+ }
+ *sesh_ap++ = "1";
+
+ /* Construct args for sesh -e 1 */
+ for (i = 0; i < nfiles; i++) {
+ if (tfd != -1)
+ close(tfd);
+ if ((tfd = open(tf[i].tfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW)) == -1) {
+ sudo_warn(U_("unable to open %s"), tf[i].tfile);
+ continue;
+ }
+ if (!sudo_check_temp_file(tfd, tf[i].tfile, user_details.cred.uid, &sb))
+ continue;
+ mtim_get(&sb, ts);
+ if (tf[i].osize == sb.st_size && sudo_timespeccmp(&tf[i].omtim, &ts, ==)) {
+ /*
+ * If mtime and size match but the user spent no measurable
+ * time in the editor we can't tell if the file was changed.
+ */
+ if (sudo_timespeccmp(&times[0], &times[1], !=)) {
+ sudo_warnx(U_("%s unchanged"), tf[i].ofile);
+ unlink(tf[i].tfile);
+ continue;
+ }
+ }
+ *sesh_ap++ = tf[i].tfile;
+ *sesh_ap++ = tf[i].ofile;
+ if (fchown(tfd, command_details->cred.uid, command_details->cred.gid) != 0) {
+ sudo_warn("unable to chown(%s) back to %d:%d", tf[i].tfile,
+ command_details->cred.uid, command_details->cred.gid);
+ }
+ }
+ *sesh_ap = NULL;
+ if (tfd != -1)
+ close(tfd);
+
+ if (sesh_ap - sesh_args > 3) {
+ /* Run sesh -e 1 <t1> <o1> ... <tn> <on> */
+ error = selinux_run_helper(command_details->cred.uid, command_details->cred.gid,
+ command_details->cred.ngroups, command_details->cred.groups, sesh_args,
+ command_details->envp);
+ switch (error) {
+ case SESH_SUCCESS:
+ ret = 0;
+ break;
+ case SESH_ERR_NO_FILES:
+ sudo_warnx("%s",
+ U_("unable to copy temporary files back to their original location"));
+ break;
+ case SESH_ERR_SOME_FILES:
+ sudo_warnx("%s",
+ U_("unable to copy some of the temporary files back to their original location"));
+ break;
+ case SESH_ERR_KILLED:
+ sudo_warnx("%s", U_("sesh: killed by a signal"));
+ break;
+ default:
+ sudo_warnx(U_("sesh: unknown error %d"), error);
+ break;
+ }
+ }
+
+done:
+ /* Contents of tf will be freed by caller. */
+ free(sesh_args);
+ free(user_str);
+
+ debug_return_int(ret);
+}
+#endif /* HAVE_SELINUX */
+
+/*
+ * Wrapper to allow users to edit privileged files with their own uid.
+ * Returns the wait status of the command on success and a wait status
+ * of 1 on failure.
+ */
+int
+sudo_edit(struct command_details *command_details)
+{
+ struct command_details saved_command_details;
+ char **nargv = NULL, **ap, **files = NULL;
+ int errors, i, ac, nargc, ret;
+ int editor_argc = 0, nfiles = 0;
+ struct timespec times[2];
+ struct tempfile *tf = NULL;
+ debug_decl(sudo_edit, SUDO_DEBUG_EDIT);
+
+ /*
+ * Set real, effective and saved uids to root.
+ * We will change the euid as needed below.
+ */
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "setuid(%u)", ROOT_UID);
+ if (setuid(ROOT_UID) != 0) {
+ sudo_warn(U_("unable to change uid to root (%u)"), ROOT_UID);
+ goto cleanup;
+ }
+
+ /* Find a temporary directory writable by the user. */
+ if (!set_tmpdir(&user_details.cred))
+ goto cleanup;
+
+ /*
+ * The user's editor must be separated from the files to be
+ * edited by a "--" option.
+ */
+ for (ap = command_details->argv; *ap != NULL; ap++) {
+ if (files)
+ nfiles++;
+ else if (strcmp(*ap, "--") == 0)
+ files = ap + 1;
+ else
+ editor_argc++;
+ }
+ if (nfiles == 0) {
+ sudo_warnx("%s", U_("plugin error: missing file list for sudoedit"));
+ goto cleanup;
+ }
+
+#ifdef HAVE_SELINUX
+ /* Compute new SELinux security context. */
+ if (ISSET(command_details->flags, CD_RBAC_ENABLED)) {
+ if (selinux_setup(command_details->selinux_role,
+ command_details->selinux_type, NULL, -1, false) != 0)
+ goto cleanup;
+ }
+#endif
+
+ /* Copy editor files to temporaries. */
+ tf = calloc(nfiles, sizeof(*tf));
+ if (tf == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto cleanup;
+ }
+#ifdef HAVE_SELINUX
+ if (ISSET(command_details->flags, CD_RBAC_ENABLED))
+ nfiles = selinux_edit_create_tfiles(command_details, tf, files, nfiles);
+ else
+#endif
+ nfiles = sudo_edit_create_tfiles(command_details, tf, files, nfiles);
+ if (nfiles <= 0)
+ goto cleanup;
+
+ /*
+ * Allocate space for the new argument vector and fill it in.
+ * We concatenate the editor with its args and the file list
+ * to create a new argv.
+ */
+ nargc = editor_argc + nfiles;
+ nargv = reallocarray(NULL, nargc + 1, sizeof(char *));
+ if (nargv == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto cleanup;
+ }
+ for (ac = 0; ac < editor_argc; ac++)
+ nargv[ac] = command_details->argv[ac];
+ for (i = 0; i < nfiles && ac < nargc; )
+ nargv[ac++] = tf[i++].tfile;
+ nargv[ac] = NULL;
+
+ /*
+ * Run the editor with the invoking user's creds and drop setuid.
+ * Keep track of the time spent in the editor to distinguish between
+ * a user editing a file and a program doing it.
+ * XXX - should run editor with user's context
+ */
+ if (sudo_gettime_real(&times[0]) == -1) {
+ sudo_warn("%s", U_("unable to read the clock"));
+ goto cleanup;
+ }
+ memcpy(&saved_command_details, command_details, sizeof(struct command_details));
+ command_details->cred = user_details.cred;
+ command_details->cred.euid = user_details.cred.uid;
+ command_details->cred.egid = user_details.cred.gid;
+ command_details->argv = nargv;
+ ret = run_command(command_details);
+ if (sudo_gettime_real(&times[1]) == -1) {
+ sudo_warn("%s", U_("unable to read the clock"));
+ goto cleanup;
+ }
+
+ /* Restore saved command_details. */
+ command_details->cred = saved_command_details.cred;
+ command_details->argv = saved_command_details.argv;
+
+ /* Copy contents of temp files to real ones. */
+#ifdef HAVE_SELINUX
+ if (ISSET(command_details->flags, CD_RBAC_ENABLED))
+ errors = selinux_edit_copy_tfiles(command_details, tf, nfiles, times);
+ else
+#endif
+ errors = sudo_edit_copy_tfiles(command_details, tf, nfiles, times);
+ if (errors) {
+ /* Preserve the edited temporary files. */
+ ret = W_EXITCODE(1, 0);
+ }
+
+ for (i = 0; i < nfiles; i++)
+ free(tf[i].tfile);
+ free(tf);
+ free(nargv);
+ debug_return_int(ret);
+
+cleanup:
+ /* Clean up temp files and return. */
+ if (tf != NULL) {
+ for (i = 0; i < nfiles; i++) {
+ if (tf[i].tfile != NULL)
+ unlink(tf[i].tfile);
+ free(tf[i].tfile);
+ }
+ }
+ free(tf);
+ free(nargv);
+ debug_return_int(W_EXITCODE(1, 0));
+}
+
+#else /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */
+
+/*
+ * Must have the ability to change the effective uid to use sudoedit.
+ */
+int
+sudo_edit(struct command_details *command_details)
+{
+ debug_decl(sudo_edit, SUDO_DEBUG_EDIT);
+ debug_return_int(W_EXITCODE(1, 0));
+}
+
+#endif /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */
diff --git a/src/sudo_edit.h b/src/sudo_edit.h
new file mode 100644
index 0000000..87e6e9f
--- /dev/null
+++ b/src/sudo_edit.h
@@ -0,0 +1,55 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef SUDO_EDIT_H
+#define SUDO_EDIT_H
+
+/*
+ * Directory open flags for use with openat(2).
+ * Use O_SEARCH/O_PATH and/or O_DIRECTORY where possible.
+ */
+#if defined(O_SEARCH)
+# if defined(O_DIRECTORY)
+# define DIR_OPEN_FLAGS (O_SEARCH|O_DIRECTORY)
+# else
+# define DIR_OPEN_FLAGS (O_SEARCH)
+# endif
+#elif defined(O_PATH)
+# if defined(O_DIRECTORY)
+# define DIR_OPEN_FLAGS (O_PATH|O_DIRECTORY)
+# else
+# define DIR_OPEN_FLAGS (O_PATH)
+# endif
+#elif defined(O_DIRECTORY)
+# define DIR_OPEN_FLAGS (O_RDONLY|O_DIRECTORY)
+#else
+# define DIR_OPEN_FLAGS (O_RDONLY|O_NONBLOCK)
+#endif
+
+/* copy_file.c */
+int sudo_copy_file(const char *src, int src_fd, off_t src_len, const char *dst, int dst_fd, off_t dst_len);
+bool sudo_check_temp_file(int tfd, const char *tname, uid_t uid, struct stat *sb);
+
+/* edit_open.c */
+struct sudo_cred;
+void switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups);
+int sudo_edit_open(char *path, int oflags, mode_t mode, int sflags, struct sudo_cred *user_cred, struct sudo_cred *cur_cred);
+int dir_is_writable(int dfd, struct sudo_cred *user_cred, struct sudo_cred *cur_cred);
+bool sudo_edit_parent_valid(char *path, int sflags, struct sudo_cred *user_cred, struct sudo_cred *cur_cred);
+
+#endif /* SUDO_EDIT_H */
diff --git a/src/sudo_exec.h b/src/sudo_exec.h
new file mode 100644
index 0000000..1e086d2
--- /dev/null
+++ b/src/sudo_exec.h
@@ -0,0 +1,115 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010-2017, 2020-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef SUDO_EXEC_H
+#define SUDO_EXEC_H
+
+/*
+ * Older systems may not support MSG_WAITALL but it shouldn't really be needed.
+ */
+#ifndef MSG_WAITALL
+# define MSG_WAITALL 0
+#endif
+
+/*
+ * Some older systems support siginfo but predate SI_USER.
+ */
+#ifdef SI_USER
+# define USER_SIGNALED(_info) ((_info) != NULL && (_info)->si_code == SI_USER)
+#else
+# define USER_SIGNALED(_info) ((_info) != NULL && (_info)->si_code <= 0)
+#endif
+
+/*
+ * Indices into io_fds[] when running a command in a pty.
+ */
+#define SFD_STDIN 0
+#define SFD_STDOUT 1
+#define SFD_STDERR 2
+#define SFD_LEADER 3
+#define SFD_FOLLOWER 4
+#define SFD_USERTTY 5
+
+/*
+ * Special values to indicate whether continuing in foreground or background.
+ */
+#define SIGCONT_FG -2
+#define SIGCONT_BG -3
+
+/*
+ * Positions in saved_signals[]
+ */
+#define SAVED_SIGALRM 0
+#define SAVED_SIGCHLD 1
+#define SAVED_SIGCONT 2
+#define SAVED_SIGHUP 3
+#define SAVED_SIGINT 4
+#define SAVED_SIGPIPE 5
+#define SAVED_SIGQUIT 6
+#define SAVED_SIGTERM 7
+#define SAVED_SIGTSTP 8
+#define SAVED_SIGTTIN 9
+#define SAVED_SIGTTOU 10
+#define SAVED_SIGUSR1 11
+#define SAVED_SIGUSR2 12
+
+/*
+ * Error codes for sesh
+ */
+#define SESH_SUCCESS 0 /* successful operation */
+#define SESH_ERR_FAILURE 1 /* unspecified error */
+#define SESH_ERR_KILLED 2 /* killed by a signal */
+#define SESH_ERR_INVALID 30 /* invalid -e arg value */
+#define SESH_ERR_BAD_PATHS 31 /* odd number of paths */
+#define SESH_ERR_NO_FILES 32 /* copy error, no files copied */
+#define SESH_ERR_SOME_FILES 33 /* copy error, some files copied */
+
+/*
+ * Symbols shared between exec.c, exec_nopty.c, exec_pty.c and exec_monitor.c
+ */
+struct command_details;
+struct command_status;
+struct stat;
+
+/* exec.c */
+void exec_cmnd(struct command_details *details, int errfd);
+void terminate_command(pid_t pid, bool use_pgrp);
+bool sudo_terminated(struct command_status *cstat);
+
+/* exec_common.c */
+int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noexec);
+char **disable_execute(char *envp[], const char *dso);
+
+/* exec_nopty.c */
+void exec_nopty(struct command_details *details, struct command_status *cstat);
+
+/* exec_pty.c */
+bool exec_pty(struct command_details *details, struct command_status *cstat);
+void pty_cleanup(void);
+int pty_make_controlling(void);
+extern int io_fds[6];
+
+/* exec_monitor.c */
+int exec_monitor(struct command_details *details, sigset_t *omask, bool foreground, int backchannel);
+
+/* utmp.c */
+bool utmp_login(const char *from_line, const char *to_line, int ttyfd,
+ const char *user);
+bool utmp_logout(const char *line, int status);
+
+#endif /* SUDO_EXEC_H */
diff --git a/src/sudo_noexec.c b/src/sudo_noexec.c
new file mode 100644
index 0000000..6025d91
--- /dev/null
+++ b/src/sudo_noexec.c
@@ -0,0 +1,258 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2004-2005, 2010-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+
+#if defined(HAVE_DECL_SECCOMP_SET_MODE_FILTER) && HAVE_DECL_SECCOMP_SET_MODE_FILTER
+# include <sys/prctl.h>
+# include <asm/unistd.h>
+# include <linux/filter.h>
+# include <linux/seccomp.h>
+#endif
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#ifdef HAVE_SPAWN_H
+#include <spawn.h>
+#endif
+#include <string.h>
+#ifdef HAVE_WORDEXP_H
+#include <wordexp.h>
+#endif
+#if defined(HAVE_SHL_LOAD)
+# include <dl.h>
+#elif defined(HAVE_DLOPEN)
+# include <dlfcn.h>
+#endif
+
+#include "sudo_compat.h"
+#include "pathnames.h"
+
+#ifdef HAVE___INTERPOSE
+/*
+ * Mac OS X 10.4 and above has support for library symbol interposition.
+ * There is a good explanation of this in the Mac OS X Internals book.
+ */
+typedef struct interpose_s {
+ void *new_func;
+ void *orig_func;
+} interpose_t;
+
+# define FN_NAME(fn) fake_ ## fn
+# define INTERPOSE(fn) \
+ __attribute__((__used__)) static const interpose_t interpose_ ## fn \
+ __attribute__((__section__("__DATA,__interpose"))) = \
+ { (void *)fake_ ## fn, (void *)fn };
+#else
+# define FN_NAME(fn) fn
+# define INTERPOSE(fn)
+#endif
+
+/*
+ * Replacements for the exec(3) family of syscalls. It is not enough to
+ * just replace execve(2) since many C libraries do not call the public
+ * execve(2) interface. Note that it is still possible to access the real
+ * syscalls via the syscall(2) interface, but that is rarely done.
+ */
+
+#define EXEC_REPL_BODY \
+{ \
+ errno = EACCES; \
+ return -1; \
+}
+
+#define EXEC_REPL1(fn, t1) \
+sudo_dso_public int \
+FN_NAME(fn)(t1 a1) \
+EXEC_REPL_BODY \
+INTERPOSE(fn)
+
+#define EXEC_REPL2(fn, t1, t2) \
+sudo_dso_public int \
+FN_NAME(fn)(t1 a1, t2 a2) \
+EXEC_REPL_BODY \
+INTERPOSE(fn)
+
+#define EXEC_REPL3(fn, t1, t2, t3) \
+sudo_dso_public int \
+FN_NAME(fn)(t1 a1, t2 a2, t3 a3) \
+EXEC_REPL_BODY \
+INTERPOSE(fn)
+
+#define EXEC_REPL6(fn, t1, t2, t3, t4, t5, t6) \
+sudo_dso_public int \
+FN_NAME(fn)(t1 a1, t2 a2, t3 a3, t4 a4, t5 a5, t6 a6) \
+EXEC_REPL_BODY \
+INTERPOSE(fn)
+
+#define EXEC_REPL_VA(fn, t1, t2) \
+sudo_dso_public int \
+FN_NAME(fn)(t1 a1, t2 a2, ...) \
+EXEC_REPL_BODY \
+INTERPOSE(fn)
+
+/*
+ * Standard exec(3) family of functions.
+ */
+EXEC_REPL_VA(execl, const char *, const char *)
+EXEC_REPL_VA(execle, const char *, const char *)
+EXEC_REPL_VA(execlp, const char *, const char *)
+EXEC_REPL2(execv, const char *, char * const *)
+EXEC_REPL2(execvp, const char *, char * const *)
+EXEC_REPL3(execve, const char *, char * const *, char * const *)
+
+/*
+ * Non-standard exec(3) functions and corresponding private versions.
+ */
+#ifdef HAVE_EXECVP
+EXEC_REPL3(execvP, const char *, const char *, char * const *)
+#endif
+#ifdef HAVE_EXECVPE
+EXEC_REPL3(execvpe, const char *, char * const *, char * const *)
+#endif
+#ifdef HAVE_EXECT
+EXEC_REPL3(exect, const char *, char * const *, char * const *)
+#endif
+
+/*
+ * Not all systems support fexecve(2), posix_spawn(2) and posix_spawnp(2).
+ */
+#ifdef HAVE_FEXECVE
+EXEC_REPL3(fexecve, int , char * const *, char * const *)
+#endif
+#ifdef HAVE_POSIX_SPAWN
+EXEC_REPL6(posix_spawn, pid_t *, const char *, const posix_spawn_file_actions_t *, const posix_spawnattr_t *, char * const *, char * const *)
+#endif
+#ifdef HAVE_POSIX_SPAWNP
+EXEC_REPL6(posix_spawnp, pid_t *, const char *, const posix_spawn_file_actions_t *, const posix_spawnattr_t *, char * const *, char * const *)
+#endif
+
+/*
+ * system(3) and popen(3).
+ * We can't use a wrapper for popen since it returns FILE *, not int.
+ */
+EXEC_REPL1(system, const char *)
+
+sudo_dso_public FILE *
+FN_NAME(popen)(const char *c, const char *t)
+{
+ errno = EACCES;
+ return NULL;
+}
+INTERPOSE(popen)
+
+#if defined(HAVE_WORDEXP) && (defined(RTLD_NEXT) || defined(HAVE_SHL_LOAD) || defined(HAVE___INTERPOSE))
+/*
+ * We can't use a wrapper for wordexp(3) since we still want to call
+ * the real wordexp(3) but with WRDE_NOCMD added to the flags argument.
+ */
+typedef int (*sudo_fn_wordexp_t)(const char *, wordexp_t *, int);
+
+sudo_dso_public int
+FN_NAME(wordexp)(const char *words, wordexp_t *we, int flags)
+{
+#if defined(HAVE___INTERPOSE)
+ return wordexp(words, we, flags | WRDE_NOCMD);
+#else
+# if defined(HAVE_DLOPEN)
+ void *fn = dlsym(RTLD_NEXT, "wordexp");
+# elif defined(HAVE_SHL_LOAD)
+ const char *name, *myname = _PATH_SUDO_NOEXEC;
+ struct shl_descriptor *desc;
+ void *fn = NULL;
+ int idx = 0;
+
+ name = strrchr(myname, '/');
+ if (name != NULL)
+ myname = name + 1;
+
+ /* Search for wordexp() but skip this shared object. */
+ while (shl_get(idx++, &desc) == 0) {
+ name = strrchr(desc->filename, '/');
+ if (name == NULL)
+ name = desc->filename;
+ else
+ name++;
+ if (strcmp(name, myname) == 0)
+ continue;
+ if (shl_findsym(&desc->handle, "wordexp", TYPE_PROCEDURE, &fn) == 0)
+ break;
+ }
+# else
+ void *fn = NULL;
+# endif
+ if (fn == NULL) {
+ errno = EACCES;
+ return -1;
+ }
+ return ((sudo_fn_wordexp_t)fn)(words, we, flags | WRDE_NOCMD);
+#endif /* HAVE___INTERPOSE */
+}
+INTERPOSE(wordexp)
+#endif /* HAVE_WORDEXP && (RTLD_NEXT || HAVE_SHL_LOAD || HAVE___INTERPOSE) */
+
+/*
+ * On Linux we can use a seccomp() filter to disable exec.
+ */
+#if defined(HAVE_DECL_SECCOMP_SET_MODE_FILTER) && HAVE_DECL_SECCOMP_SET_MODE_FILTER
+
+/* Older systems may not support execveat(2). */
+#ifndef __NR_execveat
+# define __NR_execveat -1
+#endif
+
+static void noexec_ctor(void) __attribute__((constructor));
+
+static void
+noexec_ctor(void)
+{
+ struct sock_filter exec_filter[] = {
+ /* Load syscall number into the accumulator */
+ BPF_STMT(BPF_LD | BPF_ABS, offsetof(struct seccomp_data, nr)),
+ /* Jump to deny for execve/execveat */
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execve, 2, 0),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execveat, 1, 0),
+ /* Allow non-matching syscalls */
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
+ /* Deny execve/execveat syscall */
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EACCES & SECCOMP_RET_DATA))
+ };
+ const struct sock_fprog exec_fprog = {
+ nitems(exec_filter),
+ exec_filter
+ };
+
+ /*
+ * SECCOMP_MODE_FILTER will fail unless the process has
+ * CAP_SYS_ADMIN or the no_new_privs bit is set.
+ */
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0)
+ (void)prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &exec_fprog);
+}
+#endif /* HAVE_DECL_SECCOMP_SET_MODE_FILTER */
diff --git a/src/sudo_plugin_int.h b/src/sudo_plugin_int.h
new file mode 100644
index 0000000..63e4a7d
--- /dev/null
+++ b/src/sudo_plugin_int.h
@@ -0,0 +1,133 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef SUDO_PLUGIN_INT_H
+#define SUDO_PLUGIN_INT_H
+
+/*
+ * All plugin structures start with a type and a version.
+ */
+struct generic_plugin {
+ unsigned int type;
+ unsigned int version;
+ /* the rest depends on the type... */
+};
+
+typedef int (*sudo_conv_1_7_t)(int num_msgs,
+ const struct sudo_conv_message msgs[], struct sudo_conv_reply replies[]);
+
+/*
+ * Backwards-compatible structures for API bumps.
+ */
+struct policy_plugin_1_0 {
+ unsigned int type;
+ unsigned int version;
+ int (*open)(unsigned int version, sudo_conv_1_7_t conversation,
+ sudo_printf_t sudo_printf, char * const settings[],
+ char * const user_info[], char * const user_env[]);
+ void (*close)(int exit_status, int error); /* wait status or error */
+ int (*show_version)(int verbose);
+ int (*check_policy)(int argc, char * const argv[],
+ char *env_add[], char **command_info[],
+ char **argv_out[], char **user_env_out[]);
+ int (*list)(int argc, char * const argv[], int verbose,
+ const char *list_user);
+ int (*validate)(void);
+ void (*invalidate)(int remove);
+ int (*init_session)(struct passwd *pwd);
+};
+struct io_plugin_1_0 {
+ unsigned int type;
+ unsigned int version;
+ int (*open)(unsigned int version, sudo_conv_1_7_t conversation,
+ sudo_printf_t sudo_printf, char * const settings[],
+ char * const user_info[], int argc, char * const argv[],
+ char * const user_env[]);
+ void (*close)(int exit_status, int error);
+ int (*show_version)(int verbose);
+ int (*log_ttyin)(const char *buf, unsigned int len);
+ int (*log_ttyout)(const char *buf, unsigned int len);
+ int (*log_stdin)(const char *buf, unsigned int len);
+ int (*log_stdout)(const char *buf, unsigned int len);
+ int (*log_stderr)(const char *buf, unsigned int len);
+};
+struct io_plugin_1_1 {
+ unsigned int type;
+ unsigned int version;
+ int (*open)(unsigned int version, sudo_conv_1_7_t conversation,
+ sudo_printf_t sudo_printf, char * const settings[],
+ char * const user_info[], char * const command_info[],
+ int argc, char * const argv[], char * const user_env[]);
+ void (*close)(int exit_status, int error); /* wait status or error */
+ int (*show_version)(int verbose);
+ int (*log_ttyin)(const char *buf, unsigned int len);
+ int (*log_ttyout)(const char *buf, unsigned int len);
+ int (*log_stdin)(const char *buf, unsigned int len);
+ int (*log_stdout)(const char *buf, unsigned int len);
+ int (*log_stderr)(const char *buf, unsigned int len);
+};
+
+/*
+ * Sudo plugin internals.
+ */
+struct plugin_container {
+ TAILQ_ENTRY(plugin_container) entries;
+ struct sudo_conf_debug_file_list *debug_files;
+ char *name;
+ char *path;
+ char **options;
+ void *handle;
+ int debug_instance;
+ union {
+ struct generic_plugin *generic;
+ struct policy_plugin *policy;
+ struct policy_plugin_1_0 *policy_1_0;
+ struct io_plugin *io;
+ struct io_plugin_1_0 *io_1_0;
+ struct io_plugin_1_1 *io_1_1;
+ struct audit_plugin *audit;
+ struct approval_plugin *approval;
+ } u;
+};
+TAILQ_HEAD(plugin_container_list, plugin_container);
+
+/*
+ * Private implementation of struct sudo_plugin_event.
+ */
+struct sudo_plugin_event_int {
+ struct sudo_event private; /* must be first */
+ int debug_instance; /* plugin's debug instance */
+ void *closure; /* actual user closure */
+ sudo_ev_callback_t callback; /* actual user callback */
+ struct sudo_plugin_event public; /* user-visible portion */
+};
+
+extern struct plugin_container policy_plugin;
+extern struct plugin_container_list io_plugins;
+extern struct plugin_container_list audit_plugins;
+extern struct plugin_container_list approval_plugins;
+
+int sudo_conversation(int num_msgs, const struct sudo_conv_message msgs[],
+ struct sudo_conv_reply replies[], struct sudo_conv_callback *callback);
+int sudo_conversation_1_7(int num_msgs, const struct sudo_conv_message msgs[],
+ struct sudo_conv_reply replies[]);
+int sudo_conversation_printf(int msg_type, const char *fmt, ...);
+
+bool sudo_load_plugins(void);
+
+#endif /* SUDO_PLUGIN_INT_H */
diff --git a/src/sudo_usage.h.in b/src/sudo_usage.h.in
new file mode 100644
index 0000000..afccc15
--- /dev/null
+++ b/src/sudo_usage.h.in
@@ -0,0 +1,38 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2007-2010, 2013, 2015, 2017, 2020
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef SUDO_USAGE_H
+#define SUDO_USAGE_H
+
+/*
+ * Usage strings for sudo. These are here because we
+ * need to be able to substitute values from configure.
+ */
+#define SUDO_USAGE1 " -h | -K | -k | -V"
+#define SUDO_USAGE2 " -v [-AknS] @BSDAUTH_USAGE@[-g group] [-h host] [-p prompt] [-u user]"
+#define SUDO_USAGE3 " -l [-AknS] @BSDAUTH_USAGE@[-g group] [-h host] [-p prompt] [-U user] [-u user] [command]"
+#define SUDO_USAGE4 " [-AbEHknPS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C num] [-D directory] @LOGINCAP_USAGE@[-g group] [-h host] [-p prompt] [-R directory] [-T timeout] [-u user] [VAR=value] [-i|-s] [<command>]"
+#define SUDO_USAGE5 " -e [-AknS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C num] @LOGINCAP_USAGE@[-D directory] [-g group] [-h host] [-p prompt] [-R directory] [-T timeout] [-u user] file ..."
+
+/*
+ * Configure script arguments used to build sudo.
+ */
+#define CONFIGURE_ARGS "@CONFIGURE_ARGS@"
+
+#endif /* SUDO_USAGE_H */
diff --git a/src/tcsetpgrp_nobg.c b/src/tcsetpgrp_nobg.c
new file mode 100644
index 0000000..bac0aa6
--- /dev/null
+++ b/src/tcsetpgrp_nobg.c
@@ -0,0 +1,71 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2017 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+
+#include "sudo.h"
+
+static volatile sig_atomic_t got_sigttou;
+
+/*
+ * SIGTTOU signal handler for tcsetpgrp_nobg() that just sets a flag.
+ */
+static void
+sigttou(int signo)
+{
+ got_sigttou = 1;
+}
+
+/*
+ * Like tcsetpgrp() but restarts on EINTR _except_ for SIGTTOU.
+ * Returns 0 on success or -1 on failure, setting errno.
+ * Sets got_sigttou on failure if interrupted by SIGTTOU.
+ */
+int
+tcsetpgrp_nobg(int fd, pid_t pgrp_id)
+{
+ struct sigaction sa, osa;
+ int rc;
+
+ /*
+ * If we receive SIGTTOU from tcsetpgrp() it means we are
+ * not in the foreground process group.
+ * This avoid a TOCTOU race compared to using tcgetpgrp().
+ */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0; /* do not restart syscalls */
+ sa.sa_handler = sigttou;
+ got_sigttou = 0;
+ (void)sigaction(SIGTTOU, &sa, &osa);
+ do {
+ rc = tcsetpgrp(fd, pgrp_id);
+ } while (rc != 0 && errno == EINTR && !got_sigttou);
+ (void)sigaction(SIGTTOU, &osa, NULL);
+
+ return rc;
+}
diff --git a/src/tgetpass.c b/src/tgetpass.c
new file mode 100644
index 0000000..87f01dc
--- /dev/null
+++ b/src/tgetpass.c
@@ -0,0 +1,450 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1996, 1998-2005, 2007-2021
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#ifdef __TANDEM
+# include <floss.h>
+#endif
+
+#include <config.h>
+
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "sudo.h"
+#include "sudo_plugin.h"
+
+enum tgetpass_errval {
+ TGP_ERRVAL_NOERROR,
+ TGP_ERRVAL_TIMEOUT,
+ TGP_ERRVAL_NOPASSWORD,
+ TGP_ERRVAL_READERROR
+};
+
+static volatile sig_atomic_t signo[NSIG];
+
+static void tgetpass_handler(int);
+static char *getln(int, char *, size_t, bool, enum tgetpass_errval *);
+static char *sudo_askpass(const char *, const char *);
+
+static int
+suspend(int sig, struct sudo_conv_callback *callback)
+{
+ int ret = 0;
+ debug_decl(suspend, SUDO_DEBUG_CONV);
+
+ if (callback != NULL && SUDO_API_VERSION_GET_MAJOR(callback->version) != SUDO_CONV_CALLBACK_VERSION_MAJOR) {
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
+ "callback major version mismatch, expected %u, got %u",
+ SUDO_CONV_CALLBACK_VERSION_MAJOR,
+ SUDO_API_VERSION_GET_MAJOR(callback->version));
+ callback = NULL;
+ }
+
+ if (callback != NULL && callback->on_suspend != NULL) {
+ if (callback->on_suspend(sig, callback->closure) == -1)
+ ret = -1;
+ }
+ kill(getpid(), sig);
+ if (callback != NULL && callback->on_resume != NULL) {
+ if (callback->on_resume(sig, callback->closure) == -1)
+ ret = -1;
+ }
+ debug_return_int(ret);
+}
+
+static void
+tgetpass_display_error(enum tgetpass_errval errval)
+{
+ debug_decl(tgetpass_display_error, SUDO_DEBUG_CONV);
+
+ switch (errval) {
+ case TGP_ERRVAL_NOERROR:
+ break;
+ case TGP_ERRVAL_TIMEOUT:
+ sudo_warnx("%s", U_("timed out reading password"));
+ break;
+ case TGP_ERRVAL_NOPASSWORD:
+ sudo_warnx("%s", U_("no password was provided"));
+ break;
+ case TGP_ERRVAL_READERROR:
+ sudo_warn("%s", U_("unable to read password"));
+ break;
+ }
+ debug_return;
+}
+
+/*
+ * Like getpass(3) but with timeout and echo flags.
+ */
+char *
+tgetpass(const char *prompt, int timeout, int flags,
+ struct sudo_conv_callback *callback)
+{
+ struct sigaction sa, savealrm, saveint, savehup, savequit, saveterm;
+ struct sigaction savetstp, savettin, savettou;
+ char *pass;
+ static const char *askpass;
+ static char buf[SUDO_CONV_REPL_MAX + 1];
+ int i, input, output, save_errno, ttyfd;
+ bool feedback, need_restart, neednl;
+ enum tgetpass_errval errval;
+ debug_decl(tgetpass, SUDO_DEBUG_CONV);
+
+ (void) fflush(stdout);
+
+ if (askpass == NULL) {
+ askpass = getenv_unhooked("SUDO_ASKPASS");
+ if (askpass == NULL || *askpass == '\0')
+ askpass = sudo_conf_askpass_path();
+ }
+
+restart:
+ /* Try to open /dev/tty if we are going to be using it for I/O. */
+ ttyfd = -1;
+ if (!ISSET(flags, TGP_STDIN|TGP_ASKPASS)) {
+ /* If no tty present and we need to disable echo, try askpass. */
+ ttyfd = open(_PATH_TTY, O_RDWR);
+ if (ttyfd == -1 && !ISSET(flags, TGP_ECHO|TGP_NOECHO_TRY)) {
+ if (askpass == NULL || getenv_unhooked("DISPLAY") == NULL) {
+ sudo_warnx("%s",
+ U_("a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper"));
+ debug_return_str(NULL);
+ }
+ SET(flags, TGP_ASKPASS);
+ }
+ }
+
+ /* If using a helper program to get the password, run it instead. */
+ if (ISSET(flags, TGP_ASKPASS)) {
+ if (askpass == NULL || *askpass == '\0')
+ sudo_fatalx("%s",
+ U_("no askpass program specified, try setting SUDO_ASKPASS"));
+ debug_return_str_masked(sudo_askpass(askpass, prompt));
+ }
+
+ /* Reset state. */
+ for (i = 0; i < NSIG; i++)
+ signo[i] = 0;
+ pass = NULL;
+ save_errno = 0;
+ neednl = false;
+ need_restart = false;
+ feedback = false;
+
+ /* Use tty for reading/writing if available else use stdin/stderr. */
+ if (ttyfd == -1) {
+ input = STDIN_FILENO;
+ output = STDERR_FILENO;
+ /* Don't try to mask password if /dev/tty is not available. */
+ CLR(flags, TGP_MASK);
+ } else {
+ input = ttyfd;
+ output = ttyfd;
+ }
+
+ /*
+ * If we are using a tty but are not the foreground pgrp this will
+ * return EINTR. We send ourself SIGTTOU bracketed by callbacks.
+ */
+ if (!ISSET(flags, TGP_ECHO)) {
+ for (;;) {
+ if (ISSET(flags, TGP_MASK))
+ neednl = feedback = sudo_term_cbreak(input);
+ else
+ neednl = sudo_term_noecho(input);
+ if (neednl || errno != EINTR)
+ break;
+ /* Received SIGTTOU, suspend the process. */
+ if (suspend(SIGTTOU, callback) == -1) {
+ if (ttyfd != -1)
+ (void) close(ttyfd);
+ debug_return_ptr(NULL);
+ }
+ }
+ }
+
+ /*
+ * Catch signals that would otherwise cause the user to end
+ * up with echo turned off in the shell.
+ */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0; /* don't restart system calls */
+ sa.sa_handler = tgetpass_handler;
+ (void) sigaction(SIGALRM, &sa, &savealrm);
+ (void) sigaction(SIGINT, &sa, &saveint);
+ (void) sigaction(SIGHUP, &sa, &savehup);
+ (void) sigaction(SIGQUIT, &sa, &savequit);
+ (void) sigaction(SIGTERM, &sa, &saveterm);
+ (void) sigaction(SIGTSTP, &sa, &savetstp);
+ (void) sigaction(SIGTTIN, &sa, &savettin);
+ (void) sigaction(SIGTTOU, &sa, &savettou);
+
+ if (ISSET(flags, TGP_BELL) && output != STDERR_FILENO) {
+ /* Ring the bell if requested and there is a tty. */
+ if (write(output, "\a", 1) == -1)
+ goto restore;
+ }
+ if (prompt) {
+ if (write(output, prompt, strlen(prompt)) == -1)
+ goto restore;
+ }
+
+ if (timeout > 0)
+ alarm(timeout);
+ pass = getln(input, buf, sizeof(buf), feedback, &errval);
+ alarm(0);
+ save_errno = errno;
+
+ if (neednl || pass == NULL) {
+ if (write(output, "\n", 1) == -1)
+ goto restore;
+ }
+ tgetpass_display_error(errval);
+
+restore:
+ /* Restore old signal handlers. */
+ (void) sigaction(SIGALRM, &savealrm, NULL);
+ (void) sigaction(SIGINT, &saveint, NULL);
+ (void) sigaction(SIGHUP, &savehup, NULL);
+ (void) sigaction(SIGQUIT, &savequit, NULL);
+ (void) sigaction(SIGTERM, &saveterm, NULL);
+ (void) sigaction(SIGTSTP, &savetstp, NULL);
+ (void) sigaction(SIGTTIN, &savettin, NULL);
+ (void) sigaction(SIGTTOU, &savettou, NULL);
+
+ /* Restore old tty settings. */
+ if (!ISSET(flags, TGP_ECHO)) {
+ /* Restore old tty settings if possible. */
+ (void) sudo_term_restore(input, true);
+ }
+ if (ttyfd != -1)
+ (void) close(ttyfd);
+
+ /*
+ * If we were interrupted by a signal, resend it to ourselves
+ * now that we have restored the signal handlers.
+ */
+ for (i = 0; i < NSIG; i++) {
+ if (signo[i]) {
+ switch (i) {
+ case SIGALRM:
+ break;
+ case SIGTSTP:
+ case SIGTTIN:
+ case SIGTTOU:
+ if (suspend(i, callback) == 0)
+ need_restart = true;
+ break;
+ default:
+ kill(getpid(), i);
+ break;
+ }
+ }
+ }
+ if (need_restart)
+ goto restart;
+
+ if (save_errno)
+ errno = save_errno;
+
+ debug_return_str_masked(pass);
+}
+
+/*
+ * Fork a child and exec sudo-askpass to get the password from the user.
+ */
+static char *
+sudo_askpass(const char *askpass, const char *prompt)
+{
+ static char buf[SUDO_CONV_REPL_MAX + 1], *pass;
+ struct sigaction sa, savechld;
+ enum tgetpass_errval errval;
+ int pfd[2], status;
+ pid_t child;
+ debug_decl(sudo_askpass, SUDO_DEBUG_CONV);
+
+ /* Set SIGCHLD handler to default since we call waitpid() below. */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_DFL;
+ (void) sigaction(SIGCHLD, &sa, &savechld);
+
+ if (pipe2(pfd, O_CLOEXEC) == -1)
+ sudo_fatal("%s", U_("unable to create pipe"));
+
+ child = sudo_debug_fork();
+ if (child == -1)
+ sudo_fatal("%s", U_("unable to fork"));
+
+ if (child == 0) {
+ /* child, set stdout to write side of the pipe */
+ if (dup3(pfd[1], STDOUT_FILENO, 0) == -1) {
+ sudo_warn("dup3");
+ _exit(255);
+ }
+ if (setuid(ROOT_UID) == -1)
+ sudo_warn("setuid(%d)", ROOT_UID);
+ /* Close fds before uid change to prevent prlimit sabotage on Linux. */
+ closefrom(STDERR_FILENO + 1);
+ /* Run the askpass program with the user's original resource limits. */
+ restore_limits();
+ /* But avoid a setuid() failure on Linux due to RLIMIT_NPROC. */
+ unlimit_nproc();
+ if (setgid(user_details.cred.gid)) {
+ sudo_warn(U_("unable to set gid to %u"), (unsigned int)user_details.cred.gid);
+ _exit(255);
+ }
+ if (setuid(user_details.cred.uid)) {
+ sudo_warn(U_("unable to set uid to %u"), (unsigned int)user_details.cred.uid);
+ _exit(255);
+ }
+ restore_nproc();
+ execl(askpass, askpass, prompt, (char *)NULL);
+ sudo_warn(U_("unable to run %s"), askpass);
+ _exit(255);
+ }
+
+ /* Get response from child (askpass). */
+ (void) close(pfd[1]);
+ pass = getln(pfd[0], buf, sizeof(buf), 0, &errval);
+ (void) close(pfd[0]);
+
+ tgetpass_display_error(errval);
+
+ /* Wait for child to exit. */
+ for (;;) {
+ pid_t rv = waitpid(child, &status, 0);
+ if (rv == -1 && errno != EINTR)
+ break;
+ if (rv != -1 && !WIFSTOPPED(status))
+ break;
+ }
+
+ if (pass == NULL)
+ errno = EINTR; /* make cancel button simulate ^C */
+
+ /* Restore saved SIGCHLD handler. */
+ (void) sigaction(SIGCHLD, &savechld, NULL);
+
+ debug_return_str_masked(pass);
+}
+
+extern int sudo_term_eof, sudo_term_erase, sudo_term_kill;
+
+static char *
+getln(int fd, char *buf, size_t bufsiz, bool feedback,
+ enum tgetpass_errval *errval)
+{
+ size_t left = bufsiz;
+ ssize_t nr = -1;
+ char *cp = buf;
+ char c = '\0';
+ debug_decl(getln, SUDO_DEBUG_CONV);
+
+ *errval = TGP_ERRVAL_NOERROR;
+
+ if (left == 0) {
+ *errval = TGP_ERRVAL_READERROR;
+ errno = EINVAL;
+ debug_return_str(NULL);
+ }
+
+ while (--left) {
+ nr = read(fd, &c, 1);
+ if (nr != 1 || c == '\n' || c == '\r')
+ break;
+ if (feedback) {
+ if (c == sudo_term_eof) {
+ nr = 0;
+ break;
+ } else if (c == sudo_term_kill) {
+ while (cp > buf) {
+ if (write(fd, "\b \b", 3) == -1)
+ break;
+ cp--;
+ }
+ cp = buf;
+ left = bufsiz;
+ continue;
+ } else if (c == sudo_term_erase) {
+ if (cp > buf) {
+ ignore_result(write(fd, "\b \b", 3));
+ cp--;
+ left++;
+ }
+ continue;
+ }
+ ignore_result(write(fd, "*", 1));
+ }
+ *cp++ = c;
+ }
+ *cp = '\0';
+ if (feedback) {
+ /* erase stars */
+ while (cp > buf) {
+ if (write(fd, "\b \b", 3) == -1)
+ break;
+ --cp;
+ }
+ }
+
+ switch (nr) {
+ case -1:
+ /* Read error */
+ if (errno == EINTR) {
+ if (signo[SIGALRM] == 1)
+ *errval = TGP_ERRVAL_TIMEOUT;
+ } else {
+ *errval = TGP_ERRVAL_READERROR;
+ }
+ debug_return_str(NULL);
+ case 0:
+ /* EOF is only an error if no bytes were read. */
+ if (left == bufsiz - 1) {
+ *errval = TGP_ERRVAL_NOPASSWORD;
+ debug_return_str(NULL);
+ }
+ FALLTHROUGH;
+ default:
+ debug_return_str_masked(buf);
+ }
+}
+
+static void
+tgetpass_handler(int s)
+{
+ signo[s] = 1;
+}
diff --git a/src/ttyname.c b/src/ttyname.c
new file mode 100644
index 0000000..47e4ab1
--- /dev/null
+++ b/src/ttyname.c
@@ -0,0 +1,338 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2012-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+/* Large files not supported by procfs.h on Solaris. */
+#if defined(HAVE_STRUCT_PSINFO_PR_TTYDEV)
+# undef _FILE_OFFSET_BITS
+# undef _LARGE_FILES
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#if defined(MAJOR_IN_MKDEV)
+# include <sys/mkdev.h>
+#elif defined(MAJOR_IN_SYSMACROS)
+# include <sys/sysmacros.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <dirent.h>
+#if defined(HAVE_KINFO_PROC2_NETBSD) || defined (HAVE_KINFO_PROC_OPENBSD) || defined(HAVE_KINFO_PROC_44BSD)
+# include <sys/sysctl.h>
+#elif defined(HAVE_KINFO_PROC_FREEBSD)
+# include <sys/param.h>
+# include <sys/sysctl.h>
+# include <sys/user.h>
+#endif
+#if defined(HAVE_PROCFS_H)
+# include <procfs.h>
+#elif defined(HAVE_SYS_PROCFS_H)
+# include <sys/procfs.h>
+#endif
+#ifdef HAVE_PSTAT_GETPROC
+# include <sys/pstat.h>
+#endif
+
+#include "sudo.h"
+
+/*
+ * How to access the tty device number in struct kinfo_proc.
+ */
+#if defined(HAVE_KINFO_PROC2_NETBSD)
+# define SUDO_KERN_PROC KERN_PROC2
+# define sudo_kinfo_proc kinfo_proc2
+# define sudo_kp_tdev p_tdev
+# define sudo_kp_namelen 6
+#elif defined(HAVE_KINFO_PROC_OPENBSD)
+# define SUDO_KERN_PROC KERN_PROC
+# define sudo_kinfo_proc kinfo_proc
+# define sudo_kp_tdev p_tdev
+# define sudo_kp_namelen 6
+#elif defined(HAVE_KINFO_PROC_FREEBSD)
+# define SUDO_KERN_PROC KERN_PROC
+# define sudo_kinfo_proc kinfo_proc
+# define sudo_kp_tdev ki_tdev
+# define sudo_kp_namelen 4
+#elif defined(HAVE_KINFO_PROC_44BSD)
+# define SUDO_KERN_PROC KERN_PROC
+# define sudo_kinfo_proc kinfo_proc
+# define sudo_kp_tdev kp_eproc.e_tdev
+# define sudo_kp_namelen 4
+#endif
+
+#if defined(sudo_kp_tdev)
+/*
+ * Store the name of the tty to which the process is attached in name.
+ * Returns name on success and NULL on failure, setting errno.
+ */
+char *
+get_process_ttyname(char *name, size_t namelen)
+{
+ struct sudo_kinfo_proc *ki_proc = NULL;
+ size_t size = sizeof(*ki_proc);
+ int mib[6], rc, serrno = errno;
+ char *ret = NULL;
+ debug_decl(get_process_ttyname, SUDO_DEBUG_UTIL);
+
+ /*
+ * Lookup controlling tty for this process via sysctl.
+ * This will work even if std{in,out,err} are redirected.
+ */
+ mib[0] = CTL_KERN;
+ mib[1] = SUDO_KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = (int)getpid();
+ mib[4] = sizeof(*ki_proc);
+ mib[5] = 1;
+ for (;;) {
+ struct sudo_kinfo_proc *kp;
+
+ size += size / 10;
+ if ((kp = realloc(ki_proc, size)) == NULL) {
+ rc = -1;
+ break; /* really out of memory. */
+ }
+ ki_proc = kp;
+ rc = sysctl(mib, sudo_kp_namelen, ki_proc, &size, NULL, 0);
+ if (rc != -1 || errno != ENOMEM)
+ break;
+ }
+ errno = ENOENT;
+ if (rc != -1) {
+ if ((dev_t)ki_proc->sudo_kp_tdev != (dev_t)-1) {
+ errno = serrno;
+ ret = sudo_ttyname_dev(ki_proc->sudo_kp_tdev, name, namelen);
+ if (ret == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to map device number %u to name",
+ ki_proc->sudo_kp_tdev);
+ }
+ }
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to resolve tty via KERN_PROC");
+ }
+ free(ki_proc);
+
+ debug_return_str(ret);
+}
+#elif defined(HAVE_STRUCT_PSINFO_PR_TTYDEV)
+/*
+ * Store the name of the tty to which the process is attached in name.
+ * Returns name on success and NULL on failure, setting errno.
+ */
+char *
+get_process_ttyname(char *name, size_t namelen)
+{
+ char path[PATH_MAX], *ret = NULL;
+ struct psinfo psinfo;
+ ssize_t nread;
+ int fd, serrno = errno;
+ debug_decl(get_process_ttyname, SUDO_DEBUG_UTIL);
+
+ /* Try to determine the tty from pr_ttydev in /proc/pid/psinfo. */
+ (void)snprintf(path, sizeof(path), "/proc/%u/psinfo", (unsigned int)getpid());
+ if ((fd = open(path, O_RDONLY, 0)) != -1) {
+ nread = read(fd, &psinfo, sizeof(psinfo));
+ close(fd);
+ if (nread == (ssize_t)sizeof(psinfo)) {
+ dev_t rdev = (dev_t)psinfo.pr_ttydev;
+#if defined(_AIX) && defined(DEVNO64)
+ if ((psinfo.pr_ttydev & DEVNO64) && sizeof(dev_t) == 4)
+ rdev = makedev(major64(psinfo.pr_ttydev), minor64(psinfo.pr_ttydev));
+#endif
+ if (rdev != (dev_t)-1) {
+ errno = serrno;
+ ret = sudo_ttyname_dev(rdev, name, namelen);
+ goto done;
+ }
+ }
+ }
+ errno = ENOENT;
+
+done:
+ if (ret == NULL)
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to resolve tty via %s", path);
+
+ debug_return_str(ret);
+}
+#elif defined(__linux__)
+/*
+ * Store the name of the tty to which the process is attached in name.
+ * Returns name on success and NULL on failure, setting errno.
+ */
+char *
+get_process_ttyname(char *name, size_t namelen)
+{
+ const char path[] = "/proc/self/stat";
+ char *cp, buf[1024];
+ char *ret = NULL;
+ int serrno = errno;
+ ssize_t nread;
+ int fd;
+ debug_decl(get_process_ttyname, SUDO_DEBUG_UTIL);
+
+ /*
+ * Try to determine the tty from tty_nr in /proc/self/stat.
+ * Ignore /proc/self/stat if it contains embedded NUL bytes.
+ */
+ if ((fd = open(path, O_RDONLY | O_NOFOLLOW)) != -1) {
+ cp = buf;
+ while ((nread = read(fd, cp, buf + sizeof(buf) - cp)) != 0) {
+ if (nread == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ break;
+ }
+ cp += nread;
+ if (cp >= buf + sizeof(buf))
+ break;
+ }
+ if (nread == 0 && memchr(buf, '\0', cp - buf) == NULL) {
+ /*
+ * Field 7 is the tty dev (0 if no tty).
+ * Since the process name at field 2 "(comm)" may include
+ * whitespace (including newlines), start at the last ')' found.
+ */
+ *cp = '\0';
+ cp = strrchr(buf, ')');
+ if (cp != NULL) {
+ char *ep = cp;
+ const char *errstr;
+ int field = 1;
+
+ while (*++ep != '\0') {
+ if (*ep == ' ') {
+ *ep = '\0';
+ if (++field == 7) {
+ int tty_nr = sudo_strtonum(cp, INT_MIN, INT_MAX,
+ &errstr);
+ if (errstr) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: tty device %s: %s", path, cp, errstr);
+ }
+ if (tty_nr != 0) {
+ /*
+ * Avoid sign extension when assigning tdev.
+ * tty_nr in /proc/self/stat is printed as a
+ * signed int but the actual device number is an
+ * unsigned int and dev_t is unsigned long long.
+ */
+ dev_t tdev = (unsigned int)tty_nr;
+ errno = serrno;
+ ret = sudo_ttyname_dev(tdev, name, namelen);
+ goto done;
+ }
+ break;
+ }
+ cp = ep + 1;
+ }
+ }
+ }
+ }
+ }
+ errno = ENOENT;
+
+done:
+ if (fd != -1)
+ close(fd);
+ if (ret == NULL)
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to resolve tty via %s", path);
+
+ debug_return_str(ret);
+}
+#elif defined(HAVE_PSTAT_GETPROC)
+/*
+ * Store the name of the tty to which the process is attached in name.
+ * Returns name on success and NULL on failure, setting errno.
+ */
+char *
+get_process_ttyname(char *name, size_t namelen)
+{
+ struct pst_status pst;
+ char *ret = NULL;
+ int rc, serrno = errno;
+ debug_decl(get_process_ttyname, SUDO_DEBUG_UTIL);
+
+ /*
+ * Determine the tty from psdev in struct pst_status.
+ * EOVERFLOW is not a fatal error for the fields we use.
+ * See the "EOVERFLOW Error" section of pstat_getvminfo(3).
+ */
+ rc = pstat_getproc(&pst, sizeof(pst), 0, getpid());
+ if (rc != -1 || errno == EOVERFLOW) {
+ if (pst.pst_term.psd_major != -1 && pst.pst_term.psd_minor != -1) {
+ errno = serrno;
+ ret = sudo_ttyname_dev(makedev(pst.pst_term.psd_major,
+ pst.pst_term.psd_minor), name, namelen);
+ goto done;
+ }
+ }
+ errno = ENOENT;
+
+done:
+ if (ret == NULL)
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to resolve tty via pstat");
+
+ debug_return_str(ret);
+}
+#else
+/*
+ * Store the name of the tty to which the process is attached in name.
+ * Returns name on success and NULL on failure, setting errno.
+ */
+char *
+get_process_ttyname(char *name, size_t namelen)
+{
+ char *tty;
+ debug_decl(get_process_ttyname, SUDO_DEBUG_UTIL);
+
+ if ((tty = ttyname(STDIN_FILENO)) == NULL) {
+ if ((tty = ttyname(STDOUT_FILENO)) == NULL)
+ tty = ttyname(STDERR_FILENO);
+ }
+ if (tty != NULL) {
+ if (strlcpy(name, tty, namelen) < namelen)
+ debug_return_str(name);
+ errno = ERANGE;
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to store tty from ttyname");
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to resolve tty via ttyname");
+ errno = ENOENT;
+ }
+
+ debug_return_str(NULL);
+}
+#endif
diff --git a/src/utmp.c b/src/utmp.c
new file mode 100644
index 0000000..544a375
--- /dev/null
+++ b/src/utmp.c
@@ -0,0 +1,389 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#if defined(HAVE_UTMPS_H)
+# include <utmps.h>
+#elif defined(HAVE_UTMPX_H)
+# include <utmpx.h>
+#else
+# include <utmp.h>
+#endif /* HAVE_UTMPS_H */
+#ifdef HAVE_GETTTYENT
+# include <ttyent.h>
+#endif
+#include <fcntl.h>
+#include <signal.h>
+
+#include "sudo.h"
+#include "sudo_exec.h"
+
+/*
+ * GCC 8 warns about strncpy() where the size field is the size of the buffer.
+ * However, strings in utmp may not be NUL terminated so this usage is correct.
+ */
+#if __GNUC_PREREQ__(8, 0)
+# pragma GCC diagnostic ignored "-Wstringop-truncation"
+#endif
+
+/*
+ * Simplify handling of different utmp types.
+ */
+#if defined(HAVE_GETUTSID)
+# define sudo_getutline(u) GETUTSLINE(u)
+# define sudo_pututline(u) PUTUTSLINE(u)
+# define sudo_setutent() SETUTSENT()
+# define sudo_endutent() ENDUTSENT()
+#elif defined(HAVE_GETUTXID)
+# define sudo_getutline(u) getutxline(u)
+# define sudo_pututline(u) pututxline(u)
+# define sudo_setutent() setutxent()
+# define sudo_endutent() endutxent()
+#elif defined(HAVE_GETUTID)
+# define sudo_getutline(u) getutline(u)
+# define sudo_pututline(u) pututline(u)
+# define sudo_setutent() setutent()
+# define sudo_endutent() endutent()
+#endif
+
+#if defined(HAVE_GETUTSID)
+typedef struct utmps sudo_utmp_t;
+#elif defined(HAVE_GETUTXID)
+typedef struct utmpx sudo_utmp_t;
+#else
+typedef struct utmp sudo_utmp_t;
+/* Older systems have ut_name, not ut_user */
+# if !defined(HAVE_STRUCT_UTMP_UT_USER) && !defined(ut_user)
+# define ut_user ut_name
+# endif
+#endif
+
+/* HP-UX has __e_termination and __e_exit, others may lack the __ */
+#if defined(HAVE_STRUCT_UTMP_UT_EXIT_E_TERMINATION)
+# undef __e_termination
+# define __e_termination e_termination
+# undef __e_exit
+# define __e_exit e_exit
+#endif
+
+#if defined(HAVE_STRUCT_UTMP_UT_ID)
+/*
+ * Create ut_id from the new ut_line and the old ut_id.
+ */
+static void
+utmp_setid(sudo_utmp_t *old, sudo_utmp_t *new)
+{
+ const char *line = new->ut_line;
+ size_t idlen;
+ debug_decl(utmp_setid, SUDO_DEBUG_UTMP);
+
+ /* Skip over "tty" in the id if old entry did too. */
+ if (old != NULL) {
+ /* cppcheck-suppress uninitdata */
+ if (strncmp(line, "tty", 3) == 0) {
+ idlen = MIN(sizeof(old->ut_id), 3);
+ if (strncmp(old->ut_id, "tty", idlen) != 0)
+ line += 3;
+ }
+ }
+
+ /* Store as much as will fit, skipping parts of the beginning as needed. */
+ /* cppcheck-suppress uninitdata */
+ idlen = strlen(line);
+ if (idlen > sizeof(new->ut_id)) {
+ line += idlen - sizeof(new->ut_id);
+ idlen = sizeof(new->ut_id);
+ }
+ strncpy(new->ut_id, line, idlen);
+
+ debug_return;
+}
+#endif /* HAVE_STRUCT_UTMP_UT_ID */
+
+/*
+ * Store time in utmp structure.
+ */
+static void
+utmp_settime(sudo_utmp_t *ut)
+{
+ struct timeval tv;
+ debug_decl(utmp_settime, SUDO_DEBUG_UTMP);
+
+ if (gettimeofday(&tv, NULL) == 0) {
+#if defined(HAVE_STRUCT_UTMP_UT_TV)
+ ut->ut_tv.tv_sec = tv.tv_sec;
+ ut->ut_tv.tv_usec = tv.tv_usec;
+#else
+ ut->ut_time = tv.tv_sec;
+#endif
+ }
+
+ debug_return;
+}
+
+/*
+ * Fill in a utmp entry, using an old entry as a template if there is one.
+ */
+static void
+utmp_fill(const char *line, const char *user, sudo_utmp_t *ut_old,
+ sudo_utmp_t *ut_new)
+{
+ debug_decl(utmp_file, SUDO_DEBUG_UTMP);
+
+ if (ut_old == NULL) {
+ memset(ut_new, 0, sizeof(*ut_new));
+ if (user == NULL) {
+ strncpy(ut_new->ut_user, user_details.username,
+ sizeof(ut_new->ut_user));
+ }
+ } else if (ut_old != ut_new) {
+ memcpy(ut_new, ut_old, sizeof(*ut_new));
+ }
+ if (user != NULL)
+ strncpy(ut_new->ut_user, user, sizeof(ut_new->ut_user));
+ strncpy(ut_new->ut_line, line, sizeof(ut_new->ut_line));
+#if defined(HAVE_STRUCT_UTMP_UT_ID)
+ utmp_setid(ut_old, ut_new);
+#endif
+#if defined(HAVE_STRUCT_UTMP_UT_PID)
+ ut_new->ut_pid = getpid();
+#endif
+ utmp_settime(ut_new);
+#if defined(HAVE_STRUCT_UTMP_UT_TYPE)
+ ut_new->ut_type = USER_PROCESS;
+#endif
+ debug_return;
+}
+
+/*
+ * There are two basic utmp file types:
+ *
+ * POSIX: sequential access with new entries appended to the end.
+ * Manipulated via {get,put}[sx]?utent()
+ *
+ * Legacy: sparse file indexed by ttyslot() * sizeof(struct utmp)
+ */
+#if defined(HAVE_GETUTSID) || defined(HAVE_GETUTXID) || defined(HAVE_GETUTID)
+bool
+utmp_login(const char *from_line, const char *to_line, int ttyfd,
+ const char *user)
+{
+ sudo_utmp_t utbuf, *ut_old = NULL;
+ bool ret = false;
+ debug_decl(utmp_login, SUDO_DEBUG_UTMP);
+
+ /* Strip off /dev/ prefix from line as needed. */
+ if (strncmp(to_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ to_line += sizeof(_PATH_DEV) - 1;
+ sudo_setutent();
+ if (from_line != NULL) {
+ if (strncmp(from_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ from_line += sizeof(_PATH_DEV) - 1;
+
+ /* Lookup old line. */
+ memset(&utbuf, 0, sizeof(utbuf));
+ strncpy(utbuf.ut_line, from_line, sizeof(utbuf.ut_line));
+ ut_old = sudo_getutline(&utbuf);
+ }
+ utmp_fill(to_line, user, ut_old, &utbuf);
+ if (sudo_pututline(&utbuf) != NULL)
+ ret = true;
+ sudo_endutent();
+
+ debug_return_bool(ret);
+}
+
+bool
+utmp_logout(const char *line, int status)
+{
+ bool ret = false;
+ sudo_utmp_t *ut, utbuf;
+ debug_decl(utmp_logout, SUDO_DEBUG_UTMP);
+
+ /* Strip off /dev/ prefix from line as needed. */
+ if (strncmp(line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ line += sizeof(_PATH_DEV) - 1;
+
+ memset(&utbuf, 0, sizeof(utbuf));
+ strncpy(utbuf.ut_line, line, sizeof(utbuf.ut_line));
+ if ((ut = sudo_getutline(&utbuf)) != NULL) {
+ memset(ut->ut_user, 0, sizeof(ut->ut_user));
+# if defined(HAVE_STRUCT_UTMP_UT_TYPE)
+ ut->ut_type = DEAD_PROCESS;
+# endif
+# if defined(HAVE_STRUCT_UTMP_UT_EXIT)
+ ut->ut_exit.__e_termination = WIFSIGNALED(status) ? WTERMSIG(status) : 0;
+ ut->ut_exit.__e_exit = WIFEXITED(status) ? WEXITSTATUS(status) : 0;
+# endif
+ utmp_settime(ut);
+ if (sudo_pututline(ut) != NULL)
+ ret = true;
+ }
+ debug_return_bool(ret);
+}
+
+#else /* !HAVE_GETUTSID && !HAVE_GETUTXID && !HAVE_GETUTID */
+
+/*
+ * Find the slot for the specified line (tty name and file descriptor).
+ * Returns a slot suitable for seeking into utmp on success or <= 0 on error.
+ * If getttyent() is available we can use that to compute the slot.
+ */
+# ifdef HAVE_GETTTYENT
+static int
+utmp_slot(const char *line, int ttyfd)
+{
+ int slot = 1;
+ struct ttyent *tty;
+ debug_decl(utmp_slot, SUDO_DEBUG_UTMP);
+
+ setttyent();
+ while ((tty = getttyent()) != NULL) {
+ if (strcmp(line, tty->ty_name) == 0)
+ break;
+ slot++;
+ }
+ endttyent();
+ debug_return_int(tty ? slot : 0);
+}
+# elif defined(HAVE_TTYSLOT)
+static int
+utmp_slot(const char *line, int ttyfd)
+{
+ int sfd, slot;
+ debug_decl(utmp_slot, SUDO_DEBUG_UTMP);
+
+ /*
+ * Temporarily point stdin to the tty since ttyslot()
+ * doesn't take an argument.
+ */
+ if ((sfd = dup(STDIN_FILENO)) == -1)
+ sudo_fatal("%s", U_("unable to save stdin"));
+ if (dup2(ttyfd, STDIN_FILENO) == -1)
+ sudo_fatal("%s", U_("unable to dup2 stdin"));
+ slot = ttyslot();
+ if (dup2(sfd, STDIN_FILENO) == -1)
+ sudo_fatal("%s", U_("unable to restore stdin"));
+ close(sfd);
+
+ debug_return_int(slot);
+}
+# else /* !HAVE_TTYSLOT */
+static int
+utmp_slot(const char *line, int ttyfd)
+{
+ return -1;
+}
+# endif /* HAVE_GETTTYENT */
+
+bool
+utmp_login(const char *from_line, const char *to_line, int ttyfd,
+ const char *user)
+{
+ sudo_utmp_t utbuf, *ut_old = NULL;
+ bool ret = false;
+ int slot;
+ FILE *fp;
+ debug_decl(utmp_login, SUDO_DEBUG_UTMP);
+
+ /* Strip off /dev/ prefix from line as needed. */
+ if (strncmp(to_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ to_line += sizeof(_PATH_DEV) - 1;
+
+ /* Find slot for new entry. */
+ slot = utmp_slot(to_line, ttyfd);
+ if (slot <= 0)
+ goto done;
+
+ if ((fp = fopen(_PATH_UTMP, "r+")) == NULL)
+ goto done;
+
+ if (from_line != NULL) {
+ if (strncmp(from_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ from_line += sizeof(_PATH_DEV) - 1;
+
+ /* Lookup old line. */
+ while (fread(&utbuf, sizeof(utbuf), 1, fp) == 1) {
+# ifdef HAVE_STRUCT_UTMP_UT_ID
+ if (utbuf.ut_type != LOGIN_PROCESS && utbuf.ut_type != USER_PROCESS)
+ continue;
+# endif
+ if (utbuf.ut_user[0] &&
+ !strncmp(utbuf.ut_line, from_line, sizeof(utbuf.ut_line))) {
+ ut_old = &utbuf;
+ break;
+ }
+ }
+ }
+ utmp_fill(to_line, user, ut_old, &utbuf);
+ if (fseeko(fp, slot * (off_t)sizeof(utbuf), SEEK_SET) == 0) {
+ if (fwrite(&utbuf, sizeof(utbuf), 1, fp) == 1)
+ ret = true;
+ }
+ fclose(fp);
+
+done:
+ debug_return_bool(ret);
+}
+
+bool
+utmp_logout(const char *line, int status)
+{
+ sudo_utmp_t utbuf;
+ bool ret = false;
+ FILE *fp;
+ debug_decl(utmp_logout, SUDO_DEBUG_UTMP);
+
+ if ((fp = fopen(_PATH_UTMP, "r+")) == NULL)
+ debug_return_int(ret);
+
+ /* Strip off /dev/ prefix from line as needed. */
+ if (strncmp(line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ line += sizeof(_PATH_DEV) - 1;
+
+ while (fread(&utbuf, sizeof(utbuf), 1, fp) == 1) {
+ if (!strncmp(utbuf.ut_line, line, sizeof(utbuf.ut_line))) {
+ memset(utbuf.ut_user, 0, sizeof(utbuf.ut_user));
+# if defined(HAVE_STRUCT_UTMP_UT_TYPE)
+ utbuf.ut_type = DEAD_PROCESS;
+# endif
+ utmp_settime(&utbuf);
+ /* Back up and overwrite record. */
+ if (fseeko(fp, (off_t)0 - (off_t)sizeof(utbuf), SEEK_CUR) == 0) {
+ if (fwrite(&utbuf, sizeof(utbuf), 1, fp) == 1)
+ ret = true;
+ }
+ break;
+ }
+ }
+ fclose(fp);
+
+ debug_return_bool(ret);
+}
+#endif /* HAVE_GETUTSID || HAVE_GETUTXID || HAVE_GETUTID */