summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.in718
-rw-r--r--src/conversation.c166
-rw-r--r--src/env_hooks.c297
-rw-r--r--src/exec.c480
-rw-r--r--src/exec_common.c219
-rw-r--r--src/exec_monitor.c719
-rw-r--r--src/exec_nopty.c573
-rw-r--r--src/exec_pty.c1813
-rw-r--r--src/get_pty.c195
-rw-r--r--src/hooks.c251
-rw-r--r--src/load_plugins.c369
-rw-r--r--src/net_ifs.c371
-rw-r--r--src/openbsd.c45
-rw-r--r--src/parse_args.c774
-rw-r--r--src/preload.c74
-rw-r--r--src/preserve_fds.c222
-rw-r--r--src/regress/noexec/check_noexec.c203
-rw-r--r--src/regress/ttyname/check_ttyname.c85
-rw-r--r--src/selinux.c460
-rw-r--r--src/sesh.c268
-rw-r--r--src/signal.c193
-rw-r--r--src/solaris.c122
-rw-r--r--src/sudo.c1450
-rw-r--r--src/sudo.h279
-rw-r--r--src/sudo_edit.c1101
-rw-r--r--src/sudo_exec.h111
-rw-r--r--src/sudo_noexec.c261
-rw-r--r--src/sudo_plugin_int.h117
-rw-r--r--src/sudo_usage.h.in36
-rw-r--r--src/tcsetpgrp_nobg.c78
-rw-r--r--src/tgetpass.c446
-rw-r--r--src/ttyname.c336
-rw-r--r--src/utmp.c389
33 files changed, 13221 insertions, 0 deletions
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..099193c
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,718 @@
+#
+# Copyright (c) 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.
+#
+# @configure_input@
+#
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+devdir = @devdir@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+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) -I$(top_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 --force --enable=warning,performance,portability --suppress=constStatement --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 env_hooks.o exec.o exec_common.o exec_monitor.o \
+ exec_nopty.o exec_pty.o get_pty.o hooks.o net_ifs.o load_plugins.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 = sesh.o exec_common.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)
+
+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) $(top_srcdir)/mkinstalldirs $(DESTDIR)$(bindir) \
+ $(DESTDIR)$(libexecdir)/sudo $(DESTDIR)$(noexecdir)
+ if test -n "$(INIT_SCRIPT)"; then \
+ $(SHELL) $(top_srcdir)/mkinstalldirs $(DESTDIR)$(INIT_DIR); \
+ if test -z "$(DESTDIR)"; then \
+ $(SHELL) $(top_srcdir)/mkinstalldirs \
+ `echo $(RC_LINK) | $(SED) 's,/[^/]*$$,,'`; \
+ fi; \
+ elif test -n "$(tmpfiles_d)"; then \
+ $(SHELL) $(top_srcdir)/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)/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)/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) -I$(top_srcdir) $(srcdir)/*.c
+
+cppcheck:
+ cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) -I. -I$(srcdir) -I$(top_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 \
+ ./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 *.a *.i *.plog stamp-* core *.core core.* nohup.out
+
+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_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_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_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_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_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_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 $@
+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_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_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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_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_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 $@
+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_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_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_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_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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_lbuf.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_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_lbuf.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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo_exec.h \
+ $(top_builddir)/config.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_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/sudo_exec.h \
+ $(top_builddir)/config.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_fatal.h $(incdir)/sudo_gettext.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_fatal.h $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_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_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_fatal.h \
+ $(incdir)/sudo_gettext.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)/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_fatal.h \
+ $(incdir)/sudo_gettext.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) $<
+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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_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_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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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_fatal.h \
+ $(incdir)/sudo_gettext.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..fccb3d6
--- /dev/null
+++ b/src/conversation.c
@@ -0,0 +1,166 @@
+/*
+ * 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 <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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 fd, 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. */
+ 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"));
+ }
+ memset_s(pass, SUDO_CONV_REPL_MAX, 0, strlen(pass));
+ break;
+ case SUDO_CONV_ERROR_MSG:
+ fp = stderr;
+ /* FALLTHROUGH */
+ case SUDO_CONV_INFO_MSG:
+ if (msg->msg != NULL) {
+ if (ISSET(msg->msg_type, SUDO_CONV_PREFER_TTY)) {
+ /* Try writing to /dev/tty first. */
+ if ((fd = open(_PATH_TTY, O_WRONLY)) != -1) {
+ ssize_t nwritten =
+ write(fd, msg->msg, strlen(msg->msg));
+ close(fd);
+ if (nwritten != -1)
+ break;
+ }
+ }
+ if (fputs(msg->msg, fp) == EOF)
+ 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 != 0) {
+ do {
+ struct sudo_conv_reply *repl = &replies[n];
+ if (repl->reply == NULL)
+ continue;
+ memset_s(repl->reply, SUDO_CONV_REPL_MAX, 0, strlen(repl->reply));
+ free(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, ...)
+{
+ va_list ap;
+ int len;
+ const int conv_debug_instance = sudo_debug_get_active_instance();
+
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+ switch (msg_type) {
+ case SUDO_CONV_INFO_MSG:
+ va_start(ap, fmt);
+ len = vfprintf(stdout, fmt, ap);
+ va_end(ap);
+ break;
+ case SUDO_CONV_ERROR_MSG:
+ va_start(ap, fmt);
+ len = vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ break;
+ default:
+ len = -1;
+ errno = EINVAL;
+ break;
+ }
+
+ sudo_debug_set_active_instance(conv_debug_instance);
+ return len;
+}
diff --git a/src/env_hooks.c b/src/env_hooks.c
new file mode 100644
index 0000000..8a0b26c
--- /dev/null
+++ b/src/env_hooks.c
@@ -0,0 +1,297 @@
+/*
+ * 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 <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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;
+}
+
+__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);
+}
+
+__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);
+}
+
+__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
+__dso_public void
+#else
+__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..28d847d
--- /dev/null
+++ b/src/exec.c
@@ -0,0 +1,480 @@
+/*
+ * Copyright (c) 2009-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 <sys/types.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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_event.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+#ifdef __linux__
+static struct rlimit nproclimit;
+#endif
+
+/*
+ * 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.
+ */
+static void
+unlimit_nproc(void)
+{
+#ifdef __linux__
+ struct rlimit rl;
+ debug_decl(unlimit_nproc, SUDO_DEBUG_UTIL)
+
+ if (getrlimit(RLIMIT_NPROC, &nproclimit) != 0)
+ sudo_warn("getrlimit");
+ rl.rlim_cur = rl.rlim_max = RLIM_INFINITY;
+ if (setrlimit(RLIMIT_NPROC, &rl) != 0) {
+ rl.rlim_cur = rl.rlim_max = nproclimit.rlim_max;
+ if (setrlimit(RLIMIT_NPROC, &rl) != 0)
+ sudo_warn("setrlimit");
+ }
+ debug_return;
+#endif /* __linux__ */
+}
+
+/*
+ * Restore saved value of RLIMIT_NPROC.
+ */
+static void
+restore_nproc(void)
+{
+#ifdef __linux__
+ debug_decl(restore_nproc, SUDO_DEBUG_UTIL)
+
+ if (setrlimit(RLIMIT_NPROC, &nproclimit) != 0)
+ sudo_warn("setrlimit");
+
+ debug_return;
+#endif /* __linux__ */
+}
+
+/*
+ * 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, const char *ptyname, int ptyfd)
+{
+ bool ret = false;
+ debug_decl(exec_setup, SUDO_DEBUG_EXEC)
+
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_RBAC_ENABLED)) {
+ if (selinux_setup(details->selinux_role, details->selinux_type,
+ ptyname ? ptyname : user_details.tty, ptyfd) == -1)
+ goto done;
+ }
+#endif
+
+ /* Restore coredumpsize resource limit before running. */
+ if (sudo_conf_disable_coredump())
+ disable_coredump(true);
+
+ 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, ptyname ? ptyname : user_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 and rlimits
+ * 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);
+ CLR(details->flags, CD_SET_UMASK); /* LOGIN_UMASK instead */
+ } else {
+ flags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
+ }
+ if (setusercontext(lc, details->pw, details->pw->pw_uid, flags)) {
+ sudo_warn(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(U_("unable to set process priority"));
+ goto done;
+ }
+ }
+ if (ISSET(details->flags, CD_SET_UMASK))
+ (void) umask(details->umask);
+ 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->uid, details->euid, details->euid) != 0) {
+ sudo_warn(U_("unable to change to runas uid (%u, %u)"),
+ (unsigned int)details->uid, (unsigned int)details->euid);
+ goto done;
+ }
+#elif defined(HAVE_SETREUID)
+ if (setreuid(details->uid, details->euid) != 0) {
+ sudo_warn(U_("unable to change to runas uid (%u, %u)"),
+ (unsigned int)details->uid, (unsigned int)details->euid);
+ goto done;
+ }
+#else
+ /* Cannot support real user ID that is different from effective user ID. */
+ if (setuid(details->euid) != 0) {
+ sudo_warn(U_("unable to change to runas uid (%u, %u)"),
+ (unsigned int)details->euid, (unsigned int)details->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 || 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) != 0) {
+ sudo_warn(U_("unable to change directory to %s"), details->cwd);
+ goto done;
+ }
+ }
+ }
+
+ 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, NULL, -1) == true) {
+ /* headed for execve() */
+ if (details->closefrom >= 0) {
+ int fd, maxfd;
+ unsigned char *debug_fds;
+
+ /* 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);
+ }
+#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, 13)
+# 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;
+}
+
+/*
+ * Execute a command, potentially in a pty with I/O loggging, 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(0);
+ }
+ }
+
+ /*
+ * 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 were not invoked
+ * as sudoedit, there is no command timeout and there is no close
+ * function, just exec directly. Only returns on error.
+ */
+ if (!ISSET(details->flags, CD_SET_TIMEOUT|CD_SUDOEDIT) &&
+ policy_plugin.u.policy->close == NULL) {
+ 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..53abdbf
--- /dev/null
+++ b/src/exec_common.c
@@ -0,0 +1,219 @@
+/*
+ * 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 <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#ifdef HAVE_PRIV_SET
+# include <priv.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.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;
+
+ 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(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..4a81b5d
--- /dev/null
+++ b/src/exec_monitor.c
@@ -0,0 +1,719 @@
+/*
+ * 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/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <termios.h>
+
+#include "sudo.h"
+#include "sudo_event.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)
+ strlcpy(signame, "CONT_FG", sizeof(signame));
+ else if (signo == SIGCONT_BG)
+ strlcpy(signame, "CONT_BG", sizeof(signame));
+ else if (sig2str(signo, signame) == -1)
+ 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_SLAVE], 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_SLAVE]))
+ tty_initialized = true;
+ }
+ killpg(mc->cmnd_pid, SIGCONT);
+ break;
+ case SIGCONT_BG:
+ /* Continue in background, I take controlling tty. */
+ if (tcsetpgrp(io_fds[SFD_SLAVE], 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(1); /* 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 slave 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 colums 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_SLAVE], 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_SLAVE], 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)
+ 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)
+ 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_SLAVE]);
+ 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(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 (io_fds[SFD_STDIN] != STDIN_FILENO) {
+ if (dup2(io_fds[SFD_STDIN], STDIN_FILENO) == -1)
+ sudo_fatal("dup2");
+ if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDIN]);
+ }
+ if (io_fds[SFD_STDOUT] != STDOUT_FILENO) {
+ if (dup2(io_fds[SFD_STDOUT], STDOUT_FILENO) == -1)
+ sudo_fatal("dup2");
+ if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDOUT]);
+ }
+ if (io_fds[SFD_STDERR] != STDERR_FILENO) {
+ if (dup2(io_fds[SFD_STDERR], STDERR_FILENO) == -1)
+ sudo_fatal("dup2");
+ if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
+ 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_SLAVE]) != self)
+ nanosleep(&ts, NULL);
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: got controlling tty",
+ __func__);
+ }
+
+ /* Done with the pty slave, don't leak it. */
+ if (io_fds[SFD_SLAVE] != -1)
+ close(io_fds[SFD_SLAVE]);
+
+ /* 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(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(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(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(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(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(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(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(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(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(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 master is not used by the monitor. */
+ if (io_fds[SFD_MASTER] != -1)
+ close(io_fds[SFD_MASTER]);
+
+ /* 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 slave pty 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(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(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(U_("unable to receive message from parent"));
+ }
+
+ mc.cmnd_pid = sudo_debug_fork();
+ switch (mc.cmnd_pid) {
+ case -1:
+ sudo_warn(U_("unable to fork"));
+ 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(1);
+ }
+ 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_SLAVE])
+ close(io_fds[SFD_STDIN]);
+ if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDOUT]);
+ if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
+ 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 slave. */
+ if (foreground && !ISSET(details->flags, CD_EXEC_BG)) {
+ if (tcsetpgrp(io_fds[SFD_SLAVE], 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_SLAVE], 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(U_("unable to restore tty label"));
+ }
+#endif
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
+ _exit(1);
+
+bad:
+ debug_return_int(-1);
+}
diff --git a/src/exec_nopty.c b/src/exec_nopty.c
new file mode 100644
index 0000000..0df79f0
--- /dev/null
+++ b/src/exec_nopty.c
@@ -0,0 +1,573 @@
+/*
+ * Copyright (c) 2009-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 <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include "sudo.h"
+#include "sudo_exec.h"
+#include "sudo_event.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)
+ 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 = sudo_ev_base_alloc();
+ if (ec->evbase == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ /* 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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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;
+ }
+
+ ec.cmnd_pid = sudo_debug_fork();
+ switch (ec.cmnd_pid) {
+ case -1:
+ sudo_fatal(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(1);
+ }
+ 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(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);
+ }
+
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_RBAC_ENABLED)) {
+ if (selinux_restore_tty() != 0)
+ sudo_warnx(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)
+ 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)
+ 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..c1ccd4b
--- /dev/null
+++ b/src/exec_pty.c
@@ -0,0 +1,1813 @@
+/*
+ * Copyright (c) 2009-2019 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>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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_event.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 slavename[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 void 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(slavename, 0);
+
+ debug_return;
+}
+
+/*
+ * Allocate a pty if /dev/tty is a tty.
+ * Fills in io_fds[SFD_USERTTY], io_fds[SFD_MASTER], io_fds[SFD_SLAVE]
+ * and slavename 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_MASTER], &io_fds[SFD_SLAVE],
+ slavename, sizeof(slavename), details->euid))
+ sudo_fatal(U_("unable to allocate pty"));
+
+ /* 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, slavename, io_fds[SFD_SLAVE], utmp_user);
+ }
+
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: %s fd %d, pty master fd %d, pty slave fd %d",
+ __func__, _PATH_TTY, io_fds[SFD_USERTTY], io_fds[SFD_MASTER],
+ io_fds[SFD_SLAVE]);
+
+ debug_return_bool(true);
+}
+
+/*
+ * Make the tty slave the controlling tty.
+ * This is only used by the monitor but slavename[] is static.
+ */
+int
+pty_make_controlling(void)
+{
+ if (io_fds[SFD_SLAVE] != -1) {
+#ifdef TIOCSCTTY
+ if (ioctl(io_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0)
+ return -1;
+#else
+ /* Set controlling tty by reopening slave. */
+ int fd = open(slavename, 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;
+ 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);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_ttyin = NULL;
+ }
+ 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;
+ 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);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_stdin = NULL;
+ }
+ 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;
+ 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);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_ttyout = NULL;
+ }
+ 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;
+ 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);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_stdout = NULL;
+ }
+ 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;
+ 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);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_stderr = NULL;
+ }
+ 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(int signo)
+{
+ struct plugin_container *plugin;
+ 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);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->log_suspend = NULL;
+ }
+ 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(unsigned int rows, unsigned int cols)
+{
+ struct plugin_container *plugin;
+ 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);
+ if (rc <= 0) {
+ if (rc < 0) {
+ /* Error: disable plugin's I/O function. */
+ plugin->u.io->change_winsize = NULL;
+ }
+ 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 does lazy init of the
+ * the pty slave as needed.
+ */
+static void
+check_foreground(struct exec_closure_pty *ec)
+{
+ debug_decl(check_foreground, SUDO_DEBUG_EXEC);
+
+ if (io_fds[SFD_USERTTY] != -1) {
+ foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ec->ppgrp;
+
+ /* Also check for window size changes. */
+ sync_ttysize(ec);
+ }
+
+ debug_return;
+}
+
+/*
+ * 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)
+ check_foreground(ec);
+ 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(signo);
+
+ if (sig2str(signo, signame) == -1)
+ 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(SIGCONT);
+
+ /* Check foreground/background status on resume. */
+ check_foreground(ec);
+
+ /*
+ * 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 forwared 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(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(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 forwared 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(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(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 slave pty so reads from the master 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(slavename, 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(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 == 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)
+ 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)
+ 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)
+ 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 forwared 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)
+ 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);
+
+ /* Setup event base and events. */
+ ec->evbase = sudo_ev_base_alloc();
+ if (ec->evbase == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ /* 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(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(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(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(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(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(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(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(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(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(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(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 loggging, 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;
+ 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)
+ sudo_fatal(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(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_SLAVE];
+ io_fds[SFD_STDOUT] = io_fds[SFD_SLAVE];
+ io_fds[SFD_STDERR] = io_fds[SFD_SLAVE];
+
+ if (io_fds[SFD_USERTTY] != -1) {
+ /* Read from /dev/tty, write to pty master */
+ if (!ISSET(details->flags, CD_BACKGROUND)) {
+ io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_MASTER],
+ log_ttyin, &ec, &iobufs);
+ }
+
+ /* Read from pty master, write to /dev/tty */
+ io_buf_new(io_fds[SFD_MASTER], 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 (pipe(io_pipe[STDIN_FILENO]) != 0)
+ sudo_fatal(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 (pipe(io_pipe[STDOUT_FILENO]) != 0)
+ sudo_fatal(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 (pipe(io_pipe[STDERR_FILENO]) != 0)
+ sudo_fatal(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 slave. */
+ if (!sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
+ 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(U_("unable to fork"));
+ break;
+ case 0:
+ /* child */
+ close(sv[0]);
+ (void)fcntl(sv[1], F_SETFD, FD_CLOEXEC);
+ /* 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(1);
+ }
+
+ /*
+ * We close the pty slave so only the monitor and command have a
+ * reference to it. This ensures that we can don't block reading
+ * from the master when the command and monitor have exited.
+ */
+ if (io_fds[SFD_SLAVE] != -1) {
+ close(io_fds[SFD_SLAVE]);
+ io_fds[SFD_SLAVE] = -1;
+ }
+
+ /* Tell the monitor to continue now that the slave 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(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 master
+ * and pass output from master to stdout and IO plugin.
+ */
+ add_io_events(ec.evbase);
+ if (sudo_ev_dispatch(ec.evbase) == -1)
+ sudo_warn(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 - may need to terminate command if cmnd_pid != -1 */
+ }
+
+ /* 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(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(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(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(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(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(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..3e1a26a
--- /dev/null
+++ b/src/get_pty.c
@@ -0,0 +1,195 @@
+/*
+ * 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/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#ifdef HAVE_SYS_STROPTS_H
+#include <sys/stropts.h>
+#endif /* HAVE_SYS_STROPTS_H */
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+
+#if defined(HAVE_LIBUTIL_H)
+# include <libutil.h>
+#elif defined(HAVE_UTIL_H)
+# include <util.h>
+#endif
+#ifdef HAVE_PTY_H
+# include <pty.h>
+#endif
+
+#include "sudo.h"
+
+#if defined(HAVE_OPENPTY)
+bool
+get_pty(int *master, int *slave, 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(master, slave, name, NULL, NULL) == 0) {
+ if (chown(name, ttyuid, ttygid) == 0)
+ ret = true;
+ }
+
+ debug_return_bool(ret);
+}
+
+#elif defined(HAVE__GETPTY)
+bool
+get_pty(int *master, int *slave, 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(master, O_RDWR, S_IRUSR|S_IWUSR|S_IWGRP, 0);
+ if (line != NULL) {
+ *slave = open(line, O_RDWR|O_NOCTTY, 0);
+ if (*slave != -1) {
+ (void) chown(line, ttyuid, -1);
+ strlcpy(name, line, namesz);
+ ret = true;
+ } else {
+ close(*master);
+ *master = -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 *master, int *slave, char *name, size_t namesz, uid_t ttyuid)
+{
+ char *line;
+ bool ret = false;
+ debug_decl(get_pty, SUDO_DEBUG_PTY)
+
+ *master = posix_openpt(O_RDWR|O_NOCTTY);
+ if (*master != -1) {
+ (void) grantpt(*master); /* may fork */
+ if (unlockpt(*master) != 0) {
+ close(*master);
+ goto done;
+ }
+ line = ptsname(*master);
+ if (line == NULL) {
+ close(*master);
+ goto done;
+ }
+ *slave = open(line, O_RDWR|O_NOCTTY, 0);
+ if (*slave == -1) {
+ close(*master);
+ goto done;
+ }
+# if defined(I_PUSH) && !defined(_AIX)
+ ioctl(*slave, I_PUSH, "ptem"); /* pseudo tty emulation module */
+ ioctl(*slave, 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 *master, int *slave, 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;
+ *master = open(line, O_RDWR|O_NOCTTY, 0);
+ if (*master == -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
+ *slave = open(line, O_RDWR|O_NOCTTY, 0);
+ if (*slave != -1) {
+ strlcpy(name, line, namesz);
+ ret = true; /* success */
+ goto done;
+ }
+ (void) close(*master);
+ }
+ }
+done:
+ debug_return_bool(ret);
+}
+#endif /* HAVE_OPENPTY */
diff --git a/src/hooks.c b/src/hooks.c
new file mode 100644
index 0000000..1216980
--- /dev/null
+++ b/src/hooks.c
@@ -0,0 +1,251 @@
+/*
+ * 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 <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.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/load_plugins.c b/src/load_plugins.c
new file mode 100644
index 0000000..8cac77b
--- /dev/null
+++ b/src/load_plugins.c
@@ -0,0 +1,369 @@
+/*
+ * 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/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.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);
+ sudo_warn("%s%s",
+ sudo_conf_plugin_dir_path() ? sudo_conf_plugin_dir_path() : "",
+ info->path);
+ goto done;
+ }
+ 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 */
+
+/*
+ * Load the plugin specified by "info".
+ */
+static bool
+sudo_load_plugin(struct plugin_container *policy_plugin,
+ struct plugin_container_list *io_plugins, struct plugin_info *info)
+{
+ struct plugin_container *container = NULL;
+ struct generic_plugin *plugin;
+ char path[PATH_MAX];
+ void *handle = NULL;
+ debug_decl(sudo_load_plugin, SUDO_DEBUG_PLUGIN)
+
+ /* Sanity check plugin and fill in path */
+ if (!sudo_check_plugin(info, path, sizeof(path)))
+ goto bad;
+
+ /* Open plugin and map in symbol */
+ handle = sudo_dso_load(path, SUDO_DSO_LAZY|SUDO_DSO_GLOBAL);
+ if (!handle) {
+ 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 bad;
+ }
+ plugin = sudo_dso_findsym(handle, info->symbol_name);
+ if (!plugin) {
+ 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 bad;
+ }
+
+ if (plugin->type != SUDO_POLICY_PLUGIN && plugin->type != SUDO_IO_PLUGIN) {
+ sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""),
+ _PATH_SUDO_CONF, info->lineno, info->symbol_name);
+ sudo_warnx(U_("unknown policy type %d found in %s"), plugin->type, path);
+ goto bad;
+ }
+ if (SUDO_API_VERSION_GET_MAJOR(plugin->version) != SUDO_API_VERSION_MAJOR) {
+ 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 bad;
+ }
+ if (plugin->type == SUDO_POLICY_PLUGIN) {
+ if (policy_plugin->handle != NULL) {
+ /* Ignore duplicate entries. */
+ if (strcmp(policy_plugin->name, info->symbol_name) != 0) {
+ sudo_warnx(U_("ignoring policy plugin \"%s\" in %s, line %d"),
+ info->symbol_name, _PATH_SUDO_CONF, info->lineno);
+ sudo_warnx(U_("only a single policy plugin may be specified"));
+ goto bad;
+ }
+ sudo_warnx(U_("ignoring duplicate policy plugin \"%s\" in %s, line %d"),
+ info->symbol_name, _PATH_SUDO_CONF, info->lineno);
+ goto bad;
+ }
+ policy_plugin->handle = handle;
+ policy_plugin->path = strdup(path);
+ if (policy_plugin->path == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto bad;
+ }
+ policy_plugin->name = info->symbol_name;
+ policy_plugin->options = info->options;
+ policy_plugin->debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER;
+ policy_plugin->u.generic = plugin;
+ policy_plugin->debug_files = sudo_conf_debug_files(path);
+ } else if (plugin->type == SUDO_IO_PLUGIN) {
+ /* Check for duplicate entries. */
+ TAILQ_FOREACH(container, io_plugins, entries) {
+ if (strcmp(container->name, info->symbol_name) == 0) {
+ sudo_warnx(U_("ignoring duplicate I/O plugin \"%s\" in %s, line %d"),
+ info->symbol_name, _PATH_SUDO_CONF, info->lineno);
+ sudo_dso_unload(handle);
+ handle = NULL;
+ break;
+ }
+ }
+ container = calloc(1, sizeof(*container));
+ if (container == NULL || (container->path = strdup(path)) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto bad;
+ }
+ 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);
+ TAILQ_INSERT_TAIL(io_plugins, container, entries);
+ }
+
+ /* Zero out info strings that we now own (see above). */
+ info->symbol_name = NULL;
+ info->options = NULL;
+
+ debug_return_bool(true);
+bad:
+ free(container);
+ if (handle != NULL)
+ sudo_dso_unload(handle);
+ debug_return_bool(false);
+}
+
+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);
+}
+
+/*
+ * Load the plugins listed in sudo.conf.
+ */
+bool
+sudo_load_plugins(struct plugin_container *policy_plugin,
+ struct plugin_container_list *io_plugins)
+{
+ struct plugin_container *container;
+ 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(policy_plugin, io_plugins, info);
+ 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 */
+ 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("sudoers_policy");
+ 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(policy_plugin, io_plugins, info);
+ free_plugin_info(info);
+ if (!ret)
+ goto done;
+
+ /* Default I/O plugin */
+ if (TAILQ_EMPTY(io_plugins)) {
+ 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("sudoers_io");
+ 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(policy_plugin, io_plugins, info);
+ free_plugin_info(info);
+ if (!ret)
+ goto done;
+ }
+ }
+ 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;
+ }
+
+ /* Install hooks (XXX - later). */
+ sudo_debug_set_active_instance(SUDO_DEBUG_INSTANCE_INITIALIZER);
+ if (policy_plugin->u.policy->version >= SUDO_API_MKVERSION(1, 2)) {
+ if (policy_plugin->u.policy->register_hooks != NULL)
+ policy_plugin->u.policy->register_hooks(SUDO_HOOK_VERSION, register_hook);
+ }
+ TAILQ_FOREACH(container, io_plugins, entries) {
+ if (container->u.io->version >= SUDO_API_MKVERSION(1, 2)) {
+ if (container->u.io->register_hooks != NULL)
+ container->u.io->register_hooks(SUDO_HOOK_VERSION, register_hook);
+ }
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+
+done:
+ debug_return_bool(ret);
+}
diff --git a/src/net_ifs.c b/src/net_ifs.c
new file mode 100644
index 0000000..f95cf4d
--- /dev/null
+++ b/src/net_ifs.c
@@ -0,0 +1,371 @@
+/*
+ * 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>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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 SUDO_NET_IFS_C /* to expose sudo_inet_ntop in sudo_compat.h */
+
+#define DEFAULT_TEXT_DOMAIN "sudo"
+#include "sudo_gettext.h" /* must be included before sudo_compat.h */
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_conf.h"
+#include "sudo_debug.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 ailen, len, num_interfaces = 0;
+ 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)
+ debug_return_int(0);
+ ailen = num_interfaces * 2 * INET6_ADDRSTRLEN;
+ if ((cp = malloc(ailen)) == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to allocate memory");
+ debug_return_int(-1);
+ }
+ *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 - (*addrinfo - cp),
+ "%s%s/%s", cp == *addrinfo ? "" : " ", addrstr, maskstr);
+ if (len <= 0 || len >= ailen - (*addrinfo - cp)) {
+ sudo_warnx(U_("internal error, %s overflow"), __func__);
+ goto done;
+ }
+ cp += 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 - (*addrinfo - cp),
+ "%s%s/%s", cp == *addrinfo ? "" : " ", addrstr, maskstr);
+ if (len <= 0 || len >= ailen - (*addrinfo - cp)) {
+ sudo_warnx(U_("internal error, %s overflow"), __func__);
+ goto done;
+ }
+ cp += 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 ailen, i, len, n, sock, num_interfaces = 0;
+ size_t 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));
+ strncpy(ifr_tmp->ifr_name, ifr->ifr_name, sizeof(ifr_tmp->ifr_name) - 1);
+ 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));
+ strncpy(ifr_tmp->ifr_name, ifr->ifr_name, sizeof(ifr_tmp->ifr_name) - 1);
+ 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 - (*addrinfo - cp),
+ "%s%s/%s", cp == *addrinfo ? "" : " ", addrstr, maskstr);
+ if (len <= 0 || len >= ailen - (*addrinfo - cp)) {
+ sudo_warnx(U_("internal error, %s overflow"), __func__);
+ goto done;
+ }
+ cp += 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..fd0e2f3
--- /dev/null
+++ b/src/openbsd.c
@@ -0,0 +1,45 @@
+/*
+ * 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 <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.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..d9bbe06
--- /dev/null
+++ b/src/parse_args.c
@@ -0,0 +1,774 @@
+/*
+ * Copyright (c) 1993-1996, 1998-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.
+ *
+ * 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 <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <ctype.h>
+#include <grp.h>
+#include <pwd.h>
+
+#include <sudo_usage.h>
+#include "sudo.h"
+#include "sudo_lbuf.h"
+
+#ifdef HAVE_GETOPT_LONG
+# include <getopt.h>
+# else
+# include "compat/getopt.h"
+#endif /* HAVE_GETOPT_LONG */
+
+int tgetpass_flags;
+
+/*
+ * Local functions.
+ */
+static void help(void) __attribute__((__noreturn__));
+static void usage_excl(int);
+
+/*
+ * 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 NUM_SETTINGS 23
+ { 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_SHELL)
+
+/* 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:bC:c:D:Eeg:Hh::iKklnPp: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' },
+ { "close-from", required_argument, NULL, 'C' },
+ { "login-class", required_argument, NULL, 'c' },
+ { "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' },
+ { "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(1);
+ }
+ 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 *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 *runas_user = NULL;
+ const char *runas_group = NULL;
+ const char *progname;
+ int proglen;
+ debug_decl(parse_args, SUDO_DEBUG_ARGS)
+
+ /* Is someone trying something funny? */
+ if (argc <= 0)
+ usage(1);
+
+ /* 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";
+ }
+
+ /* 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':
+ if (*optarg == '\0')
+ usage(1);
+ sudo_settings[ARG_BSDAUTH_TYPE].value = optarg;
+ break;
+#endif
+ case 'b':
+ SET(flags, MODE_BACKGROUND);
+ break;
+ case 'C':
+ if (strtonum(optarg, 3, INT_MAX, NULL) == 0) {
+ sudo_warnx(U_("the argument to -C must be a number greater than or equal to 3"));
+ usage(1);
+ }
+ sudo_settings[ARG_CLOSEFROM].value = optarg;
+ break;
+#ifdef HAVE_LOGIN_CAP_H
+ case 'c':
+ if (*optarg == '\0')
+ usage(1);
+ sudo_settings[ARG_LOGIN_CLASS].value = optarg;
+ break;
+#endif
+ case 'D':
+ /* Ignored for backwards compatibility. */
+ 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(1);
+ mode = MODE_EDIT;
+ sudo_settings[ARG_SUDOEDIT].value = "true";
+ valid_flags = MODE_NONINTERACTIVE;
+ break;
+ case 'g':
+ if (*optarg == '\0')
+ usage(1);
+ runas_group = optarg;
+ sudo_settings[ARG_RUNAS_GROUP].value = optarg;
+ break;
+ case 'H':
+ sudo_settings[ARG_SET_HOME].value = "true";
+ 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 && !is_envar &&
+ argv[optind] != NULL && argv[optind][0] != '-') {
+ sudo_settings[ARG_REMOTE_HOST].value = argv[optind++];
+ continue;
+ }
+ if (mode && mode != MODE_HELP) {
+ if (strcmp(progname, "sudoedit") != 0)
+ usage_excl(1);
+ }
+ mode = MODE_HELP;
+ valid_flags = 0;
+ break;
+ }
+ /* FALLTHROUGH */
+ case OPT_HOSTNAME:
+ if (*optarg == '\0')
+ usage(1);
+ 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(1);
+ mode = MODE_KILL;
+ valid_flags = 0;
+ break;
+ case 'l':
+ if (mode) {
+ if (mode == MODE_LIST)
+ SET(flags, MODE_LONG_LIST);
+ else
+ usage_excl(1);
+ }
+ mode = MODE_LIST;
+ valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST;
+ break;
+ case 'n':
+ SET(flags, MODE_NONINTERACTIVE);
+ sudo_settings[ARG_NONINTERACTIVE].value = "true";
+ break;
+ case 'P':
+ sudo_settings[ARG_PRESERVE_GROUPS].value = "true";
+ break;
+ case 'p':
+ /* An empty prompt is allowed. */
+ sudo_settings[ARG_PROMPT].value = optarg;
+ break;
+#ifdef HAVE_SELINUX
+ case 'r':
+ if (*optarg == '\0')
+ usage(1);
+ sudo_settings[ARG_SELINUX_ROLE].value = optarg;
+ break;
+ case 't':
+ if (*optarg == '\0')
+ usage(1);
+ sudo_settings[ARG_SELINUX_TYPE].value = optarg;
+ break;
+#endif
+ case 'T':
+ /* Plugin determines whether empty timeout is allowed. */
+ 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':
+ if (*optarg == '\0')
+ usage(1);
+ list_user = optarg;
+ break;
+ case 'u':
+ if (*optarg == '\0')
+ usage(1);
+ runas_user = optarg;
+ sudo_settings[ARG_RUNAS_USER].value = optarg;
+ break;
+ case 'v':
+ if (mode && mode != MODE_VALIDATE)
+ usage_excl(1);
+ mode = MODE_VALIDATE;
+ valid_flags = MODE_NONINTERACTIVE;
+ break;
+ case 'V':
+ if (mode && mode != MODE_VERSION)
+ usage_excl(1);
+ mode = MODE_VERSION;
+ valid_flags = 0;
+ break;
+ default:
+ usage(1);
+ }
+ } 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;
+
+ 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 && !(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(U_("you may not specify both the `-i' and `-s' options"));
+ usage(1);
+ }
+ if (ISSET(flags, MODE_PRESERVE_ENV)) {
+ sudo_warnx(U_("you may not specify both the `-i' and `-E' options"));
+ usage(1);
+ }
+ SET(flags, MODE_SHELL);
+ }
+ if ((flags & valid_flags) != flags)
+ usage(1);
+ if (mode == MODE_EDIT &&
+ (ISSET(flags, MODE_PRESERVE_ENV) || extra_env.env_len != 0)) {
+ if (ISSET(mode, MODE_PRESERVE_ENV))
+ sudo_warnx(U_("the `-E' option is not valid in edit mode"));
+ if (extra_env.env_len != 0)
+ sudo_warnx(U_("you may not specify environment variables in edit mode"));
+ usage(1);
+ }
+ if ((runas_user != NULL || runas_group != NULL) &&
+ !ISSET(mode, MODE_EDIT | MODE_RUN | MODE_CHECK | MODE_VALIDATE)) {
+ usage(1);
+ }
+ if (list_user != NULL && mode != MODE_LIST && mode != MODE_CHECK) {
+ sudo_warnx(U_("the `-U' option may only be used with the `-l' option"));
+ usage(1);
+ }
+ if (ISSET(tgetpass_flags, TGP_STDIN) && ISSET(tgetpass_flags, TGP_ASKPASS)) {
+ sudo_warnx(U_("the `-A' and `-S' options may not be used together"));
+ usage(1);
+ }
+ if ((argc == 0 && mode == MODE_EDIT) ||
+ (argc > 0 && !ISSET(mode, MODE_RUN | MODE_EDIT | MODE_CHECK)))
+ usage(1);
+ 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(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
+ char **av, *cmnd = NULL;
+ int ac = 1;
+
+ if (argc != 0) {
+ /* shell -c "command" */
+ char *src, *dst;
+ size_t cmnd_size = (size_t) (argv[argc - 1] - argv[0]) +
+ strlen(argv[argc - 1]) + 1;
+
+ cmnd = dst = reallocarray(NULL, cmnd_size, 2);
+ if (cmnd == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (!gc_add(GC_PTR, cmnd))
+ exit(1);
+
+ for (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(1);
+
+ 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;
+ }
+
+ if (mode == MODE_EDIT) {
+#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
+ /* Must have the command in argv[0]. */
+ argc++;
+ argv--;
+ argv[0] = "sudoedit";
+#else
+ sudo_fatalx(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);
+}
+
+/*
+ * Give usage message and exit.
+ * The actual usage strings are in sudo_usage.h for configure substitution.
+ */
+void
+usage(int fatal)
+{
+ 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, fatal ? usage_err : usage_out, 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);
+ if (fatal)
+ exit(1);
+}
+
+/*
+ * Tell which options are mutually exclusive and exit.
+ */
+static void
+usage_excl(int fatal)
+{
+ debug_decl(usage_excl, SUDO_DEBUG_ARGS)
+
+ sudo_warnx(U_("Only one of the -e, -h, -i, -K, -l, -s, -v or -V options may be specified"));
+ usage(fatal);
+}
+
+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);
+
+ usage(0);
+
+ sudo_lbuf_append(&lbuf, _("\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, " -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, " -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"));
+#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(0);
+}
diff --git a/src/preload.c b/src/preload.c
new file mode 100644
index 0000000..e9f83ee
--- /dev/null
+++ b/src/preload.c
@@ -0,0 +1,74 @@
+/*
+ * 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>
+
+#include <sys/types.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;
+
+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},
+ { (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..1de01ad
--- /dev/null
+++ b/src/preserve_fds.c
@@ -0,0 +1,222 @@
+/*
+ * 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 <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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..e83f420
--- /dev/null
+++ b/src/regress/noexec/check_noexec.c
@@ -0,0 +1,203 @@
+/*
+ * 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 */
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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"
+
+__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(1);
+ }
+
+ /* 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..1609cd0
--- /dev/null
+++ b/src/regress/ttyname/check_ttyname.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2013-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 <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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"
+
+__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);
+
+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. */
+ if (tty_libc != NULL && tty_sudo != NULL) {
+ if (strcmp(tty_libc, tty_sudo) == 0)
+ ret = 0;
+ } else if (tty_libc == NULL && tty_sudo == NULL) {
+ ret = 0;
+ }
+
+ 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..ed36d89
--- /dev/null
+++ b/src/selinux.c
@@ -0,0 +1,460 @@
+/*
+ * 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/types.h>
+#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_context;
+ security_context_t new_tty_context;
+ 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(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(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 zero on success, non-zero otherwise
+ */
+int
+selinux_restore_tty(void)
+{
+ int retval = 0;
+ security_context_t chk_tty_context = NULL;
+ debug_decl(selinux_restore_tty, SUDO_DEBUG_SELINUX)
+
+ if (se_state.ttyfd == -1 || se_state.new_tty_context == NULL)
+ goto skip_relabel;
+
+ /* Verify that the tty still has the context set by sudo. */
+ if ((retval = fgetfilecon(se_state.ttyfd, &chk_tty_context)) < 0) {
+ sudo_warn(U_("unable to fgetfilecon %s"), se_state.ttyn);
+ goto skip_relabel;
+ }
+
+ if ((retval = strcmp(chk_tty_context, se_state.new_tty_context))) {
+ sudo_warnx(U_("%s changed labels"), se_state.ttyn);
+ goto skip_relabel;
+ }
+
+ if ((retval = fsetfilecon(se_state.ttyfd, se_state.tty_context)) < 0)
+ sudo_warn(U_("unable to restore context for %s"), se_state.ttyn);
+
+skip_relabel:
+ if (se_state.ttyfd != -1) {
+ close(se_state.ttyfd);
+ se_state.ttyfd = -1;
+ }
+ if (chk_tty_context != NULL) {
+ freecon(chk_tty_context);
+ chk_tty_context = NULL;
+ }
+ debug_return_int(retval);
+}
+
+/*
+ * 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_context and new_tty_context 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)
+ debug_return_int(0);
+
+ /* 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) < 0) {
+ sudo_warn(U_("unable to get current tty context, not relabeling tty"));
+ goto bad;
+ }
+
+ if (tty_con) {
+ security_class_t tclass = string_to_security_class("chr_file");
+ if (tclass == 0) {
+ sudo_warn(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) < 0) {
+ sudo_warn(U_("unable to get new tty context, not relabeling tty"));
+ goto bad;
+ }
+ }
+
+ if (new_tty_con != NULL) {
+ if (fsetfilecon(se_state.ttyfd, new_tty_con) < 0) {
+ sudo_warn(U_("unable to set new tty context"));
+ goto bad;
+ }
+ }
+
+ if (ptyfd != -1) {
+ /* 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;
+ }
+ if (dup2(se_state.ttyfd, ptyfd) == -1) {
+ sudo_warn("dup2");
+ 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;
+ se_state.tty_context = tty_con;
+ se_state.new_tty_context = 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(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) {
+ sudo_warnx(U_("you must specify a role for type %s"), type);
+ errno = EINVAL;
+ goto bad;
+ }
+ if (!type) {
+ 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 extract and modify
+ * its components easily.
+ */
+ context = context_new(old_context);
+
+ /*
+ * 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) < 0) {
+ sudo_warnx(U_("%s is not a valid context"), new_context);
+ errno = EINVAL;
+ goto bad;
+ }
+
+#ifdef DEBUG
+ sudo_warnx("Your new context is %s", new_context);
+#endif
+
+ context_free(context);
+ debug_return_ptr(new_context);
+
+bad:
+ free(typebuf);
+ context_free(context);
+ freecon(new_context);
+ debug_return_ptr(NULL);
+}
+
+/*
+ * Set the exec and tty contexts in preparation for fork/exec.
+ * Must run as root, before the uid change.
+ * 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)
+{
+ 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(U_("failed to get old_context"));
+ goto done;
+ }
+
+ se_state.enforcing = security_getenforce();
+ if (se_state.enforcing < 0) {
+ sudo_warn(U_("unable to determine enforcing mode."));
+ goto done;
+ }
+
+#ifdef DEBUG
+ sudo_warnx("your old context was %s", se_state.old_context);
+#endif
+ se_state.new_context = get_exec_context(se_state.old_context, role, type);
+ if (!se_state.new_context) {
+#ifdef HAVE_LINUX_AUDIT
+ audit_role_change(se_state.old_context, "?",
+ se_state.ttyn, 0);
+#endif
+ goto done;
+ }
+
+ if (relabel_tty(ttyn, ptyfd) < 0) {
+ sudo_warn(U_("unable to set tty context to %s"), se_state.new_context);
+ goto done;
+ }
+
+#ifdef DEBUG
+ if (se_state.ttyfd != -1) {
+ sudo_warnx("your old tty context is %s", se_state.tty_context);
+ sudo_warnx("your new tty context is %s", se_state.new_tty_context);
+ }
+#endif
+
+#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);
+}
+
+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;
+ }
+
+ 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;
+ }
+
+#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;
+ }
+#endif /* HAVE_SETKEYCREATECON */
+
+ /*
+ * 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;
+ 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..873748e
--- /dev/null
+++ b/src/sesh.c
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2008, 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>
+#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_gettext.h" /* must be included before sudo_compat.h */
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_exec.h"
+#include "sudo_plugin.h"
+#include "sudo_util.h"
+
+__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(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 = 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);
+}
+
+static int
+sesh_sudoedit(int argc, char *argv[])
+{
+ int i, oflags_dst, post, ret = SESH_ERR_FAILURE;
+ int fd_src = -1, fd_dst = -1, follow = 0;
+ ssize_t nread, nwritten;
+ struct stat sb;
+ struct timespec times[2];
+ char buf[BUFSIZ];
+ debug_decl(sesh_sudoedit, SUDO_DEBUG_EDIT)
+
+ /* Check for -h flag (don't follow links). */
+ if (strcmp(argv[2], "-h") == 0) {
+ argv++;
+ argc--;
+ follow = O_NOFOLLOW;
+ }
+
+ 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 beggining 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);
+
+ /*
+ * Use O_EXCL if we are not in the post editing stage
+ * so that it's ensured that the temporary files are
+ * created by us and that we are not opening any symlinks.
+ */
+ oflags_dst = O_WRONLY|O_TRUNC|O_CREAT|(post ? follow : O_EXCL);
+ for (i = 0; i < argc - 1; i += 2) {
+ const 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, that's OK, we'll create an empty
+ * destination file.
+ */
+ if ((fd_src = open(path_src, O_RDONLY|follow, S_IRUSR|S_IWUSR)) < 0) {
+ if (errno != ENOENT) {
+ sudo_warn("%s", path_src);
+ if (post) {
+ ret = SESH_ERR_SOME_FILES;
+ goto nocleanup;
+ } else
+ goto cleanup_0;
+ }
+ }
+
+ if ((fd_dst = open(path_dst, oflags_dst, post ?
+ (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) : (S_IRUSR|S_IWUSR))) < 0) {
+ /* error - cleanup */
+ sudo_warn("%s", path_dst);
+ if (post) {
+ ret = SESH_ERR_SOME_FILES;
+ goto nocleanup;
+ } else
+ goto cleanup_0;
+ }
+
+ if (fd_src != -1) {
+ while ((nread = read(fd_src, buf, sizeof(buf))) > 0) {
+ if ((nwritten = write(fd_dst, buf, nread)) != nread) {
+ sudo_warn("%s", path_src);
+ if (post) {
+ ret = SESH_ERR_SOME_FILES;
+ goto nocleanup;
+ } else
+ goto cleanup_0;
+ }
+ }
+ }
+
+ if (!post) {
+ if (fd_src == -1 || fstat(fd_src, &sb) != 0)
+ memset(&sb, 0, sizeof(sb));
+ /* Make mtime on temp file match src. */
+ 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;
+ if (fd_src != -1) {
+ close(fd_src);
+ fd_src = -1;
+ }
+ }
+
+ ret = SESH_SUCCESS;
+ if (post) {
+ /* Remove temporary files (post=1) */
+ for (i = 0; i < argc - 1; i += 2)
+ unlink(argv[i]);
+ }
+nocleanup:
+ if (fd_dst != -1)
+ close(fd_dst);
+ if (fd_src != -1)
+ close(fd_src);
+ return(ret);
+cleanup_0:
+ /* Remove temporary files (post=0) */
+ for (i = 0; i < argc - 1; i += 2)
+ unlink(argv[i + 1]);
+ if (fd_dst != -1)
+ close(fd_dst);
+ if (fd_src != -1)
+ close(fd_src);
+ return(SESH_ERR_NO_FILES);
+}
diff --git a/src/signal.c b/src/signal.c
new file mode 100644
index 0000000..5c60225
--- /dev/null
+++ b/src/signal.c
@@ -0,0 +1,193 @@
+/*
+ * 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 <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.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..8e2fcee
--- /dev/null
+++ b/src/solaris.c
@@ -0,0 +1,122 @@
+/*
+ * 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>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#ifdef HAVE_PROJECT_H
+# include <project.h>
+# include <sys/task.h>
+#endif
+#include <errno.h>
+#include <pwd.h>
+
+#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(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(U_("the invoking task is final"));
+ break;
+ default:
+ sudo_warnx(U_("could not join project \"%s\""), proj.pj_name);
+ }
+ 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;
+ 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);
+ }
+ }
+ } 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..c81f946
--- /dev/null
+++ b/src/sudo.c
@@ -0,0 +1,1450 @@
+/*
+ * 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
+ */
+
+#ifdef __TANDEM
+# include <floss.h>
+#endif
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/resource.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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 */
+#ifdef __linux__
+# include <sys/prctl.h>
+#endif
+
+#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 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;
+
+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 int policy_open(struct plugin_container *plugin,
+ struct sudo_settings *settings,
+ char * const user_info[], char * const user_env[]);
+static void policy_close(struct plugin_container *plugin, int exit_status,
+ int error);
+static int policy_show_version(struct plugin_container *plugin, int verbose);
+static int policy_check(struct plugin_container *plugin, int argc,
+ char * const argv[], char *env_add[], char **command_info[],
+ char **argv_out[], char **user_env_out[]);
+static int policy_list(struct plugin_container *plugin, int argc,
+ char * const argv[], int verbose, const char *list_user);
+static int policy_validate(struct plugin_container *plugin);
+static void policy_invalidate(struct plugin_container *plugin, int remove);
+
+/* I/O log plugin convenience functions. */
+static int iolog_open(struct plugin_container *plugin,
+ 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(struct plugin_container *plugin, int exit_status,
+ int error);
+static int iolog_show_version(struct plugin_container *plugin, int verbose);
+static void iolog_unlink(struct plugin_container *plugin);
+static void free_plugin_container(struct plugin_container *plugin, bool ioplugin);
+
+__dso_public int main(int argc, char *argv[], char *envp[]);
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+ int nargc, ok, status = 0;
+ char **nargv, **env_add;
+ char **user_info, **command_info, **argv_out, **user_env_out;
+ struct sudo_settings *settings;
+ struct plugin_container *plugin, *next;
+ sigset_t mask;
+ debug_decl_vars(main, SUDO_DEBUG_MAIN)
+
+ /* 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(false);
+
+ /* Parse command line arguments. */
+ sudo_mode = parse_args(argc, argv, &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.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(&policy_plugin, &io_plugins))
+ sudo_fatalx(U_("fatal error, unable to load plugins"));
+
+ /* Open policy plugin. */
+ ok = policy_open(&policy_plugin, settings, user_info, envp);
+ if (ok != 1) {
+ if (ok == -2)
+ usage(1);
+ else
+ sudo_fatalx(U_("unable to initialize policy plugin"));
+ }
+
+ switch (sudo_mode & MODE_MASK) {
+ case MODE_VERSION:
+ policy_show_version(&policy_plugin, !user_details.uid);
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ ok = iolog_open(plugin, settings, user_info, NULL,
+ nargc, nargv, envp);
+ if (ok != -1)
+ iolog_show_version(plugin, !user_details.uid);
+ }
+ break;
+ case MODE_VALIDATE:
+ case MODE_VALIDATE|MODE_INVALIDATE:
+ ok = policy_validate(&policy_plugin);
+ exit(ok != 1);
+ case MODE_KILL:
+ case MODE_INVALIDATE:
+ policy_invalidate(&policy_plugin, sudo_mode == MODE_KILL);
+ exit(0);
+ break;
+ case MODE_CHECK:
+ case MODE_CHECK|MODE_INVALIDATE:
+ case MODE_LIST:
+ case MODE_LIST|MODE_INVALIDATE:
+ ok = policy_list(&policy_plugin, nargc, nargv,
+ ISSET(sudo_mode, MODE_LONG_LIST), list_user);
+ exit(ok != 1);
+ case MODE_EDIT:
+ case MODE_RUN:
+ ok = policy_check(&policy_plugin, nargc, nargv, env_add,
+ &command_info, &argv_out, &user_env_out);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "policy plugin returns %d", ok);
+ if (ok != 1) {
+ if (ok == -2)
+ usage(1);
+ exit(EXIT_FAILURE); /* plugin printed error message */
+ }
+ /* 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(U_("plugin did not return a command to execute"));
+ /* Open I/O plugins once policy plugin succeeds. */
+ TAILQ_FOREACH_SAFE(plugin, &io_plugins, entries, next) {
+ ok = iolog_open(plugin, settings, user_info,
+ command_info, nargc, nargv, user_env_out);
+ switch (ok) {
+ case 1:
+ break;
+ case 0:
+ /* I/O plugin asked to be disabled, remove and free. */
+ iolog_unlink(plugin);
+ break;
+ case -2:
+ usage(1);
+ break;
+ default:
+ sudo_fatalx(U_("error initializing I/O plugin %s"),
+ plugin->name);
+ }
+ }
+ /* Setup command details and run command/edit. */
+ command_info_to_details(command_info, &command_details);
+ command_details.argv = argv_out;
+ command_details.envp = user_env_out;
+ 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);
+ /* Become full root (not just setuid) so user cannot kill us. */
+ if (setuid(ROOT_UID) == -1)
+ sudo_warn("setuid(%d)", ROOT_UID);
+ 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(false);
+
+ 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[])
+{
+ initprogname(argc > 0 ? argv[0] : "sudo");
+#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(struct user_details *ud)
+{
+ 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.
+ */
+ ud->ngroups = sudo_conf_max_groups();
+ if (ud->ngroups > 0) {
+ ud->groups = reallocarray(NULL, ud->ngroups, sizeof(GETGROUPS_T));
+ if (ud->groups != NULL) {
+ /* No error on insufficient space if user specified max_groups. */
+ (void)sudo_getgrouplist2(ud->username, ud->gid, &ud->groups,
+ &ud->ngroups);
+ ret = 0;
+ }
+ } else {
+ ud->groups = NULL;
+ ret = sudo_getgrouplist2(ud->username, ud->gid, &ud->groups,
+ &ud->ngroups);
+ }
+ if (ret == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: %s: unable to get groups via sudo_getgrouplist2()",
+ __func__, ud->username);
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: %s: got %d groups via sudo_getgrouplist2()",
+ __func__, ud->username, ud->ngroups);
+ }
+ debug_return_int(ret);
+}
+
+static char *
+get_user_groups(struct user_details *ud)
+{
+ char *cp, *gid_list = NULL;
+ size_t glsize;
+ int i, len, group_source;
+ debug_decl(get_user_groups, SUDO_DEBUG_UTIL)
+
+ ud->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;
+
+ if ((ud->ngroups = getgroups(0, NULL)) > 0) {
+ /* Use groups from kernel if not too many or source is static. */
+ if (ud->ngroups < maxgroups || group_source == GROUP_SOURCE_STATIC) {
+ ud->groups = reallocarray(NULL, ud->ngroups, sizeof(GETGROUPS_T));
+ if (ud->groups == NULL)
+ goto done;
+ if (getgroups(ud->ngroups, ud->groups) < 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: %s: unable to get %d groups via getgroups()",
+ __func__, ud->username, ud->ngroups);
+ free(ud->groups);
+ ud->groups = NULL;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: %s: got %d groups via getgroups()",
+ __func__, ud->username, ud->ngroups);
+ }
+ }
+ }
+ }
+ if (ud->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(ud) == -1)
+ goto done;
+ }
+
+ /*
+ * Format group list as a comma-separated string of gids.
+ */
+ glsize = sizeof("groups=") - 1 + (ud->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 < ud->ngroups; i++) {
+ len = snprintf(cp, glsize - (cp - gid_list), "%s%u",
+ i ? "," : "", (unsigned int)ud->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];
+ unsigned int i = 0;
+ mode_t mask;
+ struct passwd *pw;
+ int fd;
+ 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, 32, sizeof(char *));
+ if (user_info == NULL)
+ goto oom;
+
+ ud->pid = getpid();
+ ud->ppid = getppid();
+ ud->pgid = getpgid(0);
+ ud->tcpgid = -1;
+ fd = open(_PATH_TTY, O_RDWR);
+ if (fd != -1) {
+ ud->tcpgid = tcgetpgrp(fd);
+ close(fd);
+ }
+ ud->sid = getsid(0);
+
+ ud->uid = getuid();
+ ud->euid = geteuid();
+ ud->gid = getgid();
+ ud->egid = getegid();
+
+#ifdef HAVE_SETAUTHDB
+ aix_setauthdb(IDtouser(ud->uid), NULL);
+#endif
+ pw = getpwuid(ud->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->uid) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "euid=%u", (unsigned int)ud->euid) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "gid=%u", (unsigned int)ud->gid) == -1)
+ goto oom;
+ if (asprintf(&user_info[++i], "egid=%u", (unsigned int)ud->egid) == -1)
+ goto oom;
+
+ if ((cp = get_user_groups(ud)) == 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(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;
+
+ 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->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("closefrom=", info[i], sizeof("closefrom=") - 1) == 0) {
+ cp = info[i] + sizeof("closefrom=") - 1;
+ details->closefrom = 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 = 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 = 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, NULL, NULL, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ details->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, NULL, NULL, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ details->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, NULL, NULL, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ details->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->ngroups = sudo_parse_gids(cp, NULL, &details->groups);
+ /* sudo_parse_gids() will print a warning on error. */
+ if (details->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, NULL, NULL, &errstr);
+ if (errstr != NULL)
+ sudo_fatalx(U_("%s: %s"), info[i], U_(errstr));
+ details->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 */
+ 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 = 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("use_pty=", CD_USE_PTY)
+ SET_STRING("utmp_user=", utmp_user)
+ break;
+ }
+ }
+
+ if (!ISSET(details->flags, CD_SET_EUID))
+ details->euid = details->uid;
+ if (!ISSET(details->flags, CD_SET_EGID))
+ details->egid = details->gid;
+
+#ifdef HAVE_SETAUTHDB
+ aix_setauthdb(IDtouser(details->euid), NULL);
+#endif
+ details->pw = getpwuid(details->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 || (size_t)len >= sizeof(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;
+}
+
+/*
+ * Disable core dumps to avoid dropping a core with user password in it.
+ * Called with restore set to true before executing the command.
+ * Not all operating systems disable core dumps for setuid processes.
+ */
+void
+disable_coredump(bool restore)
+{
+ struct rlimit rl;
+ static struct rlimit corelimit;
+#ifdef __linux__
+ static int dumpflag;
+#endif
+ debug_decl(disable_coredump, SUDO_DEBUG_UTIL)
+
+ if (restore) {
+ (void) setrlimit(RLIMIT_CORE, &corelimit);
+#ifdef __linux__
+ (void) prctl(PR_SET_DUMPABLE, dumpflag, 0, 0, 0);
+#endif /* __linux__ */
+ debug_return;
+ }
+
+ (void) getrlimit(RLIMIT_CORE, &corelimit);
+ rl.rlim_cur = 0;
+ rl.rlim_max = 0;
+ (void) setrlimit(RLIMIT_CORE, &rl);
+#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)
+ dumpflag = 0;
+ (void) prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
+#endif /* __linux__ */
+
+ 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->ngroups >= 0) {
+ if (sudo_setgroups(details->ngroups, details->groups) < 0) {
+ sudo_warn(U_("unable to set supplementary group IDs"));
+ goto done;
+ }
+ }
+ }
+#ifdef HAVE_SETEUID
+ if (ISSET(details->flags, CD_SET_EGID) && setegid(details->egid)) {
+ sudo_warn(U_("unable to set effective gid to runas gid %u"),
+ (unsigned int)details->egid);
+ goto done;
+ }
+#endif
+ if (ISSET(details->flags, CD_SET_GID) && setgid(details->gid)) {
+ sudo_warn(U_("unable to set gid to runas gid %u"),
+ (unsigned int)details->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 plugin_container *plugin;
+ 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. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "calling policy close with errno %d", cstat.val);
+ policy_close(&policy_plugin, 0, cstat.val);
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "calling I/O close with errno %d", cstat.val);
+ iolog_close(plugin, 0, cstat.val);
+ }
+ break;
+ case CMD_WSTATUS:
+ /* Command ran, exited or was killed. */
+ status = cstat.val;
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_SUDOEDIT_COPY))
+ break;
+#endif
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "calling policy close with wait status %d", status);
+ policy_close(&policy_plugin, status, 0);
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "calling I/O close with wait status %d", status);
+ iolog_close(plugin, status, 0);
+ }
+ break;
+ default:
+ 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 int
+policy_open(struct plugin_container *plugin, struct sudo_settings *settings,
+ char * const user_info[], char * const user_env[])
+{
+ char **plugin_settings;
+ int ret;
+ debug_decl(policy_open, 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);
+ }
+
+ /*
+ * Backwards compatibility for older API versions
+ */
+ sudo_debug_set_active_instance(SUDO_DEBUG_INSTANCE_INITIALIZER);
+ switch (plugin->u.generic->version) {
+ case SUDO_API_MKVERSION(1, 0):
+ case SUDO_API_MKVERSION(1, 1):
+ ret = plugin->u.policy_1_0->open(plugin->u.io_1_0->version,
+ sudo_conversation_1_7, sudo_conversation_printf, plugin_settings,
+ user_info, user_env);
+ break;
+ default:
+ ret = plugin->u.policy->open(SUDO_API_VERSION, sudo_conversation,
+ sudo_conversation_printf, plugin_settings, user_info, user_env,
+ plugin->options);
+ }
+
+ /* 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
+policy_close(struct plugin_container *plugin, int exit_status, int error_code)
+{
+ debug_decl(policy_close, SUDO_DEBUG_PCOMM)
+ if (plugin->u.policy->close != NULL) {
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ plugin->u.policy->close(exit_status, error_code);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ } else if (error_code) {
+ errno = error_code;
+ sudo_warn(U_("unable to execute %s"), command_details.command);
+ }
+ debug_return;
+}
+
+static int
+policy_show_version(struct plugin_container *plugin, int verbose)
+{
+ int ret;
+ debug_decl(policy_show_version, SUDO_DEBUG_PCOMM)
+
+ if (plugin->u.policy->show_version == NULL)
+ debug_return_int(true);
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ ret = plugin->u.policy->show_version(verbose);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ debug_return_int(ret);
+}
+
+static int
+policy_check(struct plugin_container *plugin, int argc, char * const argv[],
+ char *env_add[], char **command_info[], char **argv_out[],
+ char **user_env_out[])
+{
+ int ret;
+ debug_decl(policy_check, SUDO_DEBUG_PCOMM)
+
+ if (plugin->u.policy->check_policy == NULL) {
+ sudo_fatalx(U_("policy plugin %s is missing the `check_policy' method"),
+ plugin->name);
+ }
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ ret = plugin->u.policy->check_policy(argc, argv, env_add, command_info,
+ argv_out, user_env_out);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ debug_return_int(ret);
+}
+
+static int
+policy_list(struct plugin_container *plugin, int argc, char * const argv[],
+ int verbose, const char *list_user)
+{
+ int ret;
+ debug_decl(policy_list, SUDO_DEBUG_PCOMM)
+
+ if (plugin->u.policy->list == NULL) {
+ sudo_warnx(U_("policy plugin %s does not support listing privileges"),
+ plugin->name);
+ debug_return_int(false);
+ }
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ ret = plugin->u.policy->list(argc, argv, verbose, list_user);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ debug_return_int(ret);
+}
+
+static int
+policy_validate(struct plugin_container *plugin)
+{
+ int ret;
+ debug_decl(policy_validate, SUDO_DEBUG_PCOMM)
+
+ if (plugin->u.policy->validate == NULL) {
+ sudo_warnx(U_("policy plugin %s does not support the -v option"),
+ plugin->name);
+ debug_return_int(false);
+ }
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ ret = plugin->u.policy->validate();
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ debug_return_int(ret);
+}
+
+static void
+policy_invalidate(struct plugin_container *plugin, int remove)
+{
+ debug_decl(policy_invalidate, SUDO_DEBUG_PCOMM)
+ if (plugin->u.policy->invalidate == NULL) {
+ sudo_fatalx(U_("policy plugin %s does not support the -k/-K options"),
+ plugin->name);
+ }
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ plugin->u.policy->invalidate(remove);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ debug_return;
+}
+
+int
+policy_init_session(struct command_details *details)
+{
+ 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;
+ }
+
+ if (policy_plugin.u.policy->init_session) {
+ /*
+ * Backwards 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);
+ }
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+done:
+ debug_return_int(ret);
+}
+
+static int
+iolog_open(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[])
+{
+ char **plugin_settings;
+ int ret;
+ debug_decl(iolog_open, 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);
+ }
+
+ /*
+ * Backwards 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);
+ }
+
+ /* 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_close(struct plugin_container *plugin, int exit_status, int error_code)
+{
+ debug_decl(iolog_close, SUDO_DEBUG_PCOMM)
+
+ if (plugin->u.io->close != NULL) {
+ 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 int
+iolog_show_version(struct plugin_container *plugin, int verbose)
+{
+ int ret;
+ debug_decl(iolog_show_version, SUDO_DEBUG_PCOMM)
+
+ if (plugin->u.io->show_version == NULL)
+ debug_return_int(true);
+
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ ret = plugin->u.io->show_version(verbose);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ debug_return_int(ret);
+}
+
+/*
+ * Remove the specified I/O logging plugin from the io_plugins list.
+ * Deregisters any hooks before unlinking, then frees the container.
+ */
+static void
+iolog_unlink(struct plugin_container *plugin)
+{
+ debug_decl(iolog_unlink, SUDO_DEBUG_PCOMM)
+
+ /* Deregister hooks, if any. */
+ if (plugin->u.io->version >= SUDO_API_MKVERSION(1, 2)) {
+ if (plugin->u.io->deregister_hooks != NULL) {
+ sudo_debug_set_active_instance(plugin->debug_instance);
+ plugin->u.io->deregister_hooks(SUDO_HOOK_VERSION,
+ deregister_hook);
+ sudo_debug_set_active_instance(sudo_debug_instance);
+ }
+ }
+ /* Remove from io_plugins list and free. */
+ TAILQ_REMOVE(&io_plugins, plugin, entries);
+ free_plugin_container(plugin, true);
+
+ debug_return;
+}
+
+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..1d8e672
--- /dev/null
+++ b/src/sudo.h
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 1993-1996, 1998-2005, 2007-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.
+ *
+ * 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>
+#include <pathnames.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+
+#include "sudo_gettext.h" /* must be included before sudo_compat.h */
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+#ifdef HAVE_PRIV_SET
+# include <priv.h>
+#endif
+
+#ifdef __TANDEM
+# define ROOT_UID 65535
+#else
+# define ROOT_UID 0
+#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 */
+
+/* name/value pairs for command line settings. */
+struct sudo_settings {
+ const char *name;
+ const char *value;
+};
+
+struct user_details {
+ pid_t pid;
+ pid_t ppid;
+ pid_t pgid;
+ pid_t tcpgid;
+ pid_t sid;
+ uid_t uid;
+ uid_t euid;
+ uid_t gid;
+ uid_t egid;
+ const char *username;
+ const char *cwd;
+ const char *tty;
+ const char *host;
+ const char *shell;
+ GETGROUPS_T *groups;
+ int ngroups;
+ int ts_rows;
+ int ts_cols;
+};
+
+#define CD_SET_UID 0x00001
+#define CD_SET_EUID 0x00002
+#define CD_SET_GID 0x00004
+#define CD_SET_EGID 0x00008
+#define CD_PRESERVE_GROUPS 0x00010
+#define CD_NOEXEC 0x00020
+#define CD_SET_PRIORITY 0x00040
+#define CD_SET_UMASK 0x00080
+#define CD_SET_TIMEOUT 0x00100
+#define CD_SUDOEDIT 0x00200
+#define CD_BACKGROUND 0x00400
+#define CD_RBAC_ENABLED 0x00800
+#define CD_USE_PTY 0x01000
+#define CD_SET_UTMP 0x02000
+#define CD_EXEC_BG 0x04000
+#define CD_SUDOEDIT_COPY 0x08000
+#define CD_SUDOEDIT_FOLLOW 0x10000
+#define CD_SUDOEDIT_CHECKDIR 0x20000
+#define CD_SET_GROUPS 0x40000
+#define CD_LOGIN_SHELL 0x80000
+
+struct preserved_fd {
+ TAILQ_ENTRY(preserved_fd) entries;
+ int lowfd;
+ int highfd;
+ int flags;
+};
+TAILQ_HEAD(preserved_fd_list, preserved_fd);
+
+struct command_details {
+ uid_t uid;
+ uid_t euid;
+ gid_t gid;
+ gid_t egid;
+ mode_t umask;
+ int priority;
+ int timeout;
+ int ngroups;
+ int closefrom;
+ int flags;
+ int execfd;
+ struct preserved_fd_list preserved_fds;
+ struct passwd *pw;
+ GETGROUPS_T *groups;
+ const char *command;
+ const char *cwd;
+ const char *login_class;
+ const char *chroot;
+ const char *selinux_role;
+ const char *selinux_type;
+ const char *utmp_user;
+ char **argv;
+ char **envp;
+#ifdef HAVE_PRIV_SET
+ priv_set_t *privs;
+ priv_set_t *limitprivs;
+#endif
+};
+
+/* 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 *nargc, char ***nargv,
+ struct sudo_settings **settingsp, char ***env_addp);
+extern int tgetpass_flags;
+
+/* get_pty.c */
+bool get_pty(int *master, int *slave, 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);
+void disable_coredump(bool restore);
+bool set_user_groups(struct command_details *details);
+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(int);
+
+/* 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);
+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);
+
+#endif /* SUDO_SUDO_H */
diff --git a/src/sudo_edit.c b/src/sudo_edit.c
new file mode 100644
index 0000000..44b4fb3
--- /dev/null
+++ b/src/sudo_edit.c
@@ -0,0 +1,1101 @@
+/*
+ * Copyright (c) 2004-2008, 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>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <time.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "sudo.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))];
+
+static 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 ? (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;
+}
+
+#ifdef HAVE_FACCESSAT
+/*
+ * Returns true if the open directory fd is writable by the user.
+ */
+static int
+dir_is_writable(int dfd, struct user_details *ud, struct command_details *cd)
+{
+ debug_decl(dir_is_writable, SUDO_DEBUG_EDIT)
+ int rc;
+
+ /* Change uid/gid/groups to invoking user, usually needs root perms. */
+ if (cd->euid != ROOT_UID) {
+ if (seteuid(ROOT_UID) != 0)
+ sudo_fatal("seteuid(ROOT_UID)");
+ }
+ switch_user(ud->uid, ud->gid, ud->ngroups, ud->groups);
+
+ /* Access checks are done using the euid/egid and group vector. */
+ rc = faccessat(dfd, ".", W_OK, AT_EACCESS);
+
+ /* Change uid/gid/groups back to target user, may need root perms. */
+ if (ud->uid != ROOT_UID) {
+ if (seteuid(ROOT_UID) != 0)
+ sudo_fatal("seteuid(ROOT_UID)");
+ }
+ switch_user(cd->euid, cd->egid, cd->ngroups, cd->groups);
+
+ if (rc == 0)
+ debug_return_int(true);
+ if (errno == EACCES)
+ debug_return_int(false);
+ debug_return_int(-1);
+}
+#else
+static bool
+group_matches(gid_t target, gid_t gid, int ngroups, GETGROUPS_T *groups)
+{
+ int i;
+ debug_decl(group_matches, SUDO_DEBUG_EDIT)
+
+ if (target == gid) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "user gid %u matches directory gid %u", (unsigned int)gid,
+ (unsigned int)target);
+ debug_return_bool(true);
+ }
+ for (i = 0; i < ngroups; i++) {
+ if (target == groups[i]) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "user gid %u matches directory gid %u", (unsigned int)gid,
+ (unsigned int)target);
+ debug_return_bool(true);
+ }
+ }
+ debug_return_bool(false);
+}
+
+/*
+ * Returns true if the open directory fd is writable by the user.
+ */
+static int
+dir_is_writable(int dfd, struct user_details *ud, struct command_details *cd)
+{
+ 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 == ud->uid) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "user uid %u matches directory uid %u", (unsigned int)ud->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, ud->gid, ud->ngroups, ud->groups)) {
+ 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 */
+
+/*
+ * Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp
+ * Returns true on success, else false;
+ */
+static bool
+set_tmpdir(struct command_details *command_details)
+{
+ const char *tdir = NULL;
+ const char *tmpdirs[] = {
+ _PATH_VARTMP,
+#ifdef _PATH_USRTMP
+ _PATH_USRTMP,
+#endif
+ _PATH_TMP
+ };
+ unsigned int i;
+ size_t len;
+ int dfd;
+ debug_decl(set_tmpdir, SUDO_DEBUG_EDIT)
+
+ for (i = 0; tdir == NULL && i < nitems(tmpdirs); i++) {
+ if ((dfd = open(tmpdirs[i], O_RDONLY)) != -1) {
+ if (dir_is_writable(dfd, &user_details, command_details) == true)
+ tdir = tmpdirs[i];
+ close(dfd);
+ }
+ }
+ if (tdir == NULL)
+ sudo_fatalx(U_("no writable temporary directory found"));
+
+ 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_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ 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);
+}
+
+#ifndef HAVE_OPENAT
+static int
+sudo_openat(int dfd, const char *path, int flags, mode_t mode)
+{
+ int fd, odfd;
+ debug_decl(sudo_openat, SUDO_DEBUG_EDIT)
+
+ if (dfd == AT_FDCWD)
+ debug_return_int(open(path, flags, mode));
+
+ /* Save cwd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ debug_return_int(-1);
+
+ if (fchdir(dfd) == -1) {
+ close(odfd);
+ debug_return_int(-1);
+ }
+
+ fd = open(path, flags, mode);
+
+ /* Restore cwd */
+ if (fchdir(odfd) == -1)
+ sudo_fatal(U_("unable to restore current working directory"));
+ close(odfd);
+
+ debug_return_int(fd);
+}
+#define openat sudo_openat
+#endif /* HAVE_OPENAT */
+
+#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(U_("unable to restore current working directory"));
+ close(odfd);
+ }
+
+ debug_return_int(fd);
+}
+#endif /* O_NOFOLLOW */
+
+/*
+ * 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
+
+static int
+sudo_edit_open_nonwritable(char *path, int oflags, mode_t mode,
+ struct command_details *command_details)
+{
+ 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_details, command_details);
+ if (is_writable == -1) {
+ close(dfd);
+ debug_return_int(-1);
+ }
+
+ while (path[0] == '/')
+ 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
+static int
+sudo_edit_open(char *path, int oflags, mode_t mode,
+ struct command_details *command_details)
+{
+ const int sflags = command_details ? command_details->flags : 0;
+ 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_details.uid != ROOT_UID) {
+ fd = sudo_edit_open_nonwritable(path, oflags|O_NONBLOCK, mode,
+ command_details);
+ } 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
+static int
+sudo_edit_open(char *path, int oflags, mode_t mode,
+ struct command_details *command_details)
+{
+ const int sflags = command_details ? command_details->flags : 0;
+ 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_details.uid != ROOT_UID) {
+ fd = sudo_edit_open_nonwritable(path, oflags|O_NONBLOCK, mode,
+ command_details);
+ } 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 */
+
+/*
+ * 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;
+ char buf[BUFSIZ];
+ ssize_t nwritten, nread;
+ 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->euid, command_details->egid,
+ command_details->ngroups, command_details->groups);
+ ofd = sudo_edit_open(files[i], O_RDONLY,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details);
+ if (ofd != -1 || errno == ENOENT) {
+ if (ofd == -1) {
+ /* New file, verify parent dir exists unless in cwd. */
+ char *slash = strrchr(files[i], '/');
+ if (slash != NULL && slash != files[i]) {
+ int serrno = errno;
+ *slash = '\0';
+ if (stat(files[i], &sb) == 0 && S_ISDIR(sb.st_mode)) {
+ memset(&sb, 0, sizeof(sb));
+ rc = 0;
+ }
+ *slash = '/';
+ errno = serrno;
+ } else {
+ memset(&sb, 0, sizeof(sb));
+ rc = 0;
+ }
+ } else {
+ rc = fstat(ofd, &sb);
+ }
+ }
+ switch_user(ROOT_UID, user_details.egid,
+ user_details.ngroups, user_details.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;
+ mtim_get(&sb, tf[j].omtim);
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "seteuid(%u)", (unsigned int)user_details.uid);
+ if (seteuid(user_details.uid) != 0)
+ sudo_fatal("seteuid(%u)", (unsigned int)user_details.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) {
+ while ((nread = read(ofd, buf, sizeof(buf))) > 0) {
+ if ((nwritten = write(tfd, buf, nread)) != nread) {
+ if (nwritten == -1)
+ sudo_warn("%s", tf[j].tfile);
+ else
+ sudo_warnx(U_("%s: short write"), tf[j].tfile);
+ break;
+ }
+ }
+ if (nread != 0) {
+ if (nread < 0)
+ sudo_warn("%s", files[i]);
+ 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, rc, errors = 0;
+ char buf[BUFSIZ];
+ ssize_t nwritten, nread;
+ struct timespec ts;
+ struct stat sb;
+ debug_decl(sudo_edit_copy_tfiles, SUDO_DEBUG_EDIT)
+
+ /* Copy contents of temp files to real ones. */
+ for (i = 0; i < nfiles; i++) {
+ rc = -1;
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "seteuid(%u)", (unsigned int)user_details.uid);
+ if (seteuid(user_details.uid) != 0)
+ sudo_fatal("seteuid(%u)", (unsigned int)user_details.uid);
+ tfd = sudo_edit_open(tf[i].tfile, O_RDONLY,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, NULL);
+ if (tfd != -1)
+ rc = fstat(tfd, &sb);
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "seteuid(%u)", ROOT_UID);
+ if (seteuid(ROOT_UID) != 0)
+ sudo_fatal("seteuid(ROOT_UID)");
+ if (rc || !S_ISREG(sb.st_mode)) {
+ if (rc)
+ sudo_warn("%s", tf[i].tfile);
+ else
+ sudo_warnx(U_("%s: not a regular file"), tf[i].tfile);
+ 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->euid, command_details->egid,
+ command_details->ngroups, command_details->groups);
+ ofd = sudo_edit_open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, command_details);
+ switch_user(ROOT_UID, user_details.egid,
+ user_details.ngroups, user_details.groups);
+ if (ofd == -1) {
+ sudo_warn(U_("unable to write to %s"), tf[i].ofile);
+ sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
+ close(tfd);
+ errors++;
+ continue;
+ }
+ while ((nread = read(tfd, buf, sizeof(buf))) > 0) {
+ if ((nwritten = write(ofd, buf, nread)) != nread) {
+ if (nwritten == -1)
+ sudo_warn("%s", tf[i].ofile);
+ else
+ sudo_warnx(U_("%s: short write"), tf[i].ofile);
+ break;
+ }
+ }
+ if (nread == 0) {
+ /* success, got EOF */
+ unlink(tf[i].tfile);
+ } else if (nread < 0) {
+ sudo_warn(U_("unable to read temporary file"));
+ sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
+ } else {
+ sudo_warn(U_("unable to write to %s"), tf[i].ofile);
+ sudo_warnx(U_("contents of edit session left in %s"), tf[i].tfile);
+ }
+ close(ofd);
+ close(tfd);
+ }
+ debug_return_int(errors);
+}
+
+#ifdef HAVE_SELINUX
+static int
+selinux_edit_create_tfiles(struct command_details *command_details,
+ struct tempfile *tf, char *files[], int nfiles)
+{
+ char **sesh_args, **sesh_ap;
+ int i, rc, sesh_nargs;
+ struct stat sb;
+ struct command_details saved_command_details;
+ debug_decl(selinux_edit_create_tfiles, SUDO_DEBUG_EDIT)
+
+ /* Prepare selinux stuff (setexeccon) */
+ if (selinux_setup(command_details->selinux_role,
+ command_details->selinux_type, NULL, -1) != 0)
+ debug_return_int(-1);
+
+ if (nfiles < 1)
+ debug_return_int(0);
+
+ /* Construct common args for sesh */
+ memcpy(&saved_command_details, command_details, sizeof(struct command_details));
+ command_details->command = _PATH_SUDO_SESH;
+ command_details->flags |= CD_SUDOEDIT_COPY;
+
+ sesh_nargs = 4 + (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"));
+ debug_return_int(-1);
+ }
+ *sesh_ap++ = "sesh";
+ *sesh_ap++ = "-e";
+ if (!ISSET(command_details->flags, CD_SUDOEDIT_FOLLOW))
+ *sesh_ap++ = "-h";
+ *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);
+ free(sesh_args);
+ debug_return_int(-1);
+ }
+ /* 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> */
+ command_details->argv = sesh_args;
+ rc = run_command(command_details);
+ switch (rc) {
+ case SESH_SUCCESS:
+ break;
+ case SESH_ERR_BAD_PATHS:
+ sudo_fatalx(U_("sesh: internal error: odd number of paths"));
+ case SESH_ERR_NO_FILES:
+ sudo_fatalx(U_("sesh: unable to create temporary files"));
+ default:
+ sudo_fatalx(U_("sesh: unknown error %d"), rc);
+ }
+
+ /* Restore saved command_details. */
+ command_details->command = saved_command_details.command;
+ command_details->flags = saved_command_details.flags;
+ command_details->argv = saved_command_details.argv;
+
+ /* Chown to user's UID so they can edit the temporary files. */
+ for (i = 0; i < nfiles; i++) {
+ if (chown(tf[i].tfile, user_details.uid, user_details.gid) != 0) {
+ sudo_warn("unable to chown(%s) to %d:%d for editing",
+ tf[i].tfile, user_details.uid, user_details.gid);
+ }
+ }
+
+ /* Contents of tf will be freed by caller. */
+ free(sesh_args);
+
+ return (nfiles);
+}
+
+static int
+selinux_edit_copy_tfiles(struct command_details *command_details,
+ struct tempfile *tf, int nfiles, struct timespec *times)
+{
+ char **sesh_args, **sesh_ap;
+ int i, rc, sesh_nargs, ret = 1;
+ struct command_details saved_command_details;
+ struct timespec ts;
+ struct stat sb;
+ debug_decl(selinux_edit_copy_tfiles, SUDO_DEBUG_EDIT)
+
+ /* Prepare selinux stuff (setexeccon) */
+ if (selinux_setup(command_details->selinux_role,
+ command_details->selinux_type, NULL, -1) != 0)
+ debug_return_int(1);
+
+ if (nfiles < 1)
+ debug_return_int(0);
+
+ /* Construct common args for sesh */
+ memcpy(&saved_command_details, command_details, sizeof(struct command_details));
+ command_details->command = _PATH_SUDO_SESH;
+ command_details->flags |= CD_SUDOEDIT_COPY;
+
+ sesh_nargs = 3 + (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"));
+ debug_return_int(-1);
+ }
+ *sesh_ap++ = "sesh";
+ *sesh_ap++ = "-e";
+ *sesh_ap++ = "1";
+
+ /* Construct args for sesh -e 1 */
+ for (i = 0; i < nfiles; i++) {
+ if (stat(tf[i].tfile, &sb) == 0) {
+ 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 (chown(tf[i].tfile, command_details->uid, command_details->gid) != 0) {
+ sudo_warn("unable to chown(%s) back to %d:%d", tf[i].tfile,
+ command_details->uid, command_details->gid);
+ }
+ }
+ *sesh_ap = NULL;
+
+ if (sesh_ap - sesh_args > 3) {
+ /* Run sesh -e 1 <t1> <o1> ... <tn> <on> */
+ command_details->argv = sesh_args;
+ rc = run_command(command_details);
+ switch (rc) {
+ case SESH_SUCCESS:
+ ret = 0;
+ break;
+ case SESH_ERR_NO_FILES:
+ sudo_warnx(U_("unable to copy temporary files back to their original location"));
+ sudo_warnx(U_("contents of edit session left in %s"), edit_tmpdir);
+ break;
+ case SESH_ERR_SOME_FILES:
+ sudo_warnx(U_("unable to copy some of the temporary files back to their original location"));
+ sudo_warnx(U_("contents of edit session left in %s"), edit_tmpdir);
+ break;
+ default:
+ sudo_warnx(U_("sesh: unknown error %d"), rc);
+ break;
+ }
+ }
+ free(sesh_args);
+
+ /* Restore saved command_details. */
+ command_details->command = saved_command_details.command;
+ command_details->flags = saved_command_details.flags;
+ command_details->argv = saved_command_details.argv;
+
+ 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, rc;
+ int editor_argc = 0, nfiles = 0;
+ struct timespec times[2];
+ struct tempfile *tf = NULL;
+ debug_decl(sudo_edit, SUDO_DEBUG_EDIT)
+
+ if (!set_tmpdir(command_details))
+ goto cleanup;
+
+ /*
+ * 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;
+ }
+
+ /*
+ * 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(U_("plugin error: missing file list for sudoedit"));
+ goto cleanup;
+ }
+
+ /* 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,
+ * keeping track of the time spent in the editor.
+ */
+ if (sudo_gettime_real(&times[0]) == -1) {
+ sudo_warn(U_("unable to read the clock"));
+ goto cleanup;
+ }
+ memcpy(&saved_command_details, command_details, sizeof(struct command_details));
+ command_details->uid = user_details.uid;
+ command_details->euid = user_details.uid;
+ command_details->gid = user_details.gid;
+ command_details->egid = user_details.gid;
+ command_details->ngroups = user_details.ngroups;
+ command_details->groups = user_details.groups;
+ command_details->argv = nargv;
+ rc = run_command(command_details);
+ if (sudo_gettime_real(&times[1]) == -1) {
+ sudo_warn(U_("unable to read the clock"));
+ goto cleanup;
+ }
+
+ /* Restore saved command_details. */
+ command_details->uid = saved_command_details.uid;
+ command_details->euid = saved_command_details.euid;
+ command_details->gid = saved_command_details.gid;
+ command_details->egid = saved_command_details.egid;
+ command_details->ngroups = saved_command_details.ngroups;
+ command_details->groups = saved_command_details.groups;
+ 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)
+ goto cleanup;
+
+ for (i = 0; i < nfiles; i++)
+ free(tf[i].tfile);
+ free(tf);
+ free(nargv);
+ debug_return_int(rc);
+
+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);
+ 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_exec.h b/src/sudo_exec.h
new file mode 100644
index 0000000..c8c7941
--- /dev/null
+++ b/src/sudo_exec.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2010-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.
+ */
+
+#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_MASTER 3
+#define SFD_SLAVE 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_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;
+
+/* 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..c103d5f
--- /dev/null
+++ b/src/sudo_noexec.c
@@ -0,0 +1,261 @@
+/*
+ * 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
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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) dummy_ ## fn
+# define INTERPOSE(fn) \
+ __attribute__((__used__)) static const interpose_t interpose_ ## fn \
+ __attribute__((__section__("__DATA,__interpose"))) = \
+ { (void *)dummy_ ## fn, (void *)fn };
+#else
+# define FN_NAME(fn) fn
+# define INTERPOSE(fn)
+#endif
+
+/*
+ * Dummy versions of the exec(3) family of syscalls. It is not enough to
+ * just dummy out 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 DUMMY_BODY \
+{ \
+ errno = EACCES; \
+ return -1; \
+}
+
+#define DUMMY1(fn, t1) \
+__dso_public int \
+FN_NAME(fn)(t1 a1) \
+DUMMY_BODY \
+INTERPOSE(fn)
+
+#define DUMMY2(fn, t1, t2) \
+__dso_public int \
+FN_NAME(fn)(t1 a1, t2 a2) \
+DUMMY_BODY \
+INTERPOSE(fn)
+
+#define DUMMY3(fn, t1, t2, t3) \
+__dso_public int \
+FN_NAME(fn)(t1 a1, t2 a2, t3 a3) \
+DUMMY_BODY \
+INTERPOSE(fn)
+
+#define DUMMY6(fn, t1, t2, t3, t4, t5, t6) \
+__dso_public int \
+FN_NAME(fn)(t1 a1, t2 a2, t3 a3, t4 a4, t5 a5, t6 a6) \
+DUMMY_BODY \
+INTERPOSE(fn)
+
+#define DUMMY_VA(fn, t1, t2) \
+__dso_public int \
+FN_NAME(fn)(t1 a1, t2 a2, ...) \
+DUMMY_BODY \
+INTERPOSE(fn)
+
+/*
+ * Standard exec(3) family of functions.
+ */
+DUMMY_VA(execl, const char *, const char *)
+DUMMY_VA(execle, const char *, const char *)
+DUMMY_VA(execlp, const char *, const char *)
+DUMMY2(execv, const char *, char * const *)
+DUMMY2(execvp, const char *, char * const *)
+DUMMY3(execve, const char *, char * const *, char * const *)
+
+/*
+ * Non-standard exec(3) functions and corresponding private versions.
+ */
+#ifdef HAVE_EXECVP
+DUMMY3(execvP, const char *, const char *, char * const *)
+#endif
+#ifdef HAVE_EXECVPE
+DUMMY3(execvpe, const char *, char * const *, char * const *)
+#endif
+#ifdef HAVE_EXECT
+DUMMY3(exect, const char *, char * const *, char * const *)
+#endif
+
+/*
+ * Not all systems support fexecve(2), posix_spawn(2) and posix_spawnp(2).
+ */
+#ifdef HAVE_FEXECVE
+DUMMY3(fexecve, int , char * const *, char * const *)
+#endif
+#ifdef HAVE_POSIX_SPAWN
+DUMMY6(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
+DUMMY6(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.
+ */
+DUMMY1(system, const char *)
+
+__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);
+
+__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..c5fce34
--- /dev/null
+++ b/src/sudo_plugin_int.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2010-2014 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;
+ } u;
+};
+TAILQ_HEAD(plugin_container_list, plugin_container);
+
+extern struct plugin_container policy_plugin;
+extern struct plugin_container_list io_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(struct plugin_container *policy_plugin,
+ struct plugin_container_list *io_plugins);
+
+#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..95ae893
--- /dev/null
+++ b/src/sudo_usage.h.in
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2007-2010, 2013, 2015, 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.
+ */
+
+#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] @LOGINCAP_USAGE@[-g group] [-h host] [-p prompt] [-T timeout] [-u user] [VAR=value] [-i|-s] [<command>]"
+#define SUDO_USAGE5 " -e [-AknS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C num] @LOGINCAP_USAGE@[-g group] [-h host] [-p prompt] [-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..03e95c8
--- /dev/null
+++ b/src/tcsetpgrp_nobg.c
@@ -0,0 +1,78 @@
+/*
+ * 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 <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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..76b0c8b
--- /dev/null
+++ b/src/tgetpass.c
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 1996, 1998-2005, 2007-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
+ */
+
+#ifdef __TANDEM
+# include <floss.h>
+#endif
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <pwd.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 bool tty_present(void);
+static void tgetpass_handler(int);
+static char *getln(int, char *, size_t, int, enum tgetpass_errval *);
+static char *sudo_askpass(const char *, const char *);
+
+static int
+suspend(int signo, 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(signo, callback->closure) == -1)
+ ret = -1;
+ }
+ kill(getpid(), signo);
+ if (callback != NULL && callback->on_resume != NULL) {
+ if (callback->on_resume(signo, 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(U_("timed out reading password"));
+ break;
+ case TGP_ERRVAL_NOPASSWORD:
+ sudo_warnx(U_("no password was provided"));
+ break;
+ case TGP_ERRVAL_READERROR:
+ sudo_warn(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, neednl = 0, need_restart;
+ 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();
+ }
+
+ /* If no tty present and we need to disable echo, try askpass. */
+ if (!ISSET(flags, TGP_STDIN|TGP_ECHO|TGP_ASKPASS|TGP_NOECHO_TRY) &&
+ !tty_present()) {
+ if (askpass == NULL || getenv_unhooked("DISPLAY") == NULL) {
+ sudo_warnx(U_("no tty present and no askpass program specified"));
+ 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(U_("no askpass program specified, try setting SUDO_ASKPASS"));
+ debug_return_str_masked(sudo_askpass(askpass, prompt));
+ }
+
+restart:
+ for (i = 0; i < NSIG; i++)
+ signo[i] = 0;
+ pass = NULL;
+ save_errno = 0;
+ need_restart = 0;
+ /* Open /dev/tty for reading/writing if possible else use stdin/stderr. */
+ if (ISSET(flags, TGP_STDIN) ||
+ (input = output = open(_PATH_TTY, O_RDWR)) == -1) {
+ input = STDIN_FILENO;
+ output = STDERR_FILENO;
+ }
+
+ /*
+ * 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 = 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 (input != STDIN_FILENO)
+ (void) close(input);
+ 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 (prompt) {
+ if (write(output, prompt, strlen(prompt)) == -1)
+ goto restore;
+ }
+
+ if (timeout > 0)
+ alarm(timeout);
+ pass = getln(input, buf, sizeof(buf), ISSET(flags, TGP_MASK), &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 (input != STDIN_FILENO)
+ (void) close(input);
+
+ /*
+ * 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 = 1;
+ 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 (pipe(pfd) == -1)
+ sudo_fatal(U_("unable to create pipe"));
+
+ child = sudo_debug_fork();
+ if (child == -1)
+ sudo_fatal(U_("unable to fork"));
+
+ if (child == 0) {
+ /* child, point stdout to output side of the pipe and exec askpass */
+ if (dup2(pfd[1], STDOUT_FILENO) == -1) {
+ sudo_warn("dup2");
+ _exit(255);
+ }
+ if (setuid(ROOT_UID) == -1)
+ sudo_warn("setuid(%d)", ROOT_UID);
+ if (setgid(user_details.gid)) {
+ sudo_warn(U_("unable to set gid to %u"), (unsigned int)user_details.gid);
+ _exit(255);
+ }
+ if (setuid(user_details.uid)) {
+ sudo_warn(U_("unable to set uid to %u"), (unsigned int)user_details.uid);
+ _exit(255);
+ }
+ closefrom(STDERR_FILENO + 1);
+ 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, int 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); /* sanity */
+ }
+
+ 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;
+ }
+ left = bufsiz;
+ continue;
+ } else if (c == sudo_term_erase) {
+ if (cp > buf) {
+ if (write(fd, "\b \b", 3) == -1)
+ break;
+ --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;
+}
+
+static bool
+tty_present(void)
+{
+#if defined(HAVE_KINFO_PROC2_NETBSD) || defined(HAVE_KINFO_PROC_OPENBSD) || defined(HAVE_KINFO_PROC_FREEBSD) || defined(HAVE_KINFO_PROC_44BSD) || defined(HAVE_STRUCT_PSINFO_PR_TTYDEV) || defined(HAVE_PSTAT_GETPROC) || defined(__linux__)
+ debug_decl(tty_present, SUDO_DEBUG_UTIL)
+ debug_return_bool(user_details.tty != NULL);
+#else
+ int fd;
+ debug_decl(tty_present, SUDO_DEBUG_UTIL)
+
+ if ((fd = open(_PATH_TTY, O_RDWR)) != -1)
+ close(fd);
+ debug_return_bool(fd != -1);
+#endif
+}
diff --git a/src/ttyname.c b/src/ttyname.c
new file mode 100644
index 0000000..580bafd
--- /dev/null
+++ b/src/ttyname.c
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2012-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>
+
+/* 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>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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/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;
+ do {
+ 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);
+ } while (rc == -1 && errno == ENOMEM);
+ 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. */
+ 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 = 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 pstat;
+ char *ret = NULL;
+ int rc, serrno = errno;
+ debug_decl(get_process_ttyname, SUDO_DEBUG_UTIL)
+
+ /*
+ * Determine the tty from psdev in struct pst_status.
+ * We may get EOVERFLOW if the whole thing doesn't fit but that is OK.
+ */
+ rc = pstat_getproc(&pstat, sizeof(pstat), (size_t)0, (int)getpid());
+ if (rc != -1 || errno == EOVERFLOW) {
+ if (pstat.pst_term.psd_major != -1 && pstat.pst_term.psd_minor != -1) {
+ errno = serrno;
+ ret = sudo_ttyname_dev(makedev(pstat.pst_term.psd_major,
+ pstat.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..b371bbf
--- /dev/null
+++ b/src/utmp.c
@@ -0,0 +1,389 @@
+/*
+ * 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/types.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_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"
+
+/*
+ * 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_GETUTSID) || defined(HAVE_GETUTXID) || defined(HAVE_GETUTID)
+/*
+ * 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_GETUTSID || HAVE_GETUTXID || HAVE_GETUTID */
+
+/*
+ * 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);
+}
+# else
+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(U_("unable to save stdin"));
+ if (dup2(ttyfd, STDIN_FILENO) == -1)
+ sudo_fatal(U_("unable to dup2 stdin"));
+ slot = ttyslot();
+ if (dup2(sfd, STDIN_FILENO) == -1)
+ sudo_fatal(U_("unable to restore stdin"));
+ close(sfd);
+
+ debug_return_int(slot);
+}
+# 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);
+# ifdef HAVE_FSEEKO
+ if (fseeko(fp, slot * (off_t)sizeof(utbuf), SEEK_SET) == 0) {
+# else
+ if (fseek(fp, slot * (long)sizeof(utbuf), SEEK_SET) == 0) {
+# endif
+ 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. */
+# ifdef HAVE_FSEEKO
+ if (fseeko(fp, (off_t)0 - (off_t)sizeof(utbuf), SEEK_CUR) == 0) {
+# else
+ if (fseek(fp, 0L - (long)sizeof(utbuf), SEEK_CUR) == 0) {
+# endif
+ if (fwrite(&utbuf, sizeof(utbuf), 1, fp) == 1)
+ ret = true;
+ }
+ break;
+ }
+ }
+ fclose(fp);
+
+ debug_return_bool(ret);
+}
+#endif /* HAVE_GETUTSID || HAVE_GETUTXID || HAVE_GETUTID */