summaryrefslogtreecommitdiffstats
path: root/lib/util
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/util/Makefile.in1709
-rw-r--r--lib/util/aix.c290
-rw-r--r--lib/util/arc4random.c206
-rw-r--r--lib/util/arc4random_buf.c70
-rw-r--r--lib/util/arc4random_uniform.c76
-rw-r--r--lib/util/basename.c52
-rw-r--r--lib/util/cfmakeraw.c58
-rw-r--r--lib/util/chacha_private.h222
-rw-r--r--lib/util/closefrom.c183
-rw-r--r--lib/util/digest.c165
-rw-r--r--lib/util/digest_gcrypt.c160
-rw-r--r--lib/util/digest_openssl.c152
-rw-r--r--lib/util/dup3.c74
-rw-r--r--lib/util/event.c862
-rw-r--r--lib/util/event_poll.c227
-rw-r--r--lib/util/event_select.c250
-rw-r--r--lib/util/explicit_bzero.c77
-rw-r--r--lib/util/fatal.c345
-rw-r--r--lib/util/fchmodat.c69
-rw-r--r--lib/util/fchownat.c67
-rw-r--r--lib/util/fnmatch.c499
-rw-r--r--lib/util/freezero.c38
-rw-r--r--lib/util/fstatat.c70
-rw-r--r--lib/util/getaddrinfo.c406
-rw-r--r--lib/util/getcwd.c244
-rw-r--r--lib/util/getdelim.c82
-rw-r--r--lib/util/getentropy.c642
-rw-r--r--lib/util/getgrouplist.c529
-rw-r--r--lib/util/gethostname.c59
-rw-r--r--lib/util/getopt_long.c624
-rw-r--r--lib/util/gettime.c224
-rw-r--r--lib/util/getusershell.c132
-rw-r--r--lib/util/gidlist.c87
-rw-r--r--lib/util/glob.c953
-rw-r--r--lib/util/gmtime_r.c49
-rw-r--r--lib/util/hexchar.c103
-rw-r--r--lib/util/inet_ntop.c229
-rw-r--r--lib/util/inet_pton.c252
-rw-r--r--lib/util/isblank.c37
-rw-r--r--lib/util/json.c423
-rw-r--r--lib/util/key_val.c56
-rw-r--r--lib/util/lbuf.c448
-rw-r--r--lib/util/localtime_r.c49
-rw-r--r--lib/util/locking.c143
-rw-r--r--lib/util/logfac.c90
-rw-r--r--lib/util/logpri.c85
-rw-r--r--lib/util/memrchr.c51
-rw-r--r--lib/util/mkdir_parents.c199
-rw-r--r--lib/util/mkdirat.c61
-rw-r--r--lib/util/mksiglist.c52
-rw-r--r--lib/util/mksigname.c52
-rw-r--r--lib/util/mktemp.c172
-rw-r--r--lib/util/mmap_alloc.c160
-rw-r--r--lib/util/multiarch.c104
-rw-r--r--lib/util/nanosleep.c65
-rw-r--r--lib/util/openat.c63
-rw-r--r--lib/util/parseln.c131
-rw-r--r--lib/util/pipe2.c64
-rw-r--r--lib/util/pread.c48
-rw-r--r--lib/util/progname.c100
-rw-r--r--lib/util/pw_dup.c99
-rw-r--r--lib/util/pwrite.c48
-rw-r--r--lib/util/rcstr.c104
-rw-r--r--lib/util/reallocarray.c57
-rw-r--r--lib/util/regex.c194
-rw-r--r--lib/util/regress/closefrom/closefrom_test.c121
-rw-r--r--lib/util/regress/corpus/seed/sudo_conf/sudo.conf.1116
-rw-r--r--lib/util/regress/corpus/seed/sudo_conf/sudo.conf.2116
-rw-r--r--lib/util/regress/corpus/seed/sudo_conf/sudo.conf.3126
-rw-r--r--lib/util/regress/fnmatch/fnm_test.c92
-rw-r--r--lib/util/regress/fnmatch/fnm_test.in6
-rw-r--r--lib/util/regress/fuzz/fuzz_sudo_conf.c149
-rw-r--r--lib/util/regress/fuzz/fuzz_sudo_conf.dict18
-rw-r--r--lib/util/regress/getdelim/getdelim_test.c185
-rw-r--r--lib/util/regress/getgrouplist/getgids.c91
-rw-r--r--lib/util/regress/getgrouplist/getgrouplist_test.c117
-rw-r--r--lib/util/regress/glob/files47
-rw-r--r--lib/util/regress/glob/globtest.c225
-rw-r--r--lib/util/regress/glob/globtest.in64
-rwxr-xr-xlib/util/regress/harness.in109
-rw-r--r--lib/util/regress/hexchar/hexchar_test.c81
-rw-r--r--lib/util/regress/json/json_test.c235
-rw-r--r--lib/util/regress/mktemp/mktemp_test.c206
-rw-r--r--lib/util/regress/multiarch/multiarch_test.c184
-rw-r--r--lib/util/regress/open_parent_dir/open_parent_dir_test.c166
-rw-r--r--lib/util/regress/parse_gids/parse_gids_test.c123
-rw-r--r--lib/util/regress/progname/progname_test.c67
-rw-r--r--lib/util/regress/regex/regex_test.c126
-rw-r--r--lib/util/regress/strsig/strsig_test.c319
-rw-r--r--lib/util/regress/strsplit/strsplit_test.c117
-rw-r--r--lib/util/regress/strtofoo/strtobool_test.c98
-rw-r--r--lib/util/regress/strtofoo/strtoid_test.c118
-rw-r--r--lib/util/regress/strtofoo/strtomode_test.c91
-rw-r--r--lib/util/regress/strtofoo/strtonum_test.c135
-rw-r--r--lib/util/regress/sudo_conf/conf_test.c125
-rw-r--r--lib/util/regress/sudo_conf/test1.in73
-rw-r--r--lib/util/regress/sudo_conf/test1.out.ok8
-rw-r--r--lib/util/regress/sudo_conf/test2.in0
-rw-r--r--lib/util/regress/sudo_conf/test2.out.ok4
-rw-r--r--lib/util/regress/sudo_conf/test3.in2
-rw-r--r--lib/util/regress/sudo_conf/test3.out.ok6
-rw-r--r--lib/util/regress/sudo_conf/test4.err.ok1
-rw-r--r--lib/util/regress/sudo_conf/test4.in1
-rw-r--r--lib/util/regress/sudo_conf/test4.out.ok4
-rw-r--r--lib/util/regress/sudo_conf/test5.err.ok1
-rw-r--r--lib/util/regress/sudo_conf/test5.in1
-rw-r--r--lib/util/regress/sudo_conf/test5.out.ok4
-rw-r--r--lib/util/regress/sudo_conf/test6.in1
-rw-r--r--lib/util/regress/sudo_conf/test6.out.ok4
-rw-r--r--lib/util/regress/sudo_conf/test7.in4
-rw-r--r--lib/util/regress/sudo_conf/test7.out.ok8
-rw-r--r--lib/util/regress/sudo_parseln/parseln_test.c64
-rw-r--r--lib/util/regress/sudo_parseln/test1.in72
-rw-r--r--lib/util/regress/sudo_parseln/test1.out.ok72
-rw-r--r--lib/util/regress/sudo_parseln/test2.in8
-rw-r--r--lib/util/regress/sudo_parseln/test2.out.ok3
-rw-r--r--lib/util/regress/sudo_parseln/test3.in1
-rw-r--r--lib/util/regress/sudo_parseln/test3.out.ok1
-rw-r--r--lib/util/regress/sudo_parseln/test4.in4
-rw-r--r--lib/util/regress/sudo_parseln/test4.out.ok2
-rw-r--r--lib/util/regress/sudo_parseln/test5.in1
-rw-r--r--lib/util/regress/sudo_parseln/test5.out.ok0
-rw-r--r--lib/util/regress/sudo_parseln/test6.in3
-rw-r--r--lib/util/regress/sudo_parseln/test6.out.ok2
-rw-r--r--lib/util/regress/tailq/hltq_test.c205
-rw-r--r--lib/util/regress/uuid/uuid_test.c105
-rw-r--r--lib/util/roundup.c48
-rw-r--r--lib/util/secure_path.c145
-rw-r--r--lib/util/setgroups.c52
-rw-r--r--lib/util/sha2.c515
-rw-r--r--lib/util/sig2str.c100
-rw-r--r--lib/util/siglist.in56
-rw-r--r--lib/util/snprintf.c1549
-rw-r--r--lib/util/str2sig.c174
-rw-r--r--lib/util/strlcat.c69
-rw-r--r--lib/util/strlcpy.c64
-rw-r--r--lib/util/strndup.c51
-rw-r--r--lib/util/strnlen.c45
-rw-r--r--lib/util/strsignal.c52
-rw-r--r--lib/util/strsplit.c73
-rw-r--r--lib/util/strtobool.c77
-rw-r--r--lib/util/strtoid.c108
-rw-r--r--lib/util/strtomode.c65
-rw-r--r--lib/util/strtonum.c193
-rw-r--r--lib/util/sudo_conf.c776
-rw-r--r--lib/util/sudo_debug.c1167
-rw-r--r--lib/util/sudo_dso.c432
-rw-r--r--lib/util/sys_siglist.h182
-rw-r--r--lib/util/sys_signame.h182
-rw-r--r--lib/util/term.c315
-rw-r--r--lib/util/timegm.c99
-rw-r--r--lib/util/ttyname_dev.c311
-rw-r--r--lib/util/ttysize.c71
-rw-r--r--lib/util/unlinkat.c60
-rw-r--r--lib/util/util.exp.in166
-rw-r--r--lib/util/utimens.c200
-rw-r--r--lib/util/uuid.c99
157 files changed, 25625 insertions, 0 deletions
diff --git a/lib/util/Makefile.in b/lib/util/Makefile.in
new file mode 100644
index 0000000..7898eec
--- /dev/null
+++ b/lib/util/Makefile.in
@@ -0,0 +1,1709 @@
+#
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2011-2023 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@
+abs_srcdir = @abs_srcdir@
+top_srcdir = @top_srcdir@
+abs_top_srcdir = @abs_top_srcdir@
+top_builddir = @top_builddir@
+abs_top_builddir = @abs_top_builddir@
+devdir = @devdir@
+scriptdir = $(top_srcdir)/scripts
+incdir = $(top_srcdir)/include
+cross_compiling = @CROSS_COMPILING@
+
+# Where to install things...
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+sbindir = @sbindir@
+sysconfdir = @sysconfdir@
+libexecdir = @libexecdir@
+datarootdir = @datarootdir@
+localstatedir = @localstatedir@
+
+# File extension, mode and map file to use for shared libraries/objects
+shlib_enable = @SHLIB_ENABLE@
+shlib_mode = @SHLIB_MODE@
+shlib_exp = ./util.exp
+shlib_map = util.map
+shlib_opt = util.opt
+
+# Compiler & tools to use
+CC = @CC@
+HOSTCC = @CC_FOR_BUILD@
+CPP = @CPP@
+HOSTCPP = @CPP_FOR_BUILD@
+LIBTOOL = @LIBTOOL@
+SHA1SUM = @SHA1SUM@
+EGREP = @EGREP@
+SED = @SED@
+AWK = @AWK@
+
+# Our install program supports extra flags...
+INSTALL = $(SHELL) $(scriptdir)/install-sh -c
+INSTALL_OWNER = -o $(install_uid) -g $(install_gid)
+INSTALL_BACKUP = @INSTALL_BACKUP@
+
+# C preprocessor defines
+CPPDEFS = -D_PATH_SUDO_CONF=\"$(sysconfdir)/sudo.conf\"
+
+# C preprocessor flags
+CPPFLAGS = -I$(incdir) -I$(top_builddir) -I. -I$(srcdir) $(CPPDEFS) \
+ @CPPFLAGS@ -DDEFAULT_TEXT_DOMAIN=\"@PACKAGE_NAME@\"
+HOSTCPPFLAGS = -I$(incdir) -I$(top_builddir) -I. -I$(srcdir) $(CPPDEFS) \
+ @CPPFLAGS_FOR_BUILD@ -DDEFAULT_TEXT_DOMAIN=\"@PACKAGE_NAME@\"
+
+# Usually -O and/or -g
+CFLAGS = @CFLAGS@
+HOSTCFLAGS = @CFLAGS_FOR_BUILD@
+
+# Flags to pass to the link stage
+LDFLAGS = @LDFLAGS@
+LT_LDFLAGS = @LIBUTIL_LDFLAGS@ @LT_LDFLAGS@ @LT_LDEXPORTS@
+
+# Flags to pass to libtool
+LTFLAGS = @LT_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
+HARDENING_CFLAGS = @HARDENING_CFLAGS@
+HARDENING_LDFLAGS = @HARDENING_LDFLAGS@
+
+# Libtool style shared library version
+SHLIB_VERSION = 0:0:0
+
+# cppcheck options, usually set in the top-level Makefile
+CPPCHECK_OPTS = -q --enable=warning,performance,portability --suppress=constStatement --suppress=compareBoolExpressionWithInt --error-exitcode=1 --inline-suppr -Dva_copy=va_copy -U__cplusplus -UQUAD_MAX -UQUAD_MIN -UUQUAD_MAX -U_POSIX_HOST_NAME_MAX -U_POSIX_PATH_MAX -U__NBBY -DNSIG=64
+
+# splint options, usually set in the top-level Makefile
+SPLINT_OPTS = -D__restrict= -checks
+
+# PVS-studio options
+PVS_CFG = $(top_srcdir)/PVS-Studio.cfg
+PVS_IGNORE = 'V707,V011,V002,V536'
+PVS_LOG_OPTS = -a 'GA:1,2' -e -t errorfile -d $(PVS_IGNORE)
+
+# Regression tests
+TEST_PROGS = conf_test getgids getgrouplist_test hexchar_test hltq_test \
+ json_test multiarch_test open_parent_dir_test parse_gids_test \
+ parseln_test progname_test regex_test strsplit_test \
+ strtobool_test strtoid_test strtomode_test strtonum_test \
+ uuid_test @COMPAT_TEST_PROGS@
+
+TEST_LIBS = @LIBS@
+TEST_LDFLAGS = @LDFLAGS@
+TEST_VERBOSE =
+HARNESS = $(SHELL) regress/harness $(TEST_VERBOSE)
+
+# Fuzzers
+LIBFUZZSTUB = $(top_builddir)/lib/fuzzstub/libsudo_fuzzstub.la
+LIB_FUZZING_ENGINE = @FUZZ_ENGINE@
+FUZZ_PROGS = fuzz_sudo_conf
+FUZZ_SEED_CORPUS = ${FUZZ_PROGS:=_seed_corpus.zip}
+FUZZ_LIBS = $(LIB_FUZZING_ENGINE) @LIBS@
+FUZZ_LDFLAGS = @LDFLAGS@
+FUZZ_MAX_LEN = 4096
+FUZZ_RUNS = 8192
+FUZZ_VERBOSE =
+
+# User and group ids the installed files should be "owned" by
+install_uid = 0
+install_gid = 0
+
+# Set to non-empty for development mode
+DEVEL = @DEVEL@
+
+#### End of system configuration section. ####
+
+SHELL = @SHELL@
+
+LTOBJS = basename.lo @DIGEST@ event.lo fatal.lo key_val.lo gethostname.lo \
+ gettime.lo getgrouplist.lo gidlist.lo hexchar.lo json.lo lbuf.lo \
+ locking.lo logfac.lo logpri.lo mkdir_parents.lo mmap_alloc.lo \
+ multiarch.lo parseln.lo progname.lo rcstr.lo regex.lo roundup.lo \
+ secure_path.lo setgroups.lo strsplit.lo strtobool.lo strtoid.lo \
+ strtomode.lo strtonum.lo sudo_conf.lo sudo_debug.lo sudo_dso.lo \
+ term.lo ttyname_dev.lo ttysize.lo uuid.lo \
+ @COMMON_OBJS@ @LTLIBOBJS@
+
+IOBJS = $(LTOBJS:.lo=.i)
+
+POBJS = $(IOBJS:.i=.plog)
+
+MKTEMP_TEST_OBJS = mktemp_test.lo mktemp.lo
+
+PARSELN_TEST_OBJS = parseln_test.lo parseln.lo
+
+PROGNAME_TEST_OBJS = progname_test.lo progname.lo basename.lo
+
+CLOSEFROM_TEST_OBJS = closefrom_test.lo closefrom.lo
+
+CONF_TEST_OBJS = conf_test.lo sudo_conf.lo
+
+FNM_TEST_OBJS = fnm_test.lo fnmatch.lo
+
+GLOBTEST_OBJS = globtest.lo glob.lo
+
+GETDELIM_TEST_OBJS = getdelim_test.lo getdelim.lo
+
+HLTQ_TEST_OBJS = hltq_test.lo
+
+HEXCHAR_TEST_OBJS = hexchar_test.lo hexchar.lo
+
+JSON_TEST_OBJS = json_test.lo json.lo
+
+MULTIARCH_TEST_OBJS = multiarch_test.lo multiarch.lo
+
+OPEN_PARENT_DIR_TEST_OBJS = open_parent_dir_test.lo mkdir_parents.lo
+
+REGEX_TEST_OBJS = regex_test.lo regex.lo
+
+STRTOBOOL_TEST_OBJS = strtobool_test.lo strtobool.lo
+
+STRTOMODE_TEST_OBJS = strtomode_test.lo strtomode.lo
+
+STRTOID_TEST_OBJS = strtoid_test.lo strtoid.lo strtonum.lo
+
+STRTONUM_TEST_OBJS = strtonum_test.lo strtonum.lo
+
+STRSPLIT_TEST_OBJS = strsplit_test.lo strsplit.lo
+
+PARSE_GIDS_TEST_OBJS = parse_gids_test.lo gidlist.lo
+
+GETGIDS_OBJS = getgids.lo getgrouplist.lo
+
+GETGROUPLIST_TEST_OBJS = getgrouplist_test.lo getgrouplist.lo
+
+STRSIG_TEST_OBJS = strsig_test.lo sig2str.lo str2sig.lo @SIGNAME@
+
+UUID_TEST_OBJS = uuid_test.lo uuid.lo
+
+FUZZ_SUDO_CONF_OBJS = fuzz_sudo_conf.lo
+
+FUZZ_SUDO_CONF_CORPUS = $(srcdir)/regress/corpus/seed/sudo_conf/sudo.conf.*
+
+all: libsudo_util.la
+
+depend: siglist.c signame.c
+ $(scriptdir)/mkdep.pl --srcdir=$(abs_top_srcdir) \
+ --builddir=$(abs_top_builddir) lib/util/Makefile.in
+ cd $(top_builddir) && ./config.status --file lib/util/Makefile
+
+harness: $(srcdir)/regress/harness.in
+ cd $(top_builddir) && ./config.status --file lib/util/regress/harness
+
+Makefile: $(srcdir)/Makefile.in
+ cd $(top_builddir) && ./config.status --file lib/util/Makefile
+
+.SUFFIXES: .c .h .i .lo .plog
+
+.c.lo:
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_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 $@
+
+$(shlib_map): $(shlib_exp)
+ @$(AWK) 'BEGIN { print "{\n\tglobal:" } { print "\t\t"$$0";" } END { print "\tlocal:\n\t\t*;\n};" }' $(shlib_exp) > $@
+
+$(shlib_opt): $(shlib_exp)
+ @$(SED) 's/^/+e /' $(shlib_exp) > $@
+
+libsudo_util.la: $(LTOBJS) @LT_LDDEP@
+ case "$(LT_LDFLAGS)" in \
+ *-no-install*) \
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(LDFLAGS) $(LT_LDFLAGS) $(LTOBJS) @LT_DEP_LIBS@ @LIBINTL@ @LIBMD@ @LIBCRYPTO@ @LIBPTHREAD@ @LIBDL@ @LIBRT@ @NET_LIBS@;; \
+ *) \
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(LDFLAGS) $(ASAN_LDFLAGS) $(HARDENING_LDFLAGS) $(LT_LDFLAGS) $(LTOBJS) -version-info $(SHLIB_VERSION) -rpath $(libexecdir)/sudo @LT_DEP_LIBS@ @LIBINTL@ @LIBMD@ @LIBCRYPTO@ @LIBPTHREAD@ @LIBDL@ @LIBRT@ @NET_LIBS@;; \
+ esac
+
+siglist.c: mksiglist
+ ./mksiglist > $@
+
+signame.c: mksigname
+ ./mksigname > $@
+
+mksiglist: $(srcdir)/mksiglist.c mksiglist.h $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(HOSTCC) $(HOSTCPPFLAGS) $(HOSTCFLAGS) $(srcdir)/mksiglist.c -o $@
+
+mksigname: $(srcdir)/mksigname.c mksigname.h $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(HOSTCC) $(HOSTCPPFLAGS) $(HOSTCFLAGS) $(srcdir)/mksigname.c -o $@
+
+$(srcdir)/sys_siglist.h: $(srcdir)/siglist.in
+ @if [ -n "$(DEVEL)" ]; then \
+ $(AWK) 'BEGIN {print "/* public domain */\n\n#include <config.h>\n#include <sys/types.h>\n#include <signal.h>\n#include \"sudo_compat.h\"\n\nint sudo_end_of_headers;\nstatic char *sudo_sys_siglist[NSIG];\n"} /^ [A-Z]/ {printf("#ifdef SIG%s\n if (sudo_sys_siglist[SIG%s] == NULL)\n\tsudo_sys_siglist[SIG%s] = \"%s\";\n#endif\n", $$1, $$1, $$1, substr($$0, 13))}' < $(srcdir)/siglist.in > $@; \
+ fi
+
+$(srcdir)/sys_signame.h: $(srcdir)/siglist.in
+ @if [ -n "$(DEVEL)" ]; then \
+ $(AWK) 'BEGIN {print "/* public domain */\n\n#include <config.h>\n#include <sys/types.h>\n#include <signal.h>\n#include \"sudo_compat.h\"\n\nint sudo_end_of_headers;\nstatic char *sudo_sys_signame[NSIG];\n"} /^ [A-Z]/ {printf("#ifdef SIG%s\n if (sudo_sys_signame[SIG%s] == NULL)\n\tsudo_sys_signame[SIG%s] = \"%s\";\n#endif\n", $$1, $$1, $$1, $$1)}' < $(srcdir)/siglist.in > $@; \
+ fi
+
+mksiglist.h: $(srcdir)/sys_siglist.h
+ $(CPP) $(CPPFLAGS) $(srcdir)/sys_siglist.h | $(SED) -e '1,/^int sudo_end_of_headers;/d' -e '/^#/d' > mksiglist.h
+
+mksigname.h: $(srcdir)/sys_signame.h
+ $(CPP) $(CPPFLAGS) $(srcdir)/sys_signame.h | $(SED) -e '1,/^int sudo_end_of_headers;/d' -e '/^#/d' > mksigname.h
+
+closefrom_test: $(CLOSEFROM_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CLOSEFROM_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+conf_test: $(CONF_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CONF_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+fnm_test: $(FNM_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(FNM_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+globtest: $(GLOBTEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(GLOBTEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+getdelim_test: $(GETDELIM_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(GETDELIM_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+hltq_test: $(HLTQ_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(HLTQ_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+hexchar_test: $(HEXCHAR_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(HEXCHAR_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+json_test: $(JSON_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(JSON_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+mktemp_test: $(MKTEMP_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(MKTEMP_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+multiarch_test: $(MULTIARCH_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(MULTIARCH_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+open_parent_dir_test: $(OPEN_PARENT_DIR_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(OPEN_PARENT_DIR_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+parseln_test: $(PARSELN_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(PARSELN_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+progname_test: $(PROGNAME_TEST_OBJS)
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(PROGNAME_TEST_OBJS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+parse_gids_test: $(PARSE_GIDS_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(PARSE_GIDS_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+getgids: $(GETGIDS_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(GETGIDS_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+getgrouplist_test: $(GETGROUPLIST_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(GETGROUPLIST_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+strsplit_test: $(STRSPLIT_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(STRSPLIT_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+regex_test: $(REGEX_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(REGEX_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+strsig_test: $(STRSIG_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(STRSIG_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+strtobool_test: $(STRTOBOOL_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(STRTOBOOL_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+strtomode_test: $(STRTOMODE_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(STRTOMODE_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+strtonum_test: $(STRTONUM_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(STRTONUM_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+strtoid_test: $(STRTOID_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(STRTOID_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+uuid_test: $(UUID_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(UUID_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+fuzz_sudo_conf: $(FUZZ_SUDO_CONF_OBJS) $(LIBFUZZSTUB) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(FUZZ_SUDO_CONF_OBJS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(FUZZ_LDFLAGS) $(FUZZ_LIBS) libsudo_util.la
+
+fuzz_sudo_conf_seed_corpus.zip:
+ tdir=fuzz_sudo_conf.$$$$; \
+ mkdir $$tdir; \
+ for f in $(FUZZ_SUDO_CONF_CORPUS); do \
+ cp $$f $$tdir/`$(SHA1SUM) $$f | $(SED) -e 's/^.*= *//' -e 's/ .*//'`; \
+ done; \
+ zip -j $@ $$tdir/*; \
+ rm -rf $$tdir
+
+run-fuzz_sudo_conf: fuzz_sudo_conf
+ l=`locale -a 2>&1 | $(EGREP) -i '^C\.UTF-?8$$' | $(SED) 1q` || true; \
+ test -n "$$l" || l="C"; \
+ LC_ALL="$$l"; export LC_ALL; \
+ unset LANG || LANG=; \
+ unset LANGUAGE || LANGUAGE=; \
+ MALLOC_OPTIONS=S; export MALLOC_OPTIONS; \
+ MALLOC_CONF="abort:true,junk:true"; export MALLOC_CONF; \
+ umask 022; \
+ corpus=regress/corpus/sudo_conf; \
+ mkdir -p $$corpus; \
+ for f in $(FUZZ_SUDO_CONF_CORPUS); do \
+ cp $$f $$corpus; \
+ done; \
+ ./fuzz_sudo_conf -dict=$(srcdir)/regress/fuzz/fuzz_sudo_conf.dict -max_len=$(FUZZ_MAX_LEN) -runs=$(FUZZ_RUNS) $(FUZZ_VERBOSE) $$corpus
+
+pre-install:
+
+install: install-dirs
+ case "$(LT_LDFLAGS)" in \
+ *-no-install*) ;; \
+ *) if [ X"$(shlib_enable)" = X"yes" ]; then \
+ INSTALL_BACKUP='$(INSTALL_BACKUP)' $(LIBTOOL) $(LTFLAGS) --quiet --mode=install $(INSTALL) $(INSTALL_OWNER) libsudo_util.la $(DESTDIR)$(libexecdir)/sudo; \
+ fi;; \
+ esac
+
+install-dirs:
+ $(SHELL) $(scriptdir)/mkinstalldirs $(DESTDIR)$(libexecdir)/sudo
+
+install-binaries:
+
+install-includes:
+
+install-doc:
+
+install-plugin:
+
+install-fuzzer: $(FUZZ_PROGS) $(FUZZ_SEED_CORPUS)
+ @if test X"$(FUZZ_DESTDIR)" = X""; then \
+ echo "must set FUZZ_DESTDIR for install-fuzzer target"; \
+ else \
+ cp $(FUZZ_PROGS) $(FUZZ_SEED_CORPUS) $(FUZZ_DESTDIR); \
+ cp $(srcdir)/regress/fuzz/*.dict $(FUZZ_DESTDIR); \
+ fi
+
+uninstall:
+ $(LIBTOOL) $(LTFLAGS) --mode=uninstall rm -f $(DESTDIR)$(libexecdir)/sudo/libsudo_util.la
+ -test -z "$(INSTALL_BACKUP)" || \
+ rf -f $(DESTDIR)$(libexecdir)/sudo/libsudo_util.*~
+
+splint:
+ splint $(SPLINT_OPTS) -I$(incdir) -I$(top_builddir) $(srcdir)/*.c
+
+cppcheck:
+ cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) $(srcdir)/*.c
+
+pvs-log-files: $(POBJS)
+
+pvs-studio: $(POBJS)
+ plog-converter $(PVS_LOG_OPTS) $(POBJS)
+
+fuzz: run-fuzz_sudo_conf
+
+check-fuzzer: $(FUZZ_PROGS)
+ @if test X"$(cross_compiling)" != X"yes"; then \
+ l=`locale -a 2>&1 | $(EGREP) -i '^C\.UTF-?8$$' | $(SED) 1q` || true; \
+ test -n "$$l" || l="C"; \
+ LC_ALL="$$l"; export LC_ALL; \
+ unset LANG || LANG=; \
+ unset LANGUAGE || LANGUAGE=; \
+ MALLOC_OPTIONS=S; export MALLOC_OPTIONS; \
+ MALLOC_CONF="abort:true,junk:true"; export MALLOC_CONF; \
+ echo "fuzz_sudo_conf: verifying corpus"; \
+ ./fuzz_sudo_conf $(FUZZ_VERBOSE) $(FUZZ_SUDO_CONF_CORPUS); \
+ fi
+
+# Note: some regress checks are run from srcdir for consistent error messages
+check: $(TEST_PROGS) check-fuzzer
+ @if test X"$(cross_compiling)" != X"yes"; then \
+ l=`locale -a 2>&1 | $(EGREP) -i '^C\.UTF-?8$$' | $(SED) 1q` || true; \
+ test -n "$$l" || l="C"; \
+ LC_ALL="$$l"; export LC_ALL; \
+ unset LANG || LANG=; \
+ unset LANGUAGE || LANGUAGE=; \
+ MALLOC_OPTIONS=S; export MALLOC_OPTIONS; \
+ MALLOC_CONF="abort:true,junk:true"; export MALLOC_CONF; \
+ rval=0; \
+ if test -f closefrom_test; then \
+ ./closefrom_test || rval=`expr $$rval + $$?`; \
+ fi; \
+ if test -f fnm_test; then \
+ ./fnm_test $(srcdir)/regress/fnmatch/fnm_test.in || rval=`expr $$rval + $$?`; \
+ fi; \
+ if test -f globtest; then \
+ mkdir -p `$(SED) 's@/[^/]*$$@@' $(srcdir)/regress/glob/files | sort -u`; \
+ touch `cat $(srcdir)/regress/glob/files`; \
+ chmod 0755 `$(EGREP) '/r[^/]*$$' $(srcdir)/regress/glob/files`; \
+ chmod 0444 `$(EGREP) '/s[^/]*$$' $(srcdir)/regress/glob/files`; \
+ chmod 0711 `$(EGREP) '/t[^/]*$$' $(srcdir)/regress/glob/files`; \
+ ./globtest $(srcdir)/regress/glob/globtest.in || rval=`expr $$rval + $$?`; \
+ rm -rf fake; \
+ fi; \
+ if test -f getdelim_test; then \
+ ./getdelim_test || rval=`expr $$rval + $$?`; \
+ fi; \
+ if test -f mktemp_test; then \
+ ./mktemp_test || rval=`expr $$rval + $$?`; \
+ fi; \
+ if test -f strsig_test; then \
+ ./strsig_test || rval=`expr $$rval + $$?`; \
+ fi; \
+ ./getgrouplist_test || rval=`expr $$rval + $$?`; \
+ ./multiarch_test || rval=`expr $$rval + $$?`; \
+ ./open_parent_dir_test || rval=`expr $$rval + $$?`; \
+ ./parse_gids_test || rval=`expr $$rval + $$?`; \
+ ./regex_test || rval=`expr $$rval + $$?`; \
+ ./strsplit_test || rval=`expr $$rval + $$?`; \
+ ./strtobool_test || rval=`expr $$rval + $$?`; \
+ ./strtoid_test || rval=`expr $$rval + $$?`; \
+ ./strtomode_test || rval=`expr $$rval + $$?`; \
+ ./strtonum_test || rval=`expr $$rval + $$?`; \
+ ./uuid_test || rval=`expr $$rval + $$?`; \
+ ./hltq_test || rval=`expr $$rval + $$?`; \
+ ./hexchar_test || rval=`expr $$rval + $$?`; \
+ ./json_test || rval=`expr $$rval + $$?`; \
+ ./progname_test || rval=`expr $$rval + $$?`; \
+ rm -f ./progname_test2; ln -s ./progname_test ./progname_test2; \
+ ./progname_test2 || rval=`expr $$rval + $$?`; \
+ rm -f ./progname_test2; \
+ AWK=$(AWK) $(HARNESS) sudo_conf || rval=`expr $$rval + $$?`; \
+ AWK=$(AWK) $(HARNESS) sudo_parseln || rval=`expr $$rval + $$?`; \
+ exit $$rval; \
+ fi
+
+check-verbose:
+ exec $(MAKE) $(MFLAGS) TEST_VERBOSE=-v FUZZ_VERBOSE=-verbosity=1 check
+
+clean:
+ -$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f $(TEST_PROGS) $(FUZZ_PROGS) \
+ *.lo *.o *.la
+ -rm -f *.i *.plog stamp-* core *.core core.* regress/*/*.out \
+ regress/*/*.err
+ -rm -rf regress/corpus/sudo_conf
+
+mostlyclean: clean
+
+distclean: clean
+ -rm -rf Makefile mksiglist mksiglist.h siglist.c \
+ mksigname mksigname.h signame.c regress/harness \
+ .libs $(shlib_exp) $(shlib_map) $(shlib_opt)
+
+clobber: distclean
+
+realclean: distclean
+ rm -f TAGS tags
+
+cleandir: realclean
+
+.PHONY: clean mostlyclean distclean cleandir clobber realclean \
+ harness $(FUZZ_SEED_CORPUS) run-fuzz_sudo_conf
+
+# Autogenerated dependencies, do not modify
+aix.lo: $(srcdir)/aix.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.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 \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/aix.c
+aix.i: $(srcdir)/aix.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.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 \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+aix.plog: aix.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/aix.c --i-file $< --output-file $@
+arc4random.lo: $(srcdir)/arc4random.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_rand.h \
+ $(srcdir)/chacha_private.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/arc4random.c
+arc4random.i: $(srcdir)/arc4random.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_rand.h \
+ $(srcdir)/chacha_private.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+arc4random.plog: arc4random.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/arc4random.c --i-file $< --output-file $@
+arc4random_buf.lo: $(srcdir)/arc4random_buf.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_rand.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/arc4random_buf.c
+arc4random_buf.i: $(srcdir)/arc4random_buf.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_rand.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+arc4random_buf.plog: arc4random_buf.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/arc4random_buf.c --i-file $< --output-file $@
+arc4random_uniform.lo: $(srcdir)/arc4random_uniform.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_rand.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/arc4random_uniform.c
+arc4random_uniform.i: $(srcdir)/arc4random_uniform.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_rand.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+arc4random_uniform.plog: arc4random_uniform.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/arc4random_uniform.c --i-file $< --output-file $@
+basename.lo: $(srcdir)/basename.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/basename.c
+basename.i: $(srcdir)/basename.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+basename.plog: basename.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/basename.c --i-file $< --output-file $@
+cfmakeraw.lo: $(srcdir)/cfmakeraw.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/cfmakeraw.c
+cfmakeraw.i: $(srcdir)/cfmakeraw.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+cfmakeraw.plog: cfmakeraw.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/cfmakeraw.c --i-file $< --output-file $@
+closefrom.lo: $(srcdir)/closefrom.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/closefrom.c
+closefrom.i: $(srcdir)/closefrom.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+closefrom.plog: closefrom.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/closefrom.c --i-file $< --output-file $@
+closefrom_test.lo: $(srcdir)/regress/closefrom/closefrom_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/closefrom/closefrom_test.c
+closefrom_test.i: $(srcdir)/regress/closefrom/closefrom_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+closefrom_test.plog: closefrom_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/closefrom/closefrom_test.c --i-file $< --output-file $@
+conf_test.lo: $(srcdir)/regress/sudo_conf/conf_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/sudo_conf/conf_test.c
+conf_test.i: $(srcdir)/regress/sudo_conf/conf_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+conf_test.plog: conf_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/sudo_conf/conf_test.c --i-file $< --output-file $@
+digest.lo: $(srcdir)/digest.c $(incdir)/compat/sha2.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_digest.h \
+ $(incdir)/sudo_queue.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/digest.c
+digest.i: $(srcdir)/digest.c $(incdir)/compat/sha2.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_digest.h \
+ $(incdir)/sudo_queue.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+digest.plog: digest.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/digest.c --i-file $< --output-file $@
+digest_gcrypt.lo: $(srcdir)/digest_gcrypt.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_digest.h $(incdir)/sudo_queue.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/digest_gcrypt.c
+digest_gcrypt.i: $(srcdir)/digest_gcrypt.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_digest.h $(incdir)/sudo_queue.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+digest_gcrypt.plog: digest_gcrypt.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/digest_gcrypt.c --i-file $< --output-file $@
+digest_openssl.lo: $(srcdir)/digest_openssl.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_digest.h $(incdir)/sudo_queue.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/digest_openssl.c
+digest_openssl.i: $(srcdir)/digest_openssl.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_digest.h $(incdir)/sudo_queue.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+digest_openssl.plog: digest_openssl.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/digest_openssl.c --i-file $< --output-file $@
+dup3.lo: $(srcdir)/dup3.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/dup3.c
+dup3.i: $(srcdir)/dup3.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+dup3.plog: dup3.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/dup3.c --i-file $< --output-file $@
+event.lo: $(srcdir)/event.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/event.c
+event.i: $(srcdir)/event.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+event.plog: event.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/event.c --i-file $< --output-file $@
+event_poll.lo: $(srcdir)/event_poll.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/event_poll.c
+event_poll.i: $(srcdir)/event_poll.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+event_poll.plog: event_poll.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/event_poll.c --i-file $< --output-file $@
+event_select.lo: $(srcdir)/event_select.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/event_select.c
+event_select.i: $(srcdir)/event_select.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+event_select.plog: event_select.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/event_select.c --i-file $< --output-file $@
+explicit_bzero.lo: $(srcdir)/explicit_bzero.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/explicit_bzero.c
+explicit_bzero.i: $(srcdir)/explicit_bzero.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+explicit_bzero.plog: explicit_bzero.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/explicit_bzero.c --i-file $< --output-file $@
+fatal.lo: $(srcdir)/fatal.c $(incdir)/compat/getaddrinfo.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/fatal.c
+fatal.i: $(srcdir)/fatal.c $(incdir)/compat/getaddrinfo.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+fatal.plog: fatal.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/fatal.c --i-file $< --output-file $@
+fchmodat.lo: $(srcdir)/fchmodat.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/fchmodat.c
+fchmodat.i: $(srcdir)/fchmodat.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+fchmodat.plog: fchmodat.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/fchmodat.c --i-file $< --output-file $@
+fchownat.lo: $(srcdir)/fchownat.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/fchownat.c
+fchownat.i: $(srcdir)/fchownat.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+fchownat.plog: fchownat.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/fchownat.c --i-file $< --output-file $@
+fnm_test.lo: $(srcdir)/regress/fnmatch/fnm_test.c $(incdir)/compat/fnmatch.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/fnmatch/fnm_test.c
+fnm_test.i: $(srcdir)/regress/fnmatch/fnm_test.c $(incdir)/compat/fnmatch.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+fnm_test.plog: fnm_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/fnmatch/fnm_test.c --i-file $< --output-file $@
+fnmatch.lo: $(srcdir)/fnmatch.c $(incdir)/compat/charclass.h \
+ $(incdir)/compat/fnmatch.h $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/fnmatch.c
+fnmatch.i: $(srcdir)/fnmatch.c $(incdir)/compat/charclass.h \
+ $(incdir)/compat/fnmatch.h $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+fnmatch.plog: fnmatch.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/fnmatch.c --i-file $< --output-file $@
+freezero.lo: $(srcdir)/freezero.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/freezero.c
+freezero.i: $(srcdir)/freezero.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+freezero.plog: freezero.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/freezero.c --i-file $< --output-file $@
+fstatat.lo: $(srcdir)/fstatat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/fstatat.c
+fstatat.i: $(srcdir)/fstatat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+fstatat.plog: fstatat.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/fstatat.c --i-file $< --output-file $@
+fuzz_sudo_conf.lo: $(srcdir)/regress/fuzz/fuzz_sudo_conf.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/fuzz/fuzz_sudo_conf.c
+fuzz_sudo_conf.i: $(srcdir)/regress/fuzz/fuzz_sudo_conf.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+fuzz_sudo_conf.plog: fuzz_sudo_conf.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/fuzz/fuzz_sudo_conf.c --i-file $< --output-file $@
+getaddrinfo.lo: $(srcdir)/getaddrinfo.c $(incdir)/compat/getaddrinfo.h \
+ $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/getaddrinfo.c
+getaddrinfo.i: $(srcdir)/getaddrinfo.c $(incdir)/compat/getaddrinfo.h \
+ $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getaddrinfo.plog: getaddrinfo.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getaddrinfo.c --i-file $< --output-file $@
+getcwd.lo: $(srcdir)/getcwd.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/getcwd.c
+getcwd.i: $(srcdir)/getcwd.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getcwd.plog: getcwd.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getcwd.c --i-file $< --output-file $@
+getdelim.lo: $(srcdir)/getdelim.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/getdelim.c
+getdelim.i: $(srcdir)/getdelim.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getdelim.plog: getdelim.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getdelim.c --i-file $< --output-file $@
+getdelim_test.lo: $(srcdir)/regress/getdelim/getdelim_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/getdelim/getdelim_test.c
+getdelim_test.i: $(srcdir)/regress/getdelim/getdelim_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getdelim_test.plog: getdelim_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/getdelim/getdelim_test.c --i-file $< --output-file $@
+getentropy.lo: $(srcdir)/getentropy.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_digest.h $(incdir)/sudo_rand.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/getentropy.c
+getentropy.i: $(srcdir)/getentropy.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_digest.h $(incdir)/sudo_rand.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getentropy.plog: getentropy.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getentropy.c --i-file $< --output-file $@
+getgids.lo: $(srcdir)/regress/getgrouplist/getgids.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/getgrouplist/getgids.c
+getgids.i: $(srcdir)/regress/getgrouplist/getgids.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getgids.plog: getgids.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/getgrouplist/getgids.c --i-file $< --output-file $@
+getgrouplist.lo: $(srcdir)/getgrouplist.c $(incdir)/compat/nss_dbdefs.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/getgrouplist.c
+getgrouplist.i: $(srcdir)/getgrouplist.c $(incdir)/compat/nss_dbdefs.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getgrouplist.plog: getgrouplist.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getgrouplist.c --i-file $< --output-file $@
+getgrouplist_test.lo: $(srcdir)/regress/getgrouplist/getgrouplist_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/getgrouplist/getgrouplist_test.c
+getgrouplist_test.i: $(srcdir)/regress/getgrouplist/getgrouplist_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getgrouplist_test.plog: getgrouplist_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/getgrouplist/getgrouplist_test.c --i-file $< --output-file $@
+gethostname.lo: $(srcdir)/gethostname.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/gethostname.c
+gethostname.i: $(srcdir)/gethostname.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+gethostname.plog: gethostname.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/gethostname.c --i-file $< --output-file $@
+getopt_long.lo: $(srcdir)/getopt_long.c $(incdir)/compat/getopt.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/getopt_long.c
+getopt_long.i: $(srcdir)/getopt_long.c $(incdir)/compat/getopt.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getopt_long.plog: getopt_long.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getopt_long.c --i-file $< --output-file $@
+gettime.lo: $(srcdir)/gettime.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/gettime.c
+gettime.i: $(srcdir)/gettime.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+gettime.plog: gettime.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/gettime.c --i-file $< --output-file $@
+getusershell.lo: $(srcdir)/getusershell.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/getusershell.c
+getusershell.i: $(srcdir)/getusershell.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getusershell.plog: getusershell.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getusershell.c --i-file $< --output-file $@
+gidlist.lo: $(srcdir)/gidlist.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.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 $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/gidlist.c
+gidlist.i: $(srcdir)/gidlist.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.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 $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+gidlist.plog: gidlist.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/gidlist.c --i-file $< --output-file $@
+glob.lo: $(srcdir)/glob.c $(incdir)/compat/charclass.h $(incdir)/compat/glob.h \
+ $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/glob.c
+glob.i: $(srcdir)/glob.c $(incdir)/compat/charclass.h $(incdir)/compat/glob.h \
+ $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+glob.plog: glob.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/glob.c --i-file $< --output-file $@
+globtest.lo: $(srcdir)/regress/glob/globtest.c $(incdir)/compat/glob.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/glob/globtest.c
+globtest.i: $(srcdir)/regress/glob/globtest.c $(incdir)/compat/glob.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+globtest.plog: globtest.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/glob/globtest.c --i-file $< --output-file $@
+gmtime_r.lo: $(srcdir)/gmtime_r.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/gmtime_r.c
+gmtime_r.i: $(srcdir)/gmtime_r.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+gmtime_r.plog: gmtime_r.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/gmtime_r.c --i-file $< --output-file $@
+hexchar.lo: $(srcdir)/hexchar.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/hexchar.c
+hexchar.i: $(srcdir)/hexchar.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+hexchar.plog: hexchar.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/hexchar.c --i-file $< --output-file $@
+hexchar_test.lo: $(srcdir)/regress/hexchar/hexchar_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/hexchar/hexchar_test.c
+hexchar_test.i: $(srcdir)/regress/hexchar/hexchar_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+hexchar_test.plog: hexchar_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/hexchar/hexchar_test.c --i-file $< --output-file $@
+hltq_test.lo: $(srcdir)/regress/tailq/hltq_test.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/tailq/hltq_test.c
+hltq_test.i: $(srcdir)/regress/tailq/hltq_test.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+hltq_test.plog: hltq_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/tailq/hltq_test.c --i-file $< --output-file $@
+inet_pton.lo: $(srcdir)/inet_pton.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/inet_pton.c
+inet_pton.i: $(srcdir)/inet_pton.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+inet_pton.plog: inet_pton.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/inet_pton.c --i-file $< --output-file $@
+isblank.lo: $(srcdir)/isblank.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/isblank.c
+isblank.i: $(srcdir)/isblank.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+isblank.plog: isblank.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/isblank.c --i-file $< --output-file $@
+json.lo: $(srcdir)/json.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_json.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/json.c
+json.i: $(srcdir)/json.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_json.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+json.plog: json.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/json.c --i-file $< --output-file $@
+json_test.lo: $(srcdir)/regress/json/json_test.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_json.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/json/json_test.c
+json_test.i: $(srcdir)/regress/json/json_test.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_json.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+json_test.plog: json_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/json/json_test.c --i-file $< --output-file $@
+key_val.lo: $(srcdir)/key_val.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/key_val.c
+key_val.i: $(srcdir)/key_val.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+key_val.plog: key_val.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/key_val.c --i-file $< --output-file $@
+lbuf.lo: $(srcdir)/lbuf.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_lbuf.h $(incdir)/sudo_queue.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/lbuf.c
+lbuf.i: $(srcdir)/lbuf.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_lbuf.h $(incdir)/sudo_queue.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+lbuf.plog: lbuf.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/lbuf.c --i-file $< --output-file $@
+localtime_r.lo: $(srcdir)/localtime_r.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/localtime_r.c
+localtime_r.i: $(srcdir)/localtime_r.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+localtime_r.plog: localtime_r.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/localtime_r.c --i-file $< --output-file $@
+locking.lo: $(srcdir)/locking.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/locking.c
+locking.i: $(srcdir)/locking.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+locking.plog: locking.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/locking.c --i-file $< --output-file $@
+logfac.lo: $(srcdir)/logfac.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/logfac.c
+logfac.i: $(srcdir)/logfac.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+logfac.plog: logfac.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/logfac.c --i-file $< --output-file $@
+logpri.lo: $(srcdir)/logpri.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/logpri.c
+logpri.i: $(srcdir)/logpri.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+logpri.plog: logpri.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/logpri.c --i-file $< --output-file $@
+memrchr.lo: $(srcdir)/memrchr.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/memrchr.c
+memrchr.i: $(srcdir)/memrchr.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+memrchr.plog: memrchr.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/memrchr.c --i-file $< --output-file $@
+mkdir_parents.lo: $(srcdir)/mkdir_parents.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.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 $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/mkdir_parents.c
+mkdir_parents.i: $(srcdir)/mkdir_parents.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.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 $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+mkdir_parents.plog: mkdir_parents.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/mkdir_parents.c --i-file $< --output-file $@
+mkdirat.lo: $(srcdir)/mkdirat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/mkdirat.c
+mkdirat.i: $(srcdir)/mkdirat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+mkdirat.plog: mkdirat.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/mkdirat.c --i-file $< --output-file $@
+mksiglist.lo: $(srcdir)/mksiglist.c $(incdir)/sudo_compat.h \
+ $(srcdir)/mksiglist.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/mksiglist.c
+mksiglist.i: $(srcdir)/mksiglist.c $(incdir)/sudo_compat.h \
+ $(srcdir)/mksiglist.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+mksiglist.plog: mksiglist.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/mksiglist.c --i-file $< --output-file $@
+mksigname.lo: $(srcdir)/mksigname.c $(incdir)/sudo_compat.h \
+ $(srcdir)/mksigname.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/mksigname.c
+mksigname.i: $(srcdir)/mksigname.c $(incdir)/sudo_compat.h \
+ $(srcdir)/mksigname.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+mksigname.plog: mksigname.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/mksigname.c --i-file $< --output-file $@
+mktemp.lo: $(srcdir)/mktemp.c $(incdir)/sudo_compat.h $(incdir)/sudo_rand.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/mktemp.c
+mktemp.i: $(srcdir)/mktemp.c $(incdir)/sudo_compat.h $(incdir)/sudo_rand.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+mktemp.plog: mktemp.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/mktemp.c --i-file $< --output-file $@
+mktemp_test.lo: $(srcdir)/regress/mktemp/mktemp_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/mktemp/mktemp_test.c
+mktemp_test.i: $(srcdir)/regress/mktemp/mktemp_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+mktemp_test.plog: mktemp_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/mktemp/mktemp_test.c --i-file $< --output-file $@
+mmap_alloc.lo: $(srcdir)/mmap_alloc.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/mmap_alloc.c
+mmap_alloc.i: $(srcdir)/mmap_alloc.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+mmap_alloc.plog: mmap_alloc.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/mmap_alloc.c --i-file $< --output-file $@
+multiarch.lo: $(srcdir)/multiarch.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/multiarch.c
+multiarch.i: $(srcdir)/multiarch.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+multiarch.plog: multiarch.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/multiarch.c --i-file $< --output-file $@
+multiarch_test.lo: $(srcdir)/regress/multiarch/multiarch_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/multiarch/multiarch_test.c
+multiarch_test.i: $(srcdir)/regress/multiarch/multiarch_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+multiarch_test.plog: multiarch_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/multiarch/multiarch_test.c --i-file $< --output-file $@
+nanosleep.lo: $(srcdir)/nanosleep.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/nanosleep.c
+nanosleep.i: $(srcdir)/nanosleep.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+nanosleep.plog: nanosleep.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/nanosleep.c --i-file $< --output-file $@
+open_parent_dir_test.lo: \
+ $(srcdir)/regress/open_parent_dir/open_parent_dir_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/open_parent_dir/open_parent_dir_test.c
+open_parent_dir_test.i: \
+ $(srcdir)/regress/open_parent_dir/open_parent_dir_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+open_parent_dir_test.plog: open_parent_dir_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/open_parent_dir/open_parent_dir_test.c --i-file $< --output-file $@
+openat.lo: $(srcdir)/openat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/openat.c
+openat.i: $(srcdir)/openat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+openat.plog: openat.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/openat.c --i-file $< --output-file $@
+parse_gids_test.lo: $(srcdir)/regress/parse_gids/parse_gids_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/parse_gids/parse_gids_test.c
+parse_gids_test.i: $(srcdir)/regress/parse_gids/parse_gids_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+parse_gids_test.plog: parse_gids_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/parse_gids/parse_gids_test.c --i-file $< --output-file $@
+parseln.lo: $(srcdir)/parseln.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/parseln.c
+parseln.i: $(srcdir)/parseln.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+parseln.plog: parseln.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/parseln.c --i-file $< --output-file $@
+parseln_test.lo: $(srcdir)/regress/sudo_parseln/parseln_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/sudo_parseln/parseln_test.c
+parseln_test.i: $(srcdir)/regress/sudo_parseln/parseln_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+parseln_test.plog: parseln_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/sudo_parseln/parseln_test.c --i-file $< --output-file $@
+pipe2.lo: $(srcdir)/pipe2.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/pipe2.c
+pipe2.i: $(srcdir)/pipe2.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+pipe2.plog: pipe2.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/pipe2.c --i-file $< --output-file $@
+pread.lo: $(srcdir)/pread.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/pread.c
+pread.i: $(srcdir)/pread.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+pread.plog: pread.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/pread.c --i-file $< --output-file $@
+progname.lo: $(srcdir)/progname.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/progname.c
+progname.i: $(srcdir)/progname.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+progname.plog: progname.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/progname.c --i-file $< --output-file $@
+progname_test.lo: $(srcdir)/regress/progname/progname_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/progname/progname_test.c
+progname_test.i: $(srcdir)/regress/progname/progname_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+progname_test.plog: progname_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/progname/progname_test.c --i-file $< --output-file $@
+pw_dup.lo: $(srcdir)/pw_dup.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/pw_dup.c
+pw_dup.i: $(srcdir)/pw_dup.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+pw_dup.plog: pw_dup.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/pw_dup.c --i-file $< --output-file $@
+pwrite.lo: $(srcdir)/pwrite.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/pwrite.c
+pwrite.i: $(srcdir)/pwrite.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+pwrite.plog: pwrite.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/pwrite.c --i-file $< --output-file $@
+rcstr.lo: $(srcdir)/rcstr.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/rcstr.c
+rcstr.i: $(srcdir)/rcstr.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+rcstr.plog: rcstr.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/rcstr.c --i-file $< --output-file $@
+reallocarray.lo: $(srcdir)/reallocarray.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/reallocarray.c
+reallocarray.i: $(srcdir)/reallocarray.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+reallocarray.plog: reallocarray.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/reallocarray.c --i-file $< --output-file $@
+regex.lo: $(srcdir)/regex.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regex.c
+regex.i: $(srcdir)/regex.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+regex.plog: regex.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regex.c --i-file $< --output-file $@
+regex_test.lo: $(srcdir)/regress/regex/regex_test.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/regex/regex_test.c
+regex_test.i: $(srcdir)/regress/regex/regex_test.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+regex_test.plog: regex_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/regex/regex_test.c --i-file $< --output-file $@
+roundup.lo: $(srcdir)/roundup.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/roundup.c
+roundup.i: $(srcdir)/roundup.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+roundup.plog: roundup.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/roundup.c --i-file $< --output-file $@
+secure_path.lo: $(srcdir)/secure_path.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/secure_path.c
+secure_path.i: $(srcdir)/secure_path.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+secure_path.plog: secure_path.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/secure_path.c --i-file $< --output-file $@
+setgroups.lo: $(srcdir)/setgroups.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/setgroups.c
+setgroups.i: $(srcdir)/setgroups.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+setgroups.plog: setgroups.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/setgroups.c --i-file $< --output-file $@
+sha2.lo: $(srcdir)/sha2.c $(incdir)/compat/endian.h $(incdir)/compat/sha2.h \
+ $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/sha2.c
+sha2.i: $(srcdir)/sha2.c $(incdir)/compat/endian.h $(incdir)/compat/sha2.h \
+ $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+sha2.plog: sha2.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sha2.c --i-file $< --output-file $@
+sig2str.lo: $(srcdir)/sig2str.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/sig2str.c
+sig2str.i: $(srcdir)/sig2str.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+sig2str.plog: sig2str.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sig2str.c --i-file $< --output-file $@
+siglist.lo: siglist.c
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) siglist.c
+siglist.i: siglist.c
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+siglist.plog: siglist.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file siglist.c --i-file $< --output-file $@
+signame.lo: signame.c
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) signame.c
+signame.i: signame.c
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+signame.plog: signame.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file signame.c --i-file $< --output-file $@
+snprintf.lo: $(srcdir)/snprintf.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/snprintf.c
+snprintf.i: $(srcdir)/snprintf.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+snprintf.plog: snprintf.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/snprintf.c --i-file $< --output-file $@
+str2sig.lo: $(srcdir)/str2sig.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/str2sig.c
+str2sig.i: $(srcdir)/str2sig.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+str2sig.plog: str2sig.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/str2sig.c --i-file $< --output-file $@
+strlcat.lo: $(srcdir)/strlcat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/strlcat.c
+strlcat.i: $(srcdir)/strlcat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strlcat.plog: strlcat.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/strlcat.c --i-file $< --output-file $@
+strlcpy.lo: $(srcdir)/strlcpy.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/strlcpy.c
+strlcpy.i: $(srcdir)/strlcpy.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strlcpy.plog: strlcpy.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/strlcpy.c --i-file $< --output-file $@
+strndup.lo: $(srcdir)/strndup.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/strndup.c
+strndup.i: $(srcdir)/strndup.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strndup.plog: strndup.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/strndup.c --i-file $< --output-file $@
+strnlen.lo: $(srcdir)/strnlen.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/strnlen.c
+strnlen.i: $(srcdir)/strnlen.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strnlen.plog: strnlen.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/strnlen.c --i-file $< --output-file $@
+strsig_test.lo: $(srcdir)/regress/strsig/strsig_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/strsig/strsig_test.c
+strsig_test.i: $(srcdir)/regress/strsig/strsig_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strsig_test.plog: strsig_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strsig/strsig_test.c --i-file $< --output-file $@
+strsignal.lo: $(srcdir)/strsignal.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_gettext.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/strsignal.c
+strsignal.i: $(srcdir)/strsignal.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_gettext.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strsignal.plog: strsignal.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/strsignal.c --i-file $< --output-file $@
+strsplit.lo: $(srcdir)/strsplit.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/strsplit.c
+strsplit.i: $(srcdir)/strsplit.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strsplit.plog: strsplit.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/strsplit.c --i-file $< --output-file $@
+strsplit_test.lo: $(srcdir)/regress/strsplit/strsplit_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/strsplit/strsplit_test.c
+strsplit_test.i: $(srcdir)/regress/strsplit/strsplit_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strsplit_test.plog: strsplit_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strsplit/strsplit_test.c --i-file $< --output-file $@
+strtobool.lo: $(srcdir)/strtobool.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/strtobool.c
+strtobool.i: $(srcdir)/strtobool.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtobool.plog: strtobool.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/strtobool.c --i-file $< --output-file $@
+strtobool_test.lo: $(srcdir)/regress/strtofoo/strtobool_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/strtofoo/strtobool_test.c
+strtobool_test.i: $(srcdir)/regress/strtofoo/strtobool_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtobool_test.plog: strtobool_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strtofoo/strtobool_test.c --i-file $< --output-file $@
+strtoid.lo: $(srcdir)/strtoid.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/strtoid.c
+strtoid.i: $(srcdir)/strtoid.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtoid.plog: strtoid.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/strtoid.c --i-file $< --output-file $@
+strtoid_test.lo: $(srcdir)/regress/strtofoo/strtoid_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/strtofoo/strtoid_test.c
+strtoid_test.i: $(srcdir)/regress/strtofoo/strtoid_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtoid_test.plog: strtoid_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strtofoo/strtoid_test.c --i-file $< --output-file $@
+strtomode.lo: $(srcdir)/strtomode.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/strtomode.c
+strtomode.i: $(srcdir)/strtomode.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtomode.plog: strtomode.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/strtomode.c --i-file $< --output-file $@
+strtomode_test.lo: $(srcdir)/regress/strtofoo/strtomode_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/strtofoo/strtomode_test.c
+strtomode_test.i: $(srcdir)/regress/strtofoo/strtomode_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtomode_test.plog: strtomode_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strtofoo/strtomode_test.c --i-file $< --output-file $@
+strtonum.lo: $(srcdir)/strtonum.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/strtonum.c
+strtonum.i: $(srcdir)/strtonum.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtonum.plog: strtonum.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/strtonum.c --i-file $< --output-file $@
+strtonum_test.lo: $(srcdir)/regress/strtofoo/strtonum_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/strtofoo/strtonum_test.c
+strtonum_test.i: $(srcdir)/regress/strtofoo/strtonum_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtonum_test.plog: strtonum_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strtofoo/strtonum_test.c --i-file $< --output-file $@
+sudo_conf.lo: $(srcdir)/sudo_conf.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 \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/sudo_conf.c
+sudo_conf.i: $(srcdir)/sudo_conf.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 \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+sudo_conf.plog: sudo_conf.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sudo_conf.c --i-file $< --output-file $@
+sudo_debug.lo: $(srcdir)/sudo_debug.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 \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/sudo_debug.c
+sudo_debug.i: $(srcdir)/sudo_debug.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 \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+sudo_debug.plog: sudo_debug.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sudo_debug.c --i-file $< --output-file $@
+sudo_dso.lo: $(srcdir)/sudo_dso.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_dso.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/sudo_dso.c
+sudo_dso.i: $(srcdir)/sudo_dso.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_dso.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+sudo_dso.plog: sudo_dso.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sudo_dso.c --i-file $< --output-file $@
+term.lo: $(srcdir)/term.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/term.c
+term.i: $(srcdir)/term.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+term.plog: term.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/term.c --i-file $< --output-file $@
+timegm.lo: $(srcdir)/timegm.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/timegm.c
+timegm.i: $(srcdir)/timegm.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+timegm.plog: timegm.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/timegm.c --i-file $< --output-file $@
+ttyname_dev.lo: $(srcdir)/ttyname_dev.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/ttyname_dev.c
+ttyname_dev.i: $(srcdir)/ttyname_dev.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+ttyname_dev.plog: ttyname_dev.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/ttyname_dev.c --i-file $< --output-file $@
+ttysize.lo: $(srcdir)/ttysize.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/ttysize.c
+ttysize.i: $(srcdir)/ttysize.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+ttysize.plog: ttysize.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/ttysize.c --i-file $< --output-file $@
+unlinkat.lo: $(srcdir)/unlinkat.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/unlinkat.c
+unlinkat.i: $(srcdir)/unlinkat.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+unlinkat.plog: unlinkat.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/unlinkat.c --i-file $< --output-file $@
+utimens.lo: $(srcdir)/utimens.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/utimens.c
+utimens.i: $(srcdir)/utimens.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+utimens.plog: utimens.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/utimens.c --i-file $< --output-file $@
+uuid.lo: $(srcdir)/uuid.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_rand.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/uuid.c
+uuid.i: $(srcdir)/uuid.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_rand.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+uuid.plog: uuid.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/uuid.c --i-file $< --output-file $@
+uuid_test.lo: $(srcdir)/regress/uuid/uuid_test.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/uuid/uuid_test.c
+uuid_test.i: $(srcdir)/regress/uuid/uuid_test.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+uuid_test.plog: uuid_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/uuid/uuid_test.c --i-file $< --output-file $@
diff --git a/lib/util/aix.c b/lib/util/aix.c
new file mode 100644
index 0000000..8a1c750
--- /dev/null
+++ b/lib/util/aix.c
@@ -0,0 +1,290 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008, 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.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/resource.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <usersec.h>
+#include <uinfo.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+#ifdef HAVE_GETUSERATTR
+
+#ifndef HAVE_SETRLIMIT64
+# define setrlimit64(a, b) setrlimit(a, b)
+# define rlimit64 rlimit
+# define rlim64_t rlim_t
+# define RLIM64_INFINITY RLIM_INFINITY
+#endif /* HAVE_SETRLIMIT64 */
+
+#ifndef RLIM_SAVED_MAX
+# define RLIM_SAVED_MAX RLIM64_INFINITY
+#endif
+
+struct aix_limit {
+ int resource;
+ const char *soft;
+ const char *hard;
+ int factor;
+};
+
+static struct aix_limit aix_limits[] = {
+ { RLIMIT_FSIZE, S_UFSIZE, S_UFSIZE_HARD, 512 },
+ { RLIMIT_CPU, S_UCPU, S_UCPU_HARD, 1 },
+ { RLIMIT_DATA, S_UDATA, S_UDATA_HARD, 512 },
+ { RLIMIT_STACK, S_USTACK, S_USTACK_HARD, 512 },
+ { RLIMIT_RSS, S_URSS, S_URSS_HARD, 512 },
+ { RLIMIT_CORE, S_UCORE, S_UCORE_HARD, 512 },
+ { RLIMIT_NOFILE, S_UNOFILE, S_UNOFILE_HARD, 1 }
+};
+
+static int
+aix_getlimit(const char *user, const char *lim, int *valp)
+{
+ debug_decl(aix_getlimit, SUDO_DEBUG_UTIL);
+
+ if (getuserattr((char *)user, (char *)lim, valp, SEC_INT) != 0)
+ debug_return_int(-1);
+ debug_return_int(0);
+}
+
+static int
+aix_setlimits(char *user)
+{
+ struct rlimit64 rlim;
+ int val;
+ size_t n;
+ debug_decl(aix_setlimits, SUDO_DEBUG_UTIL);
+
+ if (setuserdb(S_READ) != 0) {
+ sudo_warn("%s", U_("unable to open userdb"));
+ debug_return_int(-1);
+ }
+
+ /*
+ * For each resource limit, get the soft/hard values for the user
+ * and set those values via setrlimit64(). Must be run as euid 0.
+ */
+ for (n = 0; n < nitems(aix_limits); n++) {
+ /*
+ * We have two strategies, depending on whether or not the
+ * hard limit has been defined.
+ */
+ if (aix_getlimit(user, aix_limits[n].hard, &val) == 0) {
+ rlim.rlim_max = val == -1 ? RLIM64_INFINITY : (rlim64_t)val * aix_limits[n].factor;
+ if (aix_getlimit(user, aix_limits[n].soft, &val) == 0)
+ rlim.rlim_cur = val == -1 ? RLIM64_INFINITY : (rlim64_t)val * aix_limits[n].factor;
+ else
+ rlim.rlim_cur = rlim.rlim_max; /* soft not specd, use hard */
+ } else {
+ /* No hard limit set, try soft limit, if it exists. */
+ if (aix_getlimit(user, aix_limits[n].soft, &val) == -1)
+ continue;
+ rlim.rlim_cur = val == -1 ? RLIM64_INFINITY : (rlim64_t)val * aix_limits[n].factor;
+
+ /* Set default hard limit as per limits(4). */
+ switch (aix_limits[n].resource) {
+ case RLIMIT_CPU:
+ case RLIMIT_FSIZE:
+ rlim.rlim_max = rlim.rlim_cur;
+ break;
+ case RLIMIT_STACK:
+ rlim.rlim_max = 4194304UL * aix_limits[n].factor;
+ break;
+ default:
+ rlim.rlim_max = RLIM64_INFINITY;
+ break;
+ }
+ }
+ (void)setrlimit64(aix_limits[n].resource, &rlim);
+ }
+ enduserdb();
+ debug_return_int(0);
+}
+
+#ifdef HAVE_SETAUTHDB
+
+# ifndef HAVE_AUTHDB_T
+typedef char authdb_t[16];
+# endif
+
+/* The empty string means to access all defined authentication registries. */
+static authdb_t old_registry;
+
+# if defined(HAVE_DECL_SETAUTHDB) && !HAVE_DECL_SETAUTHDB
+int setauthdb(authdb_t new, authdb_t old);
+int getauthdb(authdb_t val);
+# endif
+# if defined(HAVE_DECL_USRINFO) && !HAVE_DECL_USRINFO
+int usrinfo(int cmd, char *buf, int count);
+# endif
+
+/*
+ * Look up authentication registry for user (SYSTEM in /etc/security/user) and
+ * set it as the default for the process. This ensures that password and
+ * group lookups are made against the correct source (files, NIS, LDAP, etc).
+ * Does not modify errno even on error since callers do not check return value.
+ */
+int
+aix_getauthregistry_v1(char *user, char *saved_registry)
+{
+ int serrno = errno;
+ int ret = -1;
+ debug_decl(aix_getauthregistry, SUDO_DEBUG_UTIL);
+
+ saved_registry[0] = '\0';
+ if (user != NULL) {
+ char *registry;
+
+ if (setuserdb(S_READ) != 0) {
+ sudo_warn("%s", U_("unable to open userdb"));
+ goto done;
+ }
+ ret = getuserattr(user, (char *)S_REGISTRY, &registry, SEC_CHAR);
+ if (ret == 0) {
+ /* sizeof(authdb_t) is guaranteed to be 16 */
+ if (strlcpy(saved_registry, registry, 16) >= 16) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "registry for user %s too long: %s", user, registry);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: saved authentication registry for user %s is %s",
+ __func__, user, saved_registry);
+ }
+ enduserdb();
+ } else {
+ /* Get the process-wide registry. */
+ ret = getauthdb(saved_registry);
+ }
+done:
+ errno = serrno;
+ debug_return_int(ret);
+}
+
+/*
+ * Set the specified authentication registry for user (SYSTEM in
+ * /etc/security/user) and set it as the default for the process.
+ * This ensures that password and group lookups are made against
+ * the correct source (files, NIS, LDAP, etc).
+ * If registry is NULL, look it up based on the user name.
+ * Does not modify errno even on error since callers do not check return value.
+ */
+int
+aix_setauthdb_v1(char *user)
+{
+ return aix_setauthdb_v2(user, NULL);
+}
+
+int
+aix_setauthdb_v2(char *user, char *registry)
+{
+ authdb_t regbuf;
+ int serrno = errno;
+ int ret = -1;
+ debug_decl(aix_setauthdb, SUDO_DEBUG_UTIL);
+
+ if (user != NULL) {
+ /* Look up authentication registry if one is not provided. */
+ if (registry == NULL) {
+ if (aix_getauthregistry(user, regbuf) != 0)
+ goto done;
+ registry = regbuf;
+ }
+ ret = setauthdb(registry, old_registry);
+ if (ret != 0) {
+ sudo_warn(U_("unable to switch to registry \"%s\" for %s"),
+ registry, user);
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: setting authentication registry to %s",
+ __func__, registry);
+ }
+ }
+done:
+ errno = serrno;
+ debug_return_int(ret);
+}
+
+/*
+ * Restore the saved authentication registry, if any.
+ * Does not modify errno even on error since callers do not check return value.
+ */
+int
+aix_restoreauthdb_v1(void)
+{
+ int serrno = errno;
+ int ret = 0;
+ debug_decl(aix_setauthdb, SUDO_DEBUG_UTIL);
+
+ if (setauthdb(old_registry, NULL) != 0) {
+ sudo_warn("%s", U_("unable to restore registry"));
+ ret = -1;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: setting authentication registry to %s",
+ __func__, old_registry);
+ }
+ errno = serrno;
+ debug_return_int(ret);
+}
+#endif
+
+int
+aix_prep_user_v1(char *user, const char *tty)
+{
+ char *info;
+ int len;
+ debug_decl(aix_setauthdb, SUDO_DEBUG_UTIL);
+
+ /* set usrinfo, like login(1) does */
+ len = asprintf(&info, "NAME=%s%cLOGIN=%s%cLOGNAME=%s%cTTY=%s%c",
+ user, '\0', user, '\0', user, '\0', tty ? tty : "", '\0');
+ if (len == -1) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_int(-1);
+ }
+ (void)usrinfo(SETUINFO, info, len);
+ free(info);
+
+#ifdef HAVE_SETAUTHDB
+ /* set authentication registry */
+ if (aix_setauthdb(user, NULL) != 0)
+ debug_return_int(-1);
+#endif
+
+ /* set resource limits */
+ if (aix_setlimits(user) != 0)
+ debug_return_int(-1);
+
+ debug_return_int(0);
+}
+#endif /* HAVE_GETUSERATTR */
diff --git a/lib/util/arc4random.c b/lib/util/arc4random.c
new file mode 100644
index 0000000..4002645
--- /dev/null
+++ b/lib/util/arc4random.c
@@ -0,0 +1,206 @@
+/* $OpenBSD: arc4random.c,v 1.54 2015/09/13 08:31:47 guenther Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1996, David Mazieres <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ * Copyright (c) 2013, Markus Friedl <markus@openbsd.org>
+ * Copyright (c) 2014, Theo de Raadt <deraadt@openbsd.org>
+ *
+ * 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
+ */
+
+/*
+ * ChaCha based random number generator for OpenBSD.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_ARC4RANDOM
+
+#ifdef HAVE_SYS_RANDOM_H
+# include <sys/random.h>
+#endif
+
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_rand.h"
+
+#define KEYSTREAM_ONLY
+#include "chacha_private.h"
+
+#define minimum(a, b) ((a) < (b) ? (a) : (b))
+
+#ifdef __GNUC__
+# define inline __inline
+#else /* !__GNUC__ */
+# define inline
+#endif /* !__GNUC__ */
+
+/* Sudo isn't multithreaded */
+#define _ARC4_LOCK()
+#define _ARC4_UNLOCK()
+
+#define KEYSZ 32
+#define IVSZ 8
+#define BLOCKSZ 64
+#define RSBUFSZ (16*BLOCKSZ)
+static int rs_initialized;
+static pid_t rs_stir_pid;
+static chacha_ctx rs; /* chacha context for random keystream */
+static u_char rs_buf[RSBUFSZ]; /* keystream blocks */
+static size_t rs_have; /* valid bytes at end of rs_buf */
+static size_t rs_count; /* bytes till reseed */
+
+static inline void _rs_rekey(unsigned char *dat, size_t datlen);
+
+static inline void
+_rs_init(unsigned char *buf, size_t n)
+{
+ if (n < KEYSZ + IVSZ)
+ return;
+ chacha_keysetup(&rs, buf, KEYSZ * 8, 0);
+ chacha_ivsetup(&rs, buf + KEYSZ);
+}
+
+static void
+_rs_stir(void)
+{
+ unsigned char rnd[KEYSZ + IVSZ];
+
+ if (getentropy(rnd, sizeof rnd) == -1)
+ sudo_fatal_nodebug("getentropy");
+
+ if (!rs_initialized) {
+ rs_initialized = 1;
+ _rs_init(rnd, sizeof(rnd));
+ } else
+ _rs_rekey(rnd, sizeof(rnd));
+ explicit_bzero(rnd, sizeof(rnd)); /* discard source seed */
+
+ /* invalidate rs_buf */
+ rs_have = 0;
+ memset(rs_buf, 0, sizeof(rs_buf));
+
+ rs_count = 1600000;
+}
+
+static inline void
+_rs_stir_if_needed(size_t len)
+{
+ pid_t pid = getpid();
+
+ if (rs_count <= len || !rs_initialized || rs_stir_pid != pid) {
+ rs_stir_pid = pid;
+ _rs_stir();
+ } else
+ rs_count -= len;
+}
+
+static inline void
+_rs_rekey(unsigned char *dat, size_t datlen)
+{
+#ifndef KEYSTREAM_ONLY
+ memset(rs_buf, 0, sizeof(rs_buf));
+#endif
+ /* fill rs_buf with the keystream */
+ chacha_encrypt_bytes(&rs, rs_buf, rs_buf, sizeof(rs_buf));
+ /* mix in optional user provided data */
+ if (dat) {
+ size_t i, m;
+
+ m = minimum(datlen, KEYSZ + IVSZ);
+ for (i = 0; i < m; i++)
+ rs_buf[i] ^= dat[i];
+ }
+ /* immediately reinit for backtracking resistance */
+ _rs_init(rs_buf, KEYSZ + IVSZ);
+ memset(rs_buf, 0, KEYSZ + IVSZ); // -V::512, 1086
+ rs_have = sizeof(rs_buf) - KEYSZ - IVSZ;
+}
+
+static inline void
+_rs_random_buf(void *_buf, size_t n)
+{
+ unsigned char *buf = _buf;
+ unsigned char *keystream;
+ size_t m;
+
+ _rs_stir_if_needed(n);
+ while (n > 0) {
+ if (rs_have > 0) {
+ m = minimum(n, rs_have);
+ keystream = rs_buf + sizeof(rs_buf) - rs_have;
+ memcpy(buf, keystream, m);
+ memset(keystream, 0, m);
+ buf += m;
+ n -= m;
+ rs_have -= m;
+ }
+ if (rs_have == 0)
+ _rs_rekey(NULL, 0);
+ }
+}
+
+static inline void
+_rs_random_u32(uint32_t *val)
+{
+ unsigned char *keystream;
+
+ _rs_stir_if_needed(sizeof(*val));
+ if (rs_have < sizeof(*val))
+ _rs_rekey(NULL, 0);
+ keystream = rs_buf + sizeof(rs_buf) - rs_have;
+ memcpy(val, keystream, sizeof(*val));
+ memset(keystream, 0, sizeof(*val));
+ rs_have -= sizeof(*val);
+}
+
+uint32_t
+sudo_arc4random(void)
+{
+ uint32_t val;
+
+ _ARC4_LOCK();
+ _rs_random_u32(&val);
+ _ARC4_UNLOCK();
+ return val;
+}
+
+void
+sudo_arc4random_buf(void *buf, size_t n)
+{
+ _ARC4_LOCK();
+ _rs_random_buf(buf, n);
+ _ARC4_UNLOCK();
+}
+
+#endif /* HAVE_ARC4RANDOM */
diff --git a/lib/util/arc4random_buf.c b/lib/util/arc4random_buf.c
new file mode 100644
index 0000000..053c922
--- /dev/null
+++ b/lib/util/arc4random_buf.c
@@ -0,0 +1,70 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_ARC4RANDOM_BUF
+
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_rand.h"
+
+#define minimum(a, b) ((a) < (b) ? (a) : (b))
+
+/*
+ * Call arc4random() repeatedly to fill buf with n bytes of random data.
+ */
+void
+sudo_arc4random_buf(void *buf, size_t n)
+{
+ char *cp = buf;
+
+ while (n != 0) {
+ size_t m = minimum(n, 4);
+ uint32_t val = arc4random();
+
+ switch (m) {
+ case 4:
+ *cp++ = (val >> 24) & 0xff;
+ FALLTHROUGH;
+ case 3:
+ *cp++ = (val >> 16) & 0xff;
+ FALLTHROUGH;
+ case 2:
+ *cp++ = (val >> 8) & 0xff;
+ FALLTHROUGH;
+ case 1:
+ *cp++ = val & 0xff;
+ break;
+ }
+ n -= m;
+ }
+}
+
+#endif /* HAVE_ARC4RANDOM_BUF */
diff --git a/lib/util/arc4random_uniform.c b/lib/util/arc4random_uniform.c
new file mode 100644
index 0000000..3299a14
--- /dev/null
+++ b/lib/util/arc4random_uniform.c
@@ -0,0 +1,76 @@
+/* $OpenBSD: arc4random_uniform.c,v 1.2 2015/09/13 08:31:47 guenther Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ *
+ * 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>
+
+#ifndef HAVE_ARC4RANDOM_UNIFORM
+
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_rand.h"
+
+/*
+ * Calculate a uniformly distributed random number less than upper_bound
+ * avoiding "modulo bias".
+ *
+ * Uniformity is achieved by generating new random numbers until the one
+ * returned is outside the range [0, 2**32 % upper_bound). This
+ * guarantees the selected random number will be inside
+ * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
+ * after reduction modulo upper_bound.
+ */
+uint32_t
+sudo_arc4random_uniform(uint32_t upper_bound)
+{
+ uint32_t r, min;
+
+ if (upper_bound < 2)
+ return 0;
+
+ /* 2**32 % x == (2**32 - x) % x */
+ min = -upper_bound % upper_bound;
+
+ /*
+ * This could theoretically loop forever but each retry has
+ * p > 0.5 (worst case, usually far better) of selecting a
+ * number inside the range we need, so it should rarely need
+ * to re-roll.
+ */
+ for (;;) {
+ r = arc4random();
+ if (r >= min)
+ break;
+ }
+
+ return r % upper_bound;
+}
+
+#endif /* HAVE_ARC4RANDOM_UNIFORM */
diff --git a/lib/util/basename.c b/lib/util/basename.c
new file mode 100644
index 0000000..b757b36
--- /dev/null
+++ b/lib/util/basename.c
@@ -0,0 +1,52 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * 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 <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+/*
+ * GNU-compatible basename(3)
+ * Unlike the POSIX version, this version never modifies its argument
+ * and returns an empty string if filename ends in a slash.
+ */
+char *
+sudo_basename_v1(const char *filename)
+{
+ char *base;
+
+ if ((base = strrchr(filename, '/')) != NULL)
+ base++;
+ else
+ base = (char *)filename;
+
+ return base;
+}
diff --git a/lib/util/cfmakeraw.c b/lib/util/cfmakeraw.c
new file mode 100644
index 0000000..956001f
--- /dev/null
+++ b/lib/util/cfmakeraw.c
@@ -0,0 +1,58 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <termios.h>
+
+#include "sudo_compat.h"
+
+/* Non-standard termios input flags */
+#ifndef IUCLC
+# define IUCLC 0
+#endif
+#ifndef IMAXBEL
+# define IMAXBEL 0
+#endif
+
+/* Non-standard termios local flags */
+#ifndef IEXTEN
+# define IEXTEN 0
+#endif
+
+/*
+ * Set termios to raw mode (BSD extension).
+ */
+void
+sudo_cfmakeraw(struct termios *term)
+{
+ /* Set terminal to raw mode */
+ CLR(term->c_iflag,
+ IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IMAXBEL|IUCLC);
+ CLR(term->c_oflag, OPOST);
+ CLR(term->c_lflag, ECHO|ECHONL|ICANON|ISIG|IEXTEN);
+ CLR(term->c_cflag, CSIZE|PARENB);
+ SET(term->c_cflag, CS8);
+ term->c_cc[VMIN] = 1;
+ term->c_cc[VTIME] = 0;
+}
diff --git a/lib/util/chacha_private.h b/lib/util/chacha_private.h
new file mode 100644
index 0000000..7c3680f
--- /dev/null
+++ b/lib/util/chacha_private.h
@@ -0,0 +1,222 @@
+/*
+chacha-merged.c version 20080118
+D. J. Bernstein
+Public domain.
+*/
+
+/* $OpenBSD: chacha_private.h,v 1.2 2013/10/04 07:02:27 djm Exp $ */
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+typedef struct
+{
+ u32 input[16]; /* could be compressed */
+} chacha_ctx;
+
+#define U8C(v) (v##U)
+#define U32C(v) (v##U)
+
+#define U8V(v) ((u8)(v) & U8C(0xFF))
+#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF))
+
+#define ROTL32(v, n) \
+ (U32V((v) << (n)) | ((v) >> (32 - (n))))
+
+#define U8TO32_LITTLE(p) \
+ (((u32)((p)[0]) ) | \
+ ((u32)((p)[1]) << 8) | \
+ ((u32)((p)[2]) << 16) | \
+ ((u32)((p)[3]) << 24))
+
+#define U32TO8_LITTLE(p, v) \
+ do { \
+ (p)[0] = U8V((v) ); \
+ (p)[1] = U8V((v) >> 8); \
+ (p)[2] = U8V((v) >> 16); \
+ (p)[3] = U8V((v) >> 24); \
+ } while (0)
+
+#define ROTATE(v,c) (ROTL32(v,c))
+#define XOR(v,w) ((v) ^ (w))
+#define PLUS(v,w) (U32V((v) + (w)))
+#define PLUSONE(v) (PLUS((v),1))
+
+#define QUARTERROUND(a,b,c,d) \
+ a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \
+ c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \
+ a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \
+ c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
+
+static const char sigma[16] = "expand 32-byte k";
+static const char tau[16] = "expand 16-byte k";
+
+static void
+chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits,u32 ivbits)
+{
+ const char *constants;
+
+ x->input[4] = U8TO32_LITTLE(k + 0);
+ x->input[5] = U8TO32_LITTLE(k + 4);
+ x->input[6] = U8TO32_LITTLE(k + 8);
+ x->input[7] = U8TO32_LITTLE(k + 12);
+ if (kbits == 256) { /* recommended */
+ k += 16;
+ constants = sigma;
+ } else { /* kbits == 128 */
+ constants = tau;
+ }
+ x->input[8] = U8TO32_LITTLE(k + 0);
+ x->input[9] = U8TO32_LITTLE(k + 4);
+ x->input[10] = U8TO32_LITTLE(k + 8);
+ x->input[11] = U8TO32_LITTLE(k + 12);
+ x->input[0] = U8TO32_LITTLE(constants + 0);
+ x->input[1] = U8TO32_LITTLE(constants + 4);
+ x->input[2] = U8TO32_LITTLE(constants + 8);
+ x->input[3] = U8TO32_LITTLE(constants + 12);
+}
+
+static void
+chacha_ivsetup(chacha_ctx *x,const u8 *iv)
+{
+ x->input[12] = 0;
+ x->input[13] = 0;
+ x->input[14] = U8TO32_LITTLE(iv + 0);
+ x->input[15] = U8TO32_LITTLE(iv + 4);
+}
+
+static void
+chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes)
+{
+ u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
+ u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+ u8 *ctarget = NULL;
+ u8 tmp[64];
+ u_int i;
+
+ if (!bytes) return;
+
+ j0 = x->input[0];
+ j1 = x->input[1];
+ j2 = x->input[2];
+ j3 = x->input[3];
+ j4 = x->input[4];
+ j5 = x->input[5];
+ j6 = x->input[6];
+ j7 = x->input[7];
+ j8 = x->input[8];
+ j9 = x->input[9];
+ j10 = x->input[10];
+ j11 = x->input[11];
+ j12 = x->input[12];
+ j13 = x->input[13];
+ j14 = x->input[14];
+ j15 = x->input[15];
+
+ for (;;) {
+ if (bytes < 64) {
+ for (i = 0;i < bytes;++i) tmp[i] = m[i];
+ m = tmp;
+ ctarget = c;
+ c = tmp;
+ }
+ x0 = j0;
+ x1 = j1;
+ x2 = j2;
+ x3 = j3;
+ x4 = j4;
+ x5 = j5;
+ x6 = j6;
+ x7 = j7;
+ x8 = j8;
+ x9 = j9;
+ x10 = j10;
+ x11 = j11;
+ x12 = j12;
+ x13 = j13;
+ x14 = j14;
+ x15 = j15;
+ for (i = 20;i > 0;i -= 2) {
+ QUARTERROUND( x0, x4, x8,x12)
+ QUARTERROUND( x1, x5, x9,x13)
+ QUARTERROUND( x2, x6,x10,x14)
+ QUARTERROUND( x3, x7,x11,x15)
+ QUARTERROUND( x0, x5,x10,x15)
+ QUARTERROUND( x1, x6,x11,x12)
+ QUARTERROUND( x2, x7, x8,x13)
+ QUARTERROUND( x3, x4, x9,x14)
+ }
+ x0 = PLUS(x0,j0);
+ x1 = PLUS(x1,j1);
+ x2 = PLUS(x2,j2);
+ x3 = PLUS(x3,j3);
+ x4 = PLUS(x4,j4);
+ x5 = PLUS(x5,j5);
+ x6 = PLUS(x6,j6);
+ x7 = PLUS(x7,j7);
+ x8 = PLUS(x8,j8);
+ x9 = PLUS(x9,j9);
+ x10 = PLUS(x10,j10);
+ x11 = PLUS(x11,j11);
+ x12 = PLUS(x12,j12);
+ x13 = PLUS(x13,j13);
+ x14 = PLUS(x14,j14);
+ x15 = PLUS(x15,j15);
+
+#ifndef KEYSTREAM_ONLY
+ x0 = XOR(x0,U8TO32_LITTLE(m + 0));
+ x1 = XOR(x1,U8TO32_LITTLE(m + 4));
+ x2 = XOR(x2,U8TO32_LITTLE(m + 8));
+ x3 = XOR(x3,U8TO32_LITTLE(m + 12));
+ x4 = XOR(x4,U8TO32_LITTLE(m + 16));
+ x5 = XOR(x5,U8TO32_LITTLE(m + 20));
+ x6 = XOR(x6,U8TO32_LITTLE(m + 24));
+ x7 = XOR(x7,U8TO32_LITTLE(m + 28));
+ x8 = XOR(x8,U8TO32_LITTLE(m + 32));
+ x9 = XOR(x9,U8TO32_LITTLE(m + 36));
+ x10 = XOR(x10,U8TO32_LITTLE(m + 40));
+ x11 = XOR(x11,U8TO32_LITTLE(m + 44));
+ x12 = XOR(x12,U8TO32_LITTLE(m + 48));
+ x13 = XOR(x13,U8TO32_LITTLE(m + 52));
+ x14 = XOR(x14,U8TO32_LITTLE(m + 56));
+ x15 = XOR(x15,U8TO32_LITTLE(m + 60));
+#endif
+
+ j12 = PLUSONE(j12);
+ if (!j12) {
+ j13 = PLUSONE(j13);
+ /* stopping at 2^70 bytes per nonce is user's responsibility */
+ }
+
+ U32TO8_LITTLE(c + 0,x0);
+ U32TO8_LITTLE(c + 4,x1);
+ U32TO8_LITTLE(c + 8,x2);
+ U32TO8_LITTLE(c + 12,x3);
+ U32TO8_LITTLE(c + 16,x4);
+ U32TO8_LITTLE(c + 20,x5);
+ U32TO8_LITTLE(c + 24,x6);
+ U32TO8_LITTLE(c + 28,x7);
+ U32TO8_LITTLE(c + 32,x8);
+ U32TO8_LITTLE(c + 36,x9);
+ U32TO8_LITTLE(c + 40,x10);
+ U32TO8_LITTLE(c + 44,x11);
+ U32TO8_LITTLE(c + 48,x12);
+ U32TO8_LITTLE(c + 52,x13);
+ U32TO8_LITTLE(c + 56,x14);
+ U32TO8_LITTLE(c + 60,x15);
+
+ if (bytes <= 64) {
+ if (bytes < 64) {
+ for (i = 0;i < bytes;++i) ctarget[i] = c[i];
+ }
+ x->input[12] = j12;
+ x->input[13] = j13;
+ return;
+ }
+ bytes -= 64;
+ c += 64;
+#ifndef KEYSTREAM_ONLY
+ m += 64;
+#endif
+ }
+}
diff --git a/lib/util/closefrom.c b/lib/util/closefrom.c
new file mode 100644
index 0000000..64e9826
--- /dev/null
+++ b/lib/util/closefrom.c
@@ -0,0 +1,183 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2004-2005, 2007, 2010, 2012-2015, 2017-2022
+ * 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>
+
+#ifndef HAVE_CLOSEFROM
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <unistd.h>
+#ifdef HAVE_PSTAT_GETPROC
+# include <sys/pstat.h>
+#else
+# include <dirent.h>
+#endif
+#ifdef HAVE_LIBPROC_H
+# include <libproc.h>
+#endif
+#ifdef HAVE_LINUX_CLOSE_RANGE_H
+# include <linux/close_range.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "pathnames.h"
+
+#ifndef OPEN_MAX
+# define OPEN_MAX 256
+#endif
+
+/* Avoid potential libdispatch crash on macOS when we close its fds. */
+#ifdef __APPLE__
+# define closefrom_close(x) fcntl((x), F_SETFD, FD_CLOEXEC)
+#else
+# define closefrom_close(x) close(x)
+#endif
+
+/*
+ * Close all file descriptors greater than or equal to lowfd.
+ * This is the expensive (fallback) method.
+ */
+static void
+closefrom_fallback(int lowfd)
+{
+ long fd, maxfd;
+
+ /*
+ * Fall back on sysconf(_SC_OPEN_MAX). This is equivalent to
+ * checking the RLIMIT_NOFILE soft limit. It is possible for
+ * there to be open file descriptors past this limit but there's
+ * not much we can do about that since the hard limit may be
+ * RLIM_INFINITY (LLONG_MAX or ULLONG_MAX on modern systems).
+ */
+ maxfd = sysconf(_SC_OPEN_MAX);
+ if (maxfd < OPEN_MAX)
+ maxfd = OPEN_MAX;
+
+ /* Make sure we didn't get RLIM_INFINITY as the upper limit. */
+ if (maxfd > INT_MAX)
+ maxfd = INT_MAX;
+
+ for (fd = lowfd; fd < maxfd; fd++) {
+ (void)closefrom_close((int)fd);
+ }
+}
+
+/*
+ * Close all file descriptors greater than or equal to lowfd.
+ * We try the fast way first, falling back on the slow method.
+ */
+void
+sudo_closefrom(int lowfd)
+{
+#if defined(HAVE_PSTAT_GETPROC)
+ struct pst_status pst;
+#elif defined(HAVE_DIRFD)
+ const char *path;
+ DIR *dirp;
+#endif
+#if defined(HAVE_PROC_PIDINFO)
+ struct proc_fdinfo *buf = NULL;
+ const pid_t pid = getpid();
+ int i, n, len;
+#endif
+
+ /* Try the fast method first, if possible. */
+#if defined(HAVE_FCNTL_CLOSEM)
+ if (fcntl(lowfd, F_CLOSEM, 0) != -1)
+ return;
+#elif defined(HAVE_CLOSE_RANGE)
+ if (close_range(lowfd, ~0U, 0) != -1)
+ return;
+#elif defined(HAVE_PROC_PIDINFO)
+ len = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0);
+ switch (len) {
+ case 0:
+ /* No open files. */
+ return;
+ case -1:
+ /* Fall back on other methods. */
+ break;
+ default:
+ /* Allocate space for 4 extra fds to leave some wiggle room. */
+ buf = malloc(len + (PROC_PIDLISTFD_SIZE * 4));
+ if (buf == NULL)
+ break;
+ n = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, buf, len);
+ if (n == -1 || n > len) {
+ free(buf);
+ break;
+ }
+ n /= PROC_PIDLISTFD_SIZE;
+ for (i = 0; i < n; i++) {
+ if (buf[i].proc_fd >= lowfd) {
+ (void)closefrom_close(buf[i].proc_fd);
+ }
+ }
+ free(buf);
+ return;
+ }
+#endif /* HAVE_PROC_PIDINFO */
+#if defined(HAVE_PSTAT_GETPROC)
+ /*
+ * EOVERFLOW is not a fatal error for the fields we use.
+ * See the "EOVERFLOW Error" section of pstat_getvminfo(3).
+ */
+ if (pstat_getproc(&pst, sizeof(pst), 0, getpid()) != -1 ||
+ errno == EOVERFLOW) {
+ int fd;
+
+ for (fd = lowfd; fd <= pst.pst_highestfd; fd++)
+ (void)closefrom_close(fd);
+ return;
+ }
+#elif defined(HAVE_DIRFD)
+ /* Use /proc/self/fd (or /dev/fd on macOS) if it exists. */
+# ifdef __APPLE__
+ path = _PATH_DEV "fd";
+# else
+ path = "/proc/self/fd";
+# endif
+ if ((dirp = opendir(path)) != NULL) {
+ struct dirent *dent;
+ while ((dent = readdir(dirp)) != NULL) {
+ const char *errstr;
+ int fd = sudo_strtonum(dent->d_name, lowfd, INT_MAX, &errstr);
+ if (errstr == NULL && fd != dirfd(dirp)) {
+ (void)closefrom_close(fd);
+ }
+ }
+ (void)closedir(dirp);
+ return;
+ }
+#endif /* HAVE_DIRFD */
+
+ /* Do things the slow way. */
+ closefrom_fallback(lowfd);
+}
+
+#endif /* HAVE_CLOSEFROM */
diff --git a/lib/util/digest.c b/lib/util/digest.c
new file mode 100644
index 0000000..b48bc48
--- /dev/null
+++ b/lib/util/digest.c
@@ -0,0 +1,165 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-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 <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <unistd.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_digest.h"
+
+#ifdef HAVE_SHA224UPDATE
+# include <sha2.h>
+#else
+# include "compat/sha2.h"
+#endif
+
+static struct digest_function {
+ const unsigned int digest_len;
+ void (*init)(SHA2_CTX *);
+#ifdef SHA2_VOID_PTR
+ void (*update)(SHA2_CTX *, const void *, size_t);
+ void (*final)(void *, SHA2_CTX *);
+#else
+ void (*update)(SHA2_CTX *, const unsigned char *, size_t);
+ void (*final)(unsigned char *, SHA2_CTX *);
+#endif
+} digest_functions[] = {
+ {
+ SHA224_DIGEST_LENGTH,
+ SHA224Init,
+ SHA224Update,
+ SHA224Final
+ }, {
+ SHA256_DIGEST_LENGTH,
+ SHA256Init,
+ SHA256Update,
+ SHA256Final
+ }, {
+ SHA384_DIGEST_LENGTH,
+ SHA384Init,
+ SHA384Update,
+ SHA384Final
+ }, {
+ SHA512_DIGEST_LENGTH,
+ SHA512Init,
+ SHA512Update,
+ SHA512Final
+ }, {
+ 0
+ }
+};
+
+struct sudo_digest {
+ struct digest_function *func;
+ SHA2_CTX ctx;
+};
+
+struct sudo_digest *
+sudo_digest_alloc_v1(int digest_type)
+{
+ debug_decl(sudo_digest_alloc, SUDO_DEBUG_UTIL);
+ struct digest_function *func = NULL;
+ struct sudo_digest *dig;
+ int i;
+
+ for (i = 0; digest_functions[i].digest_len != 0; i++) {
+ if (digest_type == i) {
+ func = &digest_functions[i];
+ break;
+ }
+ }
+ if (func == NULL) {
+ errno = EINVAL;
+ debug_return_ptr(NULL);
+ }
+
+ if ((dig = malloc(sizeof(*dig))) == NULL)
+ debug_return_ptr(NULL);
+ func->init(&dig->ctx);
+ dig->func = func;
+
+ debug_return_ptr(dig);
+}
+
+void
+sudo_digest_free_v1(struct sudo_digest *dig)
+{
+ debug_decl(sudo_digest_free, SUDO_DEBUG_UTIL);
+
+ free(dig);
+
+ debug_return;
+}
+
+void
+sudo_digest_reset_v1(struct sudo_digest *dig)
+{
+ debug_decl(sudo_digest_reset, SUDO_DEBUG_UTIL);
+
+ dig->func->init(&dig->ctx);
+
+ debug_return;
+}
+
+int
+sudo_digest_getlen_v1(int digest_type)
+{
+ debug_decl(sudo_digest_getlen, SUDO_DEBUG_UTIL);
+ int i;
+
+ for (i = 0; digest_functions[i].digest_len != 0; i++) {
+ if (digest_type == i)
+ debug_return_int(digest_functions[i].digest_len);
+ }
+
+ debug_return_int(-1);
+}
+
+void
+sudo_digest_update_v1(struct sudo_digest *dig, const void *data, size_t len)
+{
+ debug_decl(sudo_digest_update, SUDO_DEBUG_UTIL);
+
+ dig->func->update(&dig->ctx, data, len);
+
+ debug_return;
+}
+
+void
+sudo_digest_final_v1(struct sudo_digest *dig, unsigned char *md)
+{
+ debug_decl(sudo_digest_final, SUDO_DEBUG_UTIL);
+
+ dig->func->final(md, &dig->ctx);
+
+ debug_return;
+}
diff --git a/lib/util/digest_gcrypt.c b/lib/util/digest_gcrypt.c
new file mode 100644
index 0000000..075e272
--- /dev/null
+++ b/lib/util/digest_gcrypt.c
@@ -0,0 +1,160 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2017-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>
+
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <gcrypt.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_digest.h"
+
+#define SUDO_LIBGCRYPT_VERSION_MIN "1.3.0"
+
+struct sudo_digest {
+ int gcry_digest_type;
+ unsigned int digest_len;
+ gcry_md_hd_t ctx;
+};
+
+/* Map sudo digest type to gcrypt digest type. */
+static int
+sudo_digest_type_to_gcry(int digest_type)
+{
+ switch (digest_type) {
+ case SUDO_DIGEST_SHA224:
+ return GCRY_MD_SHA224;
+ case SUDO_DIGEST_SHA256:
+ return GCRY_MD_SHA256;
+ case SUDO_DIGEST_SHA384:
+ return GCRY_MD_SHA384;
+ case SUDO_DIGEST_SHA512:
+ return GCRY_MD_SHA512;
+ default:
+ return -1;
+ }
+}
+
+struct sudo_digest *
+sudo_digest_alloc_v1(int digest_type)
+{
+ debug_decl(sudo_digest_alloc, SUDO_DEBUG_UTIL);
+ static bool initialized = false;
+ struct sudo_digest *dig;
+ int gcry_digest_type;
+
+ if (!initialized) {
+ if (!gcry_check_version(SUDO_LIBGCRYPT_VERSION_MIN)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "libgcrypt too old (need %s, have %s)",
+ SUDO_LIBGCRYPT_VERSION_MIN, gcry_check_version(NULL));
+ debug_return_ptr(NULL);
+ }
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+ initialized = true;
+ }
+
+ gcry_digest_type = sudo_digest_type_to_gcry(digest_type);
+ if (gcry_digest_type == -1) {
+ errno = EINVAL;
+ debug_return_ptr(NULL);
+ }
+
+ if ((dig = malloc(sizeof(*dig))) == NULL)
+ debug_return_ptr(NULL);
+ dig->gcry_digest_type = gcry_digest_type;
+ dig->digest_len = gcry_md_get_algo_dlen(gcry_digest_type);
+
+ if (gcry_md_open(&dig->ctx, gcry_digest_type, 0) != 0) {
+ free(dig);
+ debug_return_ptr(NULL);
+ }
+
+ debug_return_ptr(dig);
+}
+
+void
+sudo_digest_free_v1(struct sudo_digest *dig)
+{
+ debug_decl(sudo_digest_free, SUDO_DEBUG_UTIL);
+
+ if (dig != NULL) {
+ gcry_md_close(dig->ctx);
+ free(dig);
+ }
+
+ debug_return;
+}
+
+void
+sudo_digest_reset_v1(struct sudo_digest *dig)
+{
+ debug_decl(sudo_digest_reset, SUDO_DEBUG_UTIL);
+
+ gcry_md_reset(dig->ctx);
+
+ debug_return;
+}
+
+int
+sudo_digest_getlen_v1(int digest_type)
+{
+ debug_decl(sudo_digest_getlen, SUDO_DEBUG_UTIL);
+ int gcry_digest_type;
+
+ gcry_digest_type = sudo_digest_type_to_gcry(digest_type);
+ if (gcry_digest_type == -1)
+ debug_return_int(-1);
+
+ debug_return_int(gcry_md_get_algo_dlen(gcry_digest_type));
+}
+
+void
+sudo_digest_update_v1(struct sudo_digest *dig, const void *data, size_t len)
+{
+ debug_decl(sudo_digest_update, SUDO_DEBUG_UTIL);
+
+ gcry_md_write(dig->ctx, data, len);
+
+ debug_return;
+}
+
+void
+sudo_digest_final_v1(struct sudo_digest *dig, unsigned char *md)
+{
+ debug_decl(sudo_digest_final, SUDO_DEBUG_UTIL);
+
+ gcry_md_final(dig->ctx);
+ memcpy(md, gcry_md_read(dig->ctx, 0), dig->digest_len);
+
+ debug_return;
+}
diff --git a/lib/util/digest_openssl.c b/lib/util/digest_openssl.c
new file mode 100644
index 0000000..20be734
--- /dev/null
+++ b/lib/util/digest_openssl.c
@@ -0,0 +1,152 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <errno.h>
+
+#if defined(HAVE_WOLFSSL)
+# include <wolfssl/options.h>
+#endif
+#include <openssl/evp.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_digest.h"
+
+struct sudo_digest {
+ EVP_MD_CTX *ctx;
+ const EVP_MD *md;
+};
+
+static const EVP_MD *
+sudo_digest_type_to_md(int digest_type)
+{
+ const EVP_MD *md = NULL;
+ debug_decl(sudo_digest_type_to_md, SUDO_DEBUG_UTIL);
+
+ switch (digest_type) {
+ case SUDO_DIGEST_SHA224:
+ md = EVP_sha224();
+ break;
+ case SUDO_DIGEST_SHA256:
+ md = EVP_sha256();
+ break;
+ case SUDO_DIGEST_SHA384:
+ md = EVP_sha384();
+ break;
+ case SUDO_DIGEST_SHA512:
+ md = EVP_sha512();
+ break;
+ default:
+ errno = EINVAL;
+ break;
+ }
+ debug_return_const_ptr(md);
+}
+
+struct sudo_digest *
+sudo_digest_alloc_v1(int digest_type)
+{
+ struct sudo_digest *dig;
+ EVP_MD_CTX *mdctx = NULL;
+ const EVP_MD *md;
+ debug_decl(sudo_digest_alloc, SUDO_DEBUG_UTIL);
+
+ md = sudo_digest_type_to_md(digest_type);
+ if (md == NULL)
+ goto bad;
+
+ mdctx = EVP_MD_CTX_new();
+ if (mdctx == NULL || !EVP_DigestInit_ex(mdctx, md, NULL))
+ goto bad;
+
+ if ((dig = malloc(sizeof(*dig))) == NULL)
+ goto bad;
+ dig->md = md;
+ dig->ctx = mdctx;
+
+ debug_return_ptr(dig);
+bad:
+ EVP_MD_CTX_free(mdctx);
+ debug_return_ptr(NULL);
+}
+
+void
+sudo_digest_free_v1(struct sudo_digest *dig)
+{
+ debug_decl(sudo_digest_free, SUDO_DEBUG_UTIL);
+
+ if (dig != NULL) {
+ EVP_MD_CTX_free(dig->ctx);
+ free(dig);
+ }
+
+ debug_return;
+}
+
+void
+sudo_digest_reset_v1(struct sudo_digest *dig)
+{
+ debug_decl(sudo_digest_reset, SUDO_DEBUG_UTIL);
+
+ /* These cannot fail. */
+ EVP_MD_CTX_reset(dig->ctx);
+ EVP_DigestInit_ex(dig->ctx, dig->md, NULL);
+
+ debug_return;
+}
+
+int
+sudo_digest_getlen_v1(int digest_type)
+{
+ const EVP_MD *md;
+ debug_decl(sudo_digest_getlen, SUDO_DEBUG_UTIL);
+
+ md = sudo_digest_type_to_md(digest_type);
+ if (md == NULL)
+ debug_return_int(-1);
+
+ debug_return_int(EVP_MD_size(md));
+}
+
+void
+sudo_digest_update_v1(struct sudo_digest *dig, const void *data, size_t len)
+{
+ debug_decl(sudo_digest_update, SUDO_DEBUG_UTIL);
+
+ EVP_DigestUpdate(dig->ctx, data, len);
+
+ debug_return;
+}
+
+void
+sudo_digest_final_v1(struct sudo_digest *dig, unsigned char *md)
+{
+ debug_decl(sudo_digest_final, SUDO_DEBUG_UTIL);
+
+ EVP_DigestFinal_ex(dig->ctx, md, NULL);
+
+ debug_return;
+}
diff --git a/lib/util/dup3.c b/lib/util/dup3.c
new file mode 100644
index 0000000..68d6ebf
--- /dev/null
+++ b/lib/util/dup3.c
@@ -0,0 +1,74 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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>
+
+#ifndef HAVE_DUP3
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+int
+sudo_dup3(int oldd, int newd, int flags)
+{
+ int oflags;
+
+ if (oldd == newd) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (dup2(oldd, newd) == -1)
+ return -1;
+
+ oflags = fcntl(newd, F_GETFL, 0);
+ if (oflags == -1)
+ goto bad;
+
+ if (ISSET(flags, O_NONBLOCK)) {
+ if (!ISSET(oflags, O_NONBLOCK)) {
+ SET(oflags, O_NONBLOCK);
+ if (fcntl(newd, F_SETFL, oflags) == -1)
+ goto bad;
+ }
+ } else {
+ if (ISSET(oflags, O_NONBLOCK)) {
+ CLR(oflags, O_NONBLOCK);
+ if (fcntl(newd, F_SETFL, oflags) == -1)
+ goto bad;
+ }
+ }
+ if (ISSET(flags, O_CLOEXEC)) {
+ if (fcntl(newd, F_SETFD, FD_CLOEXEC) == -1)
+ goto bad;
+ }
+ return 0;
+bad:
+ close(newd);
+ return -1;
+}
+
+#endif /* HAVE_DUP3 */
diff --git a/lib/util/event.c b/lib/util/event.c
new file mode 100644
index 0000000..4192770
--- /dev/null
+++ b/lib/util/event.c
@@ -0,0 +1,862 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-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 <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_debug.h"
+#include "sudo_event.h"
+#include "sudo_util.h"
+
+static void sudo_ev_init(struct sudo_event *ev, int fd, short events,
+ sudo_ev_callback_t callback, void *closure);
+
+/* Default event base when none is specified. */
+static struct sudo_event_base *default_base;
+
+/* We need the event base to be available from the signal handler. */
+static struct sudo_event_base *signal_base;
+
+/*
+ * Add an event to the base's active queue and mark it active.
+ * This is extern so sudo_ev_scan_impl() can call it.
+ */
+void
+sudo_ev_activate(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ TAILQ_INSERT_TAIL(&base->active, ev, active_entries);
+ SET(ev->flags, SUDO_EVQ_ACTIVE);
+}
+
+/*
+ * Remove an event from the base's active queue and mark it inactive.
+ */
+static inline void
+sudo_ev_deactivate(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ CLR(ev->flags, SUDO_EVQ_ACTIVE);
+ TAILQ_REMOVE(&base->active, ev, active_entries);
+}
+
+/*
+ * Clear out the base's active queue and mark all events as inactive.
+ */
+static void
+sudo_ev_deactivate_all(struct sudo_event_base *base)
+{
+ struct sudo_event *ev;
+ debug_decl(sudo_ev_deactivate_all, SUDO_DEBUG_EVENT);
+
+ while ((ev = TAILQ_FIRST(&base->active)) != NULL)
+ sudo_ev_deactivate(base, ev);
+
+ debug_return;
+}
+
+/*
+ * Activate all signal events for which the corresponding signal_pending[]
+ * flag is set.
+ */
+static void
+sudo_ev_activate_sigevents(struct sudo_event_base *base)
+{
+ struct sudo_event *ev;
+ sigset_t set, oset;
+ int i;
+ debug_decl(sudo_ev_activate_sigevents, SUDO_DEBUG_EVENT);
+
+ /*
+ * We treat this as a critical section since the signal handler
+ * could modify the siginfo[] entry.
+ */
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oset);
+ base->signal_caught = 0;
+ for (i = 0; i < NSIG; i++) {
+ if (!base->signal_pending[i])
+ continue;
+ base->signal_pending[i] = 0;
+ TAILQ_FOREACH(ev, &base->signals[i], entries) {
+ if (ISSET(ev->events, SUDO_EV_SIGINFO)) {
+ struct sudo_ev_siginfo_container *sc = ev->closure;
+ if (base->siginfo[i]->si_signo == 0) {
+ /* No siginfo available. */
+ sc->siginfo = NULL;
+ } else {
+ sc->siginfo = (siginfo_t *)sc->si_buf;
+ memcpy(sc->siginfo, base->siginfo[i], sizeof(siginfo_t));
+ }
+ }
+ /* Make event active. */
+ ev->revents = ev->events & (SUDO_EV_SIGNAL|SUDO_EV_SIGINFO);
+ TAILQ_INSERT_TAIL(&base->active, ev, active_entries);
+ SET(ev->flags, SUDO_EVQ_ACTIVE);
+ }
+ }
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+
+ debug_return;
+}
+
+/*
+ * Internal callback for SUDO_EV_SIGNAL and SUDO_EV_SIGINFO.
+ */
+static void
+signal_pipe_cb(int fd, int what, void *v)
+{
+ struct sudo_event_base *base = v;
+ unsigned char ch;
+ ssize_t nread;
+ debug_decl(signal_pipe_cb, SUDO_DEBUG_EVENT);
+
+ /*
+ * Drain signal_pipe, the signal handler updated base->signals_pending.
+ * Actual processing of signal events is done when poll/select is
+ * interrupted by a signal.
+ */
+ while ((nread = read(fd, &ch, 1)) > 0) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: received signal %d", __func__, (int)ch);
+ }
+ if (nread == -1 && errno != EAGAIN) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "%s: error reading from signal pipe fd %d", __func__, fd);
+ }
+
+ /* Activate signal events. */
+ sudo_ev_activate_sigevents(base);
+
+ debug_return;
+}
+
+static int
+sudo_ev_base_init(struct sudo_event_base *base)
+{
+ int i;
+ debug_decl(sudo_ev_base_init, SUDO_DEBUG_EVENT);
+
+ TAILQ_INIT(&base->events);
+ TAILQ_INIT(&base->timeouts);
+ for (i = 0; i < NSIG; i++)
+ TAILQ_INIT(&base->signals[i]);
+ if (sudo_ev_base_alloc_impl(base) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: unable to allocate impl base", __func__);
+ goto bad;
+ }
+ if (pipe2(base->signal_pipe, O_NONBLOCK|O_CLOEXEC) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: unable to create signal pipe", __func__);
+ goto bad;
+ }
+ sudo_ev_init(&base->signal_event, base->signal_pipe[0],
+ SUDO_EV_READ|SUDO_EV_PERSIST, signal_pipe_cb, base);
+
+ debug_return_int(0);
+bad:
+ /* Note: signal_pipe[] not filled in. */
+ sudo_ev_base_free_impl(base);
+ debug_return_int(-1);
+}
+
+struct sudo_event_base *
+sudo_ev_base_alloc_v1(void)
+{
+ struct sudo_event_base *base;
+ debug_decl(sudo_ev_base_alloc, SUDO_DEBUG_EVENT);
+
+ base = calloc(1, sizeof(*base));
+ if (base == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate base", __func__);
+ debug_return_ptr(NULL);
+ }
+ if (sudo_ev_base_init(base) != 0) {
+ free(base);
+ debug_return_ptr(NULL);
+ }
+ debug_return_ptr(base);
+}
+
+void
+sudo_ev_base_free_v1(struct sudo_event_base *base)
+{
+ struct sudo_event *ev, *next;
+ int i;
+ debug_decl(sudo_ev_base_free, SUDO_DEBUG_EVENT);
+
+ if (base == NULL)
+ debug_return;
+
+ /* Reset the default base if necessary. */
+ if (default_base == base)
+ default_base = NULL;
+
+ /* Remove any existing events before freeing the base. */
+ TAILQ_FOREACH_SAFE(ev, &base->events, entries, next) {
+ sudo_ev_del(base, ev);
+ ev->base = NULL;
+ }
+ for (i = 0; i < NSIG; i++) {
+ TAILQ_FOREACH_SAFE(ev, &base->signals[i], entries, next) {
+ sudo_ev_del(base, ev);
+ ev->base = NULL;
+ }
+ free(base->siginfo[i]);
+ free(base->orig_handlers[i]);
+ }
+ sudo_ev_base_free_impl(base);
+ close(base->signal_pipe[0]);
+ close(base->signal_pipe[1]);
+ free(base);
+
+ debug_return;
+}
+
+void
+sudo_ev_base_setdef_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_base_setdef, SUDO_DEBUG_EVENT);
+
+ default_base = base;
+
+ debug_return;
+}
+
+/*
+ * Clear and fill in a struct sudo_event.
+ */
+static void
+sudo_ev_init(struct sudo_event *ev, int fd, short events,
+ sudo_ev_callback_t callback, void *closure)
+{
+ debug_decl(sudo_ev_init, SUDO_DEBUG_EVENT);
+
+ memset(ev, 0, sizeof(*ev));
+ ev->fd = fd;
+ ev->events = events & SUDO_EV_MASK;
+ ev->pfd_idx = -1;
+ ev->callback = callback;
+ ev->closure = closure;
+
+ debug_return;
+}
+
+/*
+ * Set a pre-allocated struct sudo_event.
+ * Allocates space for siginfo_t for SUDO_EV_SIGINFO as needed.
+ */
+int
+sudo_ev_set_v1(struct sudo_event *ev, int fd, short events,
+ sudo_ev_callback_t callback, void *closure)
+{
+ debug_decl(sudo_ev_set, SUDO_DEBUG_EVENT);
+
+ /* For SUDO_EV_SIGINFO we use a container to store closure + siginfo_t */
+ if (ISSET(events, SUDO_EV_SIGINFO)) {
+ struct sudo_ev_siginfo_container *container =
+ malloc(sizeof(*container) + sizeof(siginfo_t) - 1);
+ if (container == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate siginfo container", __func__);
+ debug_return_int(-1);
+ }
+ container->closure = closure;
+ closure = container;
+ }
+ sudo_ev_init(ev, fd, events, callback, closure);
+
+ debug_return_int(0);
+}
+
+struct sudo_event *
+sudo_ev_alloc_v1(int fd, short events, sudo_ev_callback_t callback, void *closure)
+{
+ struct sudo_event *ev;
+ debug_decl(sudo_ev_alloc, SUDO_DEBUG_EVENT);
+
+ ev = malloc(sizeof(*ev));
+ if (ev == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate event", __func__);
+ debug_return_ptr(NULL);
+ }
+ if (sudo_ev_set(ev, fd, events, callback, closure) == -1) {
+ free(ev);
+ debug_return_ptr(NULL);
+ }
+ debug_return_ptr(ev);
+}
+
+void
+sudo_ev_free_v1(struct sudo_event *ev)
+{
+ debug_decl(sudo_ev_free, SUDO_DEBUG_EVENT);
+
+ if (ev == NULL)
+ debug_return;
+
+ /* Make sure ev is not in use before freeing it. */
+ if (ISSET(ev->flags, SUDO_EVQ_INSERTED))
+ (void)sudo_ev_del(NULL, ev);
+ if (ISSET(ev->events, SUDO_EV_SIGINFO))
+ free(ev->closure);
+ free(ev);
+
+ debug_return;
+}
+
+static void
+sudo_ev_handler(int signo, siginfo_t *info, void *context)
+{
+ unsigned char ch = (unsigned char)signo;
+
+ if (signal_base != NULL) {
+ /*
+ * Update signals_pending[] and siginfo[].
+ * All signals must be blocked any time siginfo[] is accessed.
+ * If no siginfo available, zero out the struct in base.
+ */
+ if (info == NULL)
+ memset(signal_base->siginfo[signo], 0, sizeof(*info));
+ else
+ memcpy(signal_base->siginfo[signo], info, sizeof(*info));
+ signal_base->signal_pending[signo] = 1;
+ signal_base->signal_caught = 1;
+
+ /* Wake up the other end of the pipe. */
+ ignore_result(write(signal_base->signal_pipe[1], &ch, 1));
+ }
+}
+
+static int
+sudo_ev_add_signal(struct sudo_event_base *base, struct sudo_event *ev,
+ bool tohead)
+{
+ const int signo = ev->fd;
+ debug_decl(sudo_ev_add_signal, SUDO_DEBUG_EVENT);
+
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: adding event %p to base %p, signal %d, events %d",
+ __func__, ev, base, signo, ev->events);
+ if (signo >= NSIG) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: signo %d larger than max %d", __func__, signo, NSIG - 1);
+ debug_return_int(-1);
+ }
+ if ((ev->events & ~(SUDO_EV_SIGNAL|SUDO_EV_SIGINFO|SUDO_EV_PERSIST)) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: invalid event set 0x%x", __func__, ev->events);
+ debug_return_int(-1);
+ }
+
+ /*
+ * Allocate base->siginfo[signo] and base->orig_handlers[signo] as needed.
+ */
+ if (base->siginfo[signo] == NULL) {
+ base->siginfo[signo] = malloc(sizeof(*base->siginfo[signo]));
+ if (base->siginfo[signo] == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate siginfo for signo %d",
+ __func__, signo);
+ debug_return_int(-1);
+ }
+ }
+ if (base->orig_handlers[signo] == NULL) {
+ base->orig_handlers[signo] =
+ malloc(sizeof(*base->orig_handlers[signo]));
+ if (base->orig_handlers[signo] == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate orig_handlers for signo %d",
+ __func__, signo);
+ debug_return_int(-1);
+ }
+ }
+
+ /* Install signal handler as needed, saving the original value. */
+ if (TAILQ_EMPTY(&base->signals[signo])) {
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART|SA_SIGINFO;
+ sa.sa_sigaction = sudo_ev_handler;
+ if (sigaction(signo, &sa, base->orig_handlers[signo]) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to install handler for signo %d", __func__, signo);
+ debug_return_int(-1);
+ }
+ base->num_handlers++;
+ }
+
+ /*
+ * Insert signal event into the proper tail queue.
+ * Signal events are always persistent.
+ */
+ ev->base = base;
+ if (tohead) {
+ TAILQ_INSERT_HEAD(&base->signals[signo], ev, entries);
+ } else {
+ TAILQ_INSERT_TAIL(&base->signals[signo], ev, entries);
+ }
+ SET(ev->events, SUDO_EV_PERSIST);
+ SET(ev->flags, SUDO_EVQ_INSERTED);
+
+ /* Add the internal signal_pipe event on demand. */
+ if (!ISSET(base->signal_event.flags, SUDO_EVQ_INSERTED))
+ sudo_ev_add(base, &base->signal_event, NULL, true);
+
+ /* Update global signal base so handler to update signals_pending[] */
+ signal_base = base;
+
+ debug_return_int(0);
+}
+
+int
+sudo_ev_add_v1(struct sudo_event_base *base, struct sudo_event *ev,
+ const struct timeval *timo, bool tohead)
+{
+ struct timespec tsbuf, *ts = NULL;
+
+ if (timo != NULL) {
+ TIMEVAL_TO_TIMESPEC(timo, &tsbuf);
+ ts = &tsbuf;
+ }
+
+ return sudo_ev_add_v2(base, ev, ts, tohead);
+}
+
+int
+sudo_ev_add_v2(struct sudo_event_base *base, struct sudo_event *ev,
+ const struct timespec *timo, bool tohead)
+{
+ debug_decl(sudo_ev_add, SUDO_DEBUG_EVENT);
+
+ /* If no base specified, use existing or default base. */
+ if (base == NULL) {
+ if (ev->base != NULL) {
+ base = ev->base;
+ } else if (default_base != NULL) {
+ base = default_base;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: no base specified",
+ __func__);
+ debug_return_int(-1);
+ }
+ }
+
+ /* Only add new events to the events list. */
+ if (ISSET(ev->flags, SUDO_EVQ_INSERTED)) {
+ /* If event no longer has a timeout, remove from timeouts queue. */
+ if (timo == NULL && ISSET(ev->flags, SUDO_EVQ_TIMEOUTS)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: removing event %p from timeouts queue", __func__, ev);
+ CLR(ev->flags, SUDO_EVQ_TIMEOUTS);
+ TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries);
+ }
+ } else {
+ /* Special handling for signal events. */
+ if (ev->events & (SUDO_EV_SIGNAL|SUDO_EV_SIGINFO))
+ debug_return_int(sudo_ev_add_signal(base, ev, tohead));
+
+ /* Add event to the base. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: adding event %p to base %p, fd %d, events %d",
+ __func__, ev, base, ev->fd, ev->events);
+ if (ev->events & (SUDO_EV_READ|SUDO_EV_WRITE)) {
+ if (sudo_ev_add_impl(base, ev) != 0)
+ debug_return_int(-1);
+ }
+ ev->base = base;
+ if (tohead) {
+ TAILQ_INSERT_HEAD(&base->events, ev, entries);
+ } else {
+ TAILQ_INSERT_TAIL(&base->events, ev, entries);
+ }
+ SET(ev->flags, SUDO_EVQ_INSERTED);
+ }
+ /* Timeouts can be changed for existing events. */
+ if (timo != NULL) {
+ struct sudo_event *evtmp;
+ if (ISSET(ev->flags, SUDO_EVQ_TIMEOUTS)) {
+ /* Remove from timeouts list, then add back. */
+ TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries);
+ }
+ /* Convert to absolute time and insert in sorted order; O(n). */
+ sudo_gettime_mono(&ev->timeout);
+ sudo_timespecadd(&ev->timeout, timo, &ev->timeout);
+ TAILQ_FOREACH(evtmp, &base->timeouts, timeouts_entries) {
+ if (sudo_timespeccmp(&ev->timeout, &evtmp->timeout, <))
+ break;
+ }
+ if (evtmp != NULL) {
+ TAILQ_INSERT_BEFORE(evtmp, ev, timeouts_entries);
+ } else {
+ TAILQ_INSERT_TAIL(&base->timeouts, ev, timeouts_entries);
+ }
+ SET(ev->flags, SUDO_EVQ_TIMEOUTS);
+ }
+ debug_return_int(0);
+}
+
+/*
+ * Remove an event from the base, if specified, or the base embedded
+ * in the event if not. Note that there are multiple tail queues.
+ */
+int
+sudo_ev_del_v1(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ debug_decl(sudo_ev_del, SUDO_DEBUG_EVENT);
+
+ /* Make sure event is really in the queue. */
+ if (!ISSET(ev->flags, SUDO_EVQ_INSERTED)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: event %p not in queue",
+ __func__, ev);
+ debug_return_int(0);
+ }
+
+ /* Check for event base mismatch, if one is specified. */
+ if (base == NULL) {
+ if (ev->base == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: no base specified",
+ __func__);
+ debug_return_int(-1);
+ }
+ base = ev->base;
+ } else if (base != ev->base) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: mismatch base %p, ev->base %p",
+ __func__, base, ev->base);
+ debug_return_int(-1);
+ }
+
+ if (ev->events & (SUDO_EV_SIGNAL|SUDO_EV_SIGINFO)) {
+ const int signo = ev->fd;
+
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: removing event %p from base %p, signo %d, events %d",
+ __func__, ev, base, signo, ev->events);
+
+ /* Unlink from signal event list. */
+ TAILQ_REMOVE(&base->signals[signo], ev, entries);
+ if (TAILQ_EMPTY(&base->signals[signo])) {
+ if (sigaction(signo, base->orig_handlers[signo], NULL) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to restore handler for signo %d",
+ __func__, signo);
+ debug_return_int(-1);
+ }
+ base->num_handlers--;
+ }
+ if (base->num_handlers == 0) {
+ /* No registered signal events, remove internal event. */
+ sudo_ev_del(base, &base->signal_event);
+ }
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: removing event %p from base %p, fd %d, events %d",
+ __func__, ev, base, ev->fd, ev->events);
+
+ /* Call backend. */
+ if (ev->events & (SUDO_EV_READ|SUDO_EV_WRITE)) {
+ if (sudo_ev_del_impl(base, ev) != 0)
+ debug_return_int(-1);
+ }
+
+ /* Unlink from event list. */
+ TAILQ_REMOVE(&base->events, ev, entries);
+
+ /* Unlink from timeouts list. */
+ if (ISSET(ev->flags, SUDO_EVQ_TIMEOUTS))
+ TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries);
+ }
+
+ /* Unlink from active list. */
+ if (ISSET(ev->flags, SUDO_EVQ_ACTIVE))
+ TAILQ_REMOVE(&base->active, ev, active_entries);
+
+ /* Mark event unused. */
+ ev->flags = 0;
+ ev->pfd_idx = -1;
+
+ debug_return_int(0);
+}
+
+int
+sudo_ev_dispatch_v1(struct sudo_event_base *base)
+{
+ return sudo_ev_loop_v1(base, 0);
+}
+
+/*
+ * Run main event loop.
+ * Returns 0 on success, 1 if no events registered and -1 on error
+ */
+int
+sudo_ev_loop_v1(struct sudo_event_base *base, int flags)
+{
+ struct timespec now;
+ struct sudo_event *ev;
+ int nready, rc = 0;
+ debug_decl(sudo_ev_loop, SUDO_DEBUG_EVENT);
+
+ /*
+ * If sudo_ev_loopexit() was called when events were not running
+ * the next invocation of sudo_ev_loop() only runs once.
+ * All other base flags are ignored unless we are running events.
+ * Note that SUDO_EVLOOP_ONCE and SUDO_EVBASE_LOOPONCE are equivalent.
+ */
+ base->flags |= (flags & SUDO_EVLOOP_ONCE);
+ base->flags &= (SUDO_EVBASE_LOOPEXIT|SUDO_EVBASE_LOOPONCE);
+
+ for (;;) {
+rescan:
+ /* Make sure we have some events. */
+ if (TAILQ_EMPTY(&base->events)) {
+ rc = 1;
+ break;
+ }
+
+ /* Call backend to scan for I/O events. */
+ TAILQ_INIT(&base->active);
+ nready = sudo_ev_scan_impl(base, flags);
+ switch (nready) {
+ case -1:
+ if (errno == ENOMEM || errno == EAGAIN)
+ continue;
+ if (errno == EINTR) {
+ /* Interrupted by signal, check for sigevents. */
+ if (base->signal_caught) {
+ signal_pipe_cb(base->signal_pipe[0], SUDO_EV_READ, base);
+ break;
+ }
+ continue;
+ }
+ rc = -1;
+ goto done;
+ case 0:
+ /* Timed out, activate timeout events. */
+ sudo_gettime_mono(&now);
+ while ((ev = TAILQ_FIRST(&base->timeouts)) != NULL) {
+ if (sudo_timespeccmp(&ev->timeout, &now, >))
+ break;
+ /* Remove from timeouts list. */
+ CLR(ev->flags, SUDO_EVQ_TIMEOUTS);
+ TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries);
+ /* Make event active. */
+ ev->revents = SUDO_EV_TIMEOUT;
+ TAILQ_INSERT_TAIL(&base->active, ev, active_entries);
+ SET(ev->flags, SUDO_EVQ_ACTIVE);
+ }
+ if (ISSET(flags, SUDO_EVLOOP_NONBLOCK)) {
+ /* If nonblocking, return immediately if no active events. */
+ if (TAILQ_EMPTY(&base->active))
+ goto done;
+ }
+ break;
+ default:
+ /* I/O events active, sudo_ev_scan_impl() already added them. */
+ break;
+ }
+
+ /*
+ * Service each event in the active queue.
+ * We store the current event pointer in the base so that
+ * it can be cleared by sudo_ev_del(). This prevents a use
+ * after free if the callback frees its own event.
+ */
+ while ((ev = TAILQ_FIRST(&base->active)) != NULL) {
+ /* Pop first event off the active queue. */
+ sudo_ev_deactivate(base, ev);
+ /* Remove from base unless persistent. */
+ if (!ISSET(ev->events, SUDO_EV_PERSIST))
+ sudo_ev_del(base, ev);
+ ev->callback(ev->fd, ev->revents,
+ ev->closure == sudo_ev_self_cbarg() ? ev : ev->closure);
+ if (ISSET(base->flags, SUDO_EVBASE_LOOPBREAK)) {
+ /* Stop processing events immediately. */
+ SET(base->flags, SUDO_EVBASE_GOT_BREAK);
+ sudo_ev_deactivate_all(base);
+ goto done;
+ }
+ if (ISSET(base->flags, SUDO_EVBASE_LOOPCONT)) {
+ /* Rescan events and start polling again. */
+ CLR(base->flags, SUDO_EVBASE_LOOPCONT);
+ sudo_ev_deactivate_all(base);
+ goto rescan;
+ }
+ }
+ if (ISSET(base->flags, SUDO_EVBASE_LOOPONCE)) {
+ /* SUDO_EVBASE_LOOPEXIT is always set w/ SUDO_EVBASE_LOOPONCE */
+ if (ISSET(base->flags, SUDO_EVBASE_LOOPEXIT))
+ SET(base->flags, SUDO_EVBASE_GOT_EXIT);
+ sudo_ev_deactivate_all(base);
+ break;
+ }
+ }
+done:
+ base->flags &= SUDO_EVBASE_GOT_MASK;
+ debug_return_int(rc);
+}
+
+void
+sudo_ev_loopexit_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_loopexit, SUDO_DEBUG_EVENT);
+
+ if (base == NULL) {
+ if ((base = default_base) == NULL)
+ debug_return;
+ }
+
+ /* SUDO_EVBASE_LOOPBREAK trumps SUDO_EVBASE_LOOPEXIT */
+ if (!ISSET(base->flags, SUDO_EVBASE_LOOPBREAK)) {
+ /* SUDO_EVBASE_LOOPEXIT trumps SUDO_EVBASE_LOOPCONT */
+ CLR(base->flags, SUDO_EVBASE_LOOPCONT);
+ SET(base->flags, (SUDO_EVBASE_LOOPEXIT|SUDO_EVBASE_LOOPONCE));
+ }
+ debug_return;
+}
+
+void
+sudo_ev_loopbreak_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_loopbreak, SUDO_DEBUG_EVENT);
+
+ if (base == NULL) {
+ if ((base = default_base) == NULL)
+ debug_return;
+ }
+
+ /* SUDO_EVBASE_LOOPBREAK trumps SUDO_EVBASE_LOOP{CONT,EXIT,ONCE}. */
+ CLR(base->flags, (SUDO_EVBASE_LOOPCONT|SUDO_EVBASE_LOOPEXIT|SUDO_EVBASE_LOOPONCE));
+ SET(base->flags, SUDO_EVBASE_LOOPBREAK);
+ debug_return;
+}
+
+void
+sudo_ev_loopcontinue_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_loopcontinue, SUDO_DEBUG_EVENT);
+
+ if (base == NULL) {
+ if ((base = default_base) == NULL)
+ debug_return;
+ }
+
+ /* SUDO_EVBASE_LOOP{BREAK,EXIT} trumps SUDO_EVBASE_LOOPCONT */
+ if (!ISSET(base->flags, SUDO_EVBASE_LOOPONCE|SUDO_EVBASE_LOOPBREAK)) {
+ SET(base->flags, SUDO_EVBASE_LOOPCONT);
+ }
+ debug_return;
+}
+
+bool
+sudo_ev_got_exit_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_got_exit, SUDO_DEBUG_EVENT);
+
+ if (base == NULL) {
+ if ((base = default_base) == NULL)
+ debug_return_bool(false);
+ }
+ debug_return_bool(ISSET(base->flags, SUDO_EVBASE_GOT_EXIT));
+}
+
+bool
+sudo_ev_got_break_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_got_break, SUDO_DEBUG_EVENT);
+
+ if (base == NULL) {
+ if ((base = default_base) == NULL)
+ debug_return_bool(false);
+ }
+ debug_return_bool(ISSET(base->flags, SUDO_EVBASE_GOT_BREAK));
+}
+
+int
+sudo_ev_get_timeleft_v1(struct sudo_event *ev, struct timeval *tv)
+{
+ struct timespec ts;
+ int ret;
+
+ ret = sudo_ev_get_timeleft_v2(ev, &ts);
+ TIMESPEC_TO_TIMEVAL(tv, &ts);
+
+ return ret;
+}
+
+int
+sudo_ev_get_timeleft_v2(struct sudo_event *ev, struct timespec *ts)
+{
+ debug_decl(sudo_ev_get_timeleft, SUDO_DEBUG_EVENT);
+
+ sudo_timespecclear(ts);
+ if (sudo_ev_pending_v1(ev, SUDO_EV_TIMEOUT, ts) != SUDO_EV_TIMEOUT)
+ debug_return_int(-1);
+ debug_return_int(0);
+}
+
+int
+sudo_ev_pending_v1(struct sudo_event *ev, short events, struct timespec *ts)
+{
+ int ret;
+ debug_decl(sudo_ev_pending, SUDO_DEBUG_EVENT);
+
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: event %p, flags 0x%x, events 0x%x",
+ __func__, ev, ev->flags, ev->events);
+
+ if (!ISSET(ev->flags, SUDO_EVQ_INSERTED))
+ debug_return_int(0);
+
+ ret = ev->events & events;
+ CLR(ret, SUDO_EV_TIMEOUT);
+ if (ISSET(ev->flags, SUDO_EVQ_TIMEOUTS) && ISSET(events, SUDO_EV_TIMEOUT)) {
+ ret |= SUDO_EV_TIMEOUT;
+ if (ts != NULL) {
+ struct timespec now;
+
+ sudo_gettime_mono(&now);
+ sudo_timespecsub(&ev->timeout, &now, ts);
+ if (ts->tv_sec < 0)
+ sudo_timespecclear(ts);
+ }
+ }
+
+ debug_return_int(ret);
+}
diff --git a/lib/util/event_poll.c b/lib/util/event_poll.c
new file mode 100644
index 0000000..271719a
--- /dev/null
+++ b/lib/util/event_poll.c
@@ -0,0 +1,227 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/resource.h>
+
+#include <stdlib.h>
+#include <poll.h>
+#include <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+#include "sudo_debug.h"
+#include "sudo_event.h"
+
+int
+sudo_ev_base_alloc_impl(struct sudo_event_base *base)
+{
+ int i;
+ debug_decl(sudo_ev_base_alloc_impl, SUDO_DEBUG_EVENT);
+
+ base->pfd_high = -1;
+ base->pfd_max = 32;
+ base->pfds = reallocarray(NULL, base->pfd_max, sizeof(struct pollfd));
+ if (base->pfds == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate %d pollfds", __func__, base->pfd_max);
+ base->pfd_max = 0;
+ debug_return_int(-1);
+ }
+ for (i = 0; i < base->pfd_max; i++) {
+ base->pfds[i].fd = -1;
+ }
+
+ debug_return_int(0);
+}
+
+void
+sudo_ev_base_free_impl(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_base_free_impl, SUDO_DEBUG_EVENT);
+ free(base->pfds);
+ debug_return;
+}
+
+int
+sudo_ev_add_impl(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ static int nofile_max = -1;
+ struct pollfd *pfd;
+ debug_decl(sudo_ev_add_impl, SUDO_DEBUG_EVENT);
+
+ if (nofile_max == -1) {
+ struct rlimit rlim;
+ if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
+ nofile_max = rlim.rlim_cur;
+ }
+ }
+
+ /* If out of space in pfds array, realloc. */
+ if (base->pfd_free == base->pfd_max) {
+ struct pollfd *pfds;
+ int i, new_max;
+
+ /* Don't allow pfd_max to go over RLIM_NOFILE */
+ new_max = base->pfd_max * 2;
+ if (new_max > nofile_max)
+ new_max = nofile_max;
+ if (base->pfd_free == new_max) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: out of fds (max %d)", __func__, nofile_max);
+ debug_return_int(-1);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "%s: pfd_max %d -> %d", __func__, base->pfd_max, new_max);
+ pfds = reallocarray(base->pfds, new_max, sizeof(struct pollfd));
+ if (pfds == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate %d pollfds", __func__, new_max);
+ debug_return_int(-1);
+ }
+ base->pfds = pfds;
+ base->pfd_max = new_max;
+ for (i = base->pfd_free; i < base->pfd_max; i++) {
+ base->pfds[i].fd = -1;
+ }
+ }
+
+ /* Fill in pfd entry. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "%s: choosing free slot %d", __func__, base->pfd_free);
+ ev->pfd_idx = base->pfd_free;
+ pfd = &base->pfds[ev->pfd_idx];
+ pfd->fd = ev->fd;
+ pfd->events = 0;
+ if (ISSET(ev->events, SUDO_EV_READ))
+ pfd->events |= POLLIN;
+ if (ISSET(ev->events, SUDO_EV_WRITE))
+ pfd->events |= POLLOUT;
+
+ /* Update pfd_high and pfd_free. */
+ if (ev->pfd_idx > base->pfd_high)
+ base->pfd_high = ev->pfd_idx;
+ for (;;) {
+ if (++base->pfd_free == base->pfd_max)
+ break;
+ if (base->pfds[base->pfd_free].fd == -1)
+ break;
+ }
+
+ debug_return_int(0);
+}
+
+int
+sudo_ev_del_impl(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ debug_decl(sudo_ev_del_impl, SUDO_DEBUG_EVENT);
+
+ /* Mark pfd entry unused, add to free list and adjust high slot. */
+ base->pfds[ev->pfd_idx].fd = -1;
+ if (ev->pfd_idx < base->pfd_free) {
+ base->pfd_free = ev->pfd_idx;
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "%s: new free slot %d", __func__, base->pfd_free);
+ }
+ while (base->pfd_high >= 0 && base->pfds[base->pfd_high].fd == -1)
+ base->pfd_high--;
+
+ debug_return_int(0);
+}
+
+#ifdef HAVE_PPOLL
+static int
+sudo_ev_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timo)
+{
+ return ppoll(fds, nfds, timo, NULL);
+}
+#else
+static int
+sudo_ev_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timo)
+{
+ const int timeout =
+ timo ? (timo->tv_sec * 1000) + (timo->tv_nsec / 1000000) : -1;
+
+ return poll(fds, nfds, timeout);
+}
+#endif /* HAVE_PPOLL */
+
+int
+sudo_ev_scan_impl(struct sudo_event_base *base, int flags)
+{
+ struct timespec now, ts, *timeout;
+ struct sudo_event *ev;
+ int nready;
+ debug_decl(sudo_ev_scan_impl, SUDO_DEBUG_EVENT);
+
+ if ((ev = TAILQ_FIRST(&base->timeouts)) != NULL) {
+ sudo_gettime_mono(&now);
+ sudo_timespecsub(&ev->timeout, &now, &ts);
+ if (ts.tv_sec < 0)
+ sudo_timespecclear(&ts);
+ timeout = &ts;
+ } else {
+ if (ISSET(flags, SUDO_EVLOOP_NONBLOCK)) {
+ sudo_timespecclear(&ts);
+ timeout = &ts;
+ } else {
+ timeout = NULL;
+ }
+ }
+
+ nready = sudo_ev_poll(base->pfds, base->pfd_high + 1, timeout);
+ switch (nready) {
+ case -1:
+ /* Error: EINTR (signal) or EINVAL (nfds > RLIMIT_NOFILE) */
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "sudo_ev_poll");
+ break;
+ case 0:
+ /* Front end will activate timeout events. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: timeout", __func__);
+ break;
+ default:
+ /* Activate each I/O event that fired. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d fds ready", __func__,
+ nready);
+ TAILQ_FOREACH(ev, &base->events, entries) {
+ if (ev->pfd_idx != -1 && base->pfds[ev->pfd_idx].revents) {
+ int what = 0;
+ if (base->pfds[ev->pfd_idx].revents & (POLLIN|POLLHUP|POLLNVAL|POLLERR))
+ what |= (ev->events & SUDO_EV_READ);
+ if (base->pfds[ev->pfd_idx].revents & (POLLOUT|POLLHUP|POLLNVAL|POLLERR))
+ what |= (ev->events & SUDO_EV_WRITE);
+ /* Make event active. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "%s: polled fd %d, events %d, activating %p",
+ __func__, ev->fd, what, ev);
+ ev->revents = what;
+ sudo_ev_activate(base, ev);
+ }
+ }
+ break;
+ }
+ debug_return_int(nready);
+}
diff --git a/lib/util/event_select.c b/lib/util/event_select.c
new file mode 100644
index 0000000..8d3e8a9
--- /dev/null
+++ b/lib/util/event_select.c
@@ -0,0 +1,250 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/param.h> /* for howmany() on Linux */
+#include <sys/time.h>
+#ifdef HAVE_SYS_SYSMACROS_H
+# include <sys/sysmacros.h> /* for howmany() on Solaris */
+#endif
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif /* HAVE_SYS_SELECT_H */
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+#include "sudo_debug.h"
+#include "sudo_event.h"
+
+int
+sudo_ev_base_alloc_impl(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_base_alloc_impl, SUDO_DEBUG_EVENT);
+
+ base->maxfd = NFDBITS - 1;
+ base->readfds_in = calloc(1, sizeof(fd_mask));
+ base->writefds_in = calloc(1, sizeof(fd_mask));
+ base->readfds_out = calloc(1, sizeof(fd_mask));
+ base->writefds_out = calloc(1, sizeof(fd_mask));
+
+ if (base->readfds_in == NULL || base->writefds_in == NULL ||
+ base->readfds_out == NULL || base->writefds_out == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to calloc(1, %zu)", __func__, sizeof(fd_mask));
+ sudo_ev_base_free_impl(base);
+ debug_return_int(-1);
+ }
+ debug_return_int(0);
+}
+
+void
+sudo_ev_base_free_impl(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_base_free_impl, SUDO_DEBUG_EVENT);
+ free(base->readfds_in);
+ free(base->writefds_in);
+ free(base->readfds_out);
+ free(base->writefds_out);
+ debug_return;
+}
+
+int
+sudo_ev_add_impl(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ debug_decl(sudo_ev_add_impl, SUDO_DEBUG_EVENT);
+
+ /* If out of space in fd sets, realloc. */
+ if (ev->fd > base->maxfd) {
+ const int o = (base->maxfd + 1) / NFDBITS;
+ const int n = howmany(ev->fd + 1, NFDBITS);
+ const size_t used_bytes = o * sizeof(fd_mask);
+ const size_t new_bytes = (n - o) * sizeof(fd_mask);
+ fd_set *rfds_in, *wfds_in, *rfds_out, *wfds_out;
+
+ rfds_in = reallocarray(base->readfds_in, n, sizeof(fd_mask));
+ wfds_in = reallocarray(base->writefds_in, n, sizeof(fd_mask));
+ rfds_out = reallocarray(base->readfds_out, n, sizeof(fd_mask));
+ wfds_out = reallocarray(base->writefds_out, n, sizeof(fd_mask));
+ if (rfds_in == NULL || wfds_in == NULL ||
+ rfds_out == NULL || wfds_out == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to reallocarray(%d, %zu)",
+ __func__, n, sizeof(fd_mask));
+ free(rfds_in);
+ free(wfds_in);
+ free(rfds_out);
+ free(wfds_out);
+ debug_return_int(-1);
+ }
+
+ /* Clear newly allocated space. */
+ memset((char *)rfds_in + used_bytes, 0, new_bytes);
+ memset((char *)wfds_in + used_bytes, 0, new_bytes);
+ memset((char *)rfds_out + used_bytes, 0, new_bytes);
+ memset((char *)wfds_out + used_bytes, 0, new_bytes);
+
+ /* Update base. */
+ base->readfds_in = rfds_in;
+ base->writefds_in = wfds_in;
+ base->readfds_out = rfds_out;
+ base->writefds_out = wfds_out;
+ base->maxfd = (n * NFDBITS) - 1;
+ }
+
+ /* Set events and adjust high fd as needed. */
+ if (ISSET(ev->events, SUDO_EV_READ)) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: added fd %d to readfs",
+ __func__, ev->fd);
+ FD_SET(ev->fd, (fd_set *)base->readfds_in);
+ }
+ if (ISSET(ev->events, SUDO_EV_WRITE)) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: added fd %d to writefds",
+ __func__, ev->fd);
+ FD_SET(ev->fd, (fd_set *)base->writefds_in);
+ }
+ if (ev->fd > base->highfd)
+ base->highfd = ev->fd;
+
+ debug_return_int(0);
+}
+
+int
+sudo_ev_del_impl(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ debug_decl(sudo_ev_del_impl, SUDO_DEBUG_EVENT);
+
+ /* Remove from readfds and writefds and adjust high fd. */
+ if (ISSET(ev->events, SUDO_EV_READ)) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: removed fd %d from readfds",
+ __func__, ev->fd);
+ FD_CLR(ev->fd, (fd_set *)base->readfds_in);
+ }
+ if (ISSET(ev->events, SUDO_EV_WRITE)) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: removed fd %d from writefds",
+ __func__, ev->fd);
+ FD_CLR(ev->fd, (fd_set *)base->writefds_in);
+ }
+ if (base->highfd == ev->fd) {
+ for (;;) {
+ if (FD_ISSET(base->highfd, (fd_set *)base->readfds_in) ||
+ FD_ISSET(base->highfd, (fd_set *)base->writefds_in))
+ break;
+ if (--base->highfd < 0)
+ break;
+ }
+ }
+
+ debug_return_int(0);
+}
+
+#ifdef HAVE_PSELECT
+static int
+sudo_ev_select(int nfds, fd_set *readfds, fd_set *writefds,
+ fd_set *exceptfds, const struct timespec *timeout)
+{
+ return pselect(nfds, readfds, writefds, exceptfds, timeout, NULL);
+}
+#else
+static int
+sudo_ev_select(int nfds, fd_set *readfds, fd_set *writefds,
+ fd_set *exceptfds, const struct timespec *timeout)
+{
+ struct timeval tvbuf, *tv = NULL;
+
+ if (timeout != NULL) {
+ TIMESPEC_TO_TIMEVAL(&tvbuf, timeout);
+ tv = &tvbuf;
+ }
+ return select(nfds, readfds, writefds, exceptfds, tv);
+}
+#endif /* HAVE_PSELECT */
+
+int
+sudo_ev_scan_impl(struct sudo_event_base *base, int flags)
+{
+ struct timespec now, ts, *timeout;
+ struct sudo_event *ev;
+ size_t setsize;
+ int nready;
+ debug_decl(sudo_ev_loop, SUDO_DEBUG_EVENT);
+
+ if ((ev = TAILQ_FIRST(&base->timeouts)) != NULL) {
+ sudo_gettime_mono(&now);
+ sudo_timespecsub(&ev->timeout, &now, &ts);
+ if (ts.tv_sec < 0)
+ sudo_timespecclear(&ts);
+ timeout = &ts;
+ } else {
+ if (ISSET(flags, SUDO_EVLOOP_NONBLOCK)) {
+ sudo_timespecclear(&ts);
+ timeout = &ts;
+ } else {
+ timeout = NULL;
+ }
+ }
+
+ /* select() overwrites readfds/writefds so make a copy. */
+ setsize = howmany(base->highfd + 1, NFDBITS) * sizeof(fd_mask);
+ memcpy(base->readfds_out, base->readfds_in, setsize);
+ memcpy(base->writefds_out, base->writefds_in, setsize);
+
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: select high fd %d",
+ __func__, base->highfd);
+ nready = sudo_ev_select(base->highfd + 1, base->readfds_out,
+ base->writefds_out, NULL, timeout);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d fds ready", __func__, nready);
+ switch (nready) {
+ case -1:
+ /* Error or interrupted by signal. */
+ break;
+ case 0:
+ /* Front end will activate timeout events. */
+ break;
+ default:
+ /* Activate each I/O event that fired. */
+ TAILQ_FOREACH(ev, &base->events, entries) {
+ if (ev->fd >= 0) {
+ int what = 0;
+ if (FD_ISSET(ev->fd, (fd_set *)base->readfds_out))
+ what |= (ev->events & SUDO_EV_READ);
+ if (FD_ISSET(ev->fd, (fd_set *)base->writefds_out))
+ what |= (ev->events & SUDO_EV_WRITE);
+ if (what != 0) {
+ /* Make event active. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "%s: selected fd %d, events %d, activating %p",
+ __func__, ev->fd, what, ev);
+ ev->revents = what;
+ sudo_ev_activate(base, ev);
+ }
+ }
+ }
+ break;
+ }
+ debug_return_int(nready);
+}
diff --git a/lib/util/explicit_bzero.c b/lib/util/explicit_bzero.c
new file mode 100644
index 0000000..a7defbd
--- /dev/null
+++ b/lib/util/explicit_bzero.c
@@ -0,0 +1,77 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#define __STDC_WANT_LIB_EXT1__ 1 /* for memset_s() */
+
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_EXPLICIT_BZERO
+
+# if defined(HAVE_EXPLICIT_MEMSET)
+void
+sudo_explicit_bzero(void *s, size_t n)
+{
+ explicit_memset(s, 0, n);
+}
+# elif defined(HAVE_MEMSET_EXPLICIT)
+void
+sudo_explicit_bzero(void *s, size_t n)
+{
+ memset_explicit(s, 0, n);
+}
+# elif defined(HAVE_MEMSET_S)
+void
+sudo_explicit_bzero(void *s, size_t n)
+{
+ (void)memset_s(s, n, 0, n);
+}
+# elif defined(HAVE_BZERO)
+/* Jumping through a volatile function pointer should not be optimized away. */
+void (* volatile sudo_explicit_bzero_impl)(void *, size_t) =
+ (void (*)(void *, size_t))bzero;
+
+void
+sudo_explicit_bzero(void *s, size_t n)
+{
+ sudo_explicit_bzero_impl(s, n);
+}
+# else
+void
+sudo_explicit_bzero(void *v, size_t n)
+{
+ volatile unsigned char *s = v;
+
+ /* Updating through a volatile pointer should not be optimized away. */
+ while (n--)
+ *s++ = '\0';
+}
+# endif /* HAVE_BZERO */
+
+#endif /* HAVE_EXPLICIT_BZERO */
diff --git a/lib/util/fatal.c b/lib/util/fatal.c
new file mode 100644
index 0000000..7d2e09d
--- /dev/null
+++ b/lib/util/fatal.c
@@ -0,0 +1,345 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2004-2005, 2010-2015, 2017-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 <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <unistd.h>
+#ifndef HAVE_GETADDRINFO
+# include "compat/getaddrinfo.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_queue.h"
+#include "sudo_util.h"
+#include "sudo_plugin.h"
+
+struct sudo_fatal_callback {
+ SLIST_ENTRY(sudo_fatal_callback) entries;
+ void (*func)(void);
+};
+SLIST_HEAD(sudo_fatal_callback_list, sudo_fatal_callback);
+
+static struct sudo_fatal_callback_list callbacks = SLIST_HEAD_INITIALIZER(&callbacks);
+static sudo_conv_t sudo_warn_conversation;
+static sudo_warn_setlocale_t sudo_warn_setlocale;
+static sudo_warn_setlocale_t sudo_warn_setlocale_prev;
+
+static void warning(const char *errstr, const char *fmt, va_list ap);
+
+static void
+do_cleanup(void)
+{
+ struct sudo_fatal_callback *cb;
+
+ /* Run callbacks, removing them from the list as we go. */
+ while ((cb = SLIST_FIRST(&callbacks)) != NULL) {
+ SLIST_REMOVE_HEAD(&callbacks, entries);
+ cb->func();
+ free(cb);
+ }
+}
+
+void
+sudo_fatal_nodebug_v1(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ warning(strerror(errno), fmt, ap);
+ va_end(ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_fatalx_nodebug_v1(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ warning(NULL, fmt, ap);
+ va_end(ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_vfatal_nodebug_v1(const char *fmt, va_list ap)
+{
+ warning(strerror(errno), fmt, ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_vfatalx_nodebug_v1(const char *fmt, va_list ap)
+{
+ warning(NULL, fmt, ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_warn_nodebug_v1(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ warning(strerror(errno), fmt, ap);
+ va_end(ap);
+}
+
+void
+sudo_warnx_nodebug_v1(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ warning(NULL, fmt, ap);
+ va_end(ap);
+}
+
+void
+sudo_vwarn_nodebug_v1(const char *fmt, va_list ap)
+{
+ warning(strerror(errno), fmt, ap);
+}
+
+void
+sudo_vwarnx_nodebug_v1(const char *fmt, va_list ap)
+{
+ warning(NULL, fmt, ap);
+}
+
+void
+sudo_gai_fatal_nodebug_v1(int errnum, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ warning(gai_strerror(errnum), fmt, ap);
+ va_end(ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_gai_vfatal_nodebug_v1(int errnum, const char *fmt, va_list ap)
+{
+ warning(gai_strerror(errnum), fmt, ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_gai_warn_nodebug_v1(int errnum, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ warning(gai_strerror(errnum), fmt, ap);
+ va_end(ap);
+}
+
+void
+sudo_gai_vwarn_nodebug_v1(int errnum, const char *fmt, va_list ap)
+{
+ warning(gai_strerror(errnum), fmt, ap);
+}
+
+static void
+warning(const char *errstr, const char *fmt, va_list ap)
+{
+ int cookie;
+ const int saved_errno = errno;
+
+ /* Set user locale if setter was specified. */
+ if (sudo_warn_setlocale != NULL)
+ sudo_warn_setlocale(false, &cookie);
+
+ if (sudo_warn_conversation != NULL) {
+ struct sudo_conv_message msgs[6];
+ char static_buf[1024], *buf = static_buf;
+ int nmsgs = 0;
+
+ /* Use conversation function. */
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = getprogname();
+ if (fmt != NULL) {
+ va_list ap2;
+ int buflen;
+
+ /* Use static buffer if possible, else dynamic. */
+ va_copy(ap2, ap);
+ buflen = vsnprintf(static_buf, sizeof(static_buf), fmt, ap2);
+ va_end(ap2);
+ if (buflen >= ssizeof(static_buf)) {
+ buf = malloc(++buflen);
+ if (buf != NULL)
+ (void)vsnprintf(buf, buflen, fmt, ap);
+ else
+ buf = static_buf;
+ }
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = ": ";
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = buf;
+ }
+ if (errstr != NULL) {
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = ": ";
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = errstr;
+ }
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = "\n";
+ sudo_warn_conversation(nmsgs, msgs, NULL, NULL);
+ if (buf != static_buf)
+ free(buf);
+ } else {
+ /* Write to the standard error. */
+ fputs(getprogname(), stderr);
+ if (fmt != NULL) {
+ fputs(": ", stderr);
+ vfprintf(stderr, fmt, ap);
+ }
+ if (errstr != NULL) {
+ fputs(": ", stderr);
+ fputs(errstr, stderr);
+ }
+#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ if (sudo_term_is_raw(fileno(stderr)))
+ putc('\r', stderr);
+#endif
+ putc('\n', stderr);
+ }
+
+ /* Restore old locale as needed. */
+ if (sudo_warn_setlocale != NULL)
+ sudo_warn_setlocale(true, &cookie);
+
+ /* Do not clobber errno. */
+ errno = saved_errno;
+}
+
+/*
+ * Register a callback to be run when sudo_fatal()/sudo_fatalx() is called.
+ */
+int
+sudo_fatal_callback_register_v1(sudo_fatal_callback_t func)
+{
+ struct sudo_fatal_callback *cb;
+
+ /* Do not register the same callback twice. */
+ SLIST_FOREACH(cb, &callbacks, entries) {
+ if (func == cb->func)
+ return -1; /* dupe! */
+ }
+
+ /* Allocate and insert new callback. */
+ cb = malloc(sizeof(*cb));
+ if (cb == NULL)
+ return -1;
+ cb->func = func;
+ SLIST_INSERT_HEAD(&callbacks, cb, entries);
+
+ return 0;
+}
+
+/*
+ * Deregister a sudo_fatal()/sudo_fatalx() callback.
+ */
+int
+sudo_fatal_callback_deregister_v1(sudo_fatal_callback_t func)
+{
+ struct sudo_fatal_callback *cb, *prev = NULL;
+
+ /* Search for callback and remove if found, dupes are not allowed. */
+ SLIST_FOREACH(cb, &callbacks, entries) {
+ if (cb->func == func) {
+ if (prev == NULL)
+ SLIST_REMOVE_HEAD(&callbacks, entries);
+ else
+ SLIST_REMOVE_AFTER(prev, entries);
+ free(cb);
+ return 0;
+ }
+ prev = cb;
+ }
+
+ return -1;
+}
+
+/*
+ * Set the conversation function to use for output insteaf of the
+ * standard error. If conv is NULL, switch back to standard error.
+ */
+void
+sudo_warn_set_conversation_v1(sudo_conv_t conv)
+{
+ sudo_warn_conversation = conv;
+}
+
+/*
+ * Set the locale function so the plugin can use a non-default
+ * locale for user warnings.
+ */
+void
+sudo_warn_set_locale_func_v1(sudo_warn_setlocale_t func)
+{
+ sudo_warn_setlocale_prev = sudo_warn_setlocale;
+ sudo_warn_setlocale = func;
+}
+
+#ifdef HAVE_LIBINTL_H
+char *
+sudo_warn_gettext_v1(const char *domainname, const char *msgid)
+{
+ int cookie;
+ char *msg;
+
+ /* Set user locale if setter was specified. */
+ if (sudo_warn_setlocale != NULL)
+ sudo_warn_setlocale(false, &cookie);
+
+ msg = dgettext(domainname, msgid);
+
+ /* Restore old locale as needed. */
+ if (sudo_warn_setlocale != NULL)
+ sudo_warn_setlocale(true, &cookie);
+
+ return msg;
+}
+#endif /* HAVE_LIBINTL_H */
diff --git a/lib/util/fchmodat.c b/lib/util/fchmodat.c
new file mode 100644
index 0000000..9800296
--- /dev/null
+++ b/lib/util/fchmodat.c
@@ -0,0 +1,69 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_FCHMODAT
+int
+sudo_fchmodat(int dfd, const char *path, mode_t mode, int flag)
+{
+ int odfd, ret = -1;
+
+ if (ISSET(flag, AT_SYMLINK_NOFOLLOW)) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (dfd == (int)AT_FDCWD)
+ return chmod(path, mode);
+
+ /* Save cwd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ goto done;
+
+ if (fchdir(dfd) == -1)
+ goto done;
+
+ ret = chmod(path, mode);
+
+ /* Restore cwd */
+ if (fchdir(odfd) == -1) {
+ /* Should not happen */
+ ret = -1;
+ }
+
+done:
+ if (odfd != -1)
+ close(odfd);
+
+ return ret;
+}
+#endif /* HAVE_FCHMODAT */
diff --git a/lib/util/fchownat.c b/lib/util/fchownat.c
new file mode 100644
index 0000000..545a21e
--- /dev/null
+++ b/lib/util/fchownat.c
@@ -0,0 +1,67 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 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 <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_FCHOWNAT
+int
+sudo_fchownat(int dfd, const char *path, uid_t uid, gid_t gid, int flags)
+{
+ int odfd, ret;
+
+ if (dfd == AT_FDCWD) {
+ if (flags & AT_SYMLINK_NOFOLLOW)
+ return lchown(path, uid, gid);
+ else
+ return chown(path, uid, gid);
+ }
+
+ /* Save cwd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ return -1;
+
+ if (fchdir(dfd) == -1) {
+ close(odfd);
+ return -1;
+ }
+
+ if (flags & AT_SYMLINK_NOFOLLOW)
+ ret = lchown(path, uid, gid);
+ else
+ ret = chown(path, uid, gid);
+
+ /* Restore cwd */
+ if (fchdir(odfd) == -1) {
+ /* Should not happen */
+ ret = -1;
+ }
+ close(odfd);
+
+ return ret;
+}
+#endif /* HAVE_FCHOWNAT */
diff --git a/lib/util/fnmatch.c b/lib/util/fnmatch.c
new file mode 100644
index 0000000..f393bb4
--- /dev/null
+++ b/lib/util/fnmatch.c
@@ -0,0 +1,499 @@
+/* $OpenBSD: fnmatch.c,v 1.15 2011/02/10 21:31:59 stsp Exp $ */
+
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2011, VMware, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the VMware, Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008, 2016 Todd C. Miller <millert@openbsd.org>
+ *
+ * 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
+ */
+
+/* Authored by William A. Rowe Jr. <wrowe; apache.org, vmware.com>, April 2011
+ *
+ * Derived from The Open Group Base Specifications Issue 7, IEEE Std 1003.1-2008
+ * as described in;
+ * http://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html
+ *
+ * Filename pattern matches defined in section 2.13, "Pattern Matching Notation"
+ * from chapter 2. "Shell Command Language"
+ * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13
+ * where; 1. A bracket expression starting with an unquoted <circumflex> '^'
+ * character CONTINUES to specify a non-matching list; 2. an explicit <period> '.'
+ * in a bracket expression matching list, e.g. "[.abc]" does NOT match a leading
+ * <period> in a filename; 3. a <left-square-bracket> '[' which does not introduce
+ * a valid bracket expression is treated as an ordinary character; 4. a differing
+ * number of consecutive slashes within pattern and string will NOT match;
+ * 5. a trailing '\' in FNM_ESCAPE mode is treated as an ordinary '\' character.
+ *
+ * Bracket expansion defined in section 9.3.5, "RE Bracket Expression",
+ * from chapter 9, "Regular Expressions"
+ * http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
+ * with no support for collating symbols, equivalence class expressions or
+ * character class expressions. A partial range expression with a leading
+ * hyphen following a valid range expression will match only the ordinary
+ * <hyphen> and the ending character (e.g. "[a-m-z]" will match characters
+ * 'a' through 'm', a <hyphen> '-', or a 'z').
+ *
+ * Supports BSD extensions FNM_LEADING_DIR to match pattern to the end of one
+ * path segment of string, and FNM_CASEFOLD to ignore alpha case.
+ *
+ * NOTE: Only POSIX/C single byte locales are correctly supported at this time.
+ * Notably, non-POSIX locales with FNM_CASEFOLD produce undefined results,
+ * particularly in ranges of mixed case (e.g. "[A-z]") or spanning alpha and
+ * nonalpha characters within a range.
+ *
+ * XXX comments below indicate porting required for multi-byte character sets
+ * and non-POSIX locale collation orders; requires mbr* APIs to track shift
+ * state of pattern and string (rewinding pattern and string repeatedly).
+ *
+ * Certain parts of the code assume 0x00-0x3F are unique with any MBCS (e.g.
+ * UTF-8, SHIFT-JIS, etc). Any implementation allowing '\' as an alternate
+ * path delimiter must be aware that 0x5C is NOT unique within SHIFT-JIS.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_FNMATCH
+
+#include <ctype.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "compat/charclass.h"
+#include "compat/fnmatch.h"
+
+#define RANGE_MATCH 1
+#define RANGE_NOMATCH 0
+#define RANGE_ERROR (-1)
+
+static int
+classmatch(const char *pattern, char test, int foldcase, const char **ep)
+{
+ const char * const mismatch = pattern;
+ const char *colon;
+ struct cclass *cc;
+ int result = RANGE_NOMATCH;
+ size_t len;
+
+ if (pattern[0] != '[' || pattern[1] != ':') {
+ *ep = mismatch;
+ return RANGE_ERROR;
+ }
+ pattern += 2;
+
+ if ((colon = strchr(pattern, ':')) == NULL || colon[1] != ']') {
+ *ep = mismatch;
+ return RANGE_ERROR;
+ }
+ *ep = colon + 2;
+ len = (size_t)(colon - pattern);
+
+ if (foldcase && strncmp(pattern, "upper:]", 7) == 0)
+ pattern = "lower:]";
+ for (cc = cclasses; cc->name != NULL; cc++) {
+ if (!strncmp(pattern, cc->name, len) && cc->name[len] == '\0') {
+ if (cc->isctype((unsigned char)test))
+ result = RANGE_MATCH;
+ break;
+ }
+ }
+ if (cc->name == NULL) {
+ /* invalid character class, treat as normal text */
+ *ep = mismatch;
+ result = RANGE_ERROR;
+ }
+ return result;
+}
+
+/* Most MBCS/collation/case issues handled here. Wildcard '*' is not handled.
+ * EOS '\0' and the FNM_PATHNAME '/' delimiters are not advanced over,
+ * however the "\/" sequence is advanced to '/'.
+ *
+ * Both pattern and string are **char to support pointer increment of arbitrary
+ * multibyte characters for the given locale, in a later iteration of this code
+ */
+static int fnmatch_ch(const char **pattern, const char **string, int flags)
+{
+ const char * const mismatch = *pattern;
+ const int nocase = !!(flags & FNM_CASEFOLD);
+ const int escape = !(flags & FNM_NOESCAPE);
+ const int slash = !!(flags & FNM_PATHNAME);
+ int result = FNM_NOMATCH;
+ const char *startch;
+ int negate;
+
+ if (**pattern == '[')
+ {
+ ++*pattern;
+
+ /* Handle negation, either leading ! or ^ operators (never both) */
+ negate = ((**pattern == '!') || (**pattern == '^'));
+ if (negate)
+ ++*pattern;
+
+ /* ']' is an ordinary character at the start of the range pattern */
+ if (**pattern == ']')
+ goto leadingclosebrace;
+
+ while (**pattern)
+ {
+ if (**pattern == ']') {
+ ++*pattern;
+ /* XXX: Fix for MBCS character width */
+ ++*string;
+ return (result ^ negate);
+ }
+
+ if (escape && (**pattern == '\\')) {
+ ++*pattern;
+
+ /* Patterns must be terminated with ']', not EOS */
+ if (!**pattern)
+ break;
+ }
+
+ /* Patterns must be terminated with ']' not '/' */
+ if (slash && (**pattern == '/'))
+ break;
+
+ /* Match character classes. */
+ switch (classmatch(*pattern, **string, nocase, pattern)) {
+ case RANGE_MATCH:
+ result = 0;
+ continue;
+ case RANGE_NOMATCH:
+ /* Valid character class but no match. */
+ continue;
+ default:
+ /* Not a valid character class. */
+ break;
+ }
+ if (!**pattern)
+ break;
+
+leadingclosebrace:
+ /* Look at only well-formed range patterns;
+ * "x-]" is not allowed unless escaped ("x-\]")
+ * XXX: Fix for locale/MBCS character width
+ */
+ if (((*pattern)[1] == '-') && ((*pattern)[2] != ']'))
+ {
+ startch = *pattern;
+ *pattern += (escape && ((*pattern)[2] == '\\')) ? 3 : 2;
+
+ /* NOT a properly balanced [expr] pattern, EOS terminated
+ * or ranges containing a slash in FNM_PATHNAME mode pattern
+ * fall out to to the rewind and test '[' literal code path
+ */
+ if (!**pattern || (slash && (**pattern == '/')))
+ break;
+
+ /* XXX: handle locale/MBCS comparison, advance by MBCS char width */
+ if ((**string >= *startch) && (**string <= **pattern))
+ result = 0;
+ else if (nocase && (isupper((unsigned char)**string) ||
+ isupper((unsigned char)*startch) ||
+ isupper((unsigned char)**pattern))
+ && (tolower((unsigned char)**string) >= tolower((unsigned char)*startch))
+ && (tolower((unsigned char)**string) <= tolower((unsigned char)**pattern)))
+ result = 0;
+
+ ++*pattern;
+ continue;
+ }
+
+ /* XXX: handle locale/MBCS comparison, advance by MBCS char width */
+ if ((**string == **pattern))
+ result = 0;
+ else if (nocase && (isupper((unsigned char)**string) ||
+ isupper((unsigned char)**pattern))
+ && (tolower((unsigned char)**string) == tolower((unsigned char)**pattern)))
+ result = 0;
+
+ ++*pattern;
+ }
+
+ /* NOT a properly balanced [expr] pattern; Rewind
+ * and reset result to test '[' literal
+ */
+ *pattern = mismatch;
+ result = FNM_NOMATCH;
+ }
+ else if (**pattern == '?') {
+ /* Optimize '?' match before unescaping **pattern */
+ if (!**string || (slash && (**string == '/')))
+ return FNM_NOMATCH;
+ result = 0;
+ goto fnmatch_ch_success;
+ }
+ else if (escape && (**pattern == '\\') && (*pattern)[1]) {
+ ++*pattern;
+ }
+
+ /* XXX: handle locale/MBCS comparison, advance by the MBCS char width */
+ if (**string == **pattern)
+ result = 0;
+ else if (nocase && (isupper((unsigned char)**string) || isupper((unsigned char)**pattern))
+ && (tolower((unsigned char)**string) == tolower((unsigned char)**pattern)))
+ result = 0;
+
+ /* Refuse to advance over trailing slash or nulls
+ */
+ if (!**string || !**pattern || (slash && ((**string == '/') || (**pattern == '/'))))
+ return result;
+
+fnmatch_ch_success:
+ ++*pattern;
+ ++*string;
+ return result;
+}
+
+int sudo_fnmatch(const char *pattern, const char *string, int flags)
+{
+ static const char dummystring[2] = {' ', 0};
+ const int escape = !(flags & FNM_NOESCAPE);
+ const int slash = !!(flags & FNM_PATHNAME);
+ const int leading_dir = !!(flags & FNM_LEADING_DIR);
+ const char *strendseg;
+ const char *dummyptr;
+ const char *matchptr;
+ int wild;
+ /* For '*' wild processing only; suppress 'used before initialization'
+ * warnings with dummy initialization values;
+ */
+ const char *strstartseg = NULL;
+ const char *mismatch = NULL;
+ int matchlen = 0;
+
+ if (*pattern == '*')
+ goto firstsegment;
+
+ while (*pattern && *string)
+ {
+ /* Pre-decode "\/" which has no special significance, and
+ * match balanced slashes, starting a new segment pattern
+ */
+ if (slash && escape && (*pattern == '\\') && (pattern[1] == '/'))
+ ++pattern;
+ if (slash && (*pattern == '/') && (*string == '/')) {
+ ++pattern;
+ ++string;
+ }
+
+firstsegment:
+ /* At the beginning of each segment, validate leading period behavior.
+ */
+ if ((flags & FNM_PERIOD) && (*string == '.'))
+ {
+ if (*pattern == '.')
+ ++pattern;
+ else if (escape && (*pattern == '\\') && (pattern[1] == '.'))
+ pattern += 2;
+ else
+ return FNM_NOMATCH;
+ ++string;
+ }
+
+ /* Determine the end of string segment
+ *
+ * Presumes '/' character is unique, not composite in any MBCS encoding
+ */
+ if (slash) {
+ strendseg = strchr(string, '/');
+ if (!strendseg)
+ strendseg = strchr(string, '\0');
+ }
+ else {
+ strendseg = strchr(string, '\0');
+ }
+
+ /* Allow pattern '*' to be consumed even with no remaining string to match
+ */
+ while (*pattern)
+ {
+ if ((string > strendseg)
+ || ((string == strendseg) && (*pattern != '*')))
+ break;
+
+ if (slash && ((*pattern == '/')
+ || (escape && (*pattern == '\\')
+ && (pattern[1] == '/'))))
+ break;
+
+ /* Reduce groups of '*' and '?' to n '?' matches
+ * followed by one '*' test for simplicity
+ */
+ for (wild = 0; ((*pattern == '*') || (*pattern == '?')); ++pattern)
+ {
+ if (*pattern == '*') {
+ wild = 1;
+ }
+ else if (string < strendseg) { /* && (*pattern == '?') */
+ /* XXX: Advance 1 char for MBCS locale */
+ ++string;
+ }
+ else { /* (string >= strendseg) && (*pattern == '?') */
+ return FNM_NOMATCH;
+ }
+ }
+
+ if (wild)
+ {
+ strstartseg = string;
+ mismatch = pattern;
+
+ /* Count fixed (non '*') char matches remaining in pattern
+ * excluding '/' (or "\/") and '*'
+ */
+ for (matchptr = pattern, matchlen = 0; 1; ++matchlen)
+ {
+ if ((*matchptr == '\0')
+ || (slash && ((*matchptr == '/')
+ || (escape && (*matchptr == '\\')
+ && (matchptr[1] == '/')))))
+ {
+ /* Compare precisely this many trailing string chars,
+ * the resulting match needs no wildcard loop
+ */
+ /* XXX: Adjust for MBCS */
+ if (string + matchlen > strendseg)
+ return FNM_NOMATCH;
+
+ string = strendseg - matchlen;
+ wild = 0;
+ break;
+ }
+
+ if (*matchptr == '*')
+ {
+ /* Ensure at least this many trailing string chars remain
+ * for the first comparison
+ */
+ /* XXX: Adjust for MBCS */
+ if (string + matchlen > strendseg)
+ return FNM_NOMATCH;
+
+ /* Begin first wild comparison at the current position */
+ break;
+ }
+
+ /* Skip forward in pattern by a single character match
+ * Use a dummy fnmatch_ch() test to count one "[range]" escape
+ */
+ /* XXX: Adjust for MBCS */
+ if (escape && (*matchptr == '\\') && matchptr[1]) {
+ matchptr += 2;
+ }
+ else if (*matchptr == '[') {
+ dummyptr = dummystring;
+ fnmatch_ch(&matchptr, &dummyptr, flags);
+ }
+ else {
+ ++matchptr;
+ }
+ }
+ }
+
+ /* Incrementally match string against the pattern
+ */
+ while (*pattern && (string < strendseg))
+ {
+ /* Success; begin a new wild pattern search
+ */
+ if (*pattern == '*')
+ break;
+
+ if (slash && ((*string == '/')
+ || (*pattern == '/')
+ || (escape && (*pattern == '\\')
+ && (pattern[1] == '/'))))
+ break;
+
+ /* Compare ch's (the pattern is advanced over "\/" to the '/',
+ * but slashes will mismatch, and are not consumed)
+ */
+ if (!fnmatch_ch(&pattern, &string, flags))
+ continue;
+
+ /* Failed to match, loop against next char offset of string segment
+ * until not enough string chars remain to match the fixed pattern
+ */
+ if (wild) {
+ /* XXX: Advance 1 char for MBCS locale */
+ string = ++strstartseg;
+ if (string + matchlen > strendseg)
+ return FNM_NOMATCH;
+
+ pattern = mismatch;
+ continue;
+ }
+ else
+ return FNM_NOMATCH;
+ }
+ }
+
+ if (*string && !((slash || leading_dir) && (*string == '/')))
+ return FNM_NOMATCH;
+
+ if (*pattern && !(slash && ((*pattern == '/')
+ || (escape && (*pattern == '\\')
+ && (pattern[1] == '/')))))
+ return FNM_NOMATCH;
+
+ if (leading_dir && !*pattern && *string == '/')
+ return 0;
+ }
+
+ /* Where both pattern and string are at EOS, declare success
+ */
+ if (!*string && !*pattern)
+ return 0;
+
+ /* pattern didn't match to the end of string */
+ return FNM_NOMATCH;
+}
+#endif /* HAVE_FNMATCH */
diff --git a/lib/util/freezero.c b/lib/util/freezero.c
new file mode 100644
index 0000000..e2d2e8a
--- /dev/null
+++ b/lib/util/freezero.c
@@ -0,0 +1,38 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_FREEZERO
+void
+sudo_freezero(void *p, size_t n)
+{
+ explicit_bzero(p, n);
+ free(p);
+}
+#endif /* HAVE_FREEZERO */
diff --git a/lib/util/fstatat.c b/lib/util/fstatat.c
new file mode 100644
index 0000000..0b342d1
--- /dev/null
+++ b/lib/util/fstatat.c
@@ -0,0 +1,70 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_FSTATAT
+int
+sudo_fstatat(int dfd, const char *path, struct stat *sb, int flag)
+{
+ int odfd, ret = -1;
+
+ if (dfd == (int)AT_FDCWD) {
+ if (ISSET(flag, AT_SYMLINK_NOFOLLOW))
+ return lstat(path, sb);
+ else
+ return stat(path, sb);
+ }
+
+ /* Save cwd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ goto done;
+
+ if (fchdir(dfd) == -1)
+ goto done;
+
+ if (ISSET(flag, AT_SYMLINK_NOFOLLOW))
+ ret = lstat(path, sb);
+ else
+ ret = stat(path, sb);
+
+ /* Restore cwd */
+ if (fchdir(odfd) == -1) {
+ /* Should not happen */
+ ret = -1;
+ }
+
+done:
+ if (odfd != -1)
+ close(odfd);
+
+ return ret;
+}
+#endif /* HAVE_FSTATAT */
diff --git a/lib/util/getaddrinfo.c b/lib/util/getaddrinfo.c
new file mode 100644
index 0000000..ad0ea5f
--- /dev/null
+++ b/lib/util/getaddrinfo.c
@@ -0,0 +1,406 @@
+/*
+ * Replacement for a missing getaddrinfo.
+ *
+ * This is an implementation of getaddrinfo for systems that don't have one so
+ * that networking code can use a consistent interface without #ifdef. It is
+ * a fairly minimal implementation, with the following limitations:
+ *
+ * - IPv4 support only. IPv6 is not supported.
+ * - AI_ADDRCONFIG is ignored.
+ * - Not thread-safe due to gethostbyname and getservbyname.
+ * - SOCK_DGRAM and SOCK_STREAM only.
+ * - Multiple possible socket types only generate one addrinfo struct.
+ * - Protocol hints aren't used correctly.
+ *
+ * The last four issues could probably be easily remedied, but haven't been
+ * needed to date. Adding IPv6 support isn't worth it; systems with IPv6
+ * support should already have getaddrinfo.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <rra@stanford.edu>
+ *
+ * The authors hereby relinquish any claim to any copyright that they may have
+ * in this work, whether granted under contract or by operation of law or
+ * international treaty, and hereby commit to the public, at large, that they
+ * shall not, at any time in the future, seek to enforce any copyright in this
+ * work against any person or entity, or prevent any person or entity from
+ * copying, publishing, distributing or creating derivative works of this
+ * work.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_GETADDRINFO
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#ifdef NEED_RESOLV_H
+# include <arpa/nameser.h>
+# include <resolv.h>
+#endif /* NEED_RESOLV_H */
+
+#include "sudo_compat.h"
+#include "compat/getaddrinfo.h"
+
+/* We need access to h_errno to map errors from gethostbyname. */
+#ifndef HAVE_DECL_H_ERRNO
+extern int h_errno;
+#endif
+
+/*
+ * The netdb constants, which aren't always defined (particularly if h_errno
+ * isn't declared). We also make sure that a few of the less-used ones are
+ * defined so that we can deal with them in case statements.
+ */
+#ifndef HOST_NOT_FOUND
+# define HOST_NOT_FOUND 1
+# define TRY_AGAIN 2
+# define NO_RECOVERY 3
+# define NO_DATA 4
+#endif
+#ifndef NETDB_INTERNAL
+# define NETDB_INTERNAL -1
+#endif
+
+/*
+ * If we're running the test suite, rename the functions to avoid conflicts
+ * with the system version. Note that we don't rename the structures and
+ * constants, but that should be okay (except possibly for gai_strerror).
+ */
+#ifdef TESTING
+# define gai_strerror test_gai_strerror
+# define freeaddrinfo test_freeaddrinfo
+# define getaddrinfo test_getaddrinfo
+const char *test_gai_strerror(int);
+void test_freeaddrinfo(struct addrinfo *);
+int test_getaddrinfo(const char *, const char *, const struct addrinfo *,
+ struct addrinfo **);
+#endif
+
+/*
+ * If the platform doesn't support AI_NUMERICSERV or AI_NUMERICHOST,
+ * pick some other values for them.
+ */
+#ifdef TESTING
+# if AI_NUMERICSERV == 0
+# undef AI_NUMERICSERV
+# define AI_NUMERICSERV 0x0080
+# endif
+# if AI_NUMERICHOST == 0
+# undef AI_NUMERICHOST
+# define AI_NUMERICHOST 0x0100
+# endif
+#endif
+
+/*
+ * Value representing all of the hint flags set. Linux uses flags up to
+ * 0x0400, so be sure not to break when testing on that platform.
+ */
+#ifdef TESTING
+# ifdef HAVE_GETADDRINFO
+# define AI_INTERNAL_ALL 0x04ff
+# else
+# define AI_INTERNAL_ALL 0x01ff
+# endif
+#else
+# define AI_INTERNAL_ALL 0x007f
+#endif
+
+/* Table of strings corresponding to the EAI_* error codes. */
+static const char * const gai_errors[] = {
+ "Host name lookup failure", /* 1 EAI_AGAIN */
+ "Invalid flag value", /* 2 EAI_BADFLAGS */
+ "Unknown server error", /* 3 EAI_FAIL */
+ "Unsupported address family", /* 4 EAI_FAMILY */
+ "Memory allocation failure", /* 5 EAI_MEMORY */
+ "Host unknown or not given", /* 6 EAI_NONAME */
+ "Service not supported for socket", /* 7 EAI_SERVICE */
+ "Unsupported socket type", /* 8 EAI_SOCKTYPE */
+ "System error", /* 9 EAI_SYSTEM */
+ "Supplied buffer too small", /* 10 EAI_OVERFLOW */
+};
+
+/* Macro to set the len attribute of sockaddr_in. */
+#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
+# define sin_set_length(s) ((s)->sin_len = sizeof(struct sockaddr_in))
+#else
+# define sin_set_length(s) /* empty */
+#endif
+
+/*
+ * Return a constant string for a given EAI_* error code or a string
+ * indicating an unknown error.
+ */
+const char *
+sudo_gai_strerror(int ecode)
+{
+ if (ecode < 1 || (size_t) ecode > nitems(gai_errors))
+ return "Unknown error";
+ else
+ return gai_errors[ecode - 1];
+}
+
+
+/*
+ * Free a linked list of addrinfo structs.
+ */
+void
+sudo_freeaddrinfo(struct addrinfo *ai)
+{
+ struct addrinfo *next;
+
+ while (ai != NULL) {
+ next = ai->ai_next;
+ if (ai->ai_addr != NULL)
+ free(ai->ai_addr);
+ if (ai->ai_canonname != NULL)
+ free(ai->ai_canonname);
+ free(ai);
+ ai = next;
+ }
+}
+
+
+/*
+ * Allocate a new addrinfo struct, setting some defaults given that this
+ * implementation is IPv4 only. Also allocates an attached sockaddr_in and
+ * zeroes it, per the requirement for getaddrinfo. Takes the socktype,
+ * canonical name (which is copied if not NULL), address, and port. Returns
+ * NULL on a memory allocation failure.
+ */
+static struct addrinfo *
+gai_addrinfo_new(int socktype, const char *canonical, struct in_addr addr,
+ unsigned short port)
+{
+ struct addrinfo *ai;
+
+ ai = malloc(sizeof(*ai));
+ if (ai == NULL)
+ return NULL;
+ ai->ai_addr = malloc(sizeof(struct sockaddr_in));
+ if (ai->ai_addr == NULL) {
+ free(ai);
+ return NULL;
+ }
+ ai->ai_next = NULL;
+ if (canonical == NULL)
+ ai->ai_canonname = NULL;
+ else {
+ ai->ai_canonname = strdup(canonical);
+ if (ai->ai_canonname == NULL) {
+ freeaddrinfo(ai);
+ return NULL;
+ }
+ }
+ memset(ai->ai_addr, 0, sizeof(struct sockaddr_in));
+ ai->ai_flags = 0;
+ ai->ai_family = AF_INET;
+ ai->ai_socktype = socktype;
+ ai->ai_protocol = (socktype == SOCK_DGRAM) ? IPPROTO_UDP : IPPROTO_TCP;
+ ai->ai_addrlen = sizeof(struct sockaddr_in);
+ ((struct sockaddr_in *) ai->ai_addr)->sin_family = AF_INET;
+ ((struct sockaddr_in *) ai->ai_addr)->sin_addr = addr;
+ ((struct sockaddr_in *) ai->ai_addr)->sin_port = htons(port);
+ sin_set_length((struct sockaddr_in *) ai->ai_addr);
+ return ai;
+}
+
+
+/*
+ * Look up a service. Takes the service name (which may be numeric), the hint
+ * flags, a pointer to the socket type (used to determine whether TCP or UDP
+ * services are of interest and, if 0, is filled in with the result of
+ * getservbyname if the service was not numeric), and a pointer to the
+ * addrinfo struct to fill in. Returns 0 on success or an EAI_* error on
+ * failure.
+ */
+static int
+gai_service(const char *servname, int flags, int *type, unsigned short *port)
+{
+ struct servent *servent;
+ const char *protocol;
+ const char *errstr;
+ unsigned short value;
+
+ value = sudo_strtonum(servname, 0, USHRT_MAX, &errstr);
+ if (errstr == NULL) {
+ *port = value;
+ } else if (errno == ERANGE) {
+ return EAI_SERVICE;
+ } else {
+ if (flags & AI_NUMERICSERV)
+ return EAI_NONAME;
+ if (*type != 0)
+ protocol = (*type == SOCK_DGRAM) ? "udp" : "tcp";
+ else
+ protocol = NULL;
+
+ /*
+ * We really technically should be generating an addrinfo struct for
+ * each possible protocol unless type is set, but this works well
+ * enough for what I need this for.
+ */
+ servent = getservbyname(servname, protocol);
+ if (servent == NULL)
+ return EAI_NONAME;
+ if (strcmp(servent->s_proto, "udp") == 0)
+ *type = SOCK_DGRAM;
+ else if (strcmp(servent->s_proto, "tcp") == 0)
+ *type = SOCK_STREAM;
+ else
+ return EAI_SERVICE;
+ *port = htons(servent->s_port);
+ }
+ return 0;
+}
+
+
+/*
+ * Look up a host and fill in a linked list of addrinfo structs with the
+ * results, one per IP address of the returned host. Takes the name or IP
+ * address of the host as a string, the lookup flags, the type of socket (to
+ * fill into the addrinfo structs), the port (likewise), and a pointer to
+ * where the head of the linked list should be put. Returns 0 on success or
+ * the appropriate EAI_* error.
+ */
+static int
+gai_lookup(const char *nodename, int flags, int socktype, unsigned short port,
+ struct addrinfo **res)
+{
+ struct addrinfo *ai, *first, *prev;
+ struct in_addr addr;
+ struct hostent *host;
+ const char *canonical;
+ int i;
+
+ if (inet_pton(AF_INET, nodename, &addr)) {
+ canonical = (flags & AI_CANONNAME) ? nodename : NULL;
+ ai = gai_addrinfo_new(socktype, canonical, addr, port);
+ if (ai == NULL)
+ return EAI_MEMORY;
+ *res = ai;
+ return 0;
+ } else {
+ if (flags & AI_NUMERICHOST)
+ return EAI_NONAME;
+ host = gethostbyname(nodename);
+ if (host == NULL)
+ switch (h_errno) {
+ case HOST_NOT_FOUND:
+ return EAI_NONAME;
+ case TRY_AGAIN:
+ case NO_DATA:
+ return EAI_AGAIN;
+ case NO_RECOVERY:
+ return EAI_FAIL;
+ case NETDB_INTERNAL:
+ default:
+ return EAI_SYSTEM;
+ }
+ if (host->h_addr_list[0] == NULL)
+ return EAI_FAIL;
+ canonical = (flags & AI_CANONNAME)
+ ? ((host->h_name != NULL) ? host->h_name : nodename)
+ : NULL;
+ first = NULL;
+ prev = NULL;
+ for (i = 0; host->h_addr_list[i] != NULL; i++) {
+ if (host->h_length != sizeof(addr)) {
+ freeaddrinfo(first);
+ return EAI_FAIL;
+ }
+ memcpy(&addr, host->h_addr_list[i], sizeof(addr));
+ ai = gai_addrinfo_new(socktype, canonical, addr, port);
+ if (ai == NULL) {
+ freeaddrinfo(first);
+ return EAI_MEMORY;
+ }
+ if (first == NULL) {
+ first = ai;
+ prev = ai;
+ } else {
+ prev->ai_next = ai;
+ prev = ai;
+ }
+ }
+ *res = first;
+ return 0;
+ }
+}
+
+
+/*
+ * The actual getaddrinfo implementation.
+ */
+int
+sudo_getaddrinfo(const char *nodename, const char *servname,
+ const struct addrinfo *hints, struct addrinfo **res)
+{
+ struct addrinfo *ai;
+ struct in_addr addr;
+ int flags, socktype, status;
+ unsigned short port;
+
+ /* Take the hints into account and check them for validity. */
+ if (hints != NULL) {
+ flags = hints->ai_flags;
+ socktype = hints->ai_socktype;
+ if ((flags & AI_INTERNAL_ALL) != flags)
+ return EAI_BADFLAGS;
+ if (hints->ai_family != AF_UNSPEC && hints->ai_family != AF_INET)
+ return EAI_FAMILY;
+ if (socktype != 0 && socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
+ return EAI_SOCKTYPE;
+
+ /* EAI_SOCKTYPE isn't quite right, but there isn't anything better. */
+ if (hints->ai_protocol != 0) {
+ int protocol = hints->ai_protocol;
+ if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP)
+ return EAI_SOCKTYPE;
+ }
+ } else {
+ flags = 0;
+ socktype = 0;
+ }
+
+ /*
+ * See what we're doing. If nodename is null, either AI_PASSIVE is set or
+ * we're getting information for connecting to a service on the loopback
+ * address. Otherwise, we're getting information for connecting to a
+ * remote system.
+ */
+ if (servname == NULL)
+ port = 0;
+ else {
+ status = gai_service(servname, flags, &socktype, &port);
+ if (status != 0)
+ return status;
+ }
+ if (nodename != NULL)
+ return gai_lookup(nodename, flags, socktype, port, res);
+ else {
+ if (servname == NULL)
+ return EAI_NONAME;
+ if ((flags & AI_PASSIVE) == AI_PASSIVE)
+ addr.s_addr = INADDR_ANY;
+ else
+ addr.s_addr = htonl(0x7f000001UL);
+ ai = gai_addrinfo_new(socktype, NULL, addr, port);
+ if (ai == NULL)
+ return EAI_MEMORY;
+ *res = ai;
+ return 0;
+ }
+}
+#endif /* HAVE_GETADDRINFO */
diff --git a/lib/util/getcwd.c b/lib/util/getcwd.c
new file mode 100644
index 0000000..7cfc068
--- /dev/null
+++ b/lib/util/getcwd.c
@@ -0,0 +1,244 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_GETCWD
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <limits.h>
+
+#include "sudo_compat.h"
+
+#define ISDOT(dp) \
+ (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
+ (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
+
+#if defined(HAVE_STRUCT_DIRENT_D_NAMLEN) && HAVE_STRUCT_DIRENT_D_NAMLEN
+# define NAMLEN(dirent) (dirent)->d_namlen
+#else
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#endif
+
+char *
+sudo_getcwd(char *pt, size_t size)
+{
+ struct dirent *dp;
+ DIR *dir = NULL;
+ dev_t dev;
+ ino_t ino;
+ int first;
+ char *bpt, *bup;
+ struct stat s;
+ dev_t root_dev;
+ ino_t root_ino;
+ size_t ptsize, upsize;
+ int save_errno;
+ char *ept, *eup, *up;
+
+ /*
+ * If no buffer specified by the user, allocate one as necessary.
+ * If a buffer is specified, the size has to be non-zero. The path
+ * is built from the end of the buffer backwards.
+ */
+ if (pt) {
+ ptsize = 0;
+ if (!size) {
+ errno = EINVAL;
+ return NULL;
+ }
+ ept = pt + size;
+ } else {
+ if ((pt = malloc(ptsize = 1024 - 4)) == NULL)
+ return NULL;
+ ept = pt + ptsize;
+ }
+ bpt = ept - 1;
+ *bpt = '\0';
+
+ /*
+ * Allocate bytes (1024 - malloc space) for the string of "../"'s.
+ * Should always be enough (it's 340 levels). If it's not, allocate
+ * as necessary. Special * case the first stat, it's ".", not "..".
+ */
+ if ((up = malloc(upsize = 1024 - 4)) == NULL)
+ goto err;
+ eup = up + PATH_MAX;
+ bup = up;
+ up[0] = '.';
+ up[1] = '\0';
+
+ /* Save root values, so know when to stop. */
+ if (stat("/", &s))
+ goto err;
+ root_dev = s.st_dev;
+ root_ino = s.st_ino;
+
+ errno = 0; /* XXX readdir has no error return. */
+
+ for (first = 1;; first = 0) {
+ /* Stat the current level. */
+ if (lstat(up, &s))
+ goto err;
+
+ /* Save current node values. */
+ ino = s.st_ino;
+ dev = s.st_dev;
+
+ /* Check for reaching root. */
+ if (root_dev == dev && root_ino == ino) {
+ *--bpt = '/';
+ /*
+ * It's unclear that it's a requirement to copy the
+ * path to the beginning of the buffer, but it's always
+ * been that way and stuff would probably break.
+ */
+ memcpy(pt, bpt, ept - bpt);
+ free(up);
+ return pt;
+ }
+
+ /*
+ * Build pointer to the parent directory, allocating memory
+ * as necessary. Max length is 3 for "../", the largest
+ * possible component name, plus a trailing NULL.
+ */
+ if (bup + 3 + NAME_MAX + 1 >= eup) {
+ char *nup;
+
+ if ((nup = reallocarray(up, upsize, 2)) == NULL)
+ goto err;
+ upsize *= 2;
+ up = nup;
+ bup = up;
+ eup = up + upsize;
+ }
+ *bup++ = '.';
+ *bup++ = '.';
+ *bup = '\0';
+
+ /* Open and stat parent directory. */
+ if (!(dir = opendir(up)) || fstat(dirfd(dir), &s))
+ goto err;
+
+ /* Add trailing slash for next directory. */
+ *bup++ = '/';
+
+ /*
+ * If it's a mount point, have to stat each element because
+ * the inode number in the directory is for the entry in the
+ * parent directory, not the inode number of the mounted file.
+ */
+ save_errno = 0;
+ if (s.st_dev == dev) {
+ for (;;) {
+ if (!(dp = readdir(dir)))
+ goto notfound;
+ if (dp->d_fileno == ino)
+ break;
+ }
+ } else
+ for (;;) {
+ if (!(dp = readdir(dir)))
+ goto notfound;
+ if (ISDOT(dp))
+ continue;
+ memcpy(bup, dp->d_name, NAMLEN(dp) + 1);
+
+ /* Save the first error for later. */
+ if (lstat(up, &s)) {
+ if (!save_errno)
+ save_errno = errno;
+ errno = 0;
+ continue;
+ }
+ if (s.st_dev == dev && s.st_ino == ino)
+ break;
+ }
+
+ /*
+ * Check for length of the current name, preceding slash,
+ * leading slash.
+ */
+ if (bpt - pt <= NAMLEN(dp) + (first ? 1 : 2)) {
+ size_t len, off;
+ char *npt;
+
+ if (!ptsize) {
+ errno = ERANGE;
+ goto err;
+ }
+ off = bpt - pt;
+ len = ept - bpt;
+ if ((npt = reallocarray(pt, ptsize, 2)) == NULL)
+ goto err;
+ ptsize *= 2;
+ pt = npt;
+ bpt = pt + off;
+ ept = pt + ptsize;
+ memcpy(ept - len, bpt, len);
+ bpt = ept - len;
+ }
+ if (!first)
+ *--bpt = '/';
+ bpt -= NAMLEN(dp);
+ memcpy(bpt, dp->d_name, NAMLEN(dp));
+ (void)closedir(dir);
+
+ /* Truncate any file name. */
+ *bup = '\0';
+ }
+
+notfound:
+ /*
+ * If readdir set errno, use it, not any saved error; otherwise,
+ * didn't find the current directory in its parent directory, set
+ * errno to ENOENT.
+ */
+ if (!errno)
+ errno = save_errno ? save_errno : ENOENT;
+ /* FALLTHROUGH */
+err:
+ if (ptsize)
+ free(pt);
+ if (up)
+ free(up);
+ if (dir)
+ (void)closedir(dir);
+ return NULL;
+}
+#endif /* HAVE_GETCWD */
diff --git a/lib/util/getdelim.c b/lib/util/getdelim.c
new file mode 100644
index 0000000..53687a1
--- /dev/null
+++ b/lib/util/getdelim.c
@@ -0,0 +1,82 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_GETDELIM
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "sudo_compat.h"
+
+ssize_t
+sudo_getdelim(char **buf, size_t *bufsize, int delim, FILE *fp)
+{
+ char *cp, *ep;
+ int ch;
+
+ if (*buf == NULL || *bufsize == 0) {
+ char *tmp = realloc(*buf, LINE_MAX);
+ if (tmp == NULL)
+ return -1;
+ *buf = tmp;
+ *bufsize = LINE_MAX;
+ }
+ cp = *buf;
+ ep = cp + *bufsize;
+
+ do {
+ if (cp + 1 >= ep) {
+ char *newbuf = reallocarray(*buf, *bufsize, 2);
+ if (newbuf == NULL)
+ goto bad;
+ *bufsize *= 2;
+ cp = newbuf + (cp - *buf);
+ ep = newbuf + *bufsize;
+ *buf = newbuf;
+ }
+ if ((ch = getc(fp)) == EOF) {
+ if (feof(fp))
+ break;
+ goto bad;
+ }
+ *cp++ = ch;
+ } while (ch != delim);
+
+ /* getdelim(3) should never return a length of 0. */
+ if (cp != *buf) {
+ *cp = '\0';
+ return (ssize_t)(cp - *buf);
+ }
+bad:
+ /* Error, push back what was read if possible. */
+ while (cp > *buf) {
+ if (ungetc(*cp--, fp) == EOF)
+ break;
+ }
+ return -1;
+}
+#endif /* HAVE_GETDELIM */
diff --git a/lib/util/getentropy.c b/lib/util/getentropy.c
new file mode 100644
index 0000000..dc5c91c
--- /dev/null
+++ b/lib/util/getentropy.c
@@ -0,0 +1,642 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014 Theo de Raadt <deraadt@openbsd.org>
+ * Copyright (c) 2014 Bob Beck <beck@obtuse.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.
+ *
+ * Emulation of getentropy(2) as documented at:
+ * http://man.openbsd.org/getentropy.2
+ */
+
+/*
+ * 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>
+
+#ifndef HAVE_GETENTROPY
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_SYSCTL_H
+# include <sys/sysctl.h>
+#endif
+#ifdef HAVE_SYS_STATVFS_H
+# include <sys/statvfs.h>
+#endif
+#include <sys/stat.h>
+#include <sys/time.h>
+#ifdef HAVE_SYS_SYSCALL_H
+# include <sys/syscall.h>
+#endif
+#ifdef HAVE_LINUX_RANDOM_H
+# include <linux/types.h>
+# include <linux/random.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#ifdef HAVE_GETAUXVAL
+# include <sys/auxv.h>
+#endif
+#ifdef HAVE_DL_ITERATE_PHDR
+# include <link.h>
+#endif
+#ifdef HAVE_OPENSSL
+# if defined(HAVE_WOLFSSL)
+# include <wolfssl/options.h>
+# endif
+# include <openssl/rand.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_digest.h"
+#include "sudo_rand.h"
+
+#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS)
+# define MAP_ANON MAP_ANONYMOUS
+#endif
+
+#ifndef MAP_FAILED
+# define MAP_FAILED ((void *) -1)
+#endif
+
+#define REPEAT 5
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+
+#define HX(a, b) \
+ do { \
+ if ((a)) \
+ HD(errno); \
+ else \
+ HD(b); \
+ } while (0)
+
+#define HR(x, l) (sudo_digest_update(ctx, (char *)(x), (l)))
+#define HD(x) (sudo_digest_update(ctx, (char *)&(x), sizeof (x)))
+#define HF(x) (sudo_digest_update(ctx, (char *)&(x), sizeof (void*)))
+
+int sudo_getentropy(void *buf, size_t len);
+
+static int getentropy_getrandom(void *buf, size_t len);
+static int getentropy_sysctl(void *buf, size_t len);
+static int getentropy_urandom(void *buf, size_t len, const char *path,
+ int devfscheck);
+static int getentropy_fallback(void *buf, size_t len);
+static int gotdata(char *buf, size_t len);
+#ifdef HAVE_DL_ITERATE_PHDR
+static int getentropy_phdr(struct dl_phdr_info *info, size_t size, void *data);
+#endif
+
+static void *
+mmap_anon(void *addr, size_t len, int prot, int flags, off_t offset)
+{
+#ifdef MAP_ANON
+ return mmap(addr, len, prot, flags | MAP_ANON, -1, offset);
+#else
+ int fd;
+
+ if ((fd = open("/dev/zero", O_RDWR)) == -1)
+ return MAP_FAILED;
+ addr = mmap(addr, len, prot, flags, fd, offset);
+ close(fd);
+ return addr;
+#endif
+}
+
+int
+sudo_getentropy(void *buf, size_t len)
+{
+ int ret = -1;
+
+ if (len > 256) {
+ errno = EIO;
+ return (-1);
+ }
+
+ ret = getentropy_getrandom(buf, len);
+ if (ret != -1)
+ return (ret);
+
+#ifdef HAVE_OPENSSL
+ if (RAND_bytes(buf, len) == 1)
+ return (0);
+#endif
+
+ ret = getentropy_sysctl(buf, len);
+ if (ret != -1)
+ return (ret);
+
+ /*
+ * Try to get entropy with /dev/urandom
+ */
+ ret = getentropy_urandom(buf, len, "/dev/urandom", 0);
+ if (ret != -1)
+ return (ret);
+
+ /*
+ * Entropy collection via /dev/urandom has failed.
+ *
+ * No other API exists for collecting entropy, and we have no
+ * failsafe way to get it that is not sensitive to resource exhaustion.
+ *
+ * We have very few options:
+ * - Even syslog_r is unsafe to call at this low level, so
+ * there is no way to alert the user or program.
+ * - Cannot call abort() because some systems have unsafe
+ * corefiles.
+ * - Could raise(SIGKILL) resulting in silent program termination.
+ * - Return EIO, to hint that arc4random's stir function
+ * should raise(SIGKILL)
+ * - Do the best under the circumstances....
+ *
+ * This code path exists to bring light to the issue that the OS
+ * does not provide a failsafe API for entropy collection.
+ *
+ * We hope this demonstrates that the OS should consider
+ * providing a new failsafe API which works in a chroot or
+ * when file descriptors are exhausted.
+ */
+#undef FAIL_INSTEAD_OF_TRYING_FALLBACK
+#ifdef FAIL_INSTEAD_OF_TRYING_FALLBACK
+ raise(SIGKILL);
+#endif
+ ret = getentropy_fallback(buf, len);
+ if (ret != -1)
+ return (ret);
+
+ errno = EIO;
+ return (ret);
+}
+
+/*
+ * 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
+ */
+
+/*
+ * Basic validity checking; wish we could do better.
+ */
+static int
+gotdata(char *buf, size_t len)
+{
+ char any_set = 0;
+ size_t i;
+
+ for (i = 0; i < len; ++i)
+ any_set |= buf[i];
+ if (any_set == 0)
+ return (-1);
+ return (0);
+}
+
+static int
+getentropy_urandom(void *buf, size_t len, const char *path, int devfscheck)
+{
+ struct stat st;
+ size_t i;
+ int fd, flags;
+ int save_errno = errno;
+
+start:
+
+ /* We do not use O_NOFOLLOW since /dev/urandom is a link on Solaris. */
+ flags = O_RDONLY;
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+ fd = open(path, flags, 0);
+ if (fd == -1) {
+ if (errno == EINTR)
+ goto start;
+ goto nodevrandom;
+ }
+#ifndef O_CLOEXEC
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
+
+ /* Lightly verify that the device node looks OK */
+ if (fstat(fd, &st) == -1 || !S_ISCHR(st.st_mode)) {
+ close(fd);
+ goto nodevrandom;
+ }
+ for (i = 0; i < len; ) {
+ size_t wanted = len - i;
+ ssize_t ret = read(fd, (char *)buf + i, wanted);
+
+ if (ret == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ close(fd);
+ goto nodevrandom;
+ }
+ i += ret;
+ }
+ close(fd);
+ if (gotdata(buf, len) == 0) {
+ errno = save_errno;
+ return (0); /* satisfied */
+ }
+nodevrandom:
+ errno = EIO;
+ return (-1);
+}
+
+#if defined(HAVE_SYSCTL) && defined(KERN_ARND)
+static int
+getentropy_sysctl(void *buf, size_t len)
+{
+ int save_errno = errno;
+ int mib[2];
+ size_t i;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_ARND;
+
+ for (i = 0; i < len; ) {
+ size_t chunk = len - i;
+
+ if (sysctl(mib, 2, (char *)buf + i, &chunk, NULL, 0) == -1)
+ goto sysctlfailed;
+ i += chunk;
+ }
+ if (gotdata(buf, len) == 0) {
+ errno = save_errno;
+ return (0); /* satisfied */
+ }
+sysctlfailed:
+ errno = EIO;
+ return (-1);
+}
+#elif defined(SYS__sysctl) && defined(RANDOM_UUID)
+static int
+getentropy_sysctl(void *buf, size_t len)
+{
+ static int mib[3];
+ size_t i;
+ int save_errno = errno;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_RANDOM;
+ mib[2] = RANDOM_UUID;
+
+ for (i = 0; i < len; ) {
+ size_t chunk = min(len - i, 16);
+
+ /* SYS__sysctl because some systems already removed sysctl() */
+ struct __sysctl_args args = {
+ .name = mib,
+ .nlen = 3,
+ .oldval = (char *)buf + i,
+ .oldlenp = &chunk,
+ };
+ if (syscall(SYS__sysctl, &args) != 0)
+ goto sysctlfailed;
+ i += chunk;
+ }
+ if (gotdata(buf, len) == 0) {
+ errno = save_errno;
+ return (0); /* satisfied */
+ }
+sysctlfailed:
+ errno = EIO;
+ return (-1);
+}
+#else
+static int
+getentropy_sysctl(void *buf, size_t len)
+{
+ errno = ENOTSUP;
+ return (-1);
+}
+#endif
+
+#if defined(SYS_getrandom) && defined(GRND_NONBLOCK)
+static int
+getentropy_getrandom(void *buf, size_t len)
+{
+ int pre_errno = errno;
+ int ret;
+
+ /*
+ * Try descriptor-less getrandom(), in non-blocking mode.
+ *
+ * The design of Linux getrandom is broken. It has an
+ * uninitialized phase coupled with blocking behaviour, which
+ * is unacceptable from within a library at boot time without
+ * possible recovery. See http://bugs.python.org/issue26839#msg267745
+ */
+ do {
+ ret = syscall(SYS_getrandom, buf, len, GRND_NONBLOCK);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret < 0 || (size_t)ret != len)
+ return (-1);
+ errno = pre_errno;
+ return (0);
+}
+#else
+static int
+getentropy_getrandom(void *buf, size_t len)
+{
+ errno = ENOTSUP;
+ return (-1);
+}
+#endif
+
+#ifdef HAVE_CLOCK_GETTIME
+static const int cl[] = {
+ CLOCK_REALTIME,
+#ifdef CLOCK_MONOTONIC
+ CLOCK_MONOTONIC,
+#endif
+#ifdef CLOCK_MONOTONIC_RAW
+ CLOCK_MONOTONIC_RAW,
+#endif
+#ifdef CLOCK_TAI
+ CLOCK_TAI,
+#endif
+#ifdef CLOCK_VIRTUAL
+ CLOCK_VIRTUAL,
+#endif
+#ifdef CLOCK_UPTIME
+ CLOCK_UPTIME,
+#endif
+#ifdef CLOCK_PROCESS_CPUTIME_ID
+ CLOCK_PROCESS_CPUTIME_ID,
+#endif
+#ifdef CLOCK_THREAD_CPUTIME_ID
+ CLOCK_THREAD_CPUTIME_ID,
+#endif
+};
+#endif /* HAVE_CLOCK_GETTIME */
+
+#ifdef HAVE_DL_ITERATE_PHDR
+static int
+getentropy_phdr(struct dl_phdr_info *info, size_t size, void *data)
+{
+ struct sudo_digest *ctx = data;
+
+ sudo_digest_update(ctx, &info->dlpi_addr, sizeof (info->dlpi_addr));
+ return (0);
+}
+#endif
+
+static int
+getentropy_fallback(void *buf, size_t len)
+{
+ unsigned char *results = NULL;
+ int save_errno = errno, e, pgs = sysconf(_SC_PAGESIZE), faster = 0, repeat;
+ int ret = -1;
+ static int cnt;
+ struct timespec ts;
+ struct timeval tv;
+ struct rusage ru;
+ sigset_t set;
+ struct stat st;
+ struct sudo_digest *ctx;
+ static pid_t lastpid;
+ pid_t pid;
+ size_t i, ii, m, digest_len;
+ char *p;
+
+ if ((ctx = sudo_digest_alloc(SUDO_DIGEST_SHA512)) == NULL)
+ goto done;
+ digest_len = sudo_digest_getlen(SUDO_DIGEST_SHA512);
+ if (digest_len == (size_t)-1 || (results = malloc(digest_len)) == NULL)
+ goto done;
+
+ pid = getpid();
+ if (lastpid == pid) {
+ faster = 1;
+ repeat = 2;
+ } else {
+ faster = 0;
+ lastpid = pid;
+ repeat = REPEAT;
+ }
+ for (i = 0; i < len; ) {
+ int j;
+ for (j = 0; j < repeat; j++) {
+ HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+ if (e != -1) {
+ cnt += (int)tv.tv_sec;
+ cnt += (int)tv.tv_usec;
+ }
+#ifdef HAVE_DL_ITERATE_PHDR
+ dl_iterate_phdr(getentropy_phdr, ctx);
+#endif
+
+#ifdef HAVE_CLOCK_GETTIME
+ for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]); ii++)
+ HX(clock_gettime(cl[ii], &ts) == -1, ts);
+#endif /* HAVE_CLOCK_GETTIME */
+
+ HX((pid = getpid()) == -1, pid);
+ HX((pid = getsid(pid)) == -1, pid);
+ HX((pid = getppid()) == -1, pid);
+ HX((pid = getpgid(0)) == -1, pid);
+ HX((e = getpriority(0, 0)) == -1, e);
+
+ if (!faster) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 1;
+ (void) nanosleep(&ts, NULL);
+ }
+
+ HX(sigpending(&set) == -1, set);
+ HX(sigprocmask(SIG_BLOCK, NULL, &set) == -1, set);
+
+ HF(sudo_getentropy); /* an addr in this library */
+ HF(printf); /* an addr in libc */
+ p = (char *)&p;
+ HD(p); /* an addr on stack */
+ p = (char *)&errno;
+ HD(p); /* the addr of errno */
+
+ if (i == 0) {
+#ifdef HAVE_SYS_STATVFS_H
+ struct statvfs stvfs;
+#endif
+ struct termios tios;
+ off_t off;
+
+ /*
+ * Prime-sized mappings encourage fragmentation;
+ * thus exposing some address entropy.
+ */
+ struct mm {
+ size_t npg;
+ void *p;
+ } mm[] = {
+ { 17, MAP_FAILED }, { 3, MAP_FAILED },
+ { 11, MAP_FAILED }, { 2, MAP_FAILED },
+ { 5, MAP_FAILED }, { 3, MAP_FAILED },
+ { 7, MAP_FAILED }, { 1, MAP_FAILED },
+ { 57, MAP_FAILED }, { 3, MAP_FAILED },
+ { 131, MAP_FAILED }, { 1, MAP_FAILED },
+ };
+
+ for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+ HX(mm[m].p = mmap_anon(NULL,
+ mm[m].npg * pgs,
+ PROT_READ|PROT_WRITE,
+ MAP_PRIVATE,
+ (off_t)0), mm[m].p);
+ if (mm[m].p != MAP_FAILED) {
+ size_t mo;
+
+ /* Touch some memory... */
+ p = mm[m].p;
+ mo = cnt %
+ (mm[m].npg * pgs - 1);
+ p[mo] = 1;
+ cnt += (int)((long)(mm[m].p)
+ / pgs);
+ }
+
+#ifdef HAVE_CLOCK_GETTIME
+ /* Check cnts and times... */
+ for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]);
+ ii++) {
+ HX((e = clock_gettime(cl[ii],
+ &ts)) == -1, ts);
+ if (e != -1)
+ cnt += (int)ts.tv_nsec;
+ }
+#endif /* HAVE_CLOCK_GETTIME */
+
+ HX((e = getrusage(RUSAGE_SELF,
+ &ru)) == -1, ru);
+ if (e != -1) {
+ cnt += (int)ru.ru_utime.tv_sec;
+ cnt += (int)ru.ru_utime.tv_usec;
+ }
+ }
+
+ for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+ if (mm[m].p != MAP_FAILED)
+ munmap(mm[m].p, mm[m].npg * pgs);
+ mm[m].p = MAP_FAILED;
+ }
+
+ HX(stat(".", &st) == -1, st);
+ HX(stat("/", &st) == -1, st);
+
+#ifdef HAVE_SYS_STATVFS_H
+ HX(statvfs(".", &stvfs) == -1, stvfs);
+ HX(statvfs("/", &stvfs) == -1, stvfs);
+#endif
+ HX((e = fstat(0, &st)) == -1, st);
+ if (e == -1) {
+ if (S_ISREG(st.st_mode) ||
+ S_ISFIFO(st.st_mode) ||
+ S_ISSOCK(st.st_mode)) {
+#ifdef HAVE_SYS_STATVFS_H
+ HX(fstatvfs(0, &stvfs) == -1,
+ stvfs);
+#endif
+ HX((off = lseek(0, (off_t)0,
+ SEEK_CUR)) < 0, off);
+ }
+ if (S_ISCHR(st.st_mode)) {
+ HX(tcgetattr(0, &tios) == -1,
+ tios);
+#if 0
+ } else if (S_ISSOCK(st.st_mode)) {
+ struct sockaddr_storage ss;
+ socklen_t ssl;
+ memset(&ss, 0, sizeof ss);
+ ssl = sizeof(ss);
+ HX(getpeername(0,
+ (void *)&ss, &ssl) == -1,
+ ss);
+#endif
+ }
+ }
+
+ HX((e = getrusage(RUSAGE_CHILDREN,
+ &ru)) == -1, ru);
+ if (e != -1) {
+ cnt += (int)ru.ru_utime.tv_sec;
+ cnt += (int)ru.ru_utime.tv_usec;
+ }
+ } else {
+ /* Subsequent hashes absorb previous result */
+ HR(results, digest_len);
+ }
+
+ HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+ if (e != -1) {
+ cnt += (int)tv.tv_sec;
+ cnt += (int)tv.tv_usec;
+ }
+
+ HD(cnt);
+ }
+
+#ifdef HAVE_GETAUXVAL
+#ifdef AT_RANDOM
+ /* Not as random as you think but we take what we are given */
+ p = (char *) getauxval(AT_RANDOM);
+ if (p)
+ HR(p, 16);
+#endif
+#ifdef AT_SYSINFO_EHDR
+ p = (char *) getauxval(AT_SYSINFO_EHDR);
+ if (p)
+ HR(p, pgs);
+#endif
+#ifdef AT_BASE
+ p = (char *) getauxval(AT_BASE);
+ if (p)
+ HD(p);
+#endif
+#endif /* HAVE_GETAUXVAL */
+
+ sudo_digest_final(ctx, results);
+ sudo_digest_reset(ctx);
+ memcpy((char *)buf + i, results, min(digest_len, len - i));
+ i += min(digest_len, len - i);
+ }
+ if (gotdata(buf, len) == 0) {
+ errno = save_errno;
+ ret = 0; /* satisfied */
+ } else {
+ errno = EIO;
+ }
+done:
+ sudo_digest_free(ctx);
+ if (results != NULL)
+ freezero(results, digest_len);
+ return (ret);
+}
+
+#endif /* HAVE_GETENTROPY */
diff --git a/lib/util/getgrouplist.c b/lib/util/getgrouplist.c
new file mode 100644
index 0000000..b6170c6
--- /dev/null
+++ b/lib/util/getgrouplist.c
@@ -0,0 +1,529 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010, 2011, 2013-2021
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <grp.h>
+#include <limits.h>
+#include <unistd.h>
+#ifdef HAVE_NSS_SEARCH
+# include <errno.h>
+# include <limits.h>
+# include <nsswitch.h>
+# ifdef HAVE_NSS_DBDEFS_H
+# include <nss_dbdefs.h>
+# else
+# include "compat/nss_dbdefs.h"
+# endif
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+#ifndef HAVE_GETGROUPLIST
+int
+sudo_getgrouplist(const char *name, GETGROUPS_T basegid, GETGROUPS_T *groups,
+ int *ngroupsp)
+{
+ return sudo_getgrouplist2(name, basegid, &groups, ngroupsp);
+}
+#endif /* HAVE_GETGROUPLIST */
+
+#if defined(HAVE_GETGROUPLIST)
+
+#if defined(HAVE_GETGROUPLIST_2) && !HAVE_DECL_GETGROUPLIST_2
+int getgrouplist_2(const char *name, GETGROUPS_T basegid, GETGROUPS_T **groups);
+#endif /* HAVE_GETGROUPLIST_2 && !HAVE_DECL_GETGROUPLIST_2 */
+
+/*
+ * Extended getgrouplist(3) using getgrouplist(3) and getgrouplist_2(3)
+ */
+int
+sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid,
+ GETGROUPS_T **groupsp, int *ngroupsp)
+{
+#ifdef __APPLE__
+ int *groups = (int *)*groupsp;
+#else
+ GETGROUPS_T *groups = *groupsp;
+#endif
+ int ngroups;
+#ifndef HAVE_GETGROUPLIST_2
+ int grpsize, tries;
+#endif
+ debug_decl(sudo_getgrouplist2, SUDO_DEBUG_UTIL);
+
+ /* For static group vector, just use getgrouplist(3). */
+ if (groups != NULL)
+ debug_return_int(getgrouplist(name, basegid, groups, ngroupsp));
+
+#ifdef HAVE_GETGROUPLIST_2
+ if ((ngroups = getgrouplist_2(name, basegid, groupsp)) == -1)
+ debug_return_int(-1);
+ *ngroupsp = ngroups;
+ debug_return_int(0);
+#else
+ grpsize = (int)sysconf(_SC_NGROUPS_MAX);
+ if (grpsize < 0)
+ grpsize = NGROUPS_MAX;
+ grpsize++; /* include space for the primary gid */
+ /*
+ * It is possible to belong to more groups in the group database
+ * than NGROUPS_MAX.
+ */
+ for (tries = 0; tries < 10; tries++) {
+ free(groups);
+ groups = reallocarray(NULL, grpsize, sizeof(*groups));
+ if (groups == NULL)
+ debug_return_int(-1);
+ ngroups = grpsize;
+ if (getgrouplist(name, basegid, groups, &ngroups) != -1) {
+ *groupsp = groups;
+ *ngroupsp = ngroups;
+ debug_return_int(0);
+ }
+ if (ngroups == grpsize) {
+ /* Failed for some reason other than ngroups too small. */
+ break;
+ }
+ /* getgrouplist(3) set ngroups to the required length, use it. */
+ grpsize = ngroups;
+ }
+ free(groups);
+ debug_return_int(-1);
+#endif /* HAVE_GETGROUPLIST_2 */
+}
+
+#elif defined(HAVE_GETGRSET)
+
+/*
+ * Extended getgrouplist(3) using AIX getgrset(3)
+ */
+int
+sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid,
+ GETGROUPS_T **groupsp, int *ngroupsp)
+{
+ GETGROUPS_T *groups = *groupsp;
+ char *cp, *last, *grset = NULL;
+ const char *errstr;
+ int ngroups = 1;
+ int grpsize = *ngroupsp;
+ int ret = -1;
+ gid_t gid;
+ debug_decl(sudo_getgrouplist2, SUDO_DEBUG_UTIL);
+
+#ifdef HAVE_SETAUTHDB
+ aix_setauthdb((char *) name, NULL);
+#endif
+ if ((grset = getgrset(name)) == NULL)
+ goto done;
+
+ if (groups == NULL) {
+ /* Dynamically-sized group vector, count groups and alloc. */
+ grpsize = 1; /* reserve one for basegid */
+ if (*grset != '\0') {
+ grpsize++; /* at least one supplementary group */
+ for (cp = grset; *cp != '\0'; cp++) {
+ if (*cp == ',')
+ grpsize++;
+ }
+ }
+ groups = reallocarray(NULL, grpsize, sizeof(*groups));
+ if (groups == NULL)
+ debug_return_int(-1);
+ } else {
+ /* Static group vector. */
+ if (grpsize < 1)
+ debug_return_int(-1);
+ }
+
+ /* We support BSD semantics where the first element is the base gid */
+ groups[0] = basegid;
+
+ for (cp = strtok_r(grset, ",", &last); cp != NULL; cp = strtok_r(NULL, ",", &last)) {
+ gid = sudo_strtoid(cp, &errstr);
+ if (errstr == NULL && gid != basegid) {
+ if (ngroups == grpsize)
+ goto done;
+ groups[ngroups++] = gid;
+ }
+ }
+ ret = 0;
+
+done:
+ free(grset);
+#ifdef HAVE_SETAUTHDB
+ aix_restoreauthdb();
+#endif
+ *groupsp = groups;
+ *ngroupsp = ngroups;
+
+ debug_return_int(ret);
+}
+
+#elif defined(HAVE_NSS_SEARCH)
+
+#ifndef ALIGNBYTES
+# define ALIGNBYTES (sizeof(long) - 1L)
+#endif
+#ifndef ALIGN
+# define ALIGN(p) (((unsigned long)(p) + ALIGNBYTES) & ~ALIGNBYTES)
+#endif
+
+#if defined(HAVE__NSS_INITF_GROUP) || defined(HAVE___NSS_INITF_GROUP)
+extern void _nss_initf_group(nss_db_params_t *params);
+#else
+static void
+_nss_initf_group(nss_db_params_t *params)
+{
+ params->name = NSS_DBNAM_GROUP;
+ params->default_config = NSS_DEFCONF_GROUP;
+}
+#endif
+
+/*
+ * Convert a groups file string (instr) to a struct group (ent) using
+ * buf for storage.
+ */
+static int
+str2grp(const char *instr, int inlen, void *ent, char *buf, int buflen)
+{
+ struct group *grp = ent;
+ char *cp, *fieldsep = buf;
+ char **gr_mem, **gr_end;
+ const char *errstr;
+ int yp = 0;
+ id_t id;
+ debug_decl(str2grp, SUDO_DEBUG_UTIL);
+
+ /* Must at least have space to copy instr -> buf. */
+ if (inlen >= buflen)
+ debug_return_int(NSS_STR_PARSE_ERANGE);
+
+ /* Paranoia: buf and instr should be distinct. */
+ if (buf != instr) {
+ memmove(buf, instr, inlen);
+ buf[inlen] = '\0';
+ }
+
+ if ((fieldsep = strchr(cp = fieldsep, ':')) == NULL)
+ debug_return_int(NSS_STR_PARSE_PARSE);
+ *fieldsep++ = '\0';
+ grp->gr_name = cp;
+
+ /* Check for YP inclusion/exclusion entries. */
+ if (*cp == '+' || *cp == '-') {
+ /* Only the name is required for YP inclusion/exclusion entries. */
+ grp->gr_passwd = (char *)"";
+ grp->gr_gid = 0;
+ grp->gr_mem = NULL;
+ yp = 1;
+ }
+
+ if ((fieldsep = strchr(cp = fieldsep, ':')) == NULL)
+ debug_return_int(yp ? NSS_STR_PARSE_SUCCESS : NSS_STR_PARSE_PARSE);
+ *fieldsep++ = '\0';
+ grp->gr_passwd = cp;
+
+ if ((fieldsep = strchr(cp = fieldsep, ':')) == NULL)
+ debug_return_int(yp ? NSS_STR_PARSE_SUCCESS : NSS_STR_PARSE_PARSE);
+ *fieldsep++ = '\0';
+ id = sudo_strtoid(cp, &errstr);
+ if (errstr != NULL) {
+ /*
+ * A range error is always a fatal error, but ignore garbage
+ * at the end of YP entries since it has no meaning.
+ */
+ if (errno == ERANGE)
+ debug_return_int(NSS_STR_PARSE_ERANGE);
+ debug_return_int(yp ? NSS_STR_PARSE_SUCCESS : NSS_STR_PARSE_PARSE);
+ }
+#ifdef GID_NOBODY
+ /* Negative gids get mapped to nobody on Solaris. */
+ if (*cp == '-' && id != 0)
+ grp->gr_gid = GID_NOBODY;
+ else
+#endif
+ grp->gr_gid = (gid_t)id;
+
+ /* Store group members, taking care to use proper alignment. */
+ grp->gr_mem = NULL;
+ if (*fieldsep != '\0') {
+ grp->gr_mem = gr_mem = (char **)ALIGN(buf + inlen + 1);
+ gr_end = (char **)((unsigned long)(buf + buflen) & ~ALIGNBYTES) - 1;
+ for (;;) {
+ if (gr_mem >= gr_end)
+ debug_return_int(NSS_STR_PARSE_ERANGE); /* out of space! */
+ *gr_mem++ = cp;
+ if (fieldsep == NULL)
+ break;
+ if ((fieldsep = strchr(cp = fieldsep, ',')) != NULL)
+ *fieldsep++ = '\0';
+ }
+ *gr_mem = NULL;
+ }
+ debug_return_int(NSS_STR_PARSE_SUCCESS);
+}
+
+static nss_status_t
+process_cstr(const char *instr, int inlen, struct nss_groupsbymem *gbm,
+ int dynamic)
+{
+ const char *user = gbm->username;
+ nss_status_t ret = NSS_NOTFOUND;
+ nss_XbyY_buf_t *buf;
+ struct group *grp;
+ char **gr_mem;
+ int error, i;
+ debug_decl(process_cstr, SUDO_DEBUG_UTIL);
+
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: parsing %.*s", __func__,
+ inlen, instr);
+
+ /* Hack to let us check whether the query was handled by nscd or us. */
+ if (gbm->force_slow_way != 0)
+ gbm->force_slow_way = 2;
+
+ buf = _nss_XbyY_buf_alloc(sizeof(struct group), NSS_BUFLEN_GROUP);
+ if (buf == NULL)
+ debug_return_int(NSS_UNAVAIL);
+
+ /* Parse groups file string -> struct group. */
+ grp = buf->result;
+ error = (*gbm->str2ent)(instr, inlen, grp, buf->buffer, buf->buflen);
+ if (error != NSS_STR_PARSE_SUCCESS || grp->gr_mem == NULL)
+ goto done;
+
+ for (gr_mem = grp->gr_mem; *gr_mem != NULL; gr_mem++) {
+ if (strcmp(*gr_mem, user) == 0) {
+ const int numgids = MIN(gbm->numgids, gbm->maxgids);
+
+ /* Append to gid_array unless gr_gid is a dupe. */
+ for (i = 0; i < numgids; i++) {
+ if (gbm->gid_array[i] == grp->gr_gid)
+ goto done; /* already present */
+ }
+ if (i == gbm->maxgids && dynamic) {
+ GETGROUPS_T *tmp = reallocarray(gbm->gid_array, gbm->maxgids,
+ 2 * sizeof(GETGROUPS_T));
+ if (tmp == NULL) {
+ /* Out of memory, just return what we have. */
+ dynamic = 0;
+ } else {
+ gbm->gid_array = tmp;
+ gbm->maxgids <<= 1;
+ }
+ }
+ /* Store gid if there is space. */
+ if (i < gbm->maxgids)
+ gbm->gid_array[i] = grp->gr_gid;
+ /* Always increment numgids so we can detect when out of space. */
+ gbm->numgids++;
+ goto done;
+ }
+ }
+done:
+ _nss_XbyY_buf_free(buf);
+ debug_return_int(ret);
+}
+
+static nss_status_t
+process_cstr_static(const char *instr, int inlen, struct nss_groupsbymem *gbm)
+{
+ return process_cstr(instr, inlen, gbm, 0);
+}
+
+static nss_status_t
+process_cstr_dynamic(const char *instr, int inlen, struct nss_groupsbymem *gbm)
+{
+ return process_cstr(instr, inlen, gbm, 1);
+}
+
+/*
+ * Extended getgrouplist(3) using nss_search(3)
+ */
+int
+sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid,
+ GETGROUPS_T **groupsp, int *ngroupsp)
+{
+ struct nss_groupsbymem gbm;
+ static DEFINE_NSS_DB_ROOT(db_root);
+ debug_decl(sudo_getgrouplist2, SUDO_DEBUG_UTIL);
+
+ memset(&gbm, 0, sizeof(gbm));
+ gbm.username = name;
+ gbm.gid_array = *groupsp;
+ gbm.maxgids = *ngroupsp;
+ gbm.numgids = 1; /* for basegid */
+ gbm.force_slow_way = 1;
+ gbm.str2ent = str2grp;
+
+ if (gbm.gid_array == NULL) {
+ /* Dynamically-sized group vector. */
+ gbm.maxgids = (int)sysconf(_SC_NGROUPS_MAX);
+ if (gbm.maxgids < 0)
+ gbm.maxgids = NGROUPS_MAX;
+ gbm.gid_array = reallocarray(NULL, gbm.maxgids, 4 * sizeof(GETGROUPS_T));
+ if (gbm.gid_array == NULL)
+ debug_return_int(-1);
+ gbm.maxgids <<= 2;
+ gbm.process_cstr = process_cstr_dynamic;
+ } else {
+ /* Static group vector. */
+ if (gbm.maxgids <= 0)
+ debug_return_int(-1);
+ gbm.process_cstr = process_cstr_static;
+ }
+
+ /* We support BSD semantics where the first element is the base gid */
+ gbm.gid_array[0] = basegid;
+
+ /*
+ * Can't use nss_search return value since it may return NSS_UNAVAIL
+ * when no nsswitch.conf entry (e.g. compat mode).
+ */
+ for (;;) {
+ GETGROUPS_T *tmp;
+
+ (void)nss_search(&db_root, _nss_initf_group, NSS_DBOP_GROUP_BYMEMBER,
+ &gbm);
+
+ /*
+ * If this was a statically-sized group vector or nscd was not used
+ * we are done.
+ */
+ if (gbm.process_cstr != process_cstr_dynamic || gbm.force_slow_way == 2)
+ break;
+
+ /*
+ * If gid_array is full and the query was handled by nscd, there
+ * may be more data, so double gid_array and try again.
+ */
+ if (gbm.numgids != gbm.maxgids)
+ break;
+
+ tmp = reallocarray(gbm.gid_array, gbm.maxgids, 2 * sizeof(GETGROUPS_T));
+ if (tmp == NULL) {
+ free(gbm.gid_array);
+ debug_return_int(-1);
+ }
+ gbm.gid_array = tmp;
+ gbm.maxgids <<= 1;
+ }
+
+ /* Note: we can only detect a too-small group list if nscd is not used. */
+ *groupsp = gbm.gid_array;
+ if (gbm.numgids <= gbm.maxgids) {
+ *ngroupsp = gbm.numgids;
+ debug_return_int(0);
+ }
+ *ngroupsp = gbm.maxgids;
+ debug_return_int(-1);
+}
+
+#else /* !HAVE_GETGROUPLIST && !HAVE_GETGRSET && !HAVE__GETGROUPSBYMEMBER */
+
+/*
+ * Extended getgrouplist(3) using getgrent(3)
+ */
+int
+sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid,
+ GETGROUPS_T **groupsp, int *ngroupsp)
+{
+ GETGROUPS_T *groups = *groupsp;
+ int grpsize = *ngroupsp;
+ int i, ngroups = 1;
+ int ret = -1;
+ struct group *grp;
+ debug_decl(sudo_getgrouplist2, SUDO_DEBUG_UTIL);
+
+ if (groups == NULL) {
+ /* Dynamically-sized group vector. */
+ grpsize = (int)sysconf(_SC_NGROUPS_MAX);
+ if (grpsize < 0)
+ grpsize = NGROUPS_MAX;
+ groups = reallocarray(NULL, grpsize, 4 * sizeof(*groups));
+ if (groups == NULL)
+ debug_return_int(-1);
+ grpsize <<= 2;
+ } else {
+ /* Static group vector. */
+ if (grpsize < 1)
+ debug_return_int(-1);
+ }
+
+ /* We support BSD semantics where the first element is the base gid */
+ groups[0] = basegid;
+
+ setgrent();
+ while ((grp = getgrent()) != NULL) {
+ if (grp->gr_gid == basegid || grp->gr_mem == NULL)
+ continue;
+
+ for (i = 0; grp->gr_mem[i] != NULL; i++) {
+ if (strcmp(name, grp->gr_mem[i]) == 0)
+ break;
+ }
+ if (grp->gr_mem[i] == NULL)
+ continue; /* user not found */
+
+ /* Only add if it is not the same as an existing gid */
+ for (i = 0; i < ngroups; i++) {
+ if (grp->gr_gid == groups[i])
+ break;
+ }
+ if (i == ngroups) {
+ if (ngroups == grpsize) {
+ GETGROUPS_T *tmp;
+
+ if (*groupsp != NULL) {
+ /* Static group vector. */
+ goto done;
+ }
+ tmp = reallocarray(groups, grpsize, 2 * sizeof(*groups));
+ if (tmp == NULL) {
+ free(groups);
+ groups = NULL;
+ ngroups = 0;
+ goto done;
+ }
+ groups = tmp;
+ grpsize <<= 1;
+ }
+ groups[ngroups++] = grp->gr_gid;
+ }
+ }
+ ret = 0;
+
+done:
+ endgrent();
+ *groupsp = groups;
+ *ngroupsp = ngroups;
+
+ debug_return_int(ret);
+}
+#endif /* !HAVE_GETGROUPLIST && !HAVE_GETGRSET && !HAVE__GETGROUPSBYMEMBER */
diff --git a/lib/util/gethostname.c b/lib/util/gethostname.c
new file mode 100644
index 0000000..894aefa
--- /dev/null
+++ b/lib/util/gethostname.c
@@ -0,0 +1,59 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+/*
+ * Return a malloc()ed copy of the system hostname, or NULL if
+ * malloc() or gethostname() fails.
+ */
+char *
+sudo_gethostname_v1(void)
+{
+ char *hname;
+ size_t host_name_max;
+
+#ifdef _SC_HOST_NAME_MAX
+ host_name_max = (size_t)sysconf(_SC_HOST_NAME_MAX);
+ if (host_name_max == (size_t)-1)
+#endif
+ host_name_max = 255; /* POSIX and historic BSD */
+
+ hname = malloc(host_name_max + 1);
+ if (hname != NULL) {
+ if (gethostname(hname, host_name_max + 1) == 0 && *hname != '\0') {
+ /* Old gethostname() may not NUL-terminate if there is no room. */
+ hname[host_name_max] = '\0';
+ } else {
+ free(hname);
+ hname = NULL;
+ }
+ }
+ return hname;
+}
diff --git a/lib/util/getopt_long.c b/lib/util/getopt_long.c
new file mode 100644
index 0000000..15b04ee
--- /dev/null
+++ b/lib/util/getopt_long.c
@@ -0,0 +1,624 @@
+/* $OpenBSD: getopt_long.c,v 1.26 2013/06/08 22:47:56 millert Exp $ */
+/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */
+/* $FreeBSD: head/lib/libc/stdlib/getopt_long.c 236936 2012-06-11 22:25:20Z delphij $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2002 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
+ */
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Dieter Baron and Thomas Klausner.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "compat/getopt.h"
+
+#define GNU_COMPATIBLE /* Be more compatible with GNU getopt. */
+
+#ifdef REPLACE_GETOPT
+int opterr = 1; /* if error message should be printed */
+int optind = 1; /* index into parent argv vector */
+int optopt = '?'; /* character checked for validity */
+char *optarg; /* argument associated with option */
+#else
+extern int opterr; /* if error message should be printed */
+extern int optind; /* index into parent argv vector */
+extern int optopt; /* character checked for validity */
+extern char *optarg; /* argument associated with option */
+#endif
+#if !defined(REPLACE_GETOPT) && !defined(HAVE_OPTRESET)
+int optreset; /* reset getopt */
+#endif
+
+#define PRINT_ERROR ((opterr) && (*options != ':'))
+
+#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */
+#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */
+#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */
+
+/* return values */
+#define BADCH (int)'?'
+#define BADARG ((*options == ':') ? (int)':' : (int)'?')
+#define INORDER (int)1
+
+#define EMSG (char *)""
+
+#ifdef GNU_COMPATIBLE
+#define NO_PREFIX (-1)
+#define D_PREFIX 0
+#define DD_PREFIX 1
+#define W_PREFIX 2
+#endif
+
+static int getopt_internal(int, char * const *, const char *,
+ const struct option *, int *, int);
+static int parse_long_options(char * const *, const char *,
+ const struct option *, int *, int, int);
+static int gcd(int, int);
+static void permute_args(int, int, int, char * const *);
+
+static char *place = EMSG; /* option letter processing */
+
+/* XXX: set optreset to 1 rather than these two */
+static int nonopt_start = -1; /* first non option argument (for permute) */
+static int nonopt_end = -1; /* first option after non options (for permute) */
+
+/* Error messages */
+static const char recargchar[] = "option requires an argument -- %c";
+static const char illoptchar[] = "illegal option -- %c"; /* From P1003.2 */
+#ifdef GNU_COMPATIBLE
+static int dash_prefix = NO_PREFIX;
+static const char gnuoptchar[] = "invalid option -- %c";
+
+static const char recargstring[] = "option `%s%s' requires an argument";
+static const char ambig[] = "option `%s%.*s' is ambiguous";
+static const char noarg[] = "option `%s%.*s' doesn't allow an argument";
+static const char illoptstring[] = "unrecognized option `%s%s'";
+#else
+static const char recargstring[] = "option requires an argument -- %s";
+static const char ambig[] = "ambiguous option -- %.*s";
+static const char noarg[] = "option doesn't take an argument -- %.*s";
+static const char illoptstring[] = "unknown option -- %s";
+#endif
+
+/*
+ * Compute the greatest common divisor of a and b.
+ */
+static int
+gcd(int a, int b)
+{
+ int c;
+
+ c = a % b;
+ while (c != 0) {
+ a = b;
+ b = c;
+ c = a % b;
+ }
+
+ return (b);
+}
+
+/*
+ * Exchange the block from nonopt_start to nonopt_end with the block
+ * from nonopt_end to opt_end (keeping the same order of arguments
+ * in each block).
+ */
+static void
+permute_args(int panonopt_start, int panonopt_end, int opt_end,
+ char * const *nargv)
+{
+ int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
+ char *swap;
+
+ /*
+ * compute lengths of blocks and number and size of cycles
+ */
+ nnonopts = panonopt_end - panonopt_start;
+ nopts = opt_end - panonopt_end;
+ ncycle = gcd(nnonopts, nopts);
+ cyclelen = (opt_end - panonopt_start) / ncycle;
+
+ for (i = 0; i < ncycle; i++) {
+ cstart = panonopt_end+i;
+ pos = cstart;
+ for (j = 0; j < cyclelen; j++) {
+ if (pos >= panonopt_end)
+ pos -= nnonopts;
+ else
+ pos += nopts;
+ swap = nargv[pos];
+ /* LINTED const cast */
+ ((char **) nargv)[pos] = nargv[cstart];
+ /* LINTED const cast */
+ ((char **)nargv)[cstart] = swap;
+ }
+ }
+}
+
+/*
+ * parse_long_options --
+ * Parse long options in argc/argv argument vector.
+ * Returns -1 if short_too is set and the option does not match long_options.
+ */
+static int
+parse_long_options(char * const *nargv, const char *options,
+ const struct option *long_options, int *idx, int short_too, int flags)
+{
+ char *current_argv, *has_equal;
+#ifdef GNU_COMPATIBLE
+ const char *current_dash;
+#endif
+ size_t current_argv_len;
+ int i, match, exact_match, second_partial_match;
+
+ current_argv = place;
+#ifdef GNU_COMPATIBLE
+ switch (dash_prefix) {
+ case D_PREFIX:
+ current_dash = "-";
+ break;
+ case DD_PREFIX:
+ current_dash = "--";
+ break;
+ case W_PREFIX:
+ current_dash = "-W ";
+ break;
+ default:
+ current_dash = "";
+ break;
+ }
+#endif
+ match = -1;
+ exact_match = 0;
+ second_partial_match = 0;
+
+ optind++;
+
+ if ((has_equal = strchr(current_argv, '=')) != NULL) {
+ /* argument found (--option=arg) */
+ current_argv_len = has_equal - current_argv;
+ has_equal++;
+ } else
+ current_argv_len = strlen(current_argv);
+
+ for (i = 0; long_options[i].name; i++) {
+ /* find matching long option */
+ if (strncmp(current_argv, long_options[i].name,
+ current_argv_len))
+ continue;
+
+ if (strlen(long_options[i].name) == current_argv_len) {
+ /* exact match */
+ match = i;
+ exact_match = 1;
+ break;
+ }
+ /*
+ * If this is a known short option, don't allow
+ * a partial match of a single character.
+ */
+ if (short_too && current_argv_len == 1)
+ continue;
+
+ if (match == -1) /* first partial match */
+ match = i;
+ else if ((flags & FLAG_LONGONLY) ||
+ long_options[i].has_arg !=
+ long_options[match].has_arg ||
+ long_options[i].flag != long_options[match].flag ||
+ long_options[i].val != long_options[match].val)
+ second_partial_match = 1;
+ }
+ if (!exact_match && second_partial_match) {
+ /* ambiguous abbreviation */
+ if (PRINT_ERROR)
+ sudo_warnx(ambig,
+#ifdef GNU_COMPATIBLE
+ current_dash,
+#endif
+ (int)current_argv_len,
+ current_argv);
+ optopt = 0;
+ return (BADCH);
+ }
+ if (match != -1) { /* option found */
+ if (long_options[match].has_arg == no_argument
+ && has_equal) {
+ if (PRINT_ERROR)
+ sudo_warnx(noarg,
+#ifdef GNU_COMPATIBLE
+ current_dash,
+#endif
+ (int)current_argv_len,
+ current_argv);
+ /*
+ * XXX: GNU sets optopt to val regardless of flag
+ */
+ if (long_options[match].flag == NULL)
+ optopt = long_options[match].val;
+ else
+ optopt = 0;
+#ifdef GNU_COMPATIBLE
+ return (BADCH);
+#else
+ return (BADARG);
+#endif
+ }
+ if (long_options[match].has_arg == required_argument ||
+ long_options[match].has_arg == optional_argument) {
+ if (has_equal)
+ optarg = has_equal;
+ else if (long_options[match].has_arg ==
+ required_argument) {
+ /*
+ * optional argument doesn't use next nargv
+ */
+ optarg = nargv[optind++];
+ }
+ }
+ if ((long_options[match].has_arg == required_argument)
+ && (optarg == NULL)) {
+ /*
+ * Missing argument; leading ':' indicates no error
+ * should be generated.
+ */
+ if (PRINT_ERROR)
+ sudo_warnx(recargstring,
+#ifdef GNU_COMPATIBLE
+ current_dash,
+#endif
+ current_argv);
+ /*
+ * XXX: GNU sets optopt to val regardless of flag
+ */
+ if (long_options[match].flag == NULL)
+ optopt = long_options[match].val;
+ else
+ optopt = 0;
+ --optind;
+ return (BADARG);
+ }
+ } else { /* unknown option */
+ if (short_too) {
+ --optind;
+ return (-1);
+ }
+ if (PRINT_ERROR)
+ sudo_warnx(illoptstring,
+#ifdef GNU_COMPATIBLE
+ current_dash,
+#endif
+ current_argv);
+ optopt = 0;
+ return (BADCH);
+ }
+ if (idx)
+ *idx = match;
+ if (long_options[match].flag) {
+ *long_options[match].flag = long_options[match].val;
+ return (0);
+ } else
+ return (long_options[match].val);
+}
+
+/*
+ * getopt_internal --
+ * Parse argc/argv argument vector. Called by user level routines.
+ */
+static int
+getopt_internal(int nargc, char * const *nargv, const char *options,
+ const struct option *long_options, int *idx, int flags)
+{
+ char *oli; /* option letter list index */
+ int optchar, short_too;
+ int posixly_correct; /* no static, can be changed on the fly */
+
+ if (options == NULL)
+ return (-1);
+
+ /*
+ * Disable GNU extensions if POSIXLY_CORRECT is set or options
+ * string begins with a '+'.
+ */
+ posixly_correct = (getenv("POSIXLY_CORRECT") != NULL);
+#ifdef GNU_COMPATIBLE
+ if (*options == '-')
+ flags |= FLAG_ALLARGS;
+ else if (posixly_correct || *options == '+')
+ flags &= ~FLAG_PERMUTE;
+#else
+ if (posixly_correct || *options == '+')
+ flags &= ~FLAG_PERMUTE;
+ else if (*options == '-')
+ flags |= FLAG_ALLARGS;
+#endif
+ if (*options == '+' || *options == '-')
+ options++;
+
+ /*
+ * XXX Some GNU programs (like cvs) set optind to 0 instead of
+ * XXX using optreset. Work around this braindamage.
+ */
+ if (optind == 0)
+ optind = optreset = 1;
+
+ optarg = NULL;
+ if (optreset)
+ nonopt_start = nonopt_end = -1;
+start:
+ if (optreset || !*place) { /* update scanning pointer */
+ optreset = 0;
+ if (optind >= nargc) { /* end of argument vector */
+ place = EMSG;
+ if (nonopt_end != -1) {
+ /* do permutation, if we have to */
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ optind -= nonopt_end - nonopt_start;
+ }
+ else if (nonopt_start != -1) {
+ /*
+ * If we skipped non-options, set optind
+ * to the first of them.
+ */
+ optind = nonopt_start;
+ }
+ nonopt_start = nonopt_end = -1;
+ return (-1);
+ }
+ if (*(place = nargv[optind]) != '-' ||
+#ifdef GNU_COMPATIBLE
+ place[1] == '\0') {
+#else
+ (place[1] == '\0' && strchr(options, '-') == NULL)) {
+#endif
+ place = EMSG; /* found non-option */
+ if (flags & FLAG_ALLARGS) {
+ /*
+ * GNU extension:
+ * return non-option as argument to option 1
+ */
+ optarg = nargv[optind++];
+ return (INORDER);
+ }
+ if (!(flags & FLAG_PERMUTE)) {
+ /*
+ * If no permutation wanted, stop parsing
+ * at first non-option.
+ */
+ return (-1);
+ }
+ /* do permutation */
+ if (nonopt_start == -1)
+ nonopt_start = optind;
+ else if (nonopt_end != -1) {
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ nonopt_start = optind -
+ (nonopt_end - nonopt_start);
+ nonopt_end = -1;
+ }
+ optind++;
+ /* process next argument */
+ goto start;
+ }
+ if (nonopt_start != -1 && nonopt_end == -1)
+ nonopt_end = optind;
+
+ /*
+ * If we have "-" do nothing, if "--" we are done.
+ */
+ if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
+ optind++;
+ place = EMSG;
+ /*
+ * We found an option (--), so if we skipped
+ * non-options, we have to permute.
+ */
+ if (nonopt_end != -1) {
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ optind -= nonopt_end - nonopt_start;
+ }
+ nonopt_start = nonopt_end = -1;
+ return (-1);
+ }
+ }
+
+ /*
+ * Check long options if:
+ * 1) we were passed some
+ * 2) the arg is not just "-"
+ * 3) either the arg starts with -- we are getopt_long_only()
+ */
+ if (long_options != NULL && place != nargv[optind] &&
+ (*place == '-' || (flags & FLAG_LONGONLY))) {
+ short_too = 0;
+#ifdef GNU_COMPATIBLE
+ dash_prefix = D_PREFIX;
+#endif
+ if (*place == '-') {
+ place++; /* --foo long option */
+#ifdef GNU_COMPATIBLE
+ dash_prefix = DD_PREFIX;
+#endif
+ } else if (*place != ':' && strchr(options, *place) != NULL)
+ short_too = 1; /* could be short option too */
+
+ optchar = parse_long_options(nargv, options, long_options,
+ idx, short_too, flags);
+ if (optchar != -1) {
+ place = EMSG;
+ return (optchar);
+ }
+ }
+
+ if ((optchar = (int)*place++) == (int)':' ||
+ (optchar == (int)'-' && *place != '\0') ||
+ (oli = strchr(options, optchar)) == NULL) {
+ /*
+ * If the user specified "-" and '-' isn't listed in
+ * options, return -1 (non-option) as per POSIX.
+ * Otherwise, it is an unknown option character (or ':').
+ */
+ if (optchar == (int)'-' && *place == '\0')
+ return (-1);
+ if (!*place)
+ ++optind;
+#ifdef GNU_COMPATIBLE
+ if (PRINT_ERROR)
+ sudo_warnx(posixly_correct ? illoptchar : gnuoptchar,
+ optchar);
+#else
+ if (PRINT_ERROR)
+ sudo_warnx(illoptchar, optchar);
+#endif
+ optopt = optchar;
+ return (BADCH);
+ }
+ if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
+ /* -W long-option */
+ if (*place) /* no space */
+ /* NOTHING */;
+ else if (++optind >= nargc) { /* no arg */
+ place = EMSG;
+ if (PRINT_ERROR)
+ sudo_warnx(recargchar, optchar);
+ optopt = optchar;
+ return (BADARG);
+ } else /* white space */
+ place = nargv[optind];
+#ifdef GNU_COMPATIBLE
+ dash_prefix = W_PREFIX;
+#endif
+ optchar = parse_long_options(nargv, options, long_options,
+ idx, 0, flags);
+ place = EMSG;
+ return (optchar);
+ }
+ if (*++oli != ':') { /* doesn't take argument */
+ if (!*place)
+ ++optind;
+ } else { /* takes (optional) argument */
+ optarg = NULL;
+ if (*place) /* no white space */
+ optarg = place;
+ else if (oli[1] != ':') { /* arg not optional */
+ if (++optind >= nargc) { /* no arg */
+ place = EMSG;
+ if (PRINT_ERROR)
+ sudo_warnx(recargchar, optchar);
+ optopt = optchar;
+ return (BADARG);
+ } else
+ optarg = nargv[optind];
+ }
+ place = EMSG;
+ ++optind;
+ }
+ /* dump back option letter */
+ return (optchar);
+}
+
+#ifdef REPLACE_GETOPT
+/*
+ * getopt --
+ * Parse argc/argv argument vector.
+ */
+int
+sudo_getopt(int nargc, char * const *nargv, const char *options)
+{
+
+ /*
+ * We don't pass FLAG_PERMUTE to getopt_internal() since
+ * the BSD getopt(3) (unlike GNU) has never done this.
+ *
+ * Furthermore, since many privileged programs call getopt()
+ * before dropping privileges it makes sense to keep things
+ * as simple (and bug-free) as possible.
+ */
+ return (getopt_internal(nargc, nargv, options, NULL, NULL, 0));
+}
+#endif /* REPLACE_GETOPT */
+
+/*
+ * getopt_long --
+ * Parse argc/argv argument vector.
+ */
+int
+sudo_getopt_long(int nargc, char * const *nargv, const char *options,
+ const struct option *long_options, int *idx)
+{
+
+ return (getopt_internal(nargc, nargv, options, long_options, idx,
+ FLAG_PERMUTE));
+}
+
+/*
+ * getopt_long_only --
+ * Parse argc/argv argument vector.
+ */
+int
+sudo_getopt_long_only(int nargc, char * const *nargv, const char *options,
+ const struct option *long_options, int *idx)
+{
+
+ return (getopt_internal(nargc, nargv, options, long_options, idx,
+ FLAG_PERMUTE|FLAG_LONGONLY));
+}
diff --git a/lib/util/gettime.c b/lib/util/gettime.c
new file mode 100644
index 0000000..e7161b5
--- /dev/null
+++ b/lib/util/gettime.c
@@ -0,0 +1,224 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/time.h>
+#include <time.h>
+
+#if defined(__MACH__) && !defined(HAVE_CLOCK_GETTIME)
+# include <mach/mach.h>
+# include <mach/mach_time.h>
+# include <mach/clock.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * On Linux and FreeBSD, CLOCK_MONOTONIC does not run while sleeping.
+ * Linux provides CLOCK_BOOTTIME which runs while sleeping (FreeBSD does not).
+ * Some systems provide CLOCK_UPTIME which only runs while awake.
+ */
+#if defined(CLOCK_BOOTTIME)
+# define SUDO_CLOCK_BOOTTIME CLOCK_BOOTTIME
+#elif defined(CLOCK_MONOTONIC_RAW)
+# define SUDO_CLOCK_BOOTTIME CLOCK_MONOTONIC_RAW
+#elif defined(CLOCK_MONOTONIC)
+# define SUDO_CLOCK_BOOTTIME CLOCK_MONOTONIC
+#endif
+#if defined(CLOCK_UPTIME_RAW)
+# define SUDO_CLOCK_UPTIME CLOCK_UPTIME_RAW
+#elif defined(CLOCK_UPTIME)
+# define SUDO_CLOCK_UPTIME CLOCK_UPTIME
+#elif defined(CLOCK_MONOTONIC)
+# define SUDO_CLOCK_UPTIME CLOCK_MONOTONIC
+#endif
+
+/*
+ * Wall clock time, may run backward.
+ */
+#if defined(HAVE_CLOCK_GETTIME)
+int
+sudo_gettime_real_v1(struct timespec *ts)
+{
+ debug_decl(sudo_gettime_real, SUDO_DEBUG_UTIL);
+
+ if (clock_gettime(CLOCK_REALTIME, ts) == -1) {
+ struct timeval tv;
+
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "clock_gettime(CLOCK_REALTIME) failed, trying gettimeofday()");
+ if (gettimeofday(&tv, NULL) == -1)
+ debug_return_int(-1);
+ TIMEVAL_TO_TIMESPEC(&tv, ts);
+ }
+ debug_return_int(0);
+}
+#else
+int
+sudo_gettime_real_v1(struct timespec *ts)
+{
+ struct timeval tv;
+ debug_decl(sudo_gettime_real, SUDO_DEBUG_UTIL);
+
+ if (gettimeofday(&tv, NULL) == -1)
+ debug_return_int(-1);
+ TIMEVAL_TO_TIMESPEC(&tv, ts);
+ debug_return_int(0);
+}
+#endif
+
+/*
+ * Monotonic time, only runs forward.
+ * We use a timer that only increments while sleeping, if possible.
+ */
+#if defined(HAVE_CLOCK_GETTIME) && defined(SUDO_CLOCK_BOOTTIME)
+int
+sudo_gettime_mono_v1(struct timespec *ts)
+{
+ static int has_monoclock = -1;
+ debug_decl(sudo_gettime_mono, SUDO_DEBUG_UTIL);
+
+ /* Check whether the kernel/libc actually supports a monotonic clock. */
+# ifdef _SC_MONOTONIC_CLOCK
+ if (has_monoclock == -1)
+ has_monoclock = sysconf(_SC_MONOTONIC_CLOCK) != -1;
+# endif
+ if (!has_monoclock)
+ debug_return_int(sudo_gettime_real(ts));
+ if (clock_gettime(SUDO_CLOCK_BOOTTIME, ts) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "clock_gettime(%d) failed, using wall clock",
+ (int)SUDO_CLOCK_BOOTTIME);
+ has_monoclock = 0;
+ debug_return_int(sudo_gettime_real(ts));
+ }
+ debug_return_int(0);
+}
+#elif defined(HAVE_GETHRTIME)
+int
+sudo_gettime_mono_v1(struct timespec *ts)
+{
+ hrtime_t nsec;
+ debug_decl(sudo_gettime_mono, SUDO_DEBUG_UTIL);
+
+ nsec = gethrtime();
+ ts->tv_sec = nsec / 1000000000;
+ ts->tv_nsec = nsec % 1000000000;
+ debug_return_int(0);
+}
+#elif defined(__MACH__)
+int
+sudo_gettime_mono_v1(struct timespec *ts)
+{
+ uint64_t abstime, nsec;
+ static mach_timebase_info_data_t timebase_info;
+ debug_decl(sudo_gettime_mono, SUDO_DEBUG_UTIL);
+
+ if (timebase_info.denom == 0)
+ (void) mach_timebase_info(&timebase_info);
+#ifdef HAVE_MACH_CONTINUOUS_TIME
+ abstime = mach_continuous_time(); /* runs while asleep */
+#else
+ abstime = mach_absolute_time(); /* doesn't run while asleep */
+#endif
+ nsec = abstime * timebase_info.numer / timebase_info.denom;
+ ts->tv_sec = nsec / 1000000000;
+ ts->tv_nsec = nsec % 1000000000;
+ debug_return_int(0);
+}
+#else
+int
+sudo_gettime_mono_v1(struct timespec *ts)
+{
+ /* No monotonic clock available, use wall clock. */
+ return sudo_gettime_real(ts);
+}
+#endif
+
+/*
+ * Monotonic time, only runs forward.
+ * We use a timer that only increments while awake, if possible.
+ */
+#if defined(HAVE_CLOCK_GETTIME) && defined(SUDO_CLOCK_UPTIME)
+int
+sudo_gettime_awake_v1(struct timespec *ts)
+{
+ static int has_monoclock = -1;
+ debug_decl(sudo_gettime_awake, SUDO_DEBUG_UTIL);
+
+ /* Check whether the kernel/libc actually supports a monotonic clock. */
+# ifdef _SC_MONOTONIC_CLOCK
+ if (has_monoclock == -1)
+ has_monoclock = sysconf(_SC_MONOTONIC_CLOCK) != -1;
+# endif
+ if (!has_monoclock)
+ debug_return_int(sudo_gettime_real(ts));
+ if (clock_gettime(SUDO_CLOCK_UPTIME, ts) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "clock_gettime(%d) failed, using wall clock",
+ (int)SUDO_CLOCK_UPTIME);
+ has_monoclock = 0;
+ debug_return_int(sudo_gettime_real(ts));
+ }
+ debug_return_int(0);
+}
+#elif defined(HAVE_GETHRTIME)
+int
+sudo_gettime_awake_v1(struct timespec *ts)
+{
+ hrtime_t nsec;
+ debug_decl(sudo_gettime_awake, SUDO_DEBUG_UTIL);
+
+ /* Currently the same as sudo_gettime_mono() */
+ nsec = gethrtime();
+ ts->tv_sec = nsec / 1000000000;
+ ts->tv_nsec = nsec % 1000000000;
+ debug_return_int(0);
+}
+#elif defined(__MACH__)
+int
+sudo_gettime_awake_v1(struct timespec *ts)
+{
+ uint64_t abstime, nsec;
+ static mach_timebase_info_data_t timebase_info;
+ debug_decl(sudo_gettime_awake, SUDO_DEBUG_UTIL);
+
+ if (timebase_info.denom == 0)
+ (void) mach_timebase_info(&timebase_info);
+ abstime = mach_absolute_time();
+ nsec = abstime * timebase_info.numer / timebase_info.denom;
+ ts->tv_sec = nsec / 1000000000;
+ ts->tv_nsec = nsec % 1000000000;
+ debug_return_int(0);
+}
+#else
+int
+sudo_gettime_awake_v1(struct timespec *ts)
+{
+ /* No monotonic uptime clock available, use wall clock. */
+ return sudo_gettime_real(ts);
+}
+#endif
diff --git a/lib/util/getusershell.c b/lib/util/getusershell.c
new file mode 100644
index 0000000..7912816
--- /dev/null
+++ b/lib/util/getusershell.c
@@ -0,0 +1,132 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+static char **allowed_shells, **current_shell;
+static char *default_shells[] = {
+ "/bin/sh",
+ "/bin/ksh",
+ "/bin/ksh93",
+ "/bin/bash",
+ "/bin/dash",
+ "/bin/zsh",
+ "/bin/csh",
+ "/bin/tcsh",
+ NULL
+};
+
+static char **
+read_shells(void)
+{
+ size_t maxshells = 16, nshells = 0;
+ size_t linesize = 0;
+ char *line = NULL;
+ FILE *fp;
+ debug_decl(read_shells, SUDO_DEBUG_UTIL);
+
+ if ((fp = fopen("/etc/shells", "r")) == NULL)
+ goto bad;
+
+ free(allowed_shells);
+ allowed_shells = reallocarray(NULL, maxshells, sizeof(char *));
+ if (allowed_shells == NULL)
+ goto bad;
+
+ while (sudo_parseln(&line, &linesize, NULL, fp, PARSELN_CONT_IGN) != -1) {
+ if (nshells + 1 >= maxshells) {
+ char **new_shells;
+
+ new_shells = reallocarray(NULL, maxshells + 16, sizeof(char *));
+ if (new_shells == NULL)
+ goto bad;
+ allowed_shells = new_shells;
+ maxshells += 16;
+ }
+ if ((allowed_shells[nshells] = strdup(line)) == NULL)
+ goto bad;
+ nshells++;
+ }
+ allowed_shells[nshells] = NULL;
+
+ free(line);
+ fclose(fp);
+ debug_return_ptr(allowed_shells);
+bad:
+ free(line);
+ if (fp != NULL)
+ fclose(fp);
+ while (nshells != 0)
+ free(allowed_shells[--nshells]);
+ free(allowed_shells);
+ allowed_shells = NULL;
+ debug_return_ptr(default_shells);
+}
+
+void
+sudo_setusershell(void)
+{
+ debug_decl(setusershell, SUDO_DEBUG_UTIL);
+
+ current_shell = read_shells();
+
+ debug_return;
+}
+
+void
+sudo_endusershell(void)
+{
+ debug_decl(endusershell, SUDO_DEBUG_UTIL);
+
+ if (allowed_shells != NULL) {
+ char **shell;
+
+ for (shell = allowed_shells; *shell != NULL; shell++)
+ free(*shell);
+ free(allowed_shells);
+ allowed_shells = NULL;
+ }
+ current_shell = NULL;
+
+ debug_return;
+}
+
+char *
+sudo_getusershell(void)
+{
+ debug_decl(getusershell, SUDO_DEBUG_UTIL);
+
+ if (current_shell == NULL)
+ current_shell = read_shells();
+
+ debug_return_str(*current_shell++);
+}
diff --git a/lib/util/gidlist.c b/lib/util/gidlist.c
new file mode 100644
index 0000000..d9107cd
--- /dev/null
+++ b/lib/util/gidlist.c
@@ -0,0 +1,87 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <grp.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+/*
+ * Parse a comma-separated list of gids into an allocated array of GETGROUPS_T.
+ * If a pointer to the base gid is specified, it is stored as the first element
+ * in the array.
+ * Returns the number of gids in the allocated array.
+ */
+int
+sudo_parse_gids_v1(const char *gidstr, const gid_t *basegid, GETGROUPS_T **gidsp)
+{
+ int ngids = 0;
+ GETGROUPS_T *gids;
+ const char *cp = gidstr;
+ const char *errstr;
+ char *ep;
+ debug_decl(sudo_parse_gids, SUDO_DEBUG_UTIL);
+
+ /* Count groups. */
+ if (*cp != '\0') {
+ ngids++;
+ do {
+ if (*cp++ == ',')
+ ngids++;
+ } while (*cp != '\0');
+ }
+ /* Base gid is optional. */
+ if (basegid != NULL)
+ ngids++;
+ /* Allocate and fill in array. */
+ if (ngids != 0) {
+ gids = reallocarray(NULL, ngids, sizeof(GETGROUPS_T));
+ if (gids == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_int(-1);
+ }
+ ngids = 0;
+ if (basegid != NULL)
+ gids[ngids++] = *basegid;
+ cp = gidstr;
+ do {
+ gids[ngids] = (GETGROUPS_T) sudo_strtoidx(cp, ",", &ep, &errstr);
+ if (errstr != NULL) {
+ sudo_warnx(U_("%s: %s"), cp, U_(errstr));
+ free(gids);
+ debug_return_int(-1);
+ }
+ if (basegid == NULL || gids[ngids] != *basegid)
+ ngids++;
+ cp = ep + 1;
+ } while (*ep != '\0');
+ *gidsp = gids;
+ }
+ debug_return_int(ngids);
+}
diff --git a/lib/util/glob.c b/lib/util/glob.c
new file mode 100644
index 0000000..762e107
--- /dev/null
+++ b/lib/util/glob.c
@@ -0,0 +1,953 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2008-2014 Todd C. Miller <Todd.Miller@sudo.ws>
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)glob.c 8.3 (Berkeley) 10/13/93
+ */
+
+/*
+ * glob(3) -- a superset of the one defined in POSIX 1003.2.
+ *
+ * The [!...] convention to negate a range is supported (SysV, Posix, ksh).
+ *
+ * Optional extra services, controlled by flags not defined by POSIX:
+ *
+ * GLOB_MAGCHAR:
+ * Set in gl_flags if pattern contained a globbing character.
+ * GLOB_TILDE:
+ * expand ~user/foo to the /home/dir/of/user/foo
+ * GLOB_BRACE:
+ * expand {1,2}{a,b} to 1a 1b 2a 2b
+ * gl_matchc:
+ * Number of matches in the current invocation of glob.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_GLOB
+
+#include <sys/stat.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
+
+#include "sudo_compat.h"
+#include "compat/glob.h"
+#include "compat/charclass.h"
+
+#define DOLLAR '$'
+#define DOT '.'
+#define EOS '\0'
+#define LBRACKET '['
+#define NOT '!'
+#define QUESTION '?'
+#define QUOTE '\\'
+#define RANGE '-'
+#define RBRACKET ']'
+#define SEP '/'
+#define STAR '*'
+#define TILDE '~'
+#define UNDERSCORE '_'
+#define LBRACE '{'
+#define RBRACE '}'
+#define SLASH '/'
+#define COMMA ','
+
+#ifndef DEBUG
+
+#define M_QUOTE 0x8000
+#define M_PROTECT 0x4000
+#define M_MASK 0xffff
+#define M_ASCII 0x00ff
+
+typedef unsigned short Char;
+
+#else
+
+#define M_QUOTE 0x80
+#define M_PROTECT 0x40
+#define M_MASK 0xff
+#define M_ASCII 0x7f
+
+typedef char Char;
+
+#endif
+
+
+#define CHAR(c) ((Char)((c)&M_ASCII))
+#define META(c) ((Char)((c)|M_QUOTE))
+#define M_ALL META('*')
+#define M_END META(']')
+#define M_NOT META('!')
+#define M_ONE META('?')
+#define M_RNG META('-')
+#define M_SET META('[')
+#define M_CLASS META(':')
+#define ismeta(c) (((c)&M_QUOTE) != 0)
+
+#define GLOB_LIMIT_MALLOC 65536
+#define GLOB_LIMIT_STAT 2048
+#define GLOB_LIMIT_READDIR 16384
+
+struct glob_lim {
+ size_t glim_malloc;
+ size_t glim_stat;
+ size_t glim_readdir;
+};
+
+static int compare(const void *, const void *);
+static int g_Ctoc(const Char *, char *, size_t);
+static int g_lstat(Char *, struct stat *, glob_t *);
+static DIR *g_opendir(Char *, glob_t *);
+static Char *g_strchr(const Char *, int);
+static int g_strncmp(const Char *, const char *, size_t);
+static int g_stat(Char *, struct stat *, glob_t *);
+static int glob0(const Char *, glob_t *, struct glob_lim *);
+static int glob1(Char *, Char *, glob_t *, struct glob_lim *);
+static int glob2(Char *, Char *, Char *, Char *, Char *, Char *,
+ glob_t *, struct glob_lim *);
+static int glob3(Char *, Char *, Char *, Char *, Char *,
+ Char *, Char *, glob_t *, struct glob_lim *);
+static int globextend(const Char *, glob_t *, struct glob_lim *,
+ struct stat *);
+static const Char *
+ globtilde(const Char *, Char *, size_t, glob_t *);
+static int globexp1(const Char *, glob_t *, struct glob_lim *);
+static int globexp2(const Char *, const Char *, glob_t *,
+ struct glob_lim *);
+static int match(Char *, Char *, Char *);
+#ifdef DEBUG
+static void qprintf(const char *, Char *);
+#endif
+
+int
+sudo_glob(const char *pattern, int flags, int (*errfunc)(const char *, int),
+ glob_t *pglob)
+{
+ const unsigned char *patnext;
+ int c;
+ Char *bufnext, *bufend, patbuf[PATH_MAX];
+ struct glob_lim limit = { 0, 0, 0 };
+
+ patnext = (unsigned char *) pattern;
+ if (!(flags & GLOB_APPEND)) {
+ pglob->gl_pathc = 0;
+ pglob->gl_pathv = NULL;
+ if (!(flags & GLOB_DOOFFS))
+ pglob->gl_offs = 0;
+ }
+ pglob->gl_flags = flags & ~GLOB_MAGCHAR;
+ pglob->gl_errfunc = errfunc;
+ pglob->gl_matchc = 0;
+
+ if (pglob->gl_offs >= SSIZE_MAX || pglob->gl_pathc >= SSIZE_MAX ||
+ pglob->gl_pathc >= SSIZE_MAX - pglob->gl_offs - 1)
+ return GLOB_NOSPACE;
+
+ if (strnlen(pattern, PATH_MAX) == PATH_MAX)
+ return GLOB_NOMATCH;
+
+ bufnext = patbuf;
+ bufend = bufnext + PATH_MAX - 1;
+ if (flags & GLOB_NOESCAPE)
+ while (bufnext < bufend && (c = *patnext++) != EOS)
+ *bufnext++ = c;
+ else {
+ /* Protect the quoted characters. */
+ while (bufnext < bufend && (c = *patnext++) != EOS)
+ if (c == QUOTE) {
+ if ((c = *patnext++) == EOS) {
+ c = QUOTE;
+ --patnext;
+ }
+ *bufnext++ = c | M_PROTECT;
+ } else
+ *bufnext++ = c;
+ }
+ *bufnext = EOS;
+
+ if (flags & GLOB_BRACE)
+ return globexp1(patbuf, pglob, &limit);
+ else
+ return glob0(patbuf, pglob, &limit);
+}
+
+/*
+ * Expand recursively a glob {} pattern. When there is no more expansion
+ * invoke the standard globbing routine to glob the rest of the magic
+ * characters
+ */
+static int
+globexp1(const Char *pattern, glob_t *pglob, struct glob_lim *limitp)
+{
+ const Char* ptr = pattern;
+
+ /* Protect a single {}, for find(1), like csh */
+ if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)
+ return glob0(pattern, pglob, limitp);
+
+ if ((ptr = (const Char *) g_strchr(ptr, LBRACE)) != NULL)
+ return globexp2(ptr, pattern, pglob, limitp);
+
+ return glob0(pattern, pglob, limitp);
+}
+
+
+/*
+ * Recursive brace globbing helper. Tries to expand a single brace.
+ * If it succeeds then it invokes globexp1 with the new pattern.
+ * If it fails then it tries to glob the rest of the pattern and returns.
+ */
+static int
+globexp2(const Char *ptr, const Char *pattern, glob_t *pglob,
+ struct glob_lim *limitp)
+{
+ int i, rv;
+ Char *lm, *ls;
+ const Char *pe, *pm, *pl;
+ Char patbuf[PATH_MAX];
+
+ /* copy part up to the brace */
+ for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)
+ continue;
+ *lm = EOS;
+ ls = lm;
+
+ /* Find the balanced brace */
+ for (i = 0, pe = ++ptr; *pe; pe++)
+ if (*pe == LBRACKET) {
+ /* Ignore everything between [] */
+ for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)
+ continue;
+ if (*pe == EOS) {
+ /*
+ * We could not find a matching RBRACKET.
+ * Ignore and just look for RBRACE
+ */
+ pe = pm;
+ }
+ } else if (*pe == LBRACE)
+ i++;
+ else if (*pe == RBRACE) {
+ if (i == 0)
+ break;
+ i--;
+ }
+
+ /* Non matching braces; just glob the pattern */
+ if (i != 0 || *pe == EOS)
+ return glob0(patbuf, pglob, limitp);
+
+ for (i = 0, pl = pm = ptr; pm <= pe; pm++) {
+ switch (*pm) {
+ case LBRACKET:
+ /* Ignore everything between [] */
+ for (pl = pm++; *pm != RBRACKET && *pm != EOS; pm++)
+ continue;
+ if (*pm == EOS) {
+ /*
+ * We could not find a matching RBRACKET.
+ * Ignore and just look for RBRACE
+ */
+ pm = pl;
+ }
+ break;
+
+ case LBRACE:
+ i++;
+ break;
+
+ case RBRACE:
+ if (i) {
+ i--;
+ break;
+ }
+ FALLTHROUGH;
+ case COMMA:
+ if (i && *pm == COMMA)
+ break;
+ else {
+ /* Append the current string */
+ for (lm = ls; (pl < pm); *lm++ = *pl++)
+ continue;
+
+ /*
+ * Append the rest of the pattern after the
+ * closing brace
+ */
+ for (pl = pe + 1; (*lm++ = *pl++) != EOS; )
+ continue;
+
+ /* Expand the current pattern */
+#ifdef DEBUG
+ qprintf("globexp2:", patbuf);
+#endif
+ rv = globexp1(patbuf, pglob, limitp);
+ if (rv && rv != GLOB_NOMATCH)
+ return rv;
+
+ /* move after the comma, to the next string */
+ pl = pm + 1;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+
+
+/*
+ * expand tilde from the passwd file.
+ */
+static const Char *
+globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob)
+{
+ struct passwd *pwd;
+ char *h;
+ const Char *p;
+ Char *b, *eb;
+
+ if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE))
+ return pattern;
+
+ /* Copy up to the end of the string or / */
+ eb = &patbuf[patbuf_len - 1];
+ for (p = pattern + 1, h = (char *) patbuf;
+ h < (char *)eb && *p && *p != SLASH; *h++ = *p++)
+ continue;
+
+ *h = EOS;
+
+ if (((char *) patbuf)[0] == EOS) {
+ /*
+ * handle a plain ~ or ~/ by expanding $HOME
+ * first and then trying the password file
+ */
+ if ((h = getenv("HOME")) == NULL) {
+ if ((pwd = getpwuid(getuid())) == NULL)
+ return pattern;
+ else
+ h = pwd->pw_dir;
+ }
+ } else {
+ /*
+ * Expand a ~user
+ */
+ if ((pwd = getpwnam((char*) patbuf)) == NULL)
+ return pattern;
+ else
+ h = pwd->pw_dir;
+ }
+
+ /* Copy the home directory */
+ for (b = patbuf; b < eb && *h; *b++ = *h++)
+ continue;
+
+ /* Append the rest of the pattern */
+ while (b < eb && (*b++ = *p++) != EOS)
+ continue;
+ *b = EOS;
+
+ return patbuf;
+}
+
+static int
+g_strncmp(const Char *s1, const char *s2, size_t n)
+{
+ int rv = 0;
+
+ while (n--) {
+ rv = *(Char *)s1 - *(const unsigned char *)s2++;
+ if (rv)
+ break;
+ if (*s1++ == '\0')
+ break;
+ }
+ return rv;
+}
+
+static int
+g_charclass(const Char **patternp, Char **bufnextp)
+{
+ const Char *pattern = *patternp + 1;
+ Char *bufnext = *bufnextp;
+ const Char *colon;
+ struct cclass *cc;
+ size_t len;
+
+ if ((colon = g_strchr(pattern, ':')) == NULL || colon[1] != ']')
+ return 1; /* not a character class */
+
+ len = (size_t)(colon - pattern);
+ for (cc = cclasses; cc->name != NULL; cc++) {
+ if (!g_strncmp(pattern, cc->name, len) && cc->name[len] == '\0')
+ break;
+ }
+ if (cc->name == NULL)
+ return -1; /* invalid character class */
+ *bufnext++ = M_CLASS;
+ *bufnext++ = (Char)(cc - &cclasses[0]);
+ *bufnextp = bufnext;
+ *patternp += len + 3;
+
+ return 0;
+}
+
+/*
+ * The main glob() routine: compiles the pattern (optionally processing
+ * quotes), calls glob1() to do the real pattern matching, and finally
+ * sorts the list (unless unsorted operation is requested). Returns 0
+ * if things went well, nonzero if errors occurred. It is not an error
+ * to find no matches.
+ */
+static int
+glob0(const Char *pattern, glob_t *pglob, struct glob_lim *limitp)
+{
+ const Char *qpatnext;
+ int c, err;
+ size_t oldpathc;
+ Char *bufnext, patbuf[PATH_MAX];
+
+ qpatnext = globtilde(pattern, patbuf, PATH_MAX, pglob);
+ oldpathc = pglob->gl_pathc;
+ bufnext = patbuf;
+
+ /* We don't need to check for buffer overflow any more. */
+ while ((c = *qpatnext++) != EOS) {
+ switch (c) {
+ case LBRACKET:
+ c = *qpatnext;
+ if (c == NOT)
+ ++qpatnext;
+ if (*qpatnext == EOS ||
+ g_strchr(qpatnext+1, RBRACKET) == NULL) {
+ *bufnext++ = LBRACKET;
+ if (c == NOT)
+ --qpatnext;
+ break;
+ }
+ *bufnext++ = M_SET;
+ if (c == NOT)
+ *bufnext++ = M_NOT;
+ c = *qpatnext++;
+ do {
+ if (c == LBRACKET && *qpatnext == ':') {
+ do {
+ err = g_charclass(&qpatnext,
+ &bufnext);
+ if (err)
+ break;
+ c = *qpatnext++;
+ } while (c == LBRACKET && *qpatnext == ':');
+ if (err == -1 &&
+ !(pglob->gl_flags & GLOB_NOCHECK))
+ return GLOB_NOMATCH;
+ if (c == RBRACKET)
+ break;
+ }
+ *bufnext++ = CHAR(c);
+ if (*qpatnext == RANGE &&
+ (c = qpatnext[1]) != RBRACKET) {
+ *bufnext++ = M_RNG;
+ *bufnext++ = CHAR(c);
+ qpatnext += 2;
+ }
+ } while ((c = *qpatnext++) != RBRACKET);
+ pglob->gl_flags |= GLOB_MAGCHAR;
+ *bufnext++ = M_END;
+ break;
+ case QUESTION:
+ pglob->gl_flags |= GLOB_MAGCHAR;
+ *bufnext++ = M_ONE;
+ break;
+ case STAR:
+ pglob->gl_flags |= GLOB_MAGCHAR;
+ /* collapse adjacent stars to one,
+ * to avoid exponential behavior
+ */
+ if (bufnext == patbuf || bufnext[-1] != M_ALL)
+ *bufnext++ = M_ALL;
+ break;
+ default:
+ *bufnext++ = CHAR(c);
+ break;
+ }
+ }
+ *bufnext = EOS;
+#ifdef DEBUG
+ qprintf("glob0:", patbuf);
+#endif
+
+ if ((err = glob1(patbuf, patbuf + PATH_MAX - 1, pglob, limitp)) != 0)
+ return err;
+
+ /*
+ * If there was no match we are going to append the pattern
+ * if GLOB_NOCHECK was specified.
+ */
+ if (pglob->gl_pathc == oldpathc) {
+ if ((pglob->gl_flags & GLOB_NOCHECK))
+ return globextend(pattern, pglob, limitp, NULL);
+ else
+ return GLOB_NOMATCH;
+ }
+ if (!(pglob->gl_flags & GLOB_NOSORT)) {
+ qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,
+ pglob->gl_pathc - oldpathc, sizeof(char *), compare);
+ }
+ return 0;
+}
+
+static int
+compare(const void *p, const void *q)
+{
+ return strcmp(*(char **)p, *(char **)q);
+}
+
+static int
+glob1(Char *pattern, Char *pattern_last, glob_t *pglob, struct glob_lim *limitp)
+{
+ Char pathbuf[PATH_MAX];
+
+ /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
+ if (*pattern == EOS)
+ return 0;
+ return glob2(pathbuf, pathbuf + PATH_MAX - 1,
+ pathbuf, pathbuf + PATH_MAX - 1,
+ pattern, pattern_last, pglob, limitp);
+}
+
+/*
+ * The functions glob2 and glob3 are mutually recursive; there is one level
+ * of recursion for each segment in the pattern that contains one or more
+ * meta characters.
+ */
+static int
+glob2(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last,
+ Char *pattern, Char *pattern_last, glob_t *pglob, struct glob_lim *limitp)
+{
+ struct stat sb;
+ Char *p, *q;
+ int anymeta;
+
+ /*
+ * Loop over pattern segments until end of pattern or until
+ * segment with meta character found.
+ */
+ for (anymeta = 0;;) {
+ if (*pattern == EOS) { /* End of pattern? */
+ *pathend = EOS;
+
+ if ((pglob->gl_flags & GLOB_LIMIT) &&
+ limitp->glim_stat++ >= GLOB_LIMIT_STAT) {
+ errno = 0;
+ *pathend++ = SEP;
+ *pathend = EOS;
+ return GLOB_NOSPACE;
+ }
+ if (g_lstat(pathbuf, &sb, pglob))
+ return 0;
+
+ if (((pglob->gl_flags & GLOB_MARK) &&
+ pathend[-1] != SEP) && (S_ISDIR(sb.st_mode) ||
+ (S_ISLNK(sb.st_mode) &&
+ (g_stat(pathbuf, &sb, pglob) == 0) &&
+ S_ISDIR(sb.st_mode)))) {
+ if (pathend+1 > pathend_last)
+ return 1;
+ *pathend++ = SEP;
+ *pathend = EOS;
+ }
+ ++pglob->gl_matchc;
+ return globextend(pathbuf, pglob, limitp, &sb);
+ }
+
+ /* Find end of next segment, copy tentatively to pathend. */
+ q = pathend;
+ p = pattern;
+ while (*p != EOS && *p != SEP) {
+ if (ismeta(*p))
+ anymeta = 1;
+ if (q+1 > pathend_last)
+ return 1;
+ *q++ = *p++;
+ }
+
+ if (!anymeta) { /* No expansion, do next segment. */
+ pathend = q;
+ pattern = p;
+ while (*pattern == SEP) {
+ if (pathend+1 > pathend_last)
+ return 1;
+ *pathend++ = *pattern++;
+ }
+ } else
+ /* Need expansion, recurse. */
+ return glob3(pathbuf, pathbuf_last, pathend,
+ pathend_last, pattern, p, pattern_last,
+ pglob, limitp);
+ }
+ /* NOTREACHED */
+}
+
+static int
+glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last,
+ Char *pattern, Char *restpattern, Char *restpattern_last, glob_t *pglob,
+ struct glob_lim *limitp)
+{
+ struct dirent *dp;
+ DIR *dirp;
+ int err;
+ char buf[PATH_MAX];
+
+ if (pathend > pathend_last)
+ return 1;
+ *pathend = EOS;
+ errno = 0;
+
+ if ((dirp = g_opendir(pathbuf, pglob)) == NULL) {
+ /* TODO: don't call for ENOENT or ENOTDIR? */
+ if (pglob->gl_errfunc) {
+ if (g_Ctoc(pathbuf, buf, sizeof(buf)))
+ return GLOB_ABORTED;
+ if (pglob->gl_errfunc(buf, errno) ||
+ pglob->gl_flags & GLOB_ERR)
+ return GLOB_ABORTED;
+ }
+ return 0;
+ }
+
+ err = 0;
+
+ /* Search directory for matching names. */
+ while ((dp = readdir(dirp))) {
+ unsigned char *sc;
+ Char *dc;
+
+ if ((pglob->gl_flags & GLOB_LIMIT) &&
+ limitp->glim_readdir++ >= GLOB_LIMIT_READDIR) {
+ errno = 0;
+ *pathend++ = SEP;
+ *pathend = EOS;
+ err = GLOB_NOSPACE;
+ break;
+ }
+
+ /* Initial DOT must be matched literally. */
+ if (dp->d_name[0] == DOT && *pattern != DOT)
+ continue;
+ dc = pathend;
+ sc = (unsigned char *) dp->d_name;
+ while (dc < pathend_last && (*dc++ = *sc++) != EOS)
+ continue;
+ if (dc >= pathend_last) {
+ *dc = EOS;
+ err = 1;
+ break;
+ }
+
+ if (!match(pathend, pattern, restpattern)) {
+ *pathend = EOS;
+ continue;
+ }
+ err = glob2(pathbuf, pathbuf_last, --dc, pathend_last,
+ restpattern, restpattern_last, pglob, limitp);
+ if (err)
+ break;
+ }
+
+ closedir(dirp);
+ return err;
+}
+
+/*
+ * Extend the gl_pathv member of a glob_t structure to accommodate a new item,
+ * add the new item, and update gl_pathc.
+ *
+ * This assumes the BSD realloc, which only copies the block when its size
+ * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
+ * behavior.
+ *
+ * Return 0 if new item added, error code if memory couldn't be allocated.
+ *
+ * Invariant of the glob_t structure:
+ * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
+ * gl_pathv points to (gl_offs + gl_pathc + 1) items.
+ */
+static int
+globextend(const Char *path, glob_t *pglob, struct glob_lim *limitp,
+ struct stat *sb)
+{
+ char **pathv;
+ size_t i, newn, len;
+ char *copy = NULL;
+ const Char *p;
+
+ newn = 2 + pglob->gl_pathc + pglob->gl_offs;
+ if (pglob->gl_offs >= SSIZE_MAX ||
+ pglob->gl_pathc >= SSIZE_MAX ||
+ newn >= SSIZE_MAX ||
+ SIZE_MAX / sizeof(*pathv) <= newn) {
+ nospace:
+ for (i = pglob->gl_offs; i < newn - 2; i++) {
+ if (pglob->gl_pathv && pglob->gl_pathv[i])
+ free(pglob->gl_pathv[i]);
+ }
+ if (pglob->gl_pathv) {
+ free(pglob->gl_pathv);
+ pglob->gl_pathv = NULL;
+ }
+ return GLOB_NOSPACE;
+ }
+
+ pathv = reallocarray(pglob->gl_pathv, newn, sizeof(*pathv));
+ if (pathv == NULL)
+ goto nospace;
+ if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {
+ /* first time around -- clear initial gl_offs items */
+ pathv += pglob->gl_offs;
+ for (i = pglob->gl_offs; i > 0; i--)
+ *--pathv = NULL;
+ }
+ pglob->gl_pathv = pathv;
+
+ for (p = path; *p++;)
+ continue;
+ len = (size_t)(p - path);
+ limitp->glim_malloc += len;
+ if ((copy = malloc(len)) != NULL) {
+ if (g_Ctoc(path, copy, len)) {
+ free(copy);
+ return GLOB_NOSPACE;
+ }
+ pathv[pglob->gl_offs + pglob->gl_pathc++] = copy;
+ }
+ pathv[pglob->gl_offs + pglob->gl_pathc] = NULL;
+
+ if ((pglob->gl_flags & GLOB_LIMIT) &&
+ (newn * sizeof(*pathv)) + limitp->glim_malloc >
+ GLOB_LIMIT_MALLOC) {
+ errno = 0;
+ return GLOB_NOSPACE;
+ }
+ return copy == NULL ? GLOB_NOSPACE : 0;
+}
+
+
+/*
+ * pattern matching function for filenames. Each occurrence of the *
+ * pattern causes an iteration.
+ *
+ * Note, this function differs from the original as per the discussion
+ * here: https://research.swtch.com/glob
+ *
+ * Basically we removed the recursion and made it use the algorithm
+ * from Russ Cox to not go quadratic on cases like a file called
+ * ("a" x 100) . "x" matched against a pattern like "a*a*a*a*a*a*a*y".
+ */
+static int
+match(Char *name, Char *pat, Char *patend)
+{
+ int ok, negate_range;
+ Char c, k;
+ Char *nextp = NULL;
+ Char *nextn = NULL;
+
+loop:
+ while (pat < patend) {
+ c = *pat++;
+ switch (c & M_MASK) {
+ case M_ALL:
+ while (pat < patend && (*pat & M_MASK) == M_ALL)
+ pat++; /* eat consecutive '*' */
+ if (pat == patend)
+ return 1;
+ if (*name == EOS)
+ return 0;
+ nextn = name + 1;
+ nextp = pat - 1;
+ break;
+ case M_ONE:
+ if (*name++ == EOS)
+ goto fail;
+ break;
+ case M_SET:
+ ok = 0;
+ if ((k = *name++) == EOS)
+ goto fail;
+ if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)
+ ++pat;
+ while (((c = *pat++) & M_MASK) != M_END) {
+ if ((c & M_MASK) == M_CLASS) {
+ Char idx = *pat & M_MASK;
+ if (idx < NCCLASSES &&
+ cclasses[idx].isctype(k))
+ ok = 1;
+ ++pat;
+ }
+ if ((*pat & M_MASK) == M_RNG) {
+ if (c <= k && k <= pat[1])
+ ok = 1;
+ pat += 2;
+ } else if (c == k)
+ ok = 1;
+ }
+ if (ok == negate_range)
+ goto fail;
+ break;
+ default:
+ if (*name++ != c)
+ goto fail;
+ break;
+ }
+ }
+ if (*name == EOS)
+ return 1;
+fail:
+ if (nextn) {
+ pat = nextp;
+ name = nextn;
+ goto loop;
+ }
+ return 0;
+}
+
+/* Free allocated data belonging to a glob_t structure. */
+void
+sudo_globfree(glob_t *pglob)
+{
+ size_t i;
+ char **pp;
+
+ if (pglob->gl_pathv != NULL) {
+ pp = pglob->gl_pathv + pglob->gl_offs;
+ for (i = pglob->gl_pathc; i--; ++pp)
+ if (*pp)
+ free(*pp);
+ free(pglob->gl_pathv);
+ pglob->gl_pathv = NULL;
+ }
+}
+
+static DIR *
+g_opendir(Char *str, glob_t *pglob)
+{
+ char buf[PATH_MAX];
+
+ if (!*str) {
+ buf[0] = '.';
+ buf[1] = '\0';
+ } else {
+ if (g_Ctoc(str, buf, sizeof(buf)))
+ return NULL;
+ }
+
+ return opendir(buf);
+}
+
+static int
+g_lstat(Char *fn, struct stat *sb, glob_t *pglob)
+{
+ char buf[PATH_MAX];
+
+ if (g_Ctoc(fn, buf, sizeof(buf)))
+ return -1;
+ return lstat(buf, sb);
+}
+
+static int
+g_stat(Char *fn, struct stat *sb, glob_t *pglob)
+{
+ char buf[PATH_MAX];
+
+ if (g_Ctoc(fn, buf, sizeof(buf)))
+ return -1;
+ return stat(buf, sb);
+}
+
+static Char *
+g_strchr(const Char *str, int ch)
+{
+ do {
+ if (*str == ch)
+ return (Char *)str;
+ } while (*str++);
+ return NULL;
+}
+
+static int
+g_Ctoc(const Char *str, char *buf, size_t len)
+{
+
+ while (len--) {
+ if ((*buf++ = *str++) == EOS)
+ return 0;
+ }
+ return 1;
+}
+
+#ifdef DEBUG
+static void
+qprintf(const char *str, Char *s)
+{
+ Char *p;
+
+ (void)printf("%s:\n", str);
+ for (p = s; *p; p++)
+ (void)printf("%c", CHAR(*p));
+ (void)printf("\n");
+ for (p = s; *p; p++)
+ (void)printf("%c", *p & M_PROTECT ? '"' : ' ');
+ (void)printf("\n");
+ for (p = s; *p; p++)
+ (void)printf("%c", ismeta(*p) ? '_' : ' ');
+ (void)printf("\n");
+}
+#endif /* DEBUG */
+#endif /* HAVE_GLOB */
diff --git a/lib/util/gmtime_r.c b/lib/util/gmtime_r.c
new file mode 100644
index 0000000..b9feb1f
--- /dev/null
+++ b/lib/util/gmtime_r.c
@@ -0,0 +1,49 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * 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>
+
+#ifndef HAVE_GMTIME_R
+
+#include <sys/types.h>
+#include <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+/*
+ * Fake gmtime_r() that just stores the result.
+ * Still has the normal gmtime() side effects.
+ */
+struct tm *
+sudo_gmtime_r(const time_t *timer, struct tm *result)
+{
+ struct tm *tm;
+
+ if ((tm = gmtime(timer)) == NULL)
+ return NULL;
+ memcpy(result, tm, sizeof(struct tm));
+
+ return result;
+}
+#endif /* HAVE_GMTIME_T */
diff --git a/lib/util/hexchar.c b/lib/util/hexchar.c
new file mode 100644
index 0000000..81cd619
--- /dev/null
+++ b/lib/util/hexchar.c
@@ -0,0 +1,103 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015, 2023 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * Converts a two-byte hex string to decimal.
+ * Returns a value 0-255 on success or -1 for invalid input.
+ */
+int
+sudo_hexchar_v1(const char *s)
+{
+ unsigned char result[2];
+ int i;
+ debug_decl(sudo_hexchar, SUDO_DEBUG_UTIL);
+
+ for (i = 0; i < 2; i++) {
+ switch (s[i]) {
+ case '0':
+ result[i] = 0;
+ break;
+ case '1':
+ result[i] = 1;
+ break;
+ case '2':
+ result[i] = 2;
+ break;
+ case '3':
+ result[i] = 3;
+ break;
+ case '4':
+ result[i] = 4;
+ break;
+ case '5':
+ result[i] = 5;
+ break;
+ case '6':
+ result[i] = 6;
+ break;
+ case '7':
+ result[i] = 7;
+ break;
+ case '8':
+ result[i] = 8;
+ break;
+ case '9':
+ result[i] = 9;
+ break;
+ case 'A':
+ case 'a':
+ result[i] = 10;
+ break;
+ case 'B':
+ case 'b':
+ result[i] = 11;
+ break;
+ case 'C':
+ case 'c':
+ result[i] = 12;
+ break;
+ case 'D':
+ case 'd':
+ result[i] = 13;
+ break;
+ case 'E':
+ case 'e':
+ result[i] = 14;
+ break;
+ case 'F':
+ case 'f':
+ result[i] = 15;
+ break;
+ default:
+ /* Invalid input. */
+ debug_return_int(-1);
+ }
+ }
+ debug_return_int((result[0] << 4) | result[1]);
+}
diff --git a/lib/util/inet_ntop.c b/lib/util/inet_ntop.c
new file mode 100644
index 0000000..d72d975
--- /dev/null
+++ b/lib/util/inet_ntop.c
@@ -0,0 +1,229 @@
+/* $OpenBSD: inet_ntop.c,v 1.9 2014/02/05 14:20:43 millert Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1996 by Internet Software Consortium.
+ *
+ * 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 INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM 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>
+
+#if !defined(HAVE_INET_NTOP)
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "sudo_compat.h"
+
+#ifndef EAFNOSUPPORT
+# define EAFNOSUPPORT EINVAL
+#endif
+
+#ifndef NS_IN6ADDRSZ
+# ifdef IN6ADDRSZ
+# define NS_IN6ADDRSZ IN6ADDRSZ
+# else
+# define NS_IN6ADDRSZ 16
+# endif
+#endif
+#ifndef NS_INT16SZ
+# ifdef INT16SZ
+# define NS_INT16SZ INT16SZ
+# else
+# define NS_INT16SZ 2
+# endif
+#endif
+
+/*
+ * WARNING: Don't even consider trying to compile this on a system where
+ * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX.
+ */
+
+/* const char *
+ * inet_ntop4(src, dst, size)
+ * format an IPv4 address, more or less like inet_ntoa()
+ * return:
+ * `dst' (as a const)
+ * notes:
+ * (1) uses no statics
+ * (2) takes a unsigned char* not an in_addr as input
+ * author:
+ * Paul Vixie, 1996.
+ */
+static const char *
+inet_ntop4(const unsigned char *src, char *dst, socklen_t size)
+{
+ const char fmt[] = "%u.%u.%u.%u";
+ int len;
+
+ len = snprintf(dst, size, fmt, src[0], src[1], src[2], src[3]);
+ if (len < 0 || (size_t)len >= size) {
+ errno = ENOSPC;
+ return (NULL);
+ }
+ return (dst);
+}
+
+#ifdef HAVE_STRUCT_IN6_ADDR
+/* const char *
+ * inet_ntop6(src, dst, size)
+ * convert IPv6 binary address into presentation (printable) format
+ * author:
+ * Paul Vixie, 1996.
+ */
+static const char *
+inet_ntop6(const unsigned char *src, char *dst, socklen_t size)
+{
+ /*
+ * Note that int32_t and int16_t need only be "at least" large enough
+ * to contain a value of the specified size. On some systems, like
+ * Crays, there is no such thing as an integer variable with 16 bits.
+ * Keep this in mind if you think this function should have been coded
+ * to use pointer overlays. All the world's not a VAX.
+ */
+ char *cp, *ep;
+ struct { int base, len; } best, cur;
+ unsigned int words[NS_IN6ADDRSZ / NS_INT16SZ];
+ int i;
+ int advance;
+
+ /*
+ * Preprocess:
+ * Copy the input (bytewise) array into a wordwise array.
+ * Find the longest run of 0x00's in src[] for :: shorthanding.
+ */
+ memset(words, 0, sizeof(words));
+ for (i = 0; i < NS_IN6ADDRSZ; i++)
+ words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
+ best.base = -1;
+ best.len = 0;
+ cur.base = -1;
+ cur.len = 0;
+ for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
+ if (words[i] == 0) {
+ if (cur.base == -1)
+ cur.base = i, cur.len = 1;
+ else
+ cur.len++;
+ } else {
+ if (cur.base != -1) {
+ if (best.base == -1 || cur.len > best.len)
+ best = cur;
+ cur.base = -1;
+ }
+ }
+ }
+ if (cur.base != -1) {
+ if (best.base == -1 || cur.len > best.len)
+ best = cur;
+ }
+ if (best.base != -1 && best.len < 2)
+ best.base = -1;
+
+ /*
+ * Format the result.
+ */
+ cp = dst;
+ ep = dst + size;
+ for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ) && cp < ep; i++) {
+ /* Are we inside the best run of 0x00's? */
+ if (best.base != -1 && i >= best.base &&
+ i < (best.base + best.len)) {
+ if (i == best.base) {
+ if (cp + 1 >= ep) {
+ errno = ENOSPC;
+ return (NULL);
+ }
+ *cp++ = ':';
+ }
+ continue;
+ }
+ /* Are we following an initial run of 0x00s or any real hex? */
+ if (i != 0) {
+ if (cp + 1 >= ep) {
+ errno = ENOSPC;
+ return (NULL);
+ }
+ *cp++ = ':';
+ }
+ /* Is this address an encapsulated IPv4? */
+ if (i == 6 && best.base == 0 &&
+ (best.len == 6 ||
+ (best.len == 7 && words[7] != 0x0001) ||
+ (best.len == 5 && words[5] == 0xffff))) {
+ if (!inet_ntop4(src + 12, cp, (socklen_t)(ep - cp)))
+ return (NULL);
+ cp += strlen(cp);
+ break;
+ }
+ advance = snprintf(cp, (size_t)(ep - cp), "%x", words[i]);
+ if (advance <= 0 || advance >= ep - cp) {
+ errno = ENOSPC;
+ return (NULL);
+ }
+ cp += advance;
+ }
+ /* Was it a trailing run of 0x00's? */
+ if (best.base != -1 &&
+ (best.base + best.len) == (NS_IN6ADDRSZ / NS_INT16SZ)) {
+ if (cp + 1 >= ep) {
+ errno = ENOSPC;
+ return (NULL);
+ }
+ *cp++ = ':';
+ }
+ if (cp + 1 >= ep) {
+ errno = ENOSPC;
+ return (NULL);
+ }
+ *cp++ = '\0';
+
+ return (dst);
+}
+#endif /* HAVE_STRUCT_IN6_ADDR */
+
+/* const char *
+ * inet_ntop(af, src, dst, size)
+ * convert a network format address to presentation format.
+ * return:
+ * pointer to presentation format address (`dst'), or NULL (see errno).
+ * author:
+ * Paul Vixie, 1996.
+ */
+const char *
+sudo_inet_ntop(int af, const void *src, char *dst, socklen_t size)
+{
+ switch (af) {
+ case AF_INET:
+ return (inet_ntop4(src, dst, size));
+#ifdef HAVE_STRUCT_IN6_ADDR
+ case AF_INET6:
+ return (inet_ntop6(src, dst, size));
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return (NULL);
+ }
+ /* NOTREACHED */
+}
+
+#endif /* !HAVE_INET_NTOP */
diff --git a/lib/util/inet_pton.c b/lib/util/inet_pton.c
new file mode 100644
index 0000000..c73c9c3
--- /dev/null
+++ b/lib/util/inet_pton.c
@@ -0,0 +1,252 @@
+/* $OpenBSD: inet_pton.c,v 1.8 2010/05/06 15:47:14 claudio Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1996 by Internet Software Consortium.
+ *
+ * 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 INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM 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>
+
+#if !defined(HAVE_INET_PTON)
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <string.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+
+#ifndef EAFNOSUPPORT
+# define EAFNOSUPPORT EINVAL
+#endif
+
+#ifndef NS_INADDRSZ
+# ifdef INADDRSZ
+# define NS_INADDRSZ INADDRSZ
+# else
+# define NS_INADDRSZ 4
+# endif
+#endif
+#ifndef NS_IN6ADDRSZ
+# ifdef IN6ADDRSZ
+# define NS_IN6ADDRSZ IN6ADDRSZ
+# else
+# define NS_IN6ADDRSZ 16
+# endif
+#endif
+#ifndef NS_INT16SZ
+# ifdef INT16SZ
+# define NS_INT16SZ INT16SZ
+# else
+# define NS_INT16SZ 2
+# endif
+#endif
+
+/*
+ * WARNING: Don't even consider trying to compile this on a system where
+ * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX.
+ */
+
+/* int
+ * inet_pton4(src, dst)
+ * like inet_aton() but without all the hexadecimal and shorthand.
+ * return:
+ * 1 if `src' is a valid dotted quad, else 0.
+ * notice:
+ * does not touch `dst' unless it's returning 1.
+ * author:
+ * Paul Vixie, 1996.
+ */
+static int
+inet_pton4(const char *src, u_char *dst)
+{
+ const char digits[] = "0123456789";
+ int saw_digit, octets, ch;
+ u_char tmp[NS_INADDRSZ], *tp;
+
+ saw_digit = 0;
+ octets = 0;
+ /* cppcheck-suppress uninitvar */
+ *(tp = tmp) = '\0';
+ while ((ch = (unsigned char)*src++) != '\0') {
+ const char *pch;
+
+ if ((pch = strchr(digits, ch)) != NULL) {
+ u_int new = *tp * 10 + (pch - digits);
+
+ if (new > 255)
+ return (0);
+ if (!saw_digit) {
+ if (++octets > 4)
+ return (0);
+ saw_digit = 1;
+ }
+ *tp = new;
+ } else if (ch == '.' && saw_digit) {
+ if (octets == 4)
+ return (0);
+ *++tp = 0;
+ saw_digit = 0;
+ } else
+ return (0);
+ }
+ if (octets < 4)
+ return (0);
+
+ memcpy(dst, tmp, NS_INADDRSZ);
+ return (1);
+}
+
+#ifdef HAVE_STRUCT_IN6_ADDR
+/* int
+ * inet_pton6(src, dst)
+ * convert presentation level address to network order binary form.
+ * return:
+ * 1 if `src' is a valid [RFC1884 2.2] address, else 0.
+ * notice:
+ * does not touch `dst' unless it's returning 1.
+ * credit:
+ * inspired by Mark Andrews.
+ * author:
+ * Paul Vixie, 1996.
+ */
+static int
+inet_pton6(const char *src, u_char *dst)
+{
+ const char xdigits_l[] = "0123456789abcdef",
+ xdigits_u[] = "0123456789ABCDEF";
+ u_char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp;
+ const char *xdigits, *curtok;
+ int ch, saw_xdigit, count_xdigit;
+ u_int val;
+
+ /* cppcheck-suppress uninitvar */
+ memset((tp = tmp), 0, NS_IN6ADDRSZ);
+ endp = tp + NS_IN6ADDRSZ;
+ colonp = NULL;
+ /* Leading :: requires some special handling. */
+ if (*src == ':')
+ if (*++src != ':')
+ return (0);
+ curtok = src;
+ saw_xdigit = count_xdigit = 0;
+ val = 0;
+ while ((ch = (unsigned char)*src++) != '\0') {
+ const char *pch;
+
+ if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL)
+ pch = strchr((xdigits = xdigits_u), ch);
+ if (pch != NULL) {
+ if (count_xdigit >= 4)
+ return (0);
+ val <<= 4;
+ val |= (pch - xdigits);
+ if (val > 0xffff)
+ return (0);
+ saw_xdigit = 1;
+ count_xdigit++;
+ continue;
+ }
+ if (ch == ':') {
+ curtok = src;
+ if (!saw_xdigit) {
+ if (colonp)
+ return (0);
+ colonp = tp;
+ continue;
+ } else if (*src == '\0') {
+ return (0);
+ }
+ if (tp + NS_INT16SZ > endp)
+ return (0);
+ *tp++ = (u_char) (val >> 8) & 0xff;
+ *tp++ = (u_char) val & 0xff;
+ saw_xdigit = 0;
+ count_xdigit = 0;
+ val = 0;
+ continue;
+ }
+ if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
+ inet_pton4(curtok, tp) > 0) {
+ tp += NS_INADDRSZ;
+ saw_xdigit = 0;
+ count_xdigit = 0;
+ break; /* '\0' was seen by inet_pton4(). */
+ }
+ return (0);
+ }
+ if (saw_xdigit) {
+ if (tp + NS_INT16SZ > endp)
+ return (0);
+ *tp++ = (u_char) (val >> 8) & 0xff;
+ *tp++ = (u_char) val & 0xff;
+ }
+ if (colonp != NULL) {
+ /*
+ * Since some memmove()'s erroneously fail to handle
+ * overlapping regions, we'll do the shift by hand.
+ */
+ const long n = tp - colonp;
+ long i;
+
+ if (tp == endp)
+ return (0);
+ for (i = 1; i <= n; i++) {
+ endp[- i] = colonp[n - i];
+ colonp[n - i] = 0;
+ }
+ tp = endp;
+ }
+ if (tp != endp)
+ return (0);
+ memcpy(dst, tmp, NS_IN6ADDRSZ);
+ return (1);
+}
+#endif /* HAVE_STRUCT_IN6_ADDR */
+
+/* int
+ * inet_pton(af, src, dst)
+ * convert from presentation format (which usually means ASCII printable)
+ * to network format (which is usually some kind of binary format).
+ * return:
+ * 1 if the address was valid for the specified address family
+ * 0 if the address wasn't valid (`dst' is untouched in this case)
+ * -1 if some other error occurred (`dst' is untouched in this case, too)
+ * author:
+ * Paul Vixie, 1996.
+ */
+int
+sudo_inet_pton(int af, const char *src, void *dst)
+{
+ switch (af) {
+ case AF_INET:
+ return (inet_pton4(src, dst));
+#ifdef HAVE_STRUCT_IN6_ADDR
+ case AF_INET6:
+ return (inet_pton6(src, dst));
+#endif /* HAVE_STRUCT_IN6_ADDR */
+ default:
+ errno = EAFNOSUPPORT;
+ return (-1);
+ }
+ /* NOTREACHED */
+}
+
+#endif /* HAVE_INET_PTON */
diff --git a/lib/util/isblank.c b/lib/util/isblank.c
new file mode 100644
index 0000000..753e030
--- /dev/null
+++ b/lib/util/isblank.c
@@ -0,0 +1,37 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008, 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>
+
+#ifndef HAVE_ISBLANK
+
+#include "sudo_compat.h"
+
+#undef isblank
+int
+isblank(int ch)
+{
+ return ch == ' ' || ch == '\t';
+}
+#endif /* HAVE_ISBLANK */
diff --git a/lib/util/json.c b/lib/util/json.c
new file mode 100644
index 0000000..359498b
--- /dev/null
+++ b/lib/util/json.c
@@ -0,0 +1,423 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * 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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_json.h"
+#include "sudo_util.h"
+
+/*
+ * Double the size of the json buffer.
+ * Returns true on success, false if out of memory.
+ */
+static bool
+json_expand_buf(struct json_container *jsonc)
+{
+ char *newbuf;
+ debug_decl(json_expand_buf, SUDO_DEBUG_UTIL);
+
+ if ((newbuf = reallocarray(jsonc->buf, 2, jsonc->bufsize)) == NULL) {
+ if (jsonc->memfatal) {
+ sudo_fatalx(U_("%s: %s"),
+ __func__, U_("unable to allocate memory"));
+ }
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "%s: %s", __func__, "unable to allocate memory");
+ debug_return_bool(false);
+ }
+ jsonc->buf = newbuf;
+ jsonc->bufsize *= 2;
+
+ debug_return_bool(true);
+}
+
+/*
+ * Start a new line and indent unless formatting as minimal JSON.
+ * Append "indent" number of blank characters.
+ */
+static bool
+json_new_line(struct json_container *jsonc)
+{
+ int indent = jsonc->indent_level;
+ debug_decl(json_new_line, SUDO_DEBUG_UTIL);
+
+ /* No non-essential white space in minimal mode. */
+ if (jsonc->minimal)
+ debug_return_bool(true);
+
+ while (jsonc->buflen + 1 + indent >= jsonc->bufsize) {
+ if (!json_expand_buf(jsonc))
+ debug_return_bool(false);
+ }
+ jsonc->buf[jsonc->buflen++] = '\n';
+ while (indent--) {
+ jsonc->buf[jsonc->buflen++] = ' ';
+ }
+ jsonc->buf[jsonc->buflen] = '\0';
+
+ debug_return_bool(true);
+}
+
+/*
+ * Append a string to the JSON buffer, expanding as needed.
+ * Does not perform any quoting.
+ */
+static bool
+json_append_buf(struct json_container *jsonc, const char *str)
+{
+ size_t len;
+ debug_decl(json_append_buf, SUDO_DEBUG_UTIL);
+
+ len = strlen(str);
+ while (jsonc->buflen + len >= jsonc->bufsize) {
+ if (!json_expand_buf(jsonc))
+ debug_return_bool(false);
+ }
+
+ memcpy(jsonc->buf + jsonc->buflen, str, len);
+ jsonc->buflen += len;
+ jsonc->buf[jsonc->buflen] = '\0';
+
+ debug_return_bool(true);
+}
+
+/*
+ * Append a quoted JSON string, escaping special chars and expanding as needed.
+ * Treats strings as 8-bit ASCII, escaping control characters.
+ */
+static bool
+json_append_string(struct json_container *jsonc, const char *str)
+{
+ const char hex[] = "0123456789abcdef";
+ unsigned char ch;
+ debug_decl(json_append_string, SUDO_DEBUG_UTIL);
+
+ if (!json_append_buf(jsonc, "\""))
+ debug_return_bool(false);
+ while ((ch = *str++) != '\0') {
+ char buf[sizeof("\\u0000")], *cp = buf;
+
+ switch (ch) {
+ case '"':
+ case '\\':
+ *cp++ = '\\';
+ break;
+ case '\b':
+ *cp++ = '\\';
+ ch = 'b';
+ break;
+ case '\f':
+ *cp++ = '\\';
+ ch = 'f';
+ break;
+ case '\n':
+ *cp++ = '\\';
+ ch = 'n';
+ break;
+ case '\r':
+ *cp++ = '\\';
+ ch = 'r';
+ break;
+ case '\t':
+ *cp++ = '\\';
+ ch = 't';
+ break;
+ default:
+ if (iscntrl(ch)) {
+ /* Escape control characters like \u0000 */
+ *cp++ = '\\';
+ *cp++ = 'u';
+ *cp++ = '0';
+ *cp++ = '0';
+ *cp++ = hex[ch >> 4];
+ ch = hex[ch & 0x0f];
+ }
+ break;
+ }
+ *cp++ = ch;
+ *cp = '\0';
+ if (!json_append_buf(jsonc, buf))
+ debug_return_bool(false);
+ }
+ if (!json_append_buf(jsonc, "\""))
+ debug_return_bool(false);
+
+ debug_return_bool(true);
+}
+
+bool
+sudo_json_init_v2(struct json_container *jsonc, int indent, bool minimal,
+ bool memfatal, bool quiet)
+{
+ debug_decl(sudo_json_init, SUDO_DEBUG_UTIL);
+
+ memset(jsonc, 0, sizeof(*jsonc));
+ jsonc->indent_level = indent;
+ jsonc->indent_increment = indent;
+ jsonc->minimal = minimal;
+ jsonc->memfatal = memfatal;
+ jsonc->quiet = quiet;
+ jsonc->buf = malloc(64 * 1024);
+ if (jsonc->buf == NULL) {
+ if (jsonc->memfatal) {
+ sudo_fatalx(U_("%s: %s"),
+ __func__, U_("unable to allocate memory"));
+ }
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "%s: %s", __func__, "unable to allocate memory");
+ debug_return_bool(false);
+ }
+ *jsonc->buf = '\0';
+ jsonc->bufsize = 64 * 1024;
+
+ debug_return_bool(true);
+}
+
+bool
+sudo_json_init_v1(struct json_container *jsonc, int indent, bool minimal,
+ bool memfatal)
+{
+ return sudo_json_init_v2(jsonc, indent, minimal, memfatal, false);
+}
+
+void
+sudo_json_free_v1(struct json_container *jsonc)
+{
+ debug_decl(sudo_json_free, SUDO_DEBUG_UTIL);
+
+ free(jsonc->buf);
+ memset(jsonc, 0, sizeof(*jsonc));
+
+ debug_return;
+}
+
+bool
+sudo_json_open_object_v1(struct json_container *jsonc, const char *name)
+{
+ debug_decl(sudo_json_open_object, SUDO_DEBUG_UTIL);
+
+ /* Add comma if we are continuing an object/array. */
+ if (jsonc->need_comma) {
+ if (!json_append_buf(jsonc, ","))
+ debug_return_bool(false);
+ }
+ if (!json_new_line(jsonc))
+ debug_return_bool(false);
+
+ if (name != NULL) {
+ json_append_string(jsonc, name);
+ if (!json_append_buf(jsonc, jsonc->minimal ? ":{" : ": {"))
+ debug_return_bool(false);
+ } else {
+ if (!json_append_buf(jsonc, "{"))
+ debug_return_bool(false);
+ }
+
+ jsonc->indent_level += jsonc->indent_increment;
+ jsonc->need_comma = false;
+
+ debug_return_bool(true);
+}
+
+bool
+sudo_json_close_object_v1(struct json_container *jsonc)
+{
+ debug_decl(sudo_json_close_object, SUDO_DEBUG_UTIL);
+
+ if (!jsonc->minimal) {
+ jsonc->indent_level -= jsonc->indent_increment;
+ if (!json_new_line(jsonc))
+ debug_return_bool(false);
+ }
+ if (!json_append_buf(jsonc, "}"))
+ debug_return_bool(false);
+
+ debug_return_bool(true);
+}
+
+bool
+sudo_json_open_array_v1(struct json_container *jsonc, const char *name)
+{
+ debug_decl(sudo_json_open_array, SUDO_DEBUG_UTIL);
+
+ /* Add comma if we are continuing an object/array. */
+ if (jsonc->need_comma) {
+ if (!json_append_buf(jsonc, ","))
+ debug_return_bool(false);
+ }
+ if (!json_new_line(jsonc))
+ debug_return_bool(false);
+
+ if (name != NULL) {
+ json_append_string(jsonc, name);
+ if (!json_append_buf(jsonc, jsonc->minimal ? ":[" : ": ["))
+ debug_return_bool(false);
+ } else {
+ if (!json_append_buf(jsonc, "["))
+ debug_return_bool(false);
+ }
+
+ jsonc->indent_level += jsonc->indent_increment;
+ jsonc->need_comma = false;
+
+ debug_return_bool(true);
+}
+
+bool
+sudo_json_close_array_v1(struct json_container *jsonc)
+{
+ debug_decl(sudo_json_close_array, SUDO_DEBUG_UTIL);
+
+ if (!jsonc->minimal) {
+ jsonc->indent_level -= jsonc->indent_increment;
+ if (!json_new_line(jsonc))
+ debug_return_bool(false);
+ }
+ if (!json_append_buf(jsonc, "]"))
+ debug_return_bool(false);
+
+ debug_return_bool(true);
+}
+
+static bool
+sudo_json_add_value_int(struct json_container *jsonc, const char *name,
+ struct json_value *value, bool as_object)
+{
+ struct json_container saved_container = *jsonc;
+ char numbuf[(((sizeof(long long) * 8) + 2) / 3) + 2];
+ debug_decl(sudo_json_add_value, SUDO_DEBUG_UTIL);
+
+ /* Add comma if we are continuing an object/array. */
+ if (jsonc->need_comma) {
+ if (!json_append_buf(jsonc, ","))
+ goto bad;
+ }
+ if (!json_new_line(jsonc))
+ goto bad;
+ jsonc->need_comma = true;
+
+ if (as_object) {
+ if (!json_append_buf(jsonc, jsonc->minimal ? "{" : "{ "))
+ goto bad;
+ }
+
+ /* name */
+ if (name != NULL) {
+ if (!json_append_string(jsonc, name))
+ goto bad;
+ if (!json_append_buf(jsonc, jsonc->minimal ? ":" : ": "))
+ goto bad;
+ }
+
+ /* value */
+ switch (value->type) {
+ case JSON_STRING:
+ if (!json_append_string(jsonc, value->u.string))
+ goto bad;
+ break;
+ case JSON_ID:
+ snprintf(numbuf, sizeof(numbuf), "%u", (unsigned int)value->u.id);
+ if (!json_append_buf(jsonc, numbuf))
+ goto bad;
+ break;
+ case JSON_NUMBER:
+ snprintf(numbuf, sizeof(numbuf), "%lld", value->u.number);
+ if (!json_append_buf(jsonc, numbuf))
+ goto bad;
+ break;
+ case JSON_NULL:
+ if (!json_append_buf(jsonc, "null"))
+ goto bad;
+ break;
+ case JSON_BOOL:
+ if (!json_append_buf(jsonc, value->u.boolean ? "true" : "false"))
+ goto bad;
+ break;
+ case JSON_ARRAY:
+ if (!jsonc->quiet)
+ sudo_warnx("internal error: add JSON_ARRAY as a value");
+ goto bad;
+ case JSON_OBJECT:
+ if (!jsonc->quiet)
+ sudo_warnx("internal error: add JSON_OBJECT as a value");
+ goto bad;
+ default:
+ if (!jsonc->quiet)
+ sudo_warnx("internal error: unknown JSON type %d", value->type);
+ goto bad;
+ }
+
+ if (as_object) {
+ if (!json_append_buf(jsonc, jsonc->minimal ? "}" : " }"))
+ goto bad;
+ }
+
+ debug_return_bool(true);
+bad:
+ /* Restore container but handle reallocation of buf. */
+ saved_container.buf = jsonc->buf;
+ saved_container.bufsize = jsonc->bufsize;
+ *jsonc = saved_container;
+ jsonc->buf[jsonc->buflen] = '\0';
+ debug_return_bool(false);
+}
+
+bool
+sudo_json_add_value_v1(struct json_container *jsonc, const char *name,
+ struct json_value *value)
+{
+ return sudo_json_add_value_int(jsonc, name, value, false);
+}
+
+bool
+sudo_json_add_value_as_object_v1(struct json_container *jsonc, const char *name,
+ struct json_value *value)
+{
+ return sudo_json_add_value_int(jsonc, name, value, true);
+}
+
+char *
+sudo_json_get_buf_v1(struct json_container *jsonc)
+{
+ return jsonc->buf;
+}
+
+unsigned int
+sudo_json_get_len_v1(struct json_container *jsonc)
+{
+ return jsonc->buflen;
+}
diff --git a/lib/util/key_val.c b/lib/util/key_val.c
new file mode 100644
index 0000000..91caad1
--- /dev/null
+++ b/lib/util/key_val.c
@@ -0,0 +1,56 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010-2012, 2014-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * Create a new key=value pair and return it.
+ * The caller is responsible for freeing the string.
+ */
+char *
+sudo_new_key_val_v1(const char *key, const char *val)
+{
+ size_t key_len = strlen(key);
+ size_t val_len = strlen(val);
+ char *cp, *str;
+ debug_decl(sudo_new_key_val, SUDO_DEBUG_UTIL);
+
+ cp = str = malloc(key_len + 1 + val_len + 1);
+ if (cp != NULL) {
+ memcpy(cp, key, key_len);
+ cp += key_len;
+ *cp++ = '=';
+ memcpy(cp, val, val_len);
+ cp += val_len;
+ *cp = '\0';
+ }
+
+ debug_return_str(str);
+}
diff --git a/lib/util/lbuf.c b/lib/util/lbuf.c
new file mode 100644
index 0000000..018fa5e
--- /dev/null
+++ b/lib/util/lbuf.c
@@ -0,0 +1,448 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2007-2015, 2023 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_lbuf.h"
+#include "sudo_util.h"
+
+void
+sudo_lbuf_init_v1(struct sudo_lbuf *lbuf, sudo_lbuf_output_t output,
+ int indent, const char *continuation, int cols)
+{
+ debug_decl(sudo_lbuf_init, SUDO_DEBUG_UTIL);
+
+ lbuf->output = output;
+ lbuf->continuation = continuation;
+ lbuf->indent = indent;
+ lbuf->cols = cols;
+ lbuf->error = 0;
+ lbuf->len = 0;
+ lbuf->size = 0;
+ lbuf->buf = NULL;
+
+ debug_return;
+}
+
+void
+sudo_lbuf_destroy_v1(struct sudo_lbuf *lbuf)
+{
+ debug_decl(sudo_lbuf_destroy, SUDO_DEBUG_UTIL);
+
+ free(lbuf->buf);
+ lbuf->error = 0;
+ lbuf->len = 0;
+ lbuf->size = 0;
+ lbuf->buf = NULL;
+
+ debug_return;
+}
+
+static bool
+sudo_lbuf_expand(struct sudo_lbuf *lbuf, unsigned int extra)
+{
+ debug_decl(sudo_lbuf_expand, SUDO_DEBUG_UTIL);
+
+ if (lbuf->len + extra + 1 <= lbuf->len) {
+ errno = ENOMEM;
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "integer overflow updating lbuf->len");
+ lbuf->error = 1;
+ debug_return_bool(false);
+ }
+
+ if (lbuf->len + extra + 1 > lbuf->size) {
+ unsigned int new_size = sudo_pow2_roundup(lbuf->len + extra + 1);
+ char *new_buf;
+
+ if (new_size < lbuf->size) {
+ errno = ENOMEM;
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "integer overflow updating lbuf->size");
+ lbuf->error = 1;
+ debug_return_bool(false);
+ }
+ if (new_size < 1024)
+ new_size = 1024;
+ if ((new_buf = realloc(lbuf->buf, new_size)) == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to allocate memory");
+ lbuf->error = 1;
+ debug_return_bool(false);
+ }
+ lbuf->buf = new_buf;
+ lbuf->size = new_size;
+ }
+ debug_return_bool(true);
+}
+
+/*
+ * Escape a character in octal form (#0n) and store it as a string
+ * in buf, which must have at least 6 bytes available.
+ * Returns the length of buf, not counting the terminating NUL byte.
+ */
+static int
+escape(unsigned char ch, char *buf)
+{
+ const int len = ch < 0100 ? (ch < 010 ? 3 : 4) : 5;
+
+ /* Work backwards from the least significant digit to most significant. */
+ switch (len) {
+ case 5:
+ buf[4] = (ch & 7) + '0';
+ ch >>= 3;
+ FALLTHROUGH;
+ case 4:
+ buf[3] = (ch & 7) + '0';
+ ch >>= 3;
+ FALLTHROUGH;
+ case 3:
+ buf[2] = (ch & 7) + '0';
+ buf[1] = '0';
+ buf[0] = '#';
+ break;
+ }
+ buf[len] = '\0';
+
+ return len;
+}
+
+/*
+ * Parse the format and append strings, only %s and %% escapes are supported.
+ * Any non-printable characters are escaped in octal as #0nn.
+ */
+bool
+sudo_lbuf_append_esc_v1(struct sudo_lbuf *lbuf, int flags, const char *fmt, ...)
+{
+ unsigned int saved_len = lbuf->len;
+ bool ret = false;
+ const char *s;
+ va_list ap;
+ debug_decl(sudo_lbuf_append_esc, SUDO_DEBUG_UTIL);
+
+ if (sudo_lbuf_error(lbuf))
+ debug_return_bool(false);
+
+#define should_escape(ch) \
+ ((ISSET(flags, LBUF_ESC_CNTRL) && iscntrl((unsigned char)ch)) || \
+ (ISSET(flags, LBUF_ESC_BLANK) && isblank((unsigned char)ch)))
+#define should_quote(ch) \
+ (ISSET(flags, LBUF_ESC_QUOTE) && (ch == '\'' || ch == '\\'))
+
+ va_start(ap, fmt);
+ while (*fmt != '\0') {
+ if (fmt[0] == '%' && fmt[1] == 's') {
+ if ((s = va_arg(ap, char *)) == NULL)
+ s = "(NULL)";
+ while (*s != '\0') {
+ if (should_escape(*s)) {
+ if (!sudo_lbuf_expand(lbuf, sizeof("#0177") - 1))
+ goto done;
+ lbuf->len += escape(*s++, lbuf->buf + lbuf->len);
+ continue;
+ }
+ if (should_quote(*s)) {
+ if (!sudo_lbuf_expand(lbuf, 2))
+ goto done;
+ lbuf->buf[lbuf->len++] = '\\';
+ lbuf->buf[lbuf->len++] = *s++;
+ continue;
+ }
+ if (!sudo_lbuf_expand(lbuf, 1))
+ goto done;
+ lbuf->buf[lbuf->len++] = *s++;
+ }
+ fmt += 2;
+ continue;
+ }
+ if (should_escape(*fmt)) {
+ if (!sudo_lbuf_expand(lbuf, sizeof("#0177") - 1))
+ goto done;
+ if (*fmt == '\'') {
+ lbuf->buf[lbuf->len++] = '\\';
+ lbuf->buf[lbuf->len++] = *fmt++;
+ } else {
+ lbuf->len += escape(*fmt++, lbuf->buf + lbuf->len);
+ }
+ continue;
+ }
+ if (!sudo_lbuf_expand(lbuf, 1))
+ goto done;
+ lbuf->buf[lbuf->len++] = *fmt++;
+ }
+ ret = true;
+
+done:
+ if (!ret)
+ lbuf->len = saved_len;
+ if (lbuf->size != 0)
+ lbuf->buf[lbuf->len] = '\0';
+ va_end(ap);
+
+ debug_return_bool(ret);
+}
+
+/*
+ * Parse the format and append strings, only %s and %% escapes are supported.
+ * Any characters in set are quoted with a backslash.
+ */
+bool
+sudo_lbuf_append_quoted_v1(struct sudo_lbuf *lbuf, const char *set, const char *fmt, ...)
+{
+ unsigned int saved_len = lbuf->len;
+ bool ret = false;
+ const char *cp, *s;
+ va_list ap;
+ int len;
+ debug_decl(sudo_lbuf_append_quoted, SUDO_DEBUG_UTIL);
+
+ if (sudo_lbuf_error(lbuf))
+ debug_return_bool(false);
+
+ va_start(ap, fmt);
+ while (*fmt != '\0') {
+ if (fmt[0] == '%' && fmt[1] == 's') {
+ if ((s = va_arg(ap, char *)) == NULL)
+ s = "(NULL)";
+ while ((cp = strpbrk(s, set)) != NULL) {
+ len = (int)(cp - s);
+ if (!sudo_lbuf_expand(lbuf, len + 2))
+ goto done;
+ memcpy(lbuf->buf + lbuf->len, s, len);
+ lbuf->len += len;
+ lbuf->buf[lbuf->len++] = '\\';
+ lbuf->buf[lbuf->len++] = *cp;
+ s = cp + 1;
+ }
+ if (*s != '\0') {
+ len = strlen(s);
+ if (!sudo_lbuf_expand(lbuf, len))
+ goto done;
+ memcpy(lbuf->buf + lbuf->len, s, len);
+ lbuf->len += len;
+ }
+ fmt += 2;
+ continue;
+ }
+ if (!sudo_lbuf_expand(lbuf, 2))
+ goto done;
+ if (strchr(set, *fmt) != NULL)
+ lbuf->buf[lbuf->len++] = '\\';
+ lbuf->buf[lbuf->len++] = *fmt++;
+ }
+ ret = true;
+
+done:
+ if (!ret)
+ lbuf->len = saved_len;
+ if (lbuf->size != 0)
+ lbuf->buf[lbuf->len] = '\0';
+ va_end(ap);
+
+ debug_return_bool(ret);
+}
+
+/*
+ * Parse the format and append strings, only %s and %% escapes are supported.
+ */
+bool
+sudo_lbuf_append_v1(struct sudo_lbuf *lbuf, const char *fmt, ...)
+{
+ unsigned int saved_len = lbuf->len;
+ bool ret = false;
+ va_list ap;
+ const char *s;
+ int len;
+ debug_decl(sudo_lbuf_append, SUDO_DEBUG_UTIL);
+
+ if (sudo_lbuf_error(lbuf))
+ debug_return_bool(false);
+
+ va_start(ap, fmt);
+ while (*fmt != '\0') {
+ if (fmt[0] == '%' && fmt[1] == 's') {
+ if ((s = va_arg(ap, char *)) == NULL)
+ s = "(NULL)";
+ len = strlen(s);
+ if (!sudo_lbuf_expand(lbuf, len))
+ goto done;
+ memcpy(lbuf->buf + lbuf->len, s, len);
+ lbuf->len += len;
+ fmt += 2;
+ continue;
+ }
+ if (!sudo_lbuf_expand(lbuf, 1))
+ goto done;
+ lbuf->buf[lbuf->len++] = *fmt++;
+ }
+ ret = true;
+
+done:
+ if (!ret)
+ lbuf->len = saved_len;
+ if (lbuf->size != 0)
+ lbuf->buf[lbuf->len] = '\0';
+ va_end(ap);
+
+ debug_return_bool(ret);
+}
+
+/* XXX - check output function return value */
+static void
+sudo_lbuf_println(struct sudo_lbuf *lbuf, char *line, int len)
+{
+ char *cp, save;
+ int i, have, contlen = 0;
+ int indent = lbuf->indent;
+ bool is_comment = false;
+ debug_decl(sudo_lbuf_println, SUDO_DEBUG_UTIL);
+
+ /* Comment lines don't use continuation and only indent is for "# " */
+ if (line[0] == '#' && isblank((unsigned char)line[1])) {
+ is_comment = true;
+ indent = 2;
+ }
+ if (lbuf->continuation != NULL && !is_comment)
+ contlen = strlen(lbuf->continuation);
+
+ /*
+ * Print the buffer, splitting the line as needed on a word
+ * boundary.
+ */
+ cp = line;
+ have = lbuf->cols;
+ while (cp != NULL && *cp != '\0') {
+ char *ep = NULL;
+ int need = len - (int)(cp - line);
+
+ if (need > have) {
+ have -= contlen; /* subtract for continuation char */
+ if ((ep = memrchr(cp, ' ', have)) == NULL)
+ ep = memchr(cp + have, ' ', need - have);
+ if (ep != NULL)
+ need = (int)(ep - cp);
+ }
+ if (cp != line) {
+ if (is_comment) {
+ lbuf->output("# ");
+ } else {
+ /* indent continued lines */
+ /* XXX - build up string instead? */
+ for (i = 0; i < indent; i++)
+ lbuf->output(" ");
+ }
+ }
+ /* NUL-terminate cp for the output function and restore afterwards */
+ save = cp[need];
+ cp[need] = '\0';
+ lbuf->output(cp);
+ cp[need] = save;
+ cp = ep;
+
+ /*
+ * If there is more to print, reset have, incremement cp past
+ * the whitespace, and print a line continuaton char if needed.
+ */
+ if (cp != NULL) {
+ have = lbuf->cols - indent;
+ ep = line + len;
+ while (cp < ep && isblank((unsigned char)*cp)) {
+ cp++;
+ }
+ if (contlen)
+ lbuf->output(lbuf->continuation);
+ }
+ lbuf->output("\n");
+ }
+
+ debug_return;
+}
+
+/*
+ * Print the buffer with word wrap based on the tty width.
+ * The lbuf is reset on return.
+ * XXX - check output function return value
+ */
+void
+sudo_lbuf_print_v1(struct sudo_lbuf *lbuf)
+{
+ char *cp, *ep;
+ int len;
+ debug_decl(sudo_lbuf_print, SUDO_DEBUG_UTIL);
+
+ if (lbuf->buf == NULL || lbuf->len == 0)
+ goto done;
+
+ /* For very small widths just give up... */
+ len = lbuf->continuation ? strlen(lbuf->continuation) : 0;
+ if (lbuf->cols <= lbuf->indent + len + 20) {
+ lbuf->buf[lbuf->len] = '\0';
+ lbuf->output(lbuf->buf);
+ if (lbuf->buf[lbuf->len - 1] != '\n')
+ lbuf->output("\n");
+ goto done;
+ }
+
+ /* Print each line in the buffer */
+ for (cp = lbuf->buf; cp != NULL && *cp != '\0'; ) {
+ if (*cp == '\n') {
+ lbuf->output("\n");
+ cp++;
+ } else {
+ len = lbuf->len - (cp - lbuf->buf);
+ if ((ep = memchr(cp, '\n', len)) != NULL)
+ len = (int)(ep - cp);
+ if (len)
+ sudo_lbuf_println(lbuf, cp, len);
+ cp = ep ? ep + 1 : NULL;
+ }
+ }
+
+done:
+ lbuf->len = 0; /* reset the buffer for re-use. */
+ lbuf->error = 0;
+
+ debug_return;
+}
+
+bool
+sudo_lbuf_error_v1(struct sudo_lbuf *lbuf)
+{
+ if (lbuf != NULL && lbuf->error != 0)
+ return true;
+ return false;
+}
+
+void
+sudo_lbuf_clearerr_v1(struct sudo_lbuf *lbuf)
+{
+ if (lbuf != NULL)
+ lbuf->error = 0;
+}
diff --git a/lib/util/localtime_r.c b/lib/util/localtime_r.c
new file mode 100644
index 0000000..674cb1a
--- /dev/null
+++ b/lib/util/localtime_r.c
@@ -0,0 +1,49 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * 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>
+
+#ifndef HAVE_LOCALTIME_R
+
+#include <sys/types.h>
+#include <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+/*
+ * Fake localtime_r() that just stores the result.
+ * Still has the normal localtime() side effects.
+ */
+struct tm *
+sudo_localtime_r(const time_t *timer, struct tm *result)
+{
+ struct tm *tm;
+
+ if ((tm = localtime(timer)) == NULL)
+ return NULL;
+ memcpy(result, tm, sizeof(struct tm));
+
+ return result;
+}
+#endif /* HAVE_LOCALTIME_T */
diff --git a/lib/util/locking.c b/lib/util/locking.c
new file mode 100644
index 0000000..638b082
--- /dev/null
+++ b/lib/util/locking.c
@@ -0,0 +1,143 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1999-2005, 2007, 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.
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_debug.h"
+
+bool
+sudo_lock_file_v1(int fd, int type)
+{
+ return sudo_lock_region_v1(fd, type, 0);
+}
+
+/*
+ * Lock/unlock all or part of a file.
+ */
+#ifdef HAVE_LOCKF
+bool
+sudo_lock_region_v1(int fd, int type, off_t len)
+{
+ int op, rc;
+ off_t oldpos = -1;
+ debug_decl(sudo_lock_region, SUDO_DEBUG_UTIL);
+
+ switch (type) {
+ case SUDO_LOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: lock %d:%lld",
+ __func__, fd, (long long)len);
+ op = F_LOCK;
+ break;
+ case SUDO_TLOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: tlock %d:%lld",
+ __func__, fd, (long long)len);
+ op = F_TLOCK;
+ break;
+ case SUDO_UNLOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: unlock %d:%lld",
+ __func__, fd, (long long)len);
+ op = F_ULOCK;
+ /* Must seek to start of file to unlock the entire thing. */
+ if (len == 0 && (oldpos = lseek(fd, 0, SEEK_CUR)) != -1) {
+ if (lseek(fd, 0, SEEK_SET) == -1) {
+ sudo_debug_printf(
+ SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to seek to beginning");
+ }
+ }
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: bad lock type %d",
+ __func__, type);
+ errno = EINVAL;
+ debug_return_bool(false);
+ }
+ rc = lockf(fd, op, len);
+ if (oldpos != -1) {
+ if (lseek(fd, oldpos, SEEK_SET) == -1) {
+ sudo_debug_printf(
+ SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to restore offset");
+ }
+ }
+ debug_return_bool(rc == 0);
+}
+#else
+bool
+sudo_lock_region_v1(int fd, int type, off_t len)
+{
+ struct flock lock;
+ int func;
+ debug_decl(sudo_lock_file, SUDO_DEBUG_UTIL);
+
+ switch (type) {
+ case SUDO_LOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: lock %d:%lld",
+ __func__, fd, (long long)len);
+ lock.l_type = F_WRLCK;
+ func = F_SETLKW;
+ break;
+ case SUDO_TLOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: tlock %d:%lld",
+ __func__, fd, (long long)len);
+ lock.l_type = F_WRLCK;
+ func = F_SETLK;
+ break;
+ case SUDO_UNLOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: unlock %d:%lld",
+ __func__, fd, (long long)len);
+ lock.l_type = F_UNLCK;
+ func = F_SETLK;
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: bad lock type %d",
+ __func__, type);
+ errno = EINVAL;
+ debug_return_bool(false);
+ }
+ lock.l_start = 0;
+ lock.l_len = len;
+ lock.l_pid = 0;
+ lock.l_whence = len ? SEEK_CUR : SEEK_SET;
+
+ debug_return_bool(fcntl(fd, func, &lock) == 0);
+}
+#endif
diff --git a/lib/util/logfac.c b/lib/util/logfac.c
new file mode 100644
index 0000000..5a97ac3
--- /dev/null
+++ b/lib/util/logfac.c
@@ -0,0 +1,90 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1999-2005, 2007-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.
+ *
+ * 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 <string.h>
+#include <syslog.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * For converting between syslog numbers and strings.
+ */
+struct strmap {
+ const char *name;
+ int num;
+};
+
+static const struct strmap facilities[] = {
+#ifdef LOG_AUTHPRIV
+ { "authpriv", LOG_AUTHPRIV },
+#endif
+ { "auth", LOG_AUTH },
+ { "daemon", LOG_DAEMON },
+ { "user", LOG_USER },
+ { "local0", LOG_LOCAL0 },
+ { "local1", LOG_LOCAL1 },
+ { "local2", LOG_LOCAL2 },
+ { "local3", LOG_LOCAL3 },
+ { "local4", LOG_LOCAL4 },
+ { "local5", LOG_LOCAL5 },
+ { "local6", LOG_LOCAL6 },
+ { "local7", LOG_LOCAL7 },
+ { NULL, -1 }
+};
+
+bool
+sudo_str2logfac_v1(const char *str, int *logfac)
+{
+ const struct strmap *fac;
+ debug_decl(sudo_str2logfac, SUDO_DEBUG_UTIL);
+
+ for (fac = facilities; fac->name != NULL; fac++) {
+ if (strcmp(str, fac->name) == 0) {
+ *logfac = fac->num;
+ debug_return_bool(true);
+ }
+ }
+ debug_return_bool(false);
+}
+
+const char *
+sudo_logfac2str_v1(int num)
+{
+ const struct strmap *fac;
+ debug_decl(sudo_logfac2str, SUDO_DEBUG_UTIL);
+
+ for (fac = facilities; fac->name != NULL; fac++) {
+ if (fac->num == num)
+ break;
+ }
+ debug_return_const_str(fac->name);
+}
diff --git a/lib/util/logpri.c b/lib/util/logpri.c
new file mode 100644
index 0000000..a6503bd
--- /dev/null
+++ b/lib/util/logpri.c
@@ -0,0 +1,85 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1999-2005, 2007-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.
+ *
+ * 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 <string.h>
+#include <syslog.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * For converting between syslog numbers and strings.
+ */
+struct strmap {
+ const char *name;
+ int num;
+};
+
+static const struct strmap priorities[] = {
+ { "alert", LOG_ALERT },
+ { "crit", LOG_CRIT },
+ { "debug", LOG_DEBUG },
+ { "emerg", LOG_EMERG },
+ { "err", LOG_ERR },
+ { "info", LOG_INFO },
+ { "notice", LOG_NOTICE },
+ { "warning", LOG_WARNING },
+ { "none", -1 },
+ { NULL, -1 }
+};
+
+bool
+sudo_str2logpri_v1(const char *str, int *logpri)
+{
+ const struct strmap *pri;
+ debug_decl(sudo_str2logpri, SUDO_DEBUG_UTIL);
+
+ for (pri = priorities; pri->name != NULL; pri++) {
+ if (strcmp(str, pri->name) == 0) {
+ *logpri = pri->num;
+ debug_return_bool(true);
+ }
+ }
+ debug_return_bool(false);
+}
+
+const char *
+sudo_logpri2str_v1(int num)
+{
+ const struct strmap *pri;
+ debug_decl(sudo_logpri2str, SUDO_DEBUG_UTIL);
+
+ for (pri = priorities; pri->name != NULL; pri++) {
+ if (pri->num == num)
+ break;
+ }
+ debug_return_const_str(pri->name);
+}
diff --git a/lib/util/memrchr.c b/lib/util/memrchr.c
new file mode 100644
index 0000000..a5a1177
--- /dev/null
+++ b/lib/util/memrchr.c
@@ -0,0 +1,51 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2007, 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.
+ */
+
+/*
+ * 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>
+
+#ifndef HAVE_MEMRCHR
+
+#include <sys/types.h>
+
+#include "sudo_compat.h"
+
+/*
+ * Reverse memchr()
+ * Find the last occurrence of 'c' in the buffer 's' of size 'n'.
+ */
+void *
+sudo_memrchr(const void *s, int c, size_t n)
+{
+ const unsigned char *cp;
+
+ if (n != 0) {
+ cp = (unsigned char *)s + n;
+ do {
+ if (*(--cp) == (unsigned char)c)
+ return (void *)cp;
+ } while (--n != 0);
+ }
+ return (void *)0;
+}
+#endif /* HAVE_MEMRCHR */
diff --git a/lib/util/mkdir_parents.c b/lib/util/mkdir_parents.c
new file mode 100644
index 0000000..61867a3
--- /dev/null
+++ b/lib/util/mkdir_parents.c
@@ -0,0 +1,199 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2022 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+#ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+#endif
+
+/*
+ * Returns true if fd is a directory, else false.
+ * Warns on failure if not quiet.
+ */
+static bool
+is_dir(int dfd, const char *name, int namelen, bool quiet)
+{
+ struct stat sb;
+ debug_decl(is_dir, SUDO_DEBUG_UTIL);
+
+ if (fstat(dfd, &sb) != 0) {
+ if (!quiet) {
+ sudo_warn(U_("unable to stat %.*s"), namelen, name);
+ }
+ debug_return_bool(false);
+ }
+ if (!S_ISDIR(sb.st_mode)) {
+ if (!quiet) {
+ sudo_warnx(U_("%.*s exists but is not a directory (0%o)"),
+ namelen, name, (unsigned int) sb.st_mode);
+ }
+ debug_return_bool(false);
+ }
+
+ debug_return_bool(true);
+}
+
+/*
+ * Create any parent directories needed by path (but not path itself)
+ * and return an open fd for the parent directory or -1 on error.
+ */
+int
+sudo_open_parent_dir_v1(const char *path, uid_t uid, gid_t gid, mode_t mode,
+ bool quiet)
+{
+ const char *cp, *ep, *pathend;
+ char name[PATH_MAX];
+ int parentfd;
+ debug_decl(sudo_open_parent_dir, SUDO_DEBUG_UTIL);
+
+ /* Starting parent dir is either root or cwd. */
+ cp = path;
+ if (*cp == '/') {
+ do {
+ cp++;
+ } while (*cp == '/');
+ parentfd = open("/", O_RDONLY|O_NONBLOCK);
+ } else {
+ parentfd = open(".", O_RDONLY|O_NONBLOCK);
+ }
+ if (parentfd == -1) {
+ if (!quiet)
+ sudo_warn(U_("unable to open %s"), *path == '/' ? "/" : ".");
+ debug_return_int(-1);
+ }
+
+ /* Iterate over path components, skipping the last one. */
+ pathend = cp + strlen(cp);
+ for (cp = sudo_strsplit(cp, pathend, "/", &ep); cp != NULL && ep < pathend;
+ cp = sudo_strsplit(NULL, pathend, "/", &ep)) {
+ size_t len = (size_t)(ep - cp);
+ int dfd;
+
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "mkdir %.*s, mode 0%o, uid %d, gid %d", (int)(ep - path), path,
+ (unsigned int)mode, (int)uid, (int)gid);
+ if (len >= sizeof(name)) {
+ errno = ENAMETOOLONG;
+ if (!quiet)
+ sudo_warn(U_("unable to mkdir %.*s"), (int)(ep - path), path);
+ goto bad;
+ }
+ memcpy(name, cp, len);
+ name[len] = '\0';
+reopen:
+ dfd = openat(parentfd, name, O_RDONLY|O_NONBLOCK, 0);
+ if (dfd == -1) {
+ if (errno != ENOENT) {
+ if (!quiet) {
+ sudo_warn(U_("unable to open %.*s"),
+ (int)(ep - path), path);
+ }
+ goto bad;
+ }
+ if (mkdirat(parentfd, name, mode) == 0) {
+ dfd = openat(parentfd, name, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0);
+ if (dfd == -1) {
+ if (!quiet) {
+ sudo_warn(U_("unable to open %.*s"),
+ (int)(ep - path), path);
+ }
+ goto bad;
+ }
+ /* Make sure the path we created is still a directory. */
+ if (!is_dir(dfd, path, ep - path, quiet)) {
+ close(dfd);
+ goto bad;
+ }
+ if (uid != (uid_t)-1 && gid != (gid_t)-1) {
+ if (fchown(dfd, uid, gid) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to chown %d:%d %.*s", __func__,
+ (int)uid, (int)gid, (int)(ep - path), path);
+ }
+ }
+ } else {
+ if (errno == EEXIST)
+ goto reopen;
+ if (!quiet) {
+ sudo_warn(U_("unable to mkdir %.*s"),
+ (int)(ep - path), path);
+ }
+ goto bad;
+ }
+ } else {
+ /* Already exists, make sure it is a directory. */
+ if (!is_dir(dfd, path, ep - path, quiet)) {
+ close(dfd);
+ goto bad;
+ }
+ }
+ close(parentfd);
+ parentfd = dfd;
+ }
+
+ debug_return_int(parentfd);
+bad:
+ if (parentfd != -1)
+ close(parentfd);
+ debug_return_int(-1);
+}
+
+/*
+ * Create any parent directories needed by path (but not path itself).
+ * Not currently used.
+ */
+bool
+sudo_mkdir_parents_v1(const char *path, uid_t uid, gid_t gid, mode_t mode,
+ bool quiet)
+{
+ int fd;
+ debug_decl(sudo_mkdir_parents, SUDO_DEBUG_UTIL);
+
+ fd = sudo_open_parent_dir(path, uid, gid, mode, quiet);
+ if (fd == -1)
+ debug_return_bool(false);
+ close(fd);
+ debug_return_bool(true);
+}
diff --git a/lib/util/mkdirat.c b/lib/util/mkdirat.c
new file mode 100644
index 0000000..4dee92d
--- /dev/null
+++ b/lib/util/mkdirat.c
@@ -0,0 +1,61 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2015, 2019-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_MKDIRAT
+int
+sudo_mkdirat(int dfd, const char *path, mode_t mode)
+{
+ int ret, odfd;
+
+ if (dfd == (int)AT_FDCWD)
+ return mkdir(path, mode);
+
+ /* Save cwd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ return -1;
+
+ if (fchdir(dfd) == -1) {
+ close(odfd);
+ return -1;
+ }
+
+ ret = mkdir(path, mode);
+
+ /* Restore cwd */
+ if (fchdir(odfd) == -1) {
+ /* Should not happen */
+ ret = -1;
+ }
+ close(odfd);
+
+ return ret;
+}
+#endif /* HAVE_MKDIRAT */
diff --git a/lib/util/mksiglist.c b/lib/util/mksiglist.c
new file mode 100644
index 0000000..2c99b21
--- /dev/null
+++ b/lib/util/mksiglist.c
@@ -0,0 +1,52 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010-2012, 2015, 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <signal.h>
+
+#include "sudo_compat.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ unsigned int i;
+
+#include "mksiglist.h"
+
+ printf("const char *const sudo_sys_siglist[] = {\n");
+ for (i = 0; i < nitems(sudo_sys_siglist); i++) {
+ if (sudo_sys_siglist[i] != NULL) {
+ printf(" \"%s\",\n", sudo_sys_siglist[i]);
+ } else {
+ printf(" \"Signal %u\",\n", i);
+ }
+ }
+ printf("};\n");
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/lib/util/mksigname.c b/lib/util/mksigname.c
new file mode 100644
index 0000000..e501334
--- /dev/null
+++ b/lib/util/mksigname.c
@@ -0,0 +1,52 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010-2012, 2015, 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <signal.h>
+
+#include "sudo_compat.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ unsigned int i;
+
+#include "mksigname.h"
+
+ printf("const char *const sudo_sys_signame[] = {\n");
+ for (i = 0; i < nitems(sudo_sys_signame); i++) {
+ if (sudo_sys_signame[i] != NULL) {
+ printf(" \"%s\",\n", sudo_sys_signame[i]);
+ } else {
+ printf(" \"Signal %u\",\n", i);
+ }
+ }
+ printf("};\n");
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/lib/util/mktemp.c b/lib/util/mktemp.c
new file mode 100644
index 0000000..1540e51
--- /dev/null
+++ b/lib/util/mktemp.c
@@ -0,0 +1,172 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2001, 2003, 2004, 2008-2011, 2013, 2015, 2017, 2018, 2022
+ * 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>
+
+#if (!defined(HAVE_MKDTEMPAT) && !defined(HAVE_MKDTEMPAT_NP)) || \
+ (!defined(HAVE_MKOSTEMPSAT) && !defined(HAVE_MKOSTEMPSAT_NP))
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_rand.h"
+#include "pathnames.h"
+
+#define MKTEMP_FILE 1
+#define MKTEMP_DIR 2
+
+#define TEMPCHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+#define NUM_CHARS (sizeof(TEMPCHARS) - 1)
+#define MIN_X 6
+
+#define MKOTEMP_FLAGS (O_APPEND | O_CLOEXEC | O_SYNC)
+
+static int
+mktemp_internal(int dfd, char *path, int slen, int mode, int flags)
+{
+ char *start, *cp, *ep;
+ const char tempchars[] = TEMPCHARS;
+ unsigned int tries;
+ size_t len;
+ int fd;
+
+ len = strlen(path);
+ if (len < MIN_X || slen < 0 || (size_t)slen > len - MIN_X) {
+ errno = EINVAL;
+ return -1;
+ }
+ ep = path + len - slen;
+
+ for (start = ep; start > path && start[-1] == 'X'; start--)
+ ;
+ if (ep - start < MIN_X) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (flags & ~MKOTEMP_FLAGS) {
+ errno = EINVAL;
+ return -1;
+ }
+ flags |= O_CREAT | O_EXCL | O_RDWR;
+
+ tries = INT_MAX;
+ do {
+ cp = start;
+ do {
+ unsigned short rbuf[16];
+ unsigned int i;
+
+ /*
+ * Avoid lots of arc4random() calls by using
+ * a buffer sized for up to 16 Xs at a time.
+ */
+ arc4random_buf(rbuf, sizeof(rbuf));
+ for (i = 0; i < nitems(rbuf) && cp != ep; i++)
+ *cp++ = tempchars[rbuf[i] % NUM_CHARS];
+ } while (cp != ep);
+
+ switch (mode) {
+ case MKTEMP_FILE:
+ fd = openat(dfd, path, flags, S_IRUSR|S_IWUSR);
+ if (fd != -1 || errno != EEXIST)
+ return fd;
+ break;
+ case MKTEMP_DIR:
+ if (mkdirat(dfd, path, S_IRWXU) == 0)
+ return 0;
+ if (errno != EEXIST)
+ return -1;
+ break;
+ }
+ } while (--tries);
+
+ errno = EEXIST;
+ return -1;
+}
+
+char *
+sudo_mkdtemp(char *path)
+{
+ if (mktemp_internal(AT_FDCWD, path, 0, MKTEMP_DIR, 0) == -1)
+ return NULL;
+ return path;
+}
+
+char *
+sudo_mkdtempat(int dfd, char *path)
+{
+ if (mktemp_internal(dfd, path, 0, MKTEMP_DIR, 0) == -1)
+ return NULL;
+ return path;
+}
+
+int
+sudo_mkostempsat(int dfd, char *path, int slen, int flags)
+{
+ return mktemp_internal(dfd, path, slen, MKTEMP_FILE, flags);
+}
+
+#ifdef notyet
+int
+sudo_mkostemps(char *path, int slen, int flags)
+{
+ return mktemp_internal(AT_FDCWD, path, slen, MKTEMP_FILE, flags);
+}
+#endif
+
+int
+sudo_mkstemp(char *path)
+{
+ return mktemp_internal(AT_FDCWD, path, 0, MKTEMP_FILE, 0);
+}
+
+#ifdef notyet
+int
+sudo_mkostemp(char *path, int flags)
+{
+ return mktemp_internal(AT_FDCWD, path, 0, MKTEMP_FILE, flags);
+}
+#endif
+
+int
+sudo_mkstemps(char *path, int slen)
+{
+ return mktemp_internal(AT_FDCWD, path, slen, MKTEMP_FILE, 0);
+}
+#endif /* !HAVE_MKDTEMPAT || !HAVE_MKOSTEMPSAT */
diff --git a/lib/util/mmap_alloc.c b/lib/util/mmap_alloc.c
new file mode 100644
index 0000000..cd678a0
--- /dev/null
+++ b/lib/util/mmap_alloc.c
@@ -0,0 +1,160 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ * Copyright (c) 2022 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/mman.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS)
+# define MAP_ANON MAP_ANONYMOUS
+#endif
+
+#ifndef MAP_FAILED
+# define MAP_FAILED ((void *)-1)
+#endif
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+
+/*
+ * Allocate "size" bytes via mmap().
+ * Space is allocated to store the size for later unmapping.
+ */
+void *
+sudo_mmap_alloc_v1(size_t size)
+{
+ void *ptr;
+ unsigned long *ulp;
+#ifndef MAP_ANON
+ int fd;
+
+ /* SunOS-style mmap allocation using /dev/zero. */
+ if ((fd = open("/dev/zero", O_RDWR)) == -1)
+ return NULL;
+ size += sizeof(unsigned long);
+ ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ close(fd);
+#else
+ size += sizeof(unsigned long);
+ ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
+#endif
+ if (ptr == MAP_FAILED) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ /* Store size before the actual data. */
+ ulp = (unsigned long *)ptr;
+ ulp[0] = size;
+ return (void *)&ulp[1];
+}
+
+/*
+ * Allocate "nmemb" elements of "size" bytes via mmap().
+ * If overflow would occur, errno is set to ENOMEM and
+ * NULL is returned.
+ */
+void *
+sudo_mmap_allocarray_v1(size_t nmemb, size_t size)
+{
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return sudo_mmap_alloc_v1(nmemb * size);
+}
+
+/*
+ * Make a copy of "str" via mmap() and return it.
+ */
+char *
+sudo_mmap_strdup_v1(const char *str)
+{
+ size_t len = strlen(str);
+ char *newstr;
+
+ if (len == SIZE_MAX) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ newstr = sudo_mmap_alloc_v1(len + 1);
+ if (newstr != NULL) {
+ memcpy(newstr, str, len);
+ newstr[len] = '\0';
+ }
+
+ return newstr;
+}
+
+/*
+ * Set the page permissions for the allocation represented by "ptr" to
+ * read-only. Returns 0 on success, -1 on failure.
+ */
+int
+sudo_mmap_protect_v1(void *ptr)
+{
+ if (ptr != NULL) {
+ unsigned long *ulp = ptr;
+ const unsigned long size = ulp[-1];
+ return mprotect((void *)&ulp[-1], size, PROT_READ);
+ }
+
+ /* Can't protect NULL. */
+ errno = EINVAL;
+ return -1;
+}
+
+/*
+ * Free "ptr" allocated by sudo_mmap_alloc().
+ * The allocated size is stored (as unsigned long) in ptr[-1].
+ */
+void
+sudo_mmap_free_v1(void *ptr)
+{
+ if (ptr != NULL) {
+ unsigned long *ulp = (unsigned long *)ptr - 1;
+ const unsigned long size = ulp[0];
+ int saved_errno = errno;
+
+ (void)munmap((void *)ulp, size);
+ errno = saved_errno;
+ }
+}
diff --git a/lib/util/multiarch.c b/lib/util/multiarch.c
new file mode 100644
index 0000000..53d85d7
--- /dev/null
+++ b/lib/util/multiarch.c
@@ -0,0 +1,104 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifdef __linux__
+# include <sys/stat.h>
+# include <sys/utsname.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+# if defined(__linux__)
+/*
+ * On Linux systems that use multi-arch, the actual DSO may be in a
+ * machine-specific subdirectory. If the specified path contains
+ * /lib/ or /libexec/, insert a multi-arch directory after it.
+ * If sb is non-NULL, stat(2) will be called on the new path, filling in sb.
+ * Returns a dynamically allocated string on success and NULL on failure.
+ */
+char *
+sudo_stat_multiarch_v1(const char *path, struct stat *sb)
+{
+# if defined(__ILP32__)
+ const char *libdirs[] = { "/libx32/", "/lib/", "/libexec/", NULL };
+# elif defined(__LP64__)
+ const char *libdirs[] = { "/lib64/", "/lib/", "/libexec/", NULL };
+# else
+ const char *libdirs[] = { "/lib32/", "/lib/", "/libexec/", NULL };
+# endif
+ const char **lp, *lib, *slash;
+ struct utsname unamebuf;
+ char *newpath = NULL;
+ int len;
+
+ if (uname(&unamebuf) == -1)
+ return NULL;
+
+ for (lp = libdirs; *lp != NULL; lp++) {
+ /* Replace lib64, lib32, libx32 with lib in new path. */
+ const char *newlib = lp == libdirs ? "/lib/" : *lp;
+
+ /* Search for lib dir in path, find the trailing slash. */
+ lib = strstr(path, *lp);
+ if (lib == NULL)
+ continue;
+ slash = lib + strlen(*lp) - 1;
+
+ /* Make sure there isn't already a machine-linux-gnu dir. */
+ len = strcspn(slash + 1, "/-");
+ if (strncmp(slash + 1 + len, "-linux-gnu/", 11) == 0) {
+ /* Multiarch already present. */
+ break;
+ }
+
+ /* Add machine-linux-gnu dir after /lib/ or /libexec/. */
+ len = asprintf(&newpath, "%.*s%s%s-linux-gnu%s",
+ (int)(lib - path), path, newlib, unamebuf.machine, slash);
+ if (len == -1) {
+ newpath = NULL;
+ break;
+ }
+
+ /* If sb was set, use stat(2) to make sure newpath exists. */
+ if (sb == NULL || stat(newpath, sb) == 0)
+ break;
+ free(newpath);
+ newpath = NULL;
+ }
+
+ return newpath;
+}
+#else
+char *
+sudo_stat_multiarch_v1(const char *path, struct stat *sb)
+{
+ return NULL;
+}
+#endif /* __linux__ */
diff --git a/lib/util/nanosleep.c b/lib/util/nanosleep.c
new file mode 100644
index 0000000..96082f5
--- /dev/null
+++ b/lib/util/nanosleep.c
@@ -0,0 +1,65 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2011, 2013, 2017-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>
+
+#ifndef HAVE_NANOSLEEP
+
+#include <sys/types.h>
+#include <sys/time.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif /* HAVE_SYS_SELECT_H */
+#include <time.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+int
+sudo_nanosleep(const struct timespec *ts, struct timespec *rts)
+{
+ struct timeval timeout, endtime, now;
+ int rval;
+
+ if (ts->tv_sec == 0 && ts->tv_nsec < 1000) {
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 1;
+ } else {
+ TIMESPEC_TO_TIMEVAL(&timeout, ts);
+ }
+ if (rts != NULL) {
+ if (gettimeofday(&endtime, NULL) == -1)
+ return -1;
+ sudo_timevaladd(&endtime, &timeout, &endtime);
+ }
+ rval = select(0, NULL, NULL, NULL, &timeout);
+ if (rts != NULL && rval == -1 && errno == EINTR) {
+ if (gettimeofday(&now, NULL) == -1)
+ return -1;
+ sudo_timevalsub(&endtime, &now, &endtime);
+ TIMEVAL_TO_TIMESPEC(&endtime, rts);
+ }
+ return rval;
+}
+#endif /* HAVE_NANOSLEEP */
diff --git a/lib/util/openat.c b/lib/util/openat.c
new file mode 100644
index 0000000..5431525
--- /dev/null
+++ b/lib/util/openat.c
@@ -0,0 +1,63 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2015, 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_OPENAT
+int
+sudo_openat(int dfd, const char *path, int flags, mode_t mode)
+{
+ int fd, odfd;
+
+ if (dfd == AT_FDCWD)
+ return open(path, flags, mode);
+
+ /* Save cwd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ return -1;
+
+ if (fchdir(dfd) == -1) {
+ close(odfd);
+ return -1;
+ }
+
+ fd = open(path, flags, mode);
+
+ /* Restore cwd */
+ if (fchdir(odfd) == -1) {
+ /* Should not happen */
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+ }
+ close(odfd);
+
+ return fd;
+}
+#endif /* HAVE_OPENAT */
diff --git a/lib/util/parseln.c b/lib/util/parseln.c
new file mode 100644
index 0000000..2d86bcc
--- /dev/null
+++ b/lib/util/parseln.c
@@ -0,0 +1,131 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2007, 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.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_debug.h"
+
+/*
+ * Read a line of input, honoring line continuation chars.
+ * Remove comments and strip off leading and trailing spaces.
+ * Returns the line length and updates the buf and bufsize pointers.
+ * XXX - just use a struct w/ state, including getdelim buffer?
+ * could also make comment char and line continuation configurable
+ */
+ssize_t
+sudo_parseln_v2(char **bufp, size_t *bufsizep, unsigned int *lineno, FILE *fp, int flags)
+{
+ size_t linesize = 0, total = 0;
+ ssize_t len;
+ char *cp, *line = NULL;
+ bool continued, comment;
+ debug_decl(sudo_parseln, SUDO_DEBUG_UTIL);
+
+ do {
+ comment = false;
+ continued = false;
+ len = getdelim(&line, &linesize, '\n', fp);
+ if (len == -1)
+ break;
+ if (lineno != NULL)
+ (*lineno)++;
+
+ /* Remove trailing newline(s) if present. */
+ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+ line[--len] = '\0';
+
+ /* Remove comments or check for line continuation (but not both) */
+ if ((cp = strchr(line, '#')) != NULL) {
+ if (cp == line || !ISSET(flags, PARSELN_COMM_BOL)) {
+ *cp = '\0';
+ len = (ssize_t)(cp - line);
+ comment = true;
+ }
+ }
+ if (!comment && !ISSET(flags, PARSELN_CONT_IGN)) {
+ if (len > 0 && line[len - 1] == '\\' && (len == 1 || line[len - 2] != '\\')) {
+ line[--len] = '\0';
+ continued = true;
+ }
+ }
+
+ /* Trim leading and trailing whitespace */
+ if (!continued) {
+ while (len > 0 && isblank((unsigned char)line[len - 1]))
+ line[--len] = '\0';
+ }
+ for (cp = line; isblank((unsigned char)*cp); cp++)
+ len--;
+
+ if (*bufp == NULL || total + len >= *bufsizep) {
+ void *tmp;
+ size_t size = total + len + 1;
+
+ if (size < 64) {
+ size = 64;
+ } else if (size <= 0x80000000) {
+ /* Round up to next highest power of two. */
+ size--;
+ size |= size >> 1;
+ size |= size >> 2;
+ size |= size >> 4;
+ size |= size >> 8;
+ size |= size >> 16;
+ size++;
+ }
+ if ((tmp = realloc(*bufp, size)) == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to allocate memory");
+ len = -1;
+ total = 0;
+ break;
+ }
+ *bufp = tmp;
+ *bufsizep = size;
+ }
+ memcpy(*bufp + total, cp, len + 1);
+ total += len;
+ } while (continued);
+ free(line);
+ if (len == -1 && total == 0)
+ debug_return_ssize_t(-1);
+ debug_return_ssize_t(total);
+}
+
+ssize_t
+sudo_parseln_v1(char **bufp, size_t *bufsizep, unsigned int *lineno, FILE *fp)
+{
+ return sudo_parseln_v2(bufp, bufsizep, lineno, fp, 0);
+}
diff --git a/lib/util/pipe2.c b/lib/util/pipe2.c
new file mode 100644
index 0000000..43527a0
--- /dev/null
+++ b/lib/util/pipe2.c
@@ -0,0 +1,64 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2017 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_PIPE2
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+int
+sudo_pipe2(int fildes[2], int flags)
+{
+ if (pipe(fildes) != 0)
+ return -1;
+
+ if (ISSET(flags, O_CLOEXEC)) {
+ if (fcntl(fildes[0], F_SETFD, FD_CLOEXEC) == -1)
+ goto bad;
+ if (fcntl(fildes[1], F_SETFD, FD_CLOEXEC) == -1)
+ goto bad;
+ }
+ if (ISSET(flags, O_NONBLOCK)) {
+ int oflags = fcntl(fildes[0], F_GETFL, 0);
+ if (oflags == -1)
+ goto bad;
+ if (fcntl(fildes[0], F_SETFL, oflags | O_NONBLOCK) == -1)
+ goto bad;
+ oflags = fcntl(fildes[1], F_GETFL, 0);
+ if (oflags == -1)
+ goto bad;
+ if (fcntl(fildes[1], F_SETFL, oflags | O_NONBLOCK) == -1)
+ goto bad;
+ }
+ return 0;
+bad:
+ close(fildes[0]);
+ close(fildes[1]);
+ return -1;
+}
+
+#endif /* HAVE_PIPE2 */
diff --git a/lib/util/pread.c b/lib/util/pread.c
new file mode 100644
index 0000000..9328c54
--- /dev/null
+++ b/lib/util/pread.c
@@ -0,0 +1,48 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#if !defined(HAVE_PREAD) && !defined(HAVE_PREAD64)
+ssize_t
+sudo_pread(int fd, void *buf, size_t nbytes, off_t offset)
+{
+ ssize_t nread;
+ off_t old_offset;
+
+ old_offset = lseek(fd, (off_t)0, SEEK_CUR);
+ if (old_offset == -1 || lseek(fd, offset, SEEK_SET) == -1)
+ return -1;
+
+ nread = read(fd, buf, nbytes);
+ if (lseek(fd, old_offset, SEEK_SET) == -1)
+ return -1;
+
+ return nread;
+}
+#endif /* !HAVE_PREAD && !HAVE_PREAD64 */
diff --git a/lib/util/progname.c b/lib/util/progname.c
new file mode 100644
index 0000000..a5f93b2
--- /dev/null
+++ b/lib/util/progname.c
@@ -0,0 +1,100 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015, 2020-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+/*
+ * Declare/define __progname[] if necessary.
+ * Assumes __progname[] is present if we have getprogname(3).
+ */
+#ifndef HAVE_SETPROGNAME
+# if defined(HAVE_GETPROGNAME) || defined(HAVE___PROGNAME)
+extern const char *__progname;
+# else
+static const char *__progname = "";
+# endif /* HAVE_GETPROGNAME || HAVE___PROGNAME */
+#endif /* HAVE_SETPROGNAME */
+
+#ifndef HAVE_GETPROGNAME
+const char *
+sudo_getprogname(void)
+{
+ return __progname;
+}
+#endif
+
+#ifndef HAVE_SETPROGNAME
+void
+sudo_setprogname(const char *name)
+{
+ __progname = sudo_basename(name);
+}
+#endif
+
+void
+initprogname2(const char *name, const char * const * allowed)
+{
+ const char *progname;
+ int i;
+
+ /* Fall back on "name" if getprogname() returns an empty string. */
+ if ((progname = getprogname()) != NULL && *progname != '\0') {
+ name = progname;
+ } else {
+ /* Make sure user-specified name is relative. */
+ name = sudo_basename(name);
+ }
+
+ /* Check for libtool prefix and strip it if present. */
+ if (name[0] == 'l' && name[1] == 't' && name[2] == '-' && name[3] != '\0')
+ name += 3;
+
+ /* Check allow list if present (first element is the default). */
+ if (allowed != NULL) {
+ for (i = 0; ; i++) {
+ if (allowed[i] == NULL) {
+ name = allowed[0];
+ break;
+ }
+ if (strcmp(allowed[i], name) == 0)
+ break;
+ }
+ }
+
+ /* Update internal progname if needed. */
+ if (name != progname)
+ setprogname(name);
+ return;
+}
+
+void
+initprogname(const char *name)
+{
+ initprogname2(name, NULL);
+}
diff --git a/lib/util/pw_dup.c b/lib/util/pw_dup.c
new file mode 100644
index 0000000..ccd2fbb
--- /dev/null
+++ b/lib/util/pw_dup.c
@@ -0,0 +1,99 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2000, 2002, 2012-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.
+ *
+ * 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>
+
+#ifndef HAVE_PW_DUP
+
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+
+#include "sudo_compat.h"
+
+#define PW_SIZE(name, size) \
+do { \
+ if (pw->name) { \
+ size = strlen(pw->name) + 1; \
+ total += size; \
+ } \
+} while (0)
+
+#define PW_COPY(name, size) \
+do { \
+ if (pw->name) { \
+ (void)memcpy(cp, pw->name, size); \
+ newpw->name = cp; \
+ cp += size; \
+ } \
+} while (0)
+
+struct passwd *
+sudo_pw_dup(const struct passwd *pw)
+{
+ size_t nsize = 0, psize = 0, gsize = 0, dsize = 0, ssize = 0, total;
+#ifdef HAVE_LOGIN_CAP_H
+ size_t csize = 0;
+#endif
+ struct passwd *newpw;
+ char *cp;
+
+ /* Allocate in one big chunk for easy freeing */
+ total = sizeof(struct passwd);
+ PW_SIZE(pw_name, nsize);
+ PW_SIZE(pw_passwd, psize);
+#ifdef HAVE_LOGIN_CAP_H
+ PW_SIZE(pw_class, csize);
+#endif
+ PW_SIZE(pw_gecos, gsize);
+ PW_SIZE(pw_dir, dsize);
+ PW_SIZE(pw_shell, ssize);
+
+ if ((cp = malloc(total)) == NULL)
+ return NULL;
+ newpw = (struct passwd *)cp;
+
+ /*
+ * Copy in passwd contents and make strings relative to space
+ * at the end of the buffer.
+ */
+ (void)memcpy(newpw, pw, sizeof(struct passwd));
+ cp += sizeof(struct passwd);
+
+ PW_COPY(pw_name, nsize);
+ PW_COPY(pw_passwd, psize);
+#ifdef HAVE_LOGIN_CAP_H
+ PW_COPY(pw_class, csize);
+#endif
+ PW_COPY(pw_gecos, gsize);
+ PW_COPY(pw_dir, dsize);
+ PW_COPY(pw_shell, ssize);
+
+ return newpw;
+}
+#endif /* HAVE_PW_DUP */
diff --git a/lib/util/pwrite.c b/lib/util/pwrite.c
new file mode 100644
index 0000000..48f8cbf
--- /dev/null
+++ b/lib/util/pwrite.c
@@ -0,0 +1,48 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#if !defined(HAVE_PWRITE) && !defined(HAVE_PWRITE64)
+ssize_t
+sudo_pwrite(int fd, const void *buf, size_t nbytes, off_t offset)
+{
+ ssize_t nwritten;
+ off_t old_offset;
+
+ old_offset = lseek(fd, (off_t)0, SEEK_CUR);
+ if (old_offset == -1 || lseek(fd, offset, SEEK_SET) == -1)
+ return -1;
+
+ nwritten = write(fd, buf, nbytes);
+ if (lseek(fd, old_offset, SEEK_SET) == -1)
+ return -1;
+
+ return nwritten;
+}
+#endif /* HAVE_PWRITE && !HAVE_PWRITE64 */
diff --git a/lib/util/rcstr.c b/lib/util/rcstr.c
new file mode 100644
index 0000000..08b00bc
--- /dev/null
+++ b/lib/util/rcstr.c
@@ -0,0 +1,104 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2016-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 <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/* Trivial reference-counted strings. */
+struct rcstr {
+ int refcnt;
+ char str[1]; /* actually bigger */
+};
+
+/*
+ * Allocate a reference-counted string and copy src to it.
+ * Returns the newly-created string with a refcnt of 1.
+ */
+char *
+sudo_rcstr_dup(const char *src)
+{
+ size_t len = strlen(src);
+ char *dst;
+ debug_decl(sudo_rcstr_dup, SUDO_DEBUG_UTIL);
+
+ dst = sudo_rcstr_alloc(len);
+ if (dst != NULL) {
+ memcpy(dst, src, len);
+ dst[len] = '\0';
+ }
+ debug_return_ptr(dst);
+}
+
+char *
+sudo_rcstr_alloc(size_t len)
+{
+ struct rcstr *rcs;
+ debug_decl(sudo_rcstr_dup, SUDO_DEBUG_UTIL);
+
+ /* Note: sizeof(struct rcstr) includes space for the NUL */
+ rcs = malloc(sizeof(struct rcstr) + len);
+ if (rcs == NULL)
+ return NULL;
+
+ rcs->refcnt = 1;
+ rcs->str[0] = '\0';
+ /* cppcheck-suppress memleak */
+ debug_return_ptr(rcs->str); // -V773
+}
+
+char *
+sudo_rcstr_addref(const char *s)
+{
+ struct rcstr *rcs;
+ debug_decl(sudo_rcstr_dup, SUDO_DEBUG_UTIL);
+
+ if (s == NULL)
+ debug_return_ptr(NULL);
+
+ rcs = __containerof((const void *)s, struct rcstr, str);
+ rcs->refcnt++;
+ debug_return_ptr(rcs->str);
+}
+
+void
+sudo_rcstr_delref(const char *s)
+{
+ struct rcstr *rcs;
+ debug_decl(sudo_rcstr_dup, SUDO_DEBUG_UTIL);
+
+ if (s != NULL) {
+ rcs = __containerof((const void *)s, struct rcstr, str);
+ if (--rcs->refcnt == 0) {
+ rcs->str[0] = '\0';
+ free(rcs);
+ }
+ }
+ debug_return;
+}
diff --git a/lib/util/reallocarray.c b/lib/util/reallocarray.c
new file mode 100644
index 0000000..56283e7
--- /dev/null
+++ b/lib/util/reallocarray.c
@@ -0,0 +1,57 @@
+/* $OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $ */
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * 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>
+
+#ifndef HAVE_REALLOCARRAY
+
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <errno.h>
+#include <limits.h>
+
+#include "sudo_compat.h"
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+
+void *
+sudo_reallocarray(void *optr, size_t nmemb, size_t size)
+{
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return realloc(optr, size * nmemb);
+}
+
+#endif /* HAVE_REALLOCARRAY */
diff --git a/lib/util/regex.c b/lib/util/regex.c
new file mode 100644
index 0000000..602f43b
--- /dev/null
+++ b/lib/util/regex.c
@@ -0,0 +1,194 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2023 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 <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <regex.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+#include "sudo_gettext.h"
+
+static char errbuf[1024];
+
+/*
+ * Parse a number between 0 and INT_MAX, handling escaped digits.
+ * Returns the number on success or -1 on error.
+ * Sets endp to the first non-matching character.
+ */
+static int
+parse_num(const char *str, char **endp)
+{
+ debug_decl(check_pattern, SUDO_DEBUG_UTIL);
+ const unsigned int lastval = INT_MAX / 10;
+ const unsigned int remainder = INT_MAX % 10;
+ unsigned int result = 0;
+ unsigned char ch;
+
+ while ((ch = *str++) != '\0') {
+ if (ch == '\\' && isdigit((unsigned int)str[0]))
+ ch = *str++;
+ else if (!isdigit(ch))
+ break;
+ ch -= '0';
+ if (result > lastval || (result == lastval && ch > remainder)) {
+ result = -1;
+ break;
+ }
+ result *= 10;
+ result += ch;
+ }
+ *endp = (char *)(str - 1);
+
+ debug_return_int(result);
+}
+
+/*
+ * Check pattern for invalid repetition sequences.
+ * This is implementation-specific behavior, not all regcomp(3) forbid them.
+ * Glibc allows it but uses excessive memory for repeated '+' ops.
+ */
+static int
+check_pattern(const char *pattern)
+{
+ debug_decl(check_pattern, SUDO_DEBUG_UTIL);
+ const char *cp = pattern;
+ int b1, b2 = 0;
+ char ch, *ep, prev = '\0';
+
+ while ((ch = *cp++) != '\0') {
+ switch (ch) {
+ case '\\':
+ if (*cp != '\0') {
+ /* Skip escaped character. */
+ cp++;
+ prev = '\0';
+ continue;
+ }
+ break;
+ case '?':
+ case '*':
+ case '+':
+ if (prev == '?' || prev == '*' || prev == '+' || prev == '{' ) {
+ /* Invalid repetition operator. */
+ debug_return_int(REG_BADRPT);
+ }
+ break;
+ case '{':
+ /*
+ * Try to match bound: {[0-9\\]*\?,[0-9\\]*}
+ * GNU libc supports escaped digits and commas.
+ */
+ b1 = parse_num(cp, &ep);
+ switch (ep[0]) {
+ case '\\':
+ if (ep[1] != ',')
+ break;
+ ep++;
+ FALLTHROUGH;
+ case ',':
+ cp = ep + 1;
+ b2 = parse_num(cp, &ep);
+ break;
+ }
+ cp = ep;
+ if (*cp == '}') {
+ if (b1 < 0 || b1 > 255 || b2 < 0 || b2 > 255) {
+ /* Invalid bound value. */
+ debug_return_int(REG_BADBR);
+ }
+ if (prev == '?' || prev == '*' || prev == '+' || prev == '{' ) {
+ /* Invalid repetition operator. */
+ debug_return_int(REG_BADRPT);
+ }
+ /* Skip past '}', prev will be set to '{' below */
+ cp++;
+ break;
+ }
+ prev = '\0';
+ continue;
+ }
+ prev = ch;
+ }
+
+ debug_return_int(0);
+}
+
+/*
+ * Wrapper around regcomp() that handles a regex starting with (?i).
+ * Avoid using regex_t in the function args so we don't need to
+ * include regex.h everywhere.
+ */
+bool
+sudo_regex_compile_v1(void *v, const char *pattern, const char **errstr)
+{
+ int errcode, cflags = REG_EXTENDED|REG_NOSUB;
+ regex_t *preg;
+ char *copy = NULL;
+ const char *cp;
+ regex_t rebuf;
+ debug_decl(regex_compile, SUDO_DEBUG_UTIL);
+
+ /* Some callers just want to check the validity of the pattern. */
+ preg = v ? v : &rebuf;
+
+ /* Limit the length of regular expressions to avoid fuzzer issues. */
+ if (strlen(pattern) > 1024) {
+ *errstr = N_("regular expression too large");
+ debug_return_bool(false);
+ }
+
+ /* Check for (?i) to enable case-insensitive matching. */
+ cp = pattern[0] == '^' ? pattern + 1 : pattern;
+ if (strncmp(cp, "(?i)", 4) == 0) {
+ cflags |= REG_ICASE;
+ copy = strdup(pattern + 4);
+ if (copy == NULL) {
+ *errstr = N_("unable to allocate memory");
+ debug_return_bool(false);
+ }
+ if (pattern[0] == '^')
+ copy[0] = '^';
+ pattern = copy;
+ }
+
+ errcode = check_pattern(pattern);
+ if (errcode == 0)
+ errcode = regcomp(preg, pattern, cflags);
+ if (errcode == 0) {
+ if (preg == &rebuf)
+ regfree(&rebuf);
+ } else {
+ regerror(errcode, preg, errbuf, sizeof(errbuf));
+ *errstr = errbuf;
+ }
+ free(copy);
+
+ debug_return_bool(errcode == 0);
+}
diff --git a/lib/util/regress/closefrom/closefrom_test.c b/lib/util/regress/closefrom/closefrom_test.c
new file mode 100644
index 0000000..375bb51
--- /dev/null
+++ b/lib/util/regress/closefrom/closefrom_test.c
@@ -0,0 +1,121 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_closefrom() works as expected.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ int ch, fds[2], flag, maxfd, minfd, errors = 0, ntests = 0;
+ initprogname(argc > 0 ? argv[0] : "closefrom_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* We use pipe() because it doesn't rely on the filesystem. */
+ ntests++;
+ if (pipe(fds) == -1) {
+ sudo_warn("%s", "pipe");
+ errors++;
+ goto done;
+ }
+ maxfd = MAX(fds[0], fds[1]);
+ minfd = MIN(fds[0], fds[1]);
+
+ /* Close any fds greater than fds[0] and fds[1]. */
+ sudo_closefrom(maxfd + 1);
+
+ /* Verify that sudo_closefrom() didn't close fds[0] or fds[1]. */
+ ntests++;
+ if (fcntl(fds[0], F_GETFL, 0) == -1) {
+ sudo_warnx("fd %d closed prematurely", fds[0]);
+ errors++;
+ goto done;
+ }
+ ntests++;
+ if (fcntl(fds[1], F_GETFL, 0) == -1) {
+ sudo_warnx("fd %d closed prematurely", fds[1]);
+ errors++;
+ goto done;
+ }
+
+ /* Close fds[0], fds[1] and above. */
+ sudo_closefrom(minfd);
+
+ /* Verify that sudo_closefrom() closed both fds. */
+ ntests++;
+ flag = fcntl(fds[0], F_GETFD, 0);
+#ifdef __APPLE__
+ /* We only set the close-on-exec flag on macOS. */
+ if (flag == 1)
+ flag = -1;
+#endif
+ if (flag != -1) {
+ sudo_warnx("fd %d still open", fds[0]);
+ errors++;
+ goto done;
+ }
+ ntests++;
+ flag = fcntl(fds[1], F_GETFD, 0);
+#ifdef __APPLE__
+ /* We only set the close-on-exec flag on macOS. */
+ if (flag == 1)
+ flag = -1;
+#endif
+ if (flag != -1) {
+ sudo_warnx("fd %d still open", fds[1]);
+ errors++;
+ goto done;
+ }
+
+done:
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ return errors;
+}
diff --git a/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.1 b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.1
new file mode 100644
index 0000000..1a58c87
--- /dev/null
+++ b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.1
@@ -0,0 +1,116 @@
+#
+# Default /etc/sudo.conf file
+#
+# Sudo plugins:
+# Plugin plugin_name plugin_path plugin_options ...
+#
+# The plugin_path is relative to /usr/local/libexec/sudo unless
+# fully qualified.
+# The plugin_name corresponds to a global symbol in the plugin
+# that contains the plugin interface structure.
+# The plugin_options are optional.
+#
+# The sudoers plugin is used by default if no Plugin lines are present.
+#Plugin sudoers_policy sudoers.so
+#Plugin sudoers_io sudoers.so
+#Plugin sudoers_audit sudoers.so
+
+#
+# Sudo askpass:
+# Path askpass /path/to/askpass
+#
+# An askpass helper program may be specified to provide a graphical
+# password prompt for "sudo -A" support. Sudo does not ship with its
+# own askpass program but can use the OpenSSH askpass.
+#
+# Use the OpenSSH askpass
+#Path askpass /usr/X11R6/bin/ssh-askpass
+#
+# Use the Gnome OpenSSH askpass
+#Path askpass /usr/libexec/openssh/gnome-ssh-askpass
+
+#
+# Sudo device search path:
+# Path devsearch /dev/path1:/dev/path2:/dev
+#
+# A colon-separated list of paths to check when searching for a user's
+# terminal device.
+#
+#Path devsearch /dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev
+
+#
+# Sudo noexec:
+# Path noexec /path/to/sudo_noexec.so
+#
+# Path to a shared library containing replacements for the execv(),
+# execve() and fexecve() library functions that just return an error.
+# This is used to implement the "noexec" functionality on systems that
+# support LD_PRELOAD or its equivalent.
+#
+# The compiled-in value is usually sufficient and should only be changed
+# if you rename or move the sudo_noexec.so file.
+#
+#Path noexec /usr/local/libexec/sudo/sudo_noexec.so
+
+#
+# Sudo plugin directory:
+# Path plugin_dir /path/to/plugins
+#
+# The default directory to use when searching for plugins that are
+# specified without a fully qualified path name.
+#
+#Path plugin_dir /usr/local/libexec/sudo
+
+#
+# Core dumps:
+# Set disable_coredump true|false
+#
+# By default, sudo disables core dumps while it is executing (they
+# are re-enabled for the command that is run).
+# To aid in debugging sudo problems, you may wish to enable core
+# dumps by setting "disable_coredump" to false.
+#
+#Set disable_coredump false
+
+#
+# User groups:
+# Set group_source static|dynamic|adaptive
+#
+# Sudo passes the user's group list to the policy plugin.
+# If the user is a member of the maximum number of groups (usually 16),
+# sudo will query the group database directly to be sure to include
+# the full list of groups.
+#
+# On some systems, this can be expensive so the behavior is configurable.
+# The "group_source" setting has three possible values:
+# static - use the user's list of groups returned by the kernel.
+# dynamic - query the group database to find the list of groups.
+# adaptive - if user is in less than the maximum number of groups.
+# use the kernel list, else query the group database.
+#
+#Set group_source static
+
+#
+# Sudo interface probing:
+# Set probe_interfaces true|false
+#
+# By default, sudo will probe the system's network interfaces and
+# pass the IP address of each enabled interface to the policy plugin.
+# On systems with a large number of virtual interfaces this may take
+# a noticeable amount of time.
+#
+#Set probe_interfaces false
+
+#
+# Sudo debug files:
+# Debug program /path/to/debug_log subsystem@priority[,subsyste@priority]
+#
+# Sudo and related programs support logging debug information to a file.
+# The program is typically sudo, sudoers.so, sudoreplay or visudo.
+#
+# Subsystems vary based on the program; "all" matches all subsystems.
+# Priority may be crit, err, warn, notice, diag, info, trace or debug.
+# Multiple subsystem@priority may be specified, separated by a comma.
+#
+#Debug sudo /var/log/sudo_debug all@debug
+#Debug sudoers.so /var/log/sudoers_debug all@debug
diff --git a/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.2 b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.2
new file mode 100644
index 0000000..05039a5
--- /dev/null
+++ b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.2
@@ -0,0 +1,116 @@
+#
+# Default /etc/sudo.conf file
+#
+# Sudo plugins:
+# Plugin plugin_name plugin_path plugin_options ...
+#
+# The plugin_path is relative to /usr/local/libexec/sudo unless
+# fully qualified.
+# The plugin_name corresponds to a global symbol in the plugin
+# that contains the plugin interface structure.
+# The plugin_options are optional.
+#
+# The sudoers plugin is used by default if no Plugin lines are present.
+Plugin sudoers_policy sudoers.so
+Plugin sudoers_io sudoers.so
+Plugin sudoers_audit sudoers.so
+
+#
+# Sudo askpass:
+# Path askpass /path/to/askpass
+#
+# An askpass helper program may be specified to provide a graphical
+# password prompt for "sudo -A" support. Sudo does not ship with its
+# own askpass program but can use the OpenSSH askpass.
+#
+# Use the OpenSSH askpass
+Path askpass /usr/X11R6/bin/ssh-askpass
+#
+# Use the Gnome OpenSSH askpass
+Path askpass /usr/libexec/openssh/gnome-ssh-askpass
+
+#
+# Sudo device search path:
+# Path devsearch /dev/path1:/dev/path2:/dev
+#
+# A colon-separated list of paths to check when searching for a user's
+# terminal device.
+#
+Path devsearch /dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev
+
+#
+# Sudo noexec:
+# Path noexec /path/to/sudo_noexec.so
+#
+# Path to a shared library containing replacements for the execv(),
+# execve() and fexecve() library functions that just return an error.
+# This is used to implement the "noexec" functionality on systems that
+# support LD_PRELOAD or its equivalent.
+#
+# The compiled-in value is usually sufficient and should only be changed
+# if you rename or move the sudo_noexec.so file.
+#
+Path noexec /usr/local/libexec/sudo/sudo_noexec.so
+
+#
+# Sudo plugin directory:
+# Path plugin_dir /path/to/plugins
+#
+# The default directory to use when searching for plugins that are
+# specified without a fully qualified path name.
+#
+Path plugin_dir /usr/local/libexec/sudo
+
+#
+# Core dumps:
+# Set disable_coredump true|false
+#
+# By default, sudo disables core dumps while it is executing (they
+# are re-enabled for the command that is run).
+# To aid in debugging sudo problems, you may wish to enable core
+# dumps by setting "disable_coredump" to false.
+#
+Set disable_coredump false
+
+#
+# User groups:
+# Set group_source static|dynamic|adaptive
+#
+# Sudo passes the user's group list to the policy plugin.
+# If the user is a member of the maximum number of groups (usually 16),
+# sudo will query the group database directly to be sure to include
+# the full list of groups.
+#
+# On some systems, this can be expensive so the behavior is configurable.
+# The "group_source" setting has three possible values:
+# static - use the user's list of groups returned by the kernel.
+# dynamic - query the group database to find the list of groups.
+# adaptive - if user is in less than the maximum number of groups.
+# use the kernel list, else query the group database.
+#
+Set group_source static
+
+#
+# Sudo interface probing:
+# Set probe_interfaces true|false
+#
+# By default, sudo will probe the system's network interfaces and
+# pass the IP address of each enabled interface to the policy plugin.
+# On systems with a large number of virtual interfaces this may take
+# a noticeable amount of time.
+#
+Set probe_interfaces false
+
+#
+# Sudo debug files:
+# Debug program /path/to/debug_log subsystem@priority[,subsyste@priority]
+#
+# Sudo and related programs support logging debug information to a file.
+# The program is typically sudo, sudoers.so, sudoreplay or visudo.
+#
+# Subsystems vary based on the program; "all" matches all subsystems.
+# Priority may be crit, err, warn, notice, diag, info, trace or debug.
+# Multiple subsystem@priority may be specified, separated by a comma.
+#
+Debug sudo /var/log/sudo_debug all@debug
+Debug sudoers.so /var/log/sudoers_debug all@debug
diff --git a/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.3 b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.3
new file mode 100644
index 0000000..bcfafb2
--- /dev/null
+++ b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.3
@@ -0,0 +1,126 @@
+#
+# Default /etc/sudo.conf file
+#
+# Sudo plugins:
+# Plugin plugin_name plugin_path plugin_options ...
+#
+# The plugin_path is relative to /usr/local/libexec/sudo unless
+# fully qualified.
+# The plugin_name corresponds to a global symbol in the plugin
+# that contains the plugin interface structure.
+# The plugin_options are optional.
+#
+# The sudoers plugin is used by default if no Plugin lines are present.
+Plugin sudoers_policy sudoers.so
+Plugin sudoers_io sudoers.so
+Plugin sudoers_audit sudoers.so
+
+#
+# Sudo askpass:
+# Path askpass /path/to/askpass
+#
+# An askpass helper program may be specified to provide a graphical
+# password prompt for "sudo -A" support. Sudo does not ship with its
+# own askpass program but can use the OpenSSH askpass.
+#
+# Use the OpenSSH askpass
+Path askpass /usr/X11R6/bin/ssh-askpass
+#
+# Use the Gnome OpenSSH askpass
+Path askpass /usr/libexec/openssh/gnome-ssh-askpass
+
+#
+# Sudo device search path:
+# Path devsearch /dev/path1:/dev/path2:/dev
+#
+# A colon-separated list of paths to check when searching for a user's
+# terminal device.
+#
+Path devsearch /dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev
+
+#
+# Sudo noexec:
+# Path noexec /path/to/sudo_noexec.so
+#
+# Path to a shared library containing replacements for the execv(),
+# execve() and fexecve() library functions that just return an error.
+# This is used to implement the "noexec" functionality on systems that
+# support LD_PRELOAD or its equivalent.
+#
+# The compiled-in value is usually sufficient and should only be changed
+# if you rename or move the sudo_noexec.so file.
+#
+Path noexec /usr/local/libexec/sudo/sudo_noexec.so
+
+#
+# Sudo plugin directory:
+# Path plugin_dir /path/to/plugins
+#
+# The default directory to use when searching for plugins that are
+# specified without a fully qualified path name.
+#
+Path plugin_dir /usr/local/libexec/sudo
+
+#
+# Path to the sesh binary for SELinux support
+#
+Path sesh /usr/local/libexec/sudo/sesh
+
+#
+# Core dumps:
+# Set disable_coredump true|false
+#
+# By default, sudo disables core dumps while it is executing (they
+# are re-enabled for the command that is run).
+# To aid in debugging sudo problems, you may wish to enable core
+# dumps by setting "disable_coredump" to false.
+#
+Set disable_coredump true
+
+#
+# User groups:
+# Set group_source static|dynamic|adaptive
+#
+# Sudo passes the user's group list to the policy plugin.
+# If the user is a member of the maximum number of groups (usually 16),
+# sudo will query the group database directly to be sure to include
+# the full list of groups.
+#
+# On some systems, this can be expensive so the behavior is configurable.
+# The "group_source" setting has three possible values:
+# static - use the user's list of groups returned by the kernel.
+# dynamic - query the group database to find the list of groups.
+# adaptive - if user is in less than the maximum number of groups.
+# use the kernel list, else query the group database.
+#
+Set group_source dynamic
+
+#
+# Maximum number of groups to use
+#
+Set max_groups 8
+
+#
+# Sudo interface probing:
+# Set probe_interfaces true|false
+#
+# By default, sudo will probe the system's network interfaces and
+# pass the IP address of each enabled interface to the policy plugin.
+# On systems with a large number of virtual interfaces this may take
+# a noticeable amount of time.
+#
+Set probe_interfaces true
+
+#
+# Sudo debug files:
+# Debug program /path/to/debug_log subsystem@priority[,subsyste@priority]
+#
+# Sudo and related programs support logging debug information to a file.
+# The program is typically sudo, sudoers.so, sudoreplay or visudo.
+#
+# Subsystems vary based on the program; "all" matches all subsystems.
+# Priority may be crit, err, warn, notice, diag, info, trace or debug.
+# Multiple subsystem@priority may be specified, separated by a comma.
+#
+Debug sudo /var/log/sudo_debug all@debug
+Debug sudoers.so /var/log/sudoers_debug all@debug
diff --git a/lib/util/regress/fnmatch/fnm_test.c b/lib/util/regress/fnmatch/fnm_test.c
new file mode 100644
index 0000000..126683b
--- /dev/null
+++ b/lib/util/regress/fnmatch/fnm_test.c
@@ -0,0 +1,92 @@
+/* $OpenBSD: fnm_test.c,v 1.1 2008/10/01 23:04:58 millert Exp $ */
+
+/*
+ * Public domain, 2008, Todd C. Miller <Todd.Miller@sudo.ws>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef HAVE_FNMATCH
+# include <fnmatch.h>
+#else
+# include "compat/fnmatch.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp = stdin;
+ char pattern[1024], string[1024], flagstr[1024];
+ int ch, errors = 0, ntests = 0, flags, got, want;
+
+ initprogname(argc > 0 ? argv[0] : "fnm_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0) {
+ if ((fp = fopen(argv[0], "r")) == NULL) {
+ perror(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /*
+ * Read in test file, which is formatted thusly:
+ *
+ * pattern string flags expected_result
+ *
+ */
+ for (;;) {
+ got = fscanf(fp, "%s %s %s %d\n", pattern, string, flagstr,
+ &want);
+ if (got == EOF)
+ break;
+ if (got == 4) {
+ flags = 0;
+ if (strcmp(flagstr, "FNM_NOESCAPE") == 0)
+ flags |= FNM_NOESCAPE;
+ else if (strcmp(flagstr, "FNM_PATHNAME") == 0)
+ flags |= FNM_PATHNAME;
+ else if (strcmp(flagstr, "FNM_PERIOD") == 0)
+ flags |= FNM_PERIOD;
+ else if (strcmp(flagstr, "FNM_LEADING_DIR") == 0)
+ flags |= FNM_LEADING_DIR;
+ else if (strcmp(flagstr, "FNM_CASEFOLD") == 0)
+ flags |= FNM_CASEFOLD;
+ got = fnmatch(pattern, string, flags);
+ if (got != want) {
+ fprintf(stderr,
+ "fnmatch: %s %s %d: want %d, got %d\n",
+ pattern, string, flags, want, got);
+ errors++;
+ }
+ ntests++;
+ }
+ }
+ if (ntests != 0) {
+ printf("fnmatch: %d test%s run, %d errors, %d%% success rate\n",
+ ntests, ntests == 1 ? "" : "s", errors,
+ (ntests - errors) * 100 / ntests);
+ }
+ return errors;
+}
diff --git a/lib/util/regress/fnmatch/fnm_test.in b/lib/util/regress/fnmatch/fnm_test.in
new file mode 100644
index 0000000..3f53f93
--- /dev/null
+++ b/lib/util/regress/fnmatch/fnm_test.in
@@ -0,0 +1,6 @@
+/bin/[[:alpha:][:alnum:]]* /bin/ls FNM_PATHNAME 0
+/bin/[[:alpha:][:alnum:]]* /bin/LS FNM_CASEFOLD 0
+/bin/[[:opper:][:alnum:]]* /bin/ls NONE 1
+[[:alpha:][:alnum:]]*.c foo1.c FNM_PERIOD 0
+[[:upper:]]* FOO NONE 0
+[![:space:]]* bar NONE 0
diff --git a/lib/util/regress/fuzz/fuzz_sudo_conf.c b/lib/util/regress/fuzz/fuzz_sudo_conf.c
new file mode 100644
index 0000000..4e70086
--- /dev/null
+++ b/lib/util/regress/fuzz/fuzz_sudo_conf.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_plugin.h"
+#include "sudo_util.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+static int
+fuzz_conversation(int num_msgs, const struct sudo_conv_message msgs[],
+ struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
+{
+ int n;
+
+ for (n = 0; n < num_msgs; n++) {
+ const struct sudo_conv_message *msg = &msgs[n];
+
+ switch (msg->msg_type & 0xff) {
+ case SUDO_CONV_PROMPT_ECHO_ON:
+ case SUDO_CONV_PROMPT_MASK:
+ case SUDO_CONV_PROMPT_ECHO_OFF:
+ /* input not supported */
+ return -1;
+ case SUDO_CONV_ERROR_MSG:
+ case SUDO_CONV_INFO_MSG:
+ /* no output for fuzzers */
+ break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ struct plugin_info_list *plugins = sudo_conf_plugins();
+ struct sudo_conf_debug_list *debug_list = sudo_conf_debugging();
+ struct sudo_conf_debug_file_list *debug_files;
+ char tempfile[] = "/tmp/sudo_conf.XXXXXX";
+ struct sudo_conf_debug *debug_spec;
+ struct sudo_debug_file *debug_file;
+ struct plugin_info *info;
+ size_t nwritten;
+ int fd;
+
+ initprogname("fuzz_sudo_conf");
+ if (getenv("SUDO_FUZZ_VERBOSE") == NULL)
+ sudo_warn_set_conversation(fuzz_conversation);
+
+ /* sudo_conf_read() uses a conf file path, not an open file. */
+ fd = mkstemp(tempfile);
+ if (fd == -1)
+ return 0;
+ nwritten = write(fd, data, size);
+ if (nwritten != size) {
+ close(fd);
+ return 0;
+ }
+ close(fd);
+
+ /* sudo_conf_read() will re-init and free old data each time it runs. */
+ sudo_conf_clear_paths();
+ sudo_conf_read(tempfile, SUDO_CONF_ALL);
+
+ /* Path settings. */
+ if (sudo_conf_askpass_path() != NULL)
+ sudo_warnx("Path askpass %s", sudo_conf_askpass_path());
+ if (sudo_conf_sesh_path() != NULL)
+ sudo_warnx("Path sesh %s", sudo_conf_sesh_path());
+ if (sudo_conf_intercept_path() != NULL)
+ sudo_warnx("Path intercept %s", sudo_conf_intercept_path());
+ if (sudo_conf_noexec_path() != NULL)
+ sudo_warnx("Path noexec %s", sudo_conf_noexec_path());
+ if (sudo_conf_plugin_dir_path() != NULL)
+ sudo_warnx("Path plugin_dir %s", sudo_conf_plugin_dir_path());
+
+ /* Other settings. */
+ sudo_warnx("Set disable_coredump %s",
+ sudo_conf_disable_coredump() ? "true" : "false");
+ sudo_warnx("Set group_source %s",
+ sudo_conf_group_source() == GROUP_SOURCE_ADAPTIVE ? "adaptive" :
+ sudo_conf_group_source() == GROUP_SOURCE_STATIC ? "static" : "dynamic");
+ sudo_warnx("Set max_groups %d", sudo_conf_max_groups());
+ sudo_warnx("Set probe_interfaces %s",
+ sudo_conf_probe_interfaces() ? "true" : "false");
+
+ /* Plugins. */
+ plugins = sudo_conf_plugins();
+ TAILQ_FOREACH(info, plugins, entries) {
+ /* We don't bother with the plugin options. */
+ sudo_warnx("Plugin %s %s", info->symbol_name, info->path);
+ }
+
+ /* Debug settings. */
+ debug_list = sudo_conf_debugging();
+ TAILQ_FOREACH(debug_spec, debug_list, entries) {
+ TAILQ_FOREACH(debug_file, &debug_spec->debug_files, entries) {
+ sudo_warnx("Debug %s %s %s", debug_spec->progname,
+ debug_file->debug_file, debug_file->debug_flags);
+ }
+ }
+
+ debug_files = sudo_conf_debug_files(getprogname());
+ if (debug_files != NULL) {
+ TAILQ_FOREACH(debug_file, debug_files, entries) {
+ sudo_warnx("Debug %s %s %s", getprogname(),
+ debug_file->debug_file, debug_file->debug_flags);
+ }
+ }
+
+ unlink(tempfile);
+
+ fflush(stdout);
+
+ return 0;
+}
diff --git a/lib/util/regress/fuzz/fuzz_sudo_conf.dict b/lib/util/regress/fuzz/fuzz_sudo_conf.dict
new file mode 100644
index 0000000..4b26917
--- /dev/null
+++ b/lib/util/regress/fuzz/fuzz_sudo_conf.dict
@@ -0,0 +1,18 @@
+# sudo.conf keywords
+"Debug"
+"Path"
+"Plugin"
+"Set"
+
+# Paths
+"askpass"
+"sesh"
+"noexec"
+"plugin_dir"
+"devsearch"
+
+# Variables
+"disable_coredump"
+"group_source"
+"max_groups"
+"probe_interfaces"
diff --git a/lib/util/regress/getdelim/getdelim_test.c b/lib/util/regress/getdelim/getdelim_test.c
new file mode 100644
index 0000000..2e77fd2
--- /dev/null
+++ b/lib/util/regress/getdelim/getdelim_test.c
@@ -0,0 +1,185 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <limits.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+ssize_t sudo_getdelim(char **bufp, size_t *bufsizep, int delim, FILE *fp);
+
+/*
+ * Test that sudo_getdelim() works as expected.
+ */
+
+struct getdelim_test {
+ const char *input;
+ const char *output[4];
+ int delim;
+};
+
+static char longstr[LINE_MAX * 4];
+static struct getdelim_test test_data[] = {
+ { "a\nb\nc\n", { "a\n", "b\n", "c\n", NULL }, '\n' },
+ { "a\nb\nc", { "a\n", "b\n", "c", NULL }, '\n' },
+ { "a\tb\tc\t", { "a\t", "b\t", "c\t", NULL }, '\t' },
+ { "a\tb\tc", { "a\t", "b\t", "c", NULL }, '\t' },
+ { longstr, { longstr, NULL }, '\n' },
+ { NULL, { NULL }, '\0' }
+};
+
+static int errors = 0, ntests = 0;
+
+static void
+runtests(char **buf, size_t *buflen)
+{
+ int i, j, sv[2];
+ pid_t pid;
+ FILE *fp;
+
+ /* Exercise realloc case by injecting an entry > LINE_MAX. */
+ memset(longstr, 'A', sizeof(longstr) - 2);
+ longstr[sizeof(longstr) - 2] = '\n';
+ longstr[sizeof(longstr) - 1] = '\0';
+
+ for (i = 0; test_data[i].input != NULL; i++) {
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1)
+ sudo_fatal_nodebug("socketpair");
+
+ switch ((pid = fork())) {
+ case -1:
+ sudo_fatal_nodebug("fork");
+ case 0:
+ /* child */
+ close(sv[0]);
+ if (send(sv[1], test_data[i].input, strlen(test_data[i].input), 0) == -1) {
+ sudo_warn_nodebug("send");
+ _exit(127);
+ }
+ _exit(EXIT_SUCCESS);
+ break;
+ default:
+ /* parent */
+ break;
+ }
+
+ close(sv[1]);
+ if ((fp = fdopen(sv[0], "r")) == NULL)
+ sudo_fatal_nodebug("fdopen");
+
+ for (j = 0; test_data[i].output[j] != NULL; j++) {
+ ntests++;
+ alarm(10);
+ if (sudo_getdelim(buf, buflen, test_data[i].delim, fp) == -1)
+ sudo_fatal_nodebug("sudo_getdelim");
+ alarm(0);
+ if (strcmp(*buf, test_data[i].output[j]) != 0) {
+ sudo_warnx_nodebug("failed test #%d: expected %s, got %s",
+ ntests, test_data[i].output[j], *buf);
+ errors++;
+ }
+ }
+
+ /* test EOF */
+ ntests++;
+ alarm(30);
+ if (sudo_getdelim(buf, buflen, test_data[i].delim, fp) != -1) {
+ sudo_warnx_nodebug("failed test #%d: expected EOF, got %s",
+ ntests, *buf);
+ errors++;
+ } else {
+ if (!feof(fp)) {
+ sudo_warn_nodebug("failed test #%d: expected EOF, got error",
+ ntests);
+ errors++;
+ }
+ }
+
+ /* test error by closing the underlying fd. */
+ clearerr(fp);
+ close(fileno(fp));
+ ntests++;
+ alarm(30);
+ if (sudo_getdelim(buf, buflen, test_data[i].delim, fp) != -1) {
+ sudo_warnx_nodebug("failed test #%d: expected error, got %s",
+ ntests, *buf);
+ errors++;
+ } else {
+ /* Use feof(3), not ferror(3) so we can detect out of memory. */
+ if (feof(fp)) {
+ sudo_warn_nodebug("failed test #%d: expected error, got EOF",
+ ntests);
+ errors++;
+ }
+ }
+
+ fclose(fp);
+ waitpid(pid, NULL, 0);
+ alarm(0);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ size_t buflen = 0;
+ char *buf = NULL;
+ int ch;
+
+ initprogname(argc > 0 ? argv[0] : "getdelim_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ runtests(&buf, &buflen);
+ free(buf);
+
+ /* XXX - redo tests with preallocated buffer filled with junk */
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ return errors;
+}
diff --git a/lib/util/regress/getgrouplist/getgids.c b/lib/util/regress/getgrouplist/getgids.c
new file mode 100644
index 0000000..f366f83
--- /dev/null
+++ b/lib/util/regress/getgrouplist/getgids.c
@@ -0,0 +1,91 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Implement "id -G" using sudo_getgrouplist2().
+ */
+
+int
+main(int argc, char *argv[])
+{
+ char *username = NULL;
+ GETGROUPS_T *groups = NULL;
+ struct passwd *pw;
+ int ch, i, ngroups;
+ gid_t basegid;
+
+ initprogname(argc > 0 ? argv[0] : "getgids");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v] [user]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ username = argv[0];
+
+ if (username != NULL) {
+ if ((pw = getpwnam(username)) == NULL)
+ sudo_fatalx("unknown user name %s", username);
+ } else {
+ if ((pw = getpwuid(getuid())) == NULL)
+ sudo_fatalx("unknown user ID %u", (unsigned int)getuid());
+ }
+ basegid = pw->pw_gid;
+ if ((username = strdup(pw->pw_name)) == NULL)
+ sudo_fatal(NULL);
+
+ if (sudo_getgrouplist2(username, basegid, &groups, &ngroups) == -1)
+ sudo_fatal("sudo_getgroulist2");
+
+ for (i = 0; i < ngroups; i++) {
+ printf("%s%u", i ? " " : "", (unsigned int)groups[i]);
+ }
+ putchar('\n');
+ return EXIT_SUCCESS;
+}
diff --git a/lib/util/regress/getgrouplist/getgrouplist_test.c b/lib/util/regress/getgrouplist/getgrouplist_test.c
new file mode 100644
index 0000000..8429528
--- /dev/null
+++ b/lib/util/regress/getgrouplist/getgrouplist_test.c
@@ -0,0 +1,117 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <pwd.h>
+#include <grp.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_getgrouplist2() works as expected.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ int errors = 0;
+#ifndef HAVE_GETGROUPLIST_2
+ GETGROUPS_T *groups = NULL;
+ struct passwd *pw;
+ struct group *grp;
+ char *username;
+ int ch, i, j, ntests = 0;
+ int ngroups;
+ gid_t basegid;
+
+ initprogname(argc > 0 ? argv[0] : "getgrouplist_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if ((pw = getpwuid(0)) == NULL)
+ sudo_fatal_nodebug("getpwuid(0)");
+ basegid = pw->pw_gid;
+ if ((username = strdup(pw->pw_name)) == NULL)
+ sudo_fatal_nodebug(NULL);
+
+ if (sudo_getgrouplist2(username, basegid, &groups, &ngroups) == -1)
+ sudo_fatal_nodebug("sudo_getgroulist2");
+
+ for (i = 0; i < ngroups; i++) {
+ ntests++;
+
+ /* Verify group ID exists. */
+ if ((grp = getgrgid(groups[i])) == NULL) {
+ sudo_warnx_nodebug("unable to look up group ID %u",
+ (unsigned int)groups[i]);
+ errors++;
+ continue;
+ }
+
+ /* Check user's primary gid from the passwd file. */
+ if (grp->gr_gid == basegid)
+ continue;
+
+ /* Verify group membership. */
+ for (j = 0; grp->gr_mem[j] != NULL; j++) {
+ if (strcmp(username, grp->gr_mem[j]) == 0) {
+ /* match */
+ break;
+ }
+ }
+ if (grp->gr_mem[j] == NULL) {
+ sudo_warnx_nodebug("unable to find %s in group %s",
+ username, grp->gr_name);
+ errors++;
+ continue;
+ }
+ }
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ free(username);
+ free(groups);
+#endif /* HAVE_GETGROUPLIST_2 */
+ return errors;
+}
diff --git a/lib/util/regress/glob/files b/lib/util/regress/glob/files
new file mode 100644
index 0000000..c5e92aa
--- /dev/null
+++ b/lib/util/regress/glob/files
@@ -0,0 +1,47 @@
+fake/bin/[
+fake/bin/cat
+fake/bin/chgrp
+fake/bin/chio
+fake/bin/chmod
+fake/bin/cksum
+fake/bin/cp
+fake/bin/cpio
+fake/bin/csh
+fake/bin/date
+fake/bin/dd
+fake/bin/df
+fake/bin/domainname
+fake/bin/echo
+fake/bin/ed
+fake/bin/eject
+fake/bin/expr
+fake/bin/hostname
+fake/bin/kill
+fake/bin/ksh
+fake/bin/ln
+fake/bin/ls
+fake/bin/md5
+fake/bin/mkdir
+fake/bin/mt
+fake/bin/mv
+fake/bin/pax
+fake/bin/ps
+fake/bin/pwd
+fake/bin/rcp
+fake/bin/rksh
+fake/bin/rm
+fake/bin/rmail
+fake/bin/rmd160
+fake/bin/rmdir
+fake/bin/sh
+fake/bin/sha1
+fake/bin/sha256
+fake/bin/sha384
+fake/bin/sha512
+fake/bin/sleep
+fake/bin/stty
+fake/bin/sum
+fake/bin/sync
+fake/bin/systrace
+fake/bin/tar
+fake/bin/test
diff --git a/lib/util/regress/glob/globtest.c b/lib/util/regress/glob/globtest.c
new file mode 100644
index 0000000..866535a
--- /dev/null
+++ b/lib/util/regress/glob/globtest.c
@@ -0,0 +1,225 @@
+/* $OpenBSD: globtest.c,v 1.1 2008/10/01 23:04:36 millert Exp $ */
+
+/*
+ * Public domain, 2008, Todd C. Miller <Todd.Miller@sudo.ws>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_GLOB
+# include <glob.h>
+#else
+# include "compat/glob.h"
+#endif
+#include <errno.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#define MAX_RESULTS 256
+
+struct gl_entry {
+ int flags;
+ int nresults;
+ char pattern[1024];
+ char *results[MAX_RESULTS];
+};
+
+int test_glob(struct gl_entry *);
+sudo_dso_public int main(int argc, char *argv[]);
+
+int
+main(int argc, char **argv)
+{
+ FILE *fp = stdin;
+ char buf[2048], *cp, *ep;
+ int ch, errors = 0, ntests = 0, lineno;
+ struct gl_entry entry;
+ size_t len;
+
+ initprogname(argc > 0 ? argv[0] : "globtest");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0) {
+ if ((fp = fopen(argv[0], "r")) == NULL) {
+ perror(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /*
+ * Read in test file, which is formatted thusly:
+ *
+ * [pattern] <flags>
+ * result1
+ * result2
+ * result3
+ * ...
+ *
+ */
+ lineno = 0;
+ memset(&entry, 0, sizeof(entry));
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ lineno++;
+ len = strlen(buf);
+ if (len > 0) {
+ if (buf[len - 1] != '\n') {
+ fprintf(stderr,
+ "globtest: missing newline at EOF\n");
+ return EXIT_FAILURE;
+ }
+ buf[--len] = '\0';
+ }
+ if (len == 0)
+ continue; /* blank line */
+
+ if (buf[0] == '[') {
+ /* check previous pattern */
+ if (entry.pattern[0]) {
+ errors += test_glob(&entry);
+ ntests++;
+ }
+
+ /* start new entry */
+ if ((cp = strrchr(buf + 1, ']')) == NULL) {
+ fprintf(stderr,
+ "globtest: invalid entry on line %d\n",
+ lineno);
+ return EXIT_FAILURE;
+ }
+ len = cp - buf - 1;
+ if (len >= sizeof(entry.pattern)) {
+ fprintf(stderr,
+ "globtest: pattern too big on line %d\n",
+ lineno);
+ return EXIT_FAILURE;
+ }
+ memcpy(entry.pattern, buf + 1, len);
+ entry.pattern[len] = '\0';
+
+ cp += 2;
+ if (*cp++ != '<') {
+ fprintf(stderr,
+ "globtest: invalid entry on line %d\n",
+ lineno);
+ return EXIT_FAILURE;
+ }
+ ep = strchr(cp, '>');
+ if (ep == NULL) {
+ fprintf(stderr,
+ "globtest: invalid entry on line %d\n",
+ lineno);
+ return EXIT_FAILURE;
+ }
+ *ep = '\0';
+ entry.flags = 0;
+ for ((cp = strtok_r(cp, "|", &ep)); cp != NULL; (cp = strtok_r(NULL, "|", &ep))) {
+ if (strcmp(cp, "GLOB_APPEND") == 0)
+ entry.flags |= GLOB_APPEND;
+ else if (strcmp(cp, "GLOB_DOOFFS") == 0)
+ entry.flags |= GLOB_DOOFFS;
+ else if (strcmp(cp, "GLOB_ERR") == 0)
+ entry.flags |= GLOB_ERR;
+ else if (strcmp(cp, "GLOB_MARK") == 0)
+ entry.flags |= GLOB_MARK;
+ else if (strcmp(cp, "GLOB_NOCHECK") == 0)
+ entry.flags |= GLOB_NOCHECK;
+ else if (strcmp(cp, "GLOB_NOSORT") == 0)
+ entry.flags |= GLOB_NOSORT;
+ else if (strcmp(cp, "GLOB_NOESCAPE") == 0)
+ entry.flags |= GLOB_NOESCAPE;
+ else if (strcmp(cp, "GLOB_BRACE") == 0)
+ entry.flags |= GLOB_BRACE;
+ else if (strcmp(cp, "GLOB_TILDE") == 0)
+ entry.flags |= GLOB_TILDE;
+ else if (strcmp(cp, "NONE") != 0) {
+ fprintf(stderr,
+ "globtest: invalid flags on line %d\n",
+ lineno);
+ return EXIT_FAILURE;
+ }
+ }
+ entry.nresults = 0;
+ continue;
+ }
+ if (!entry.pattern[0]) {
+ fprintf(stderr, "globtest: missing entry on line %d\n",
+ lineno);
+ return EXIT_FAILURE;
+ }
+
+ if (entry.nresults + 1 > MAX_RESULTS) {
+ fprintf(stderr,
+ "globtest: too many results for %s, max %d\n",
+ entry.pattern, MAX_RESULTS);
+ return EXIT_FAILURE;
+ }
+ entry.results[entry.nresults++] = strdup(buf);
+ }
+ if (entry.pattern[0]) {
+ errors += test_glob(&entry); /* test last pattern */
+ ntests++;
+ }
+ if (ntests != 0) {
+ printf("glob: %d test%s run, %d errors, %d%% success rate\n",
+ ntests, ntests == 1 ? "" : "s", errors,
+ (ntests - errors) * 100 / ntests);
+ }
+ return errors;
+}
+
+static int
+test_glob(struct gl_entry *entry)
+{
+ glob_t gl;
+ char **ap;
+ int nmatches = 0, i = 0;
+
+ if (glob(entry->pattern, entry->flags, NULL, &gl) != 0) {
+ fprintf(stderr, "glob failed: %s: %s\n", entry->pattern,
+ strerror(errno));
+ return 1;
+ }
+
+ for (ap = gl.gl_pathv; *ap != NULL; ap++)
+ nmatches++;
+
+ if (nmatches != entry->nresults)
+ goto mismatch;
+
+ for (i = 0; i < entry->nresults; i++) {
+ if (strcmp(gl.gl_pathv[i], entry->results[i]) != 0)
+ goto mismatch;
+ free(entry->results[i]);
+ }
+ return 0;
+ mismatch:
+ if (nmatches != entry->nresults) {
+ fprintf(stderr,
+ "globtest: mismatch in number of results (found %d, expected %d) for pattern %s\n",
+ nmatches, entry->nresults, entry->pattern);
+ } else {
+ fprintf(stderr, "globtest: mismatch for pattern %s, flags 0x%x "
+ "(found \"%s\", expected \"%s\")\n", entry->pattern, entry->flags,
+ gl.gl_pathv[i], entry->results[i]);
+ while (i < entry->nresults)
+ free(entry->results[i++]);
+ }
+ return 1;
+}
diff --git a/lib/util/regress/glob/globtest.in b/lib/util/regress/glob/globtest.in
new file mode 100644
index 0000000..20a86c1
--- /dev/null
+++ b/lib/util/regress/glob/globtest.in
@@ -0,0 +1,64 @@
+[fake/bin/[[:alpha:]]*] <NONE>
+fake/bin/cat
+fake/bin/chgrp
+fake/bin/chio
+fake/bin/chmod
+fake/bin/cksum
+fake/bin/cp
+fake/bin/cpio
+fake/bin/csh
+fake/bin/date
+fake/bin/dd
+fake/bin/df
+fake/bin/domainname
+fake/bin/echo
+fake/bin/ed
+fake/bin/eject
+fake/bin/expr
+fake/bin/hostname
+fake/bin/kill
+fake/bin/ksh
+fake/bin/ln
+fake/bin/ls
+fake/bin/md5
+fake/bin/mkdir
+fake/bin/mt
+fake/bin/mv
+fake/bin/pax
+fake/bin/ps
+fake/bin/pwd
+fake/bin/rcp
+fake/bin/rksh
+fake/bin/rm
+fake/bin/rmail
+fake/bin/rmd160
+fake/bin/rmdir
+fake/bin/sh
+fake/bin/sha1
+fake/bin/sha256
+fake/bin/sha384
+fake/bin/sha512
+fake/bin/sleep
+fake/bin/stty
+fake/bin/sum
+fake/bin/sync
+fake/bin/systrace
+fake/bin/tar
+fake/bin/test
+
+[fake/bin/rm{,dir,ail}] <GLOB_BRACE>
+fake/bin/rm
+fake/bin/rmdir
+fake/bin/rmail
+
+[fake/bin/sha[[:digit:]]] <NONE>
+fake/bin/sha1
+
+[fake/bin/sha[[:digit:]]*] <NONE>
+fake/bin/sha1
+fake/bin/sha256
+fake/bin/sha384
+fake/bin/sha512
+
+[fake/bin/ca[a-z]] <NONE>
+fake/bin/cat
diff --git a/lib/util/regress/harness.in b/lib/util/regress/harness.in
new file mode 100755
index 0000000..05fd298
--- /dev/null
+++ b/lib/util/regress/harness.in
@@ -0,0 +1,109 @@
+#!/bin/sh
+#
+# Copyright (c) 2022 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.
+#
+# Simple test harness for libsudo_util tests.
+# usage: harness [-v] test_group [test_name ...]
+#
+srcdir="@abs_srcdir@"
+builddir="@abs_builddir@"
+SHELL=@SHELL@
+verbose=0
+rval=0
+ntests=0
+errors=0
+
+umask 022
+
+if [ "$1" = "-v" ]; then
+ verbose=1
+ shift
+fi
+
+if [ $# -eq 0 ]; then
+ echo "usage: harness test_group [test_name ...]" >&2
+ exit 1
+fi
+group="$1"
+shift
+srcdir=${srcdir%"/regress"}
+builddir=${builddir%"/regress"}
+
+cd $srcdir || exit 1
+
+if [ ! -d "regress/$group" ]; then
+ echo "missing test group: regress/$group" >&2
+ exit 1
+fi
+
+mkdir -p "$builddir/regress/$group"
+if [ $# -eq 0 ]; then
+ tests=
+ for t in regress/$group/*.in; do
+ tests="$tests `basename $t .in`"
+ done
+ set -- $tests
+fi
+
+
+while [ $# -ne 0 ]; do
+ test="$1"
+ shift
+ in="regress/$group/${test}.in"
+ out="$builddir/regress/$group/${test}.out"
+ out_ok="regress/$group/${test}.out.ok"
+ err="$builddir/regress/$group/${test}.err"
+ err_ok="regress/$group/${test}.err.ok"
+
+ if [ "$group" = "sudo_conf" ]; then
+ $builddir/conf_test $in >$out 2>$err
+ else
+ $builddir/parseln_test <$in >$out 2>$err
+ fi
+
+ ntests=`expr $ntests + 1`
+ if cmp $out $out_ok >/dev/null; then
+ if [ $verbose -eq 1 ]; then
+ echo "$group/$test: OK"
+ fi
+ else
+ errors=`expr $errors + 1`
+ echo "$group/$test: FAIL"
+ diff $out $out_ok || true
+ fi
+
+ ntests=`expr $ntests + 1`
+ if test -s $err_ok; then
+ if cmp $err $err_ok >/dev/null; then
+ if [ $verbose -eq 1 ]; then
+ echo "$group/$test (stderr): OK"
+ fi
+ else
+ errors=`expr $errors + 1`
+ echo "$group/$test (stderr): FAIL"
+ diff $err $err_ok || true
+ fi
+ elif test -s $err; then
+ errors=`expr $errors + 1`
+ echo "$group/$test (stderr): FAIL"
+ fi
+done
+${AWK-awk} -v group=$group -v ntests=$ntests -v errors=$errors \
+ 'END {printf("%s: %d tests run, %d errors, %d%% success rate\n", group, ntests, errors, (ntests - errors) * 100 / ntests)}' < /dev/null
+if test $errors -ne 0; then
+ rval=`expr $rval + $errors`
+fi
+
+exit $rval
diff --git a/lib/util/regress/hexchar/hexchar_test.c b/lib/util/regress/hexchar/hexchar_test.c
new file mode 100644
index 0000000..a51bf7f
--- /dev/null
+++ b/lib/util/regress/hexchar/hexchar_test.c
@@ -0,0 +1,81 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014-2015, 2023 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+struct hexchar_test {
+ char hex[3];
+ int value;
+};
+
+int
+main(int argc, char *argv[])
+{
+ struct hexchar_test *test_data;
+ int i, ntests, result, errors = 0;
+ static const char xdigs_lower[] = "0123456789abcdef";
+ static const char xdigs_upper[] = "0123456789ABCDEF";
+
+ initprogname(argc > 0 ? argv[0] : "hexchar_test");
+
+ /* Build up test data. */
+ ntests = 256 + 256 + 3;
+ test_data = calloc(sizeof(*test_data), ntests);
+ for (i = 0; i < 256; i++) {
+ /* lower case */
+ test_data[i].value = i;
+ test_data[i].hex[1] = xdigs_lower[ (i & 0x0f)];
+ test_data[i].hex[0] = xdigs_lower[((i & 0xf0) >> 4)];
+ /* upper case */
+ test_data[i + 256].value = i;
+ test_data[i + 256].hex[1] = xdigs_upper[ (i & 0x0f)];
+ test_data[i + 256].hex[0] = xdigs_upper[((i & 0xf0) >> 4)];
+ }
+ /* Also test invalid data */
+ test_data[ntests - 3].hex[0] = '\0';
+ test_data[ntests - 3].value = -1;
+ strlcpy(test_data[ntests - 2].hex, "AG", sizeof(test_data[ntests - 2].hex));
+ test_data[ntests - 2].value = -1;
+ strlcpy(test_data[ntests - 1].hex, "-1", sizeof(test_data[ntests - 1].hex));
+ test_data[ntests - 1].value = -1;
+
+ for (i = 0; i < ntests; i++) {
+ result = sudo_hexchar(test_data[i].hex);
+ if (result != test_data[i].value) {
+ fprintf(stderr, "%s: expected %d, got %d\n", getprogname(),
+ test_data[i].value, result);
+ errors++;
+ }
+ }
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ exit(errors);
+}
diff --git a/lib/util/regress/json/json_test.c b/lib/util/regress/json/json_test.c
new file mode 100644
index 0000000..8dc6719
--- /dev/null
+++ b/lib/util/regress/json/json_test.c
@@ -0,0 +1,235 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 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 <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_json.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/* Expected JSON output */
+const char outbuf[] = "\n"
+ " \"test1\": {\n"
+ " \"string1\": \"test\\\\\\b\\f\\n\\r\\t string1\",\n"
+ " \"id1\": 4294967295,\n"
+ " \"number1\": -1,\n"
+ " \"bool1\": true,\n"
+ " \"bool2\": false,\n"
+ " \"null1\": null,\n"
+ " \"array1\": [\n"
+ " \"string2\": \"test\\f\\u0011string2\",\n"
+ " \"number2\": -9223372036854775808,\n"
+ " \"number3\": 9223372036854775807\n"
+ " ]\n"
+ " }";
+
+/*
+ * Simple tests for sudo json functions()
+ */
+int
+main(int argc, char *argv[])
+{
+ struct json_container jsonc;
+ struct json_value value;
+ int ch, errors = 0, ntests = 0;
+
+ initprogname(argc > 0 ? argv[0] : "json_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ ntests++;
+ if (!sudo_json_init(&jsonc, 4, false, true, true)) {
+ sudo_warnx("unable to initialize json");
+ errors++;
+ goto done;
+ }
+
+ /* Open main JSON object. */
+ ntests++;
+ if (!sudo_json_open_object(&jsonc, "test1")) {
+ sudo_warnx("unable to open json object");
+ errors++;
+ goto done;
+ }
+
+ /* Verify invalid value is detected. */
+ value.type = -1;
+ value.u.string = NULL;
+ ntests++;
+ if (sudo_json_add_value(&jsonc, "bogus1", &value)) {
+ /* should have failed, not a fatal error */
+ sudo_warnx("should not be able to add bogus type value");
+ errors++;
+ }
+
+ /* Verify that adding an array is not allowed. */
+ value.type = JSON_ARRAY;
+ value.u.string = NULL;
+ ntests++;
+ if (sudo_json_add_value(&jsonc, "bogus2", &value)) {
+ /* should have failed, not a fatal error */
+ sudo_warnx("should not be able to add array type value");
+ errors++;
+ }
+
+ /* Verify that adding an object is not allowed. */
+ value.type = JSON_OBJECT;
+ value.u.string = NULL;
+ ntests++;
+ if (sudo_json_add_value(&jsonc, "bogus3", &value)) {
+ /* should have failed, not a fatal error */
+ sudo_warnx("should not be able to add object type value");
+ errors++;
+ }
+
+ value.type = JSON_STRING;
+ value.u.string = "test\\\b\f\n\r\t string1";
+ ntests++;
+ if (!sudo_json_add_value(&jsonc, "string1", &value)) {
+ /* not a fatal error */
+ sudo_warnx("unable to add string value (string1)");
+ errors++;
+ }
+
+ value.type = JSON_ID;
+ value.u.id = 0xffffffff;
+ ntests++;
+ if (!sudo_json_add_value(&jsonc, "id1", &value)) {
+ /* not a fatal error */
+ sudo_warnx("unable to add ID value (0xffffffff)");
+ errors++;
+ }
+
+ value.type = JSON_NUMBER;
+ value.u.number = -1;
+ ntests++;
+ if (!sudo_json_add_value(&jsonc, "number1", &value)) {
+ /* not a fatal error */
+ sudo_warnx("unable to add number value (-1)");
+ errors++;
+ }
+
+ value.type = JSON_BOOL;
+ value.u.boolean = true;
+ ntests++;
+ if (!sudo_json_add_value(&jsonc, "bool1", &value)) {
+ /* not a fatal error */
+ sudo_warnx("unable to add bool value (true)");
+ errors++;
+ }
+ value.u.boolean = false;
+ ntests++;
+ if (!sudo_json_add_value(&jsonc, "bool2", &value)) {
+ /* not a fatal error */
+ sudo_warnx("unable to add bool value (false)");
+ errors++;
+ }
+
+ value.type = JSON_NULL;
+ ntests++;
+ if (!sudo_json_add_value(&jsonc, "null1", &value)) {
+ /* not a fatal error */
+ sudo_warnx("unable to add null value");
+ errors++;
+ }
+
+ /* Open JSON array. */
+ ntests++;
+ if (!sudo_json_open_array(&jsonc, "array1")) {
+ sudo_warnx("unable to open json array");
+ errors++;
+ goto done;
+ }
+
+ value.type = JSON_STRING;
+ value.u.string = "test\x0c\x11string2";
+ ntests++;
+ if (!sudo_json_add_value(&jsonc, "string2", &value)) {
+ /* not a fatal error */
+ sudo_warnx("unable to add string value (string2)");
+ errors++;
+ }
+
+ value.type = JSON_NUMBER;
+ value.u.number = LLONG_MIN;
+ ntests++;
+ if (!sudo_json_add_value(&jsonc, "number2", &value)) {
+ /* not a fatal error */
+ sudo_warnx("unable to add number value (LLONG_MIN)");
+ errors++;
+ }
+ value.u.number = LLONG_MAX;
+ ntests++;
+ if (!sudo_json_add_value(&jsonc, "number3", &value)) {
+ /* not a fatal error */
+ sudo_warnx("unable to add number value (LLONG_MAX)");
+ errors++;
+ }
+
+ /* Close JSON array. */
+ if (!sudo_json_close_array(&jsonc)) {
+ sudo_warnx("unable to close json array");
+ errors++;
+ goto done;
+ }
+
+ /* Close main JSON object. */
+ if (!sudo_json_close_object(&jsonc)) {
+ sudo_warnx("unable to close json object");
+ errors++;
+ goto done;
+ }
+
+ if (strcmp(outbuf, jsonc.buf) != 0) {
+ fprintf(stderr, "Expected:\n%s\n", outbuf);
+ fprintf(stderr, "Received:\n%s\n", jsonc.buf);
+ }
+
+done:
+ sudo_json_free(&jsonc);
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/util/regress/mktemp/mktemp_test.c b/lib/util/regress/mktemp/mktemp_test.c
new file mode 100644
index 0000000..134f89e
--- /dev/null
+++ b/lib/util/regress/mktemp/mktemp_test.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2010 Philip Guenther <guenther@openbsd.org>
+ *
+ * Public domain.
+ *
+ * Verify that mkdtemp() and mkstemps() doesn't overrun or underrun
+ * the template buffer and that it can generate names that don't
+ * contain any X's
+ */
+
+#include <config.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+#ifndef MAP_ANON
+# if defined(MAP_ANONYMOUS)
+# define MAP_ANON MAP_ANONYMOUS
+# endif
+#endif
+
+#ifndef MAP_FAILED
+# define MAP_FAILED ((void *) -1)
+#endif
+
+#define MAX_TEMPLATE_LEN 10
+#define MAX_TRIES 100
+#define MIN_Xs 6
+
+#define SUFFIX ".suff"
+#define SLEN (sizeof SUFFIX - 1)
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * verify that a path generated by mkdtemp() or mkstemp() looks like a
+ * reasonable expansion of the template and matches the fd. Returns true
+ * if all the X's were replaced with non-X's
+ */
+static int
+check(int fd, char const *kind, char const *path, char const *prefix,
+ size_t plen, char const *suffix, size_t slen, int tlen)
+{
+ struct stat sb, fsb;
+ char const *p;
+
+ if (tlen < MIN_Xs) {
+ if (fd != -1)
+ sudo_fatalx("%s(%s) succeed with too few Xs", kind, path);
+ if (errno != EINVAL)
+ sudo_fatal("%s(%s) failed with wrong errno: %d", kind, path, errno);
+ return 1;
+ }
+ if (fd == -1)
+ sudo_fatal("%s(%s)", kind, path);
+ if (stat(path, &sb))
+ sudo_fatal("%s: stat(%s)", kind, path);
+ if (fd >= 0) {
+ if (fstat(fd, &fsb))
+ sudo_fatal("%s: fstat(%d==%s)", kind, fd, path);
+ if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino)
+ sudo_fatalx("%s: stat mismatch", kind);
+ }
+ if (memcmp(path, prefix, plen) != 0)
+ sudo_fatalx("%s: prefix changed! %s vs %s", kind, prefix, path);
+ if (memcmp(path + plen + tlen, suffix, slen + 1) != 0)
+ sudo_fatalx("%s: suffix changed! %s vs %s", kind, suffix, path);
+ for (p = path + plen; p < path + plen + tlen; p++)
+ if (*p == '\0')
+ sudo_fatalx("%s: unexpected truncation", kind);
+ else if (*p == 'X')
+ return 0;
+ return 1;
+}
+
+static void
+try_mkdtemp(char *p, char const *prefix, int len)
+{
+ size_t plen = strlen(prefix);
+ int fd, tries, ok;
+
+ for (tries = 0; tries < MAX_TRIES; tries++) {
+ memcpy(p, prefix, plen);
+ memset(p + plen, 'X', len);
+ p[plen + len] = '\0';
+ fd = mkdtemp(p) ? -2 : -1;
+ ok = check(fd, "mkdtemp", p, prefix, plen, "", 0, len);
+ rmdir(p);
+ if (ok)
+ return;
+ }
+ sudo_fatalx("mkdtemp: exceeded MAX_TRIES");
+}
+
+static void
+try_mkstemps(char *p, char const *prefix, int len, char const *suffix)
+{
+ size_t plen = strlen(prefix);
+ size_t slen = strlen(suffix);
+ int tries, fd, ok;
+
+ for (tries = 0; tries < MAX_TRIES; tries++) {
+ memcpy(p, prefix, plen);
+ memset(p + plen, 'X', len);
+ memcpy(p + plen + len, suffix, slen + 1);
+ fd = mkstemps(p, slen);
+ ok = check(fd, "mkstemp", p, prefix, plen, suffix, slen, len);
+ close(fd);
+ unlink(p);
+ if (ok)
+ return;
+ }
+ sudo_fatalx("mkstemps: exceeded MAX_TRIES");
+}
+
+int
+main(int argc, char *argv[])
+{
+ char cwd[PATH_MAX + 1];
+ char *p;
+ size_t clen;
+ long pg;
+ int ch, i;
+
+ initprogname(argc > 0 ? argv[0] : "mktemp_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ pg = sysconf(_SC_PAGESIZE);
+ if (getcwd(cwd, sizeof cwd - 1) == NULL)
+ sudo_fatal("getcwd");
+ clen = strlen(cwd);
+ cwd[clen++] = '/';
+ cwd[clen] = '\0';
+#ifdef MAP_ANON
+ p = mmap(NULL, pg * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
+#else
+ i = open("/dev/zero", O_RDWR);
+ if (i == -1)
+ sudo_fatal("/dev/zero");
+ p = mmap(NULL, pg * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE, i, 0);
+#endif
+ if (p == MAP_FAILED)
+ sudo_fatal("mmap");
+ if (mprotect(p, pg, PROT_NONE) || mprotect(p + pg * 2, pg, PROT_NONE))
+ sudo_fatal("mprotect");
+ p += pg;
+
+ i = MAX_TEMPLATE_LEN + 1;
+ while (i-- > 0) {
+ /* try first at the start of a page, no prefix */
+ try_mkdtemp(p, "", i);
+ /* now at the end of the page, no prefix */
+ try_mkdtemp(p + pg - i - 1, "", i);
+ /* start of the page, prefixed with the cwd */
+ try_mkdtemp(p, cwd, i);
+ /* how about at the end of the page, prefixed with cwd? */
+ try_mkdtemp(p + pg - clen - i - 1, cwd, i);
+
+ /* again, with mkstemps() and an empty suffix */
+ /* try first at the start of a page, no prefix */
+ try_mkstemps(p, "", i, "");
+ /* now at the end of the page, no prefix */
+ try_mkstemps(p + pg - i - 1, "", i, "");
+ /* start of the page, prefixed with the cwd */
+ try_mkstemps(p, cwd, i, "");
+ /* how about at the end of the page, prefixed with cwd? */
+ try_mkstemps(p + pg - clen - i - 1, cwd, i, "");
+
+ /* mkstemps() and a non-empty suffix */
+ /* try first at the start of a page, no prefix */
+ try_mkstemps(p, "", i, SUFFIX);
+ /* now at the end of the page, no prefix */
+ try_mkstemps(p + pg - i - SLEN - 1, "", i, SUFFIX);
+ /* start of the page, prefixed with the cwd */
+ try_mkstemps(p, cwd, i, SUFFIX);
+ /* how about at the end of the page, prefixed with cwd? */
+ try_mkstemps(p + pg - clen - i - SLEN - 1, cwd, i, SUFFIX);
+ }
+
+ return 0;
+}
diff --git a/lib/util/regress/multiarch/multiarch_test.c b/lib/util/regress/multiarch/multiarch_test.c
new file mode 100644
index 0000000..1c60aff
--- /dev/null
+++ b/lib/util/regress/multiarch/multiarch_test.c
@@ -0,0 +1,184 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 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 <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+#ifdef __linux__
+# include <sys/utsname.h>
+
+# if defined(__ILP32__)
+# define ARCH_LIB "libx32"
+# elif defined(__LP64__)
+# define ARCH_LIB "lib64"
+# else
+# define ARCH_LIB "lib32"
+# endif
+
+struct multiarch_test {
+ const char *inpath;
+ char *outpath;
+};
+
+static struct multiarch_test *
+make_test_data(void)
+{
+ struct multiarch_test *test_data;
+ struct utsname unamebuf;
+ int i;
+
+ if (uname(&unamebuf) == -1)
+ return NULL;
+
+ test_data = calloc(7, sizeof(*test_data));
+ if (test_data == NULL)
+ return NULL;
+
+ test_data[0].inpath = "/usr/" ARCH_LIB "/libfoo.so";
+ i = asprintf(&test_data[0].outpath, "/usr/lib/%s-linux-gnu/libfoo.so",
+ unamebuf.machine);
+ if (i == -1) {
+ test_data[0].outpath = NULL;
+ goto bad;
+ }
+
+ test_data[1].inpath = "/usr/lib/something.so";
+ i = asprintf(&test_data[1].outpath, "/usr/lib/%s-linux-gnu/something.so",
+ unamebuf.machine);
+ if (i == -1) {
+ test_data[1].outpath = NULL;
+ goto bad;
+ }
+
+ test_data[2].inpath = "/usr/libexec/libbar.so";
+ i = asprintf(&test_data[2].outpath, "/usr/libexec/%s-linux-gnu/libbar.so",
+ unamebuf.machine);
+ if (i == -1) {
+ test_data[2].outpath = NULL;
+ goto bad;
+ }
+
+ test_data[3].inpath = "/usr/local/lib/sudo/libsudo_util.so";
+ i = asprintf(&test_data[3].outpath, "/usr/local/lib/%s-linux-gnu/sudo/libsudo_util.so",
+ unamebuf.machine);
+ if (i == -1) {
+ test_data[3].outpath = NULL;
+ goto bad;
+ }
+
+ test_data[4].inpath = "/opt/sudo/lib/sudoers.so";
+ i = asprintf(&test_data[4].outpath, "/opt/sudo/lib/%s-linux-gnu/sudoers.so",
+ unamebuf.machine);
+ if (i == -1) {
+ test_data[4].outpath = NULL;
+ goto bad;
+ }
+
+ i = asprintf(&test_data[5].outpath, "/usr/lib/%s-linux-gnu/something.so",
+ unamebuf.machine);
+ if (i == -1) {
+ test_data[5].outpath = NULL;
+ goto bad;
+ }
+ test_data[5].inpath = test_data[5].outpath;
+ test_data[5].outpath = NULL;
+
+ return test_data;
+bad:
+ for (i = 0; test_data[i].outpath != NULL; i++)
+ free(test_data[i].outpath);
+ free(test_data);
+ return NULL;
+}
+#endif /* __linux__ */
+
+int
+main(int argc, char *argv[])
+{
+ int ch, errors = 0;
+#ifdef __linux__
+ int ntests = 0;
+ struct multiarch_test *test_data;
+#endif
+
+ initprogname(argc > 0 ? argv[0] : "multiarch_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef __linux__
+ test_data = make_test_data();
+ if (test_data == NULL) {
+ sudo_warnx("%s", "failed to generate test data");
+ return EXIT_FAILURE;
+ }
+
+ for (ch = 0; test_data[ch].inpath != NULL; ch++) {
+ char *outpath = sudo_stat_multiarch(test_data[ch].inpath, NULL);
+ ntests++;
+ if (outpath == NULL) {
+ if (test_data[ch].outpath != NULL) {
+ sudo_warnx("%s: sudo_stat_multiarch failed",
+ test_data[ch].inpath);
+ errors++;
+ }
+ } else if (strcmp(outpath, test_data[ch].outpath) != 0) {
+ sudo_warnx("%s: expected %s got %s", test_data[ch].inpath,
+ test_data[ch].outpath, outpath);
+ errors++;
+ }
+ /* For test_data[5], inpath is allocated and outpath is NULL. */
+ if (test_data[ch].outpath != NULL)
+ free(test_data[ch].outpath);
+ else
+ free((char *)test_data[ch].inpath);
+ free(outpath);
+ }
+ free(test_data);
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+#endif /* __linux__ */
+ return errors;
+}
diff --git a/lib/util/regress/open_parent_dir/open_parent_dir_test.c b/lib/util/regress/open_parent_dir/open_parent_dir_test.c
new file mode 100644
index 0000000..95f1a09
--- /dev/null
+++ b/lib/util/regress/open_parent_dir/open_parent_dir_test.c
@@ -0,0 +1,166 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 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 <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static int errors = 0, ntests = 0;
+
+static int
+run_test(const char *tdir, const char *path, uid_t uid, gid_t gid)
+{
+ char *cp, fullpath[PATH_MAX];
+ struct stat sb1, sb2;
+ int dfd, len;
+ int ret = -1;
+
+ /* Test creating full path. */
+ len = snprintf(fullpath, sizeof(fullpath), "%s/%s", tdir, path);
+ if (len < 0 || len >= ssizeof(fullpath)) {
+ errno = ENAMETOOLONG;
+ sudo_fatal("%s/%s", tdir, path);
+ }
+ ntests++;
+ dfd = sudo_open_parent_dir(fullpath, uid, gid, 0700, false);
+ if (dfd == -1) {
+ errors++;
+ goto done;
+ }
+
+ /* Verify that we only created the parent dir, not full path. */
+ ntests++;
+ if (stat(fullpath, &sb1) == 0) {
+ sudo_warnx("created full path \"%s\", not just parent dir",
+ fullpath);
+ errors++;
+ goto done;
+ }
+
+ /* Verify that dfd refers to the parent dir. */
+ ntests++;
+ cp = strrchr(fullpath, '/');
+ *cp = '\0';
+ if (stat(fullpath, &sb1) == -1) {
+ sudo_warn("%s", fullpath);
+ errors++;
+ goto done;
+ }
+ if (fstat(dfd, &sb2) == -1) {
+ sudo_warn("%s", fullpath);
+ errors++;
+ goto done;
+ }
+ if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
+ sudo_warn("%s: sudo_open_parent_dir fd mismatch", fullpath);
+ errors++;
+ goto done;
+ }
+
+done:
+ if (dfd != -1)
+ close(dfd);
+ return ret;
+}
+
+int
+main(int argc, char *argv[])
+{
+ char tdir[] = "open_parent_dir.XXXXXXXX";
+ int ch, dfd, fd, len;
+ char cmd[1024];
+ uid_t uid;
+ gid_t gid;
+
+ initprogname(argc > 0 ? argv[0] : "open_parent_dir_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ uid = geteuid();
+ gid = getegid();
+
+ /* All tests relative to tdir. */
+ if (mkdtemp(tdir) == NULL)
+ sudo_fatal("%s", tdir);
+
+ /* Test creating new path. */
+ dfd = run_test(tdir, "level1/level2/level3", uid, gid);
+
+ /* Verify we can create a new file in the new parent dir. */
+ if (dfd != -1) {
+ ntests++;
+ fd = openat(dfd, "testfile", O_WRONLY|O_CREAT|O_EXCL, 0600);
+ if (fd == -1) {
+ errors++;
+ } else {
+ close(fd);
+ }
+ close(dfd);
+ dfd = -1;
+ }
+
+ /* Test exiting path when final component exists. */
+ dfd = run_test(tdir, "level1/level2/testfile", uid, gid);
+ if (dfd != -1) {
+ unlinkat(dfd, "testfile", 0);
+ close(dfd);
+ }
+
+ /* Test exiting path when final component doesn't exist. */
+ dfd = run_test(tdir, "level1/level2/testfile", uid, gid);
+ if (dfd != -1)
+ close(dfd);
+
+ /* Cleanup */
+ len = snprintf(cmd, sizeof(cmd), "rm -rf \"%s\"", tdir);
+ if (len < 0 || len >= ssizeof(cmd)) {
+ errno = ENAMETOOLONG;
+ sudo_fatalx("rm -rf %s", tdir);
+ }
+ ignore_result(system(cmd));
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ return errors;
+}
diff --git a/lib/util/regress/parse_gids/parse_gids_test.c b/lib/util/regress/parse_gids/parse_gids_test.c
new file mode 100644
index 0000000..241e81b
--- /dev/null
+++ b/lib/util/regress/parse_gids/parse_gids_test.c
@@ -0,0 +1,123 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_parse_gids() works as expected.
+ */
+
+struct parse_gids_test {
+ const char *gids;
+ gid_t *baseptr;
+ gid_t basegid;
+ int ngids;
+ const GETGROUPS_T *gidlist;
+};
+
+static const GETGROUPS_T test1_out[] = { 0, 1, 2, 3, 4 };
+static const GETGROUPS_T test2_out[] = { 1, 2, 3, 4 };
+static const GETGROUPS_T test3_out[] = { 0, 1, (gid_t)-2, 3, 4 };
+
+/* XXX - test syntax errors too */
+static struct parse_gids_test test_data[] = {
+ { "1,2,3,4", &test_data[0].basegid, 0, 5, test1_out },
+ { "1,2,3,4", NULL, 0, 4, test2_out },
+ { "1,-2,3,4", &test_data[2].basegid, 0, 5, test3_out },
+ { NULL, false, 0, 0, NULL }
+};
+
+static void
+dump_gids(const char *prefix, int ngids, const GETGROUPS_T *gidlist)
+{
+ int i;
+
+ fprintf(stderr, "%s: %s: ", getprogname(), prefix);
+ for (i = 0; i < ngids; i++) {
+ fprintf(stderr, "%s%d", i ? ", " : "", (int)gidlist[i]);
+ }
+ fputc('\n', stderr);
+}
+
+int
+main(int argc, char *argv[])
+{
+ GETGROUPS_T *gidlist = NULL;
+ int i, j, errors = 0, ntests = 0;
+ int ch, ngids;
+
+ initprogname(argc > 0 ? argv[0] : "parse_gids_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (i = 0; test_data[i].gids != NULL; i++) {
+ free(gidlist);
+ gidlist = NULL;
+ ngids = sudo_parse_gids(test_data[i].gids, test_data[i].baseptr, &gidlist);
+ if (ngids == -1)
+ sudo_fatal_nodebug("sudo_parse_gids");
+ ntests++;
+ if (ngids != test_data[i].ngids) {
+ sudo_warnx_nodebug("test #%d: expected %d gids, got %d",
+ ntests, test_data[i].ngids, ngids);
+ dump_gids("expected", test_data[i].ngids, test_data[i].gidlist);
+ dump_gids("received", ngids, gidlist);
+ errors++;
+ continue;
+ }
+ ntests++;
+ for (j = 0; j < ngids; j++) {
+ if (test_data[i].gidlist[j] != gidlist[j]) {
+ sudo_warnx_nodebug("test #%d: gid mismatch", ntests);
+ dump_gids("expected", test_data[i].ngids, test_data[i].gidlist);
+ dump_gids("received", ngids, gidlist);
+ errors++;
+ break;
+ }
+ }
+ }
+ free(gidlist);
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ return errors;
+}
diff --git a/lib/util/regress/progname/progname_test.c b/lib/util/regress/progname/progname_test.c
new file mode 100644
index 0000000..0a88faf
--- /dev/null
+++ b/lib/util/regress/progname/progname_test.c
@@ -0,0 +1,67 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that getprogname() returns the expected result.
+ * On some systems (AIX), we may have issues with symbolic links.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ const char *progbase = "progname_test";
+ int ch;
+
+ if (argc > 0)
+ progbase = sudo_basename(argv[0]);
+ initprogname(progbase);
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", progbase);
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Make sure getprogname() matches basename of argv[0]. */
+ if (strcmp(getprogname(), progbase) != 0) {
+ printf("%s: FAIL: incorrect program name \"%s\"\n",
+ progbase, getprogname());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/lib/util/regress/regex/regex_test.c b/lib/util/regress/regex/regex_test.c
new file mode 100644
index 0000000..ed6986e
--- /dev/null
+++ b/lib/util/regress/regex/regex_test.c
@@ -0,0 +1,126 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 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 <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+struct regex_test {
+ const char *pattern;
+ bool result;
+};
+
+static struct regex_test test_data[] = {
+ { "ab++", false },
+ { "ab\\++", true },
+ { "ab+\\+", true },
+ { "ab**", false },
+ { "ab\\**", true },
+ { "ab*\\*", true },
+ { "ab??", false },
+ { "ab\\??", true },
+ { "ab?\\?", true },
+ { "ab{1}{1}", false },
+ { "ab{1}{1,1}", false },
+ { "ab{1}{,1}", false },
+ { "ab{1}{1,}", false },
+ { "ab{1}\\{1}", true },
+ { "ab{1}\\{1,1}", true },
+ { "ab{1}\\{,1}", true },
+ { "ab{1}\\{1,}", true },
+ { "ab+*", false },
+ { "ab\\+*", true },
+ { "ab+\\*", true },
+ { "ab*+", false },
+ { "ab\\*+", true },
+ { "ab*\\+", true },
+ { "ab?*", false },
+ { "ab\\?*", true },
+ { "ab?\\*", true },
+ { "ab{1}*", false },
+ { "ab\\{1}*", true },
+ { "ab{1}\\*", true },
+ { "ab{256}", false },
+ { "ab{,256}", false },
+ { "ab{256,}", false },
+ { "ab{1,256}", false },
+ { "ab{1,\\256}", false },
+ { "ab{1,2\\56}", false },
+ { "ab{256,1}", false },
+ { "ab{\\256,1}", false },
+ { "ab{2\\56,1}", false },
+ { NULL }
+};
+
+int
+main(int argc, char *argv[])
+{
+ struct regex_test *td;
+ const char *errstr;
+ int errors = 0, ntests = 0;
+ bool result;
+ int ch;
+
+ initprogname(argc > 0 ? argv[0] : "regex_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (td = test_data; td->pattern != NULL; td++) {
+ ntests++;
+ result = sudo_regex_compile(NULL, td->pattern, &errstr);
+ if (result != td->result) {
+ sudo_warnx("%s: expected %d, got %d", td->pattern, (int)td->result,
+ (int)result);
+ errors++;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ return errors;
+}
diff --git a/lib/util/regress/strsig/strsig_test.c b/lib/util/regress/strsig/strsig_test.c
new file mode 100644
index 0000000..30aaa2d
--- /dev/null
+++ b/lib/util/regress/strsig/strsig_test.c
@@ -0,0 +1,319 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Note: we do not test SIGUNUSED as it may not appear in sys_sigabbrev[]
+ * on Linux. FreeBSD is missing SIGLWP (aka SIGTHR) in sys_signame[].
+ */
+static struct signal_data {
+ int rval;
+ int signo;
+ const char *sigstr;
+ const char *altstr;
+} signal_data[] = {
+#ifdef SIGHUP
+ { 0, SIGHUP, "HUP", NULL },
+#endif
+#ifdef SIGINT
+ { 0, SIGINT, "INT", NULL },
+#endif
+#ifdef SIGQUIT
+ { 0, SIGQUIT, "QUIT", NULL },
+#endif
+#ifdef SIGILL
+ { 0, SIGILL, "ILL", NULL },
+#endif
+#ifdef SIGTRAP
+ { 0, SIGTRAP, "TRAP", NULL },
+#endif
+#ifdef SIGABRT
+ { 0, SIGABRT, "ABRT", "IOT" },
+#endif
+#ifdef SIGIOT
+ { 0, SIGIOT, "IOT", "ABRT" },
+#endif
+#ifdef SIGEMT
+ { 0, SIGEMT, "EMT", NULL },
+#endif
+#ifdef SIGFPE
+ { 0, SIGFPE, "FPE", NULL },
+#endif
+#ifdef SIGKILL
+ { 0, SIGKILL, "KILL", NULL },
+#endif
+#ifdef SIGBUS
+ { 0, SIGBUS, "BUS", NULL },
+#endif
+#ifdef SIGSEGV
+ { 0, SIGSEGV, "SEGV", NULL },
+#endif
+#ifdef SIGSYS
+ { 0, SIGSYS, "SYS", NULL },
+#endif
+#ifdef SIGPIPE
+ { 0, SIGPIPE, "PIPE", NULL },
+#endif
+#ifdef SIGALRM
+ { 0, SIGALRM, "ALRM", NULL },
+#endif
+#ifdef SIGTERM
+ { 0, SIGTERM, "TERM", NULL },
+#endif
+#ifdef SIGSTKFLT
+ { 0, SIGSTKFLT, "STKFLT", NULL },
+#endif
+#ifdef SIGIO
+ { 0, SIGIO, "IO", "POLL"},
+#endif
+#ifdef SIGXCPU
+ { 0, SIGXCPU, "XCPU", NULL },
+#endif
+#ifdef SIGXFSZ
+ { 0, SIGXFSZ, "XFSZ", NULL },
+#endif
+#ifdef SIGVTALRM
+ { 0, SIGVTALRM, "VTALRM", NULL },
+#endif
+#ifdef SIGPROF
+ { 0, SIGPROF, "PROF", NULL },
+#endif
+#ifdef SIGWINCH
+ { 0, SIGWINCH, "WINCH", NULL },
+#endif
+#ifdef SIGLOST
+ { 0, SIGLOST, "LOST", NULL },
+#endif
+#ifdef SIGUSR1
+ { 0, SIGUSR1, "USR1", NULL },
+#endif
+#ifdef SIGUSR2
+ { 0, SIGUSR2, "USR2", NULL },
+#endif
+#ifdef SIGPWR
+ { 0, SIGPWR, "PWR", NULL },
+#endif
+#ifdef SIGPOLL
+ { 0, SIGPOLL, "POLL", "IO" },
+#endif
+#ifdef SIGSTOP
+ { 0, SIGSTOP, "STOP", NULL },
+#endif
+#ifdef SIGTSTP
+ { 0, SIGTSTP, "TSTP", NULL },
+#endif
+#ifdef SIGCONT
+ { 0, SIGCONT, "CONT", NULL },
+#endif
+#ifdef SIGCHLD
+ { 0, SIGCHLD, "CHLD", "CLD" },
+#endif
+#ifdef SIGCLD
+ { 0, SIGCLD, "CLD", "CHLD" },
+#endif
+#ifdef SIGTTIN
+ { 0, SIGTTIN, "TTIN", NULL },
+#endif
+#ifdef SIGTTOU
+ { 0, SIGTTOU, "TTOU", NULL },
+#endif
+#ifdef SIGINFO
+ { 0, SIGINFO, "INFO", NULL },
+#endif
+#ifdef SIGURG
+ { 0, SIGURG, "URG", NULL },
+#endif
+#ifdef SIGWAITING
+ { 0, SIGWAITING, "WAITING", NULL },
+#endif
+#if defined(SIGLWP) && !defined(__FreeBSD__)
+ { 0, SIGLWP, "LWP", NULL },
+#endif
+#ifdef SIGFREEZE
+ { 0, SIGFREEZE, "FREEZE", NULL },
+#endif
+#ifdef SIGTHAW
+ { 0, SIGTHAW, "THAW", NULL },
+#endif
+#ifdef SIGCANCEL
+ { 0, SIGCANCEL, "CANCEL", NULL },
+#endif
+#if defined(SIGRTMIN) && defined(SIGRTMAX)
+ { 0, -1, "RTMIN", NULL },
+ { 0, -1, "RTMIN+1", NULL },
+ { 0, -1, "RTMIN+2", NULL },
+ { 0, -1, "RTMIN+3", NULL },
+ { 0, -1, "RTMAX-3", NULL },
+ { 0, -1, "RTMAX-2", NULL },
+ { 0, -1, "RTMAX-1", NULL },
+ { 0, -1, "RTMAX", NULL },
+#endif
+ { -1, 1024, "QWERT", NULL }, /* invalid */
+ { -1, 0, NULL, NULL }
+};
+
+#ifndef HAVE_SIG2STR
+static int
+test_sig2str(int *ntests)
+{
+ struct signal_data *d;
+ int rval, errors = 0;
+ char sigstr[SIG2STR_MAX];
+
+ for (d = signal_data; d->signo != 0; d++) {
+ (*ntests)++;
+ rval = sudo_sig2str(d->signo, sigstr);
+ if (rval != d->rval) {
+ sudo_warnx_nodebug("FAIL: sig2str(SIG%s): %d != %d",
+ d->sigstr, rval, d->rval);
+ errors++;
+ continue;
+ }
+ if (rval != 0)
+ continue;
+ if (strcmp(sigstr, d->sigstr) != 0 &&
+ (d->altstr != NULL && strcmp(sigstr, d->altstr) != 0)) {
+ sudo_warnx_nodebug("FAIL: signal %d: %s != %s", d->signo,
+ sigstr, d->sigstr);
+ errors++;
+ continue;
+ }
+ }
+
+ return errors;
+}
+#else
+static int
+test_sig2str(int *ntests)
+{
+ return 0;
+}
+#endif /* HAVE_SIG2STR */
+
+#ifndef HAVE_STR2SIG
+static int
+test_str2sig(int *ntests)
+{
+ struct signal_data *d;
+ int rval, errors = 0;
+ int signo;
+
+ for (d = signal_data; d->sigstr != NULL; d++) {
+ (*ntests)++;
+ rval = sudo_str2sig(d->sigstr, &signo);
+ if (rval != d->rval) {
+ sudo_warnx_nodebug("FAIL: str2sig(SIG%s): %d != %d",
+ d->sigstr, rval, d->rval);
+ errors++;
+ continue;
+ }
+ if (rval != 0)
+ continue;
+ if (signo != d->signo) {
+ sudo_warnx_nodebug("FAIL: signal SIG%s: %d != %d", d->sigstr,
+ signo, d->signo);
+ errors++;
+ continue;
+ }
+ }
+
+ return errors;
+}
+#else
+static int
+test_str2sig(int *ntests)
+{
+ return 0;
+}
+#endif /* HAVE_STR2SIG */
+
+#if defined(SIGRTMIN) && defined(SIGRTMAX)
+static
+void init_sigrt(void)
+{
+ int i;
+
+ /* Initialize real-time signal values. */
+ for (i = 0; signal_data[i].signo != -1; i++)
+ continue;
+ signal_data[i++].signo = SIGRTMIN;
+ signal_data[i++].signo = SIGRTMIN + 1;
+ signal_data[i++].signo = SIGRTMIN + 2;
+ signal_data[i++].signo = SIGRTMIN + 3;
+ signal_data[i++].signo = SIGRTMAX - 3;
+ signal_data[i++].signo = SIGRTMAX - 2;
+ signal_data[i++].signo = SIGRTMAX - 1;
+ signal_data[i++].signo = SIGRTMAX;
+
+}
+#else
+static
+void init_sigrt(void)
+{
+ /* No real-time signals. */
+ return;
+}
+#endif
+
+/*
+ * Simple tests for sig2str() and str2sig().
+ */
+int
+main(int argc, char *argv[])
+{
+ int ch, errors = 0, ntests = 0;
+
+ initprogname(argc > 0 ? argv[0] : "strsig_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ init_sigrt();
+ errors += test_sig2str(&ntests);
+ errors += test_str2sig(&ntests);
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/util/regress/strsplit/strsplit_test.c b/lib/util/regress/strsplit/strsplit_test.c
new file mode 100644
index 0000000..7f0e071
--- /dev/null
+++ b/lib/util/regress/strsplit/strsplit_test.c
@@ -0,0 +1,117 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_strsplit() works as expected.
+ */
+
+struct strsplit_test {
+ const char *input;
+ size_t input_len;
+ const char **output;
+};
+
+static const char test1_in[] = " vi ";
+static const char *test1_out[] = { "vi", NULL };
+static const char test2_in[] = "vi -r ";
+static const char *test2_out[] = { "vi", "-r", NULL };
+static const char test3_in[] = "vi -r -R abc\tdef ";
+static const char *test3_out[] = { "vi", "-r", "-R", "abc", "def", NULL };
+static const char test4_in[] = "vi -r -R abc\tdef ";
+static const char *test4_out[] = { "vi", "-r", "-R", "abc", NULL };
+static const char test5_in[] = "";
+static const char *test5_out[] = { NULL };
+
+static struct strsplit_test test_data[] = {
+ { test1_in, sizeof(test1_in) - 1, test1_out },
+ { test2_in, sizeof(test2_in) - 1, test2_out },
+ { test3_in, sizeof(test3_in) - 1, test3_out },
+ { test4_in, sizeof(test4_in) - 5, test4_out },
+ { test5_in, sizeof(test5_in) - 1, test5_out },
+ { NULL, 0, NULL }
+};
+
+int
+main(int argc, char *argv[])
+{
+ const char *cp, *ep, *input_end;
+ int ch, i, j, errors = 0, ntests = 0;
+ size_t len;
+
+ initprogname(argc > 0 ? argv[0] : "strsplit_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (i = 0; test_data[i].input != NULL; i++) {
+ input_end = test_data[i].input + test_data[i].input_len;
+ cp = sudo_strsplit(test_data[i].input, input_end, " \t", &ep);
+ for (j = 0; test_data[i].output[j] != NULL; j++) {
+ ntests++;
+ len = strlen(test_data[i].output[j]);
+ if ((size_t)(ep - cp) != len) {
+ sudo_warnx_nodebug("failed test #%d: bad length, expected "
+ "%zu, got %zu", ntests, len, (size_t)(ep - cp));
+ errors++;
+ continue;
+ }
+ ntests++;
+ if (strncmp(cp, test_data[i].output[j], len) != 0) {
+ sudo_warnx_nodebug("failed test #%d: expected %s, got %.*s",
+ ntests, test_data[i].output[j], (int)(ep - cp), cp);
+ errors++;
+ continue;
+ }
+ cp = sudo_strsplit(NULL, input_end, " \t", &ep);
+ }
+ ntests++;
+ if (cp != NULL) {
+ sudo_warnx_nodebug("failed test #%d: extra tokens \"%.*s\"",
+ ntests, (int)(input_end - cp), cp);
+ errors++;
+ }
+ }
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ exit(errors);
+}
diff --git a/lib/util/regress/strtofoo/strtobool_test.c b/lib/util/regress/strtofoo/strtobool_test.c
new file mode 100644
index 0000000..5b5a6cf
--- /dev/null
+++ b/lib/util/regress/strtofoo/strtobool_test.c
@@ -0,0 +1,98 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/* sudo_strtobool() tests */
+static struct strtobool_data {
+ const char *bool_str;
+ int value;
+} strtobool_data[] = {
+ { "true", true },
+ { "false", false },
+ { "TrUe", true },
+ { "fAlSe", false },
+ { "1", true },
+ { "0", false },
+ { "on", true },
+ { "off", false },
+ { "yes", true },
+ { "no", false },
+ { "nope", -1 },
+ { "10", -1 },
+ { "one", -1 },
+ { "zero", -1 },
+ { NULL, 0 }
+};
+
+/*
+ * Simple tests for sudo_strtobool()
+ */
+int
+main(int argc, char *argv[])
+{
+ struct strtobool_data *d;
+ int errors = 0, ntests = 0;
+ int ch, value;
+
+ initprogname(argc > 0 ? argv[0] : "strtobool_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (d = strtobool_data; d->bool_str != NULL; d++) {
+ ntests++;
+ value = sudo_strtobool(d->bool_str);
+ if (value != d->value) {
+ sudo_warnx_nodebug("FAIL: %s != %d", d->bool_str, d->value);
+ errors++;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/util/regress/strtofoo/strtoid_test.c b/lib/util/regress/strtofoo/strtoid_test.c
new file mode 100644
index 0000000..eec9d04
--- /dev/null
+++ b/lib/util/regress/strtofoo/strtoid_test.c
@@ -0,0 +1,118 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/* sudo_strtoidx() tests */
+static struct strtoidx_data {
+ const char *idstr;
+ id_t id;
+ const char *sep;
+ const char *ep;
+ int errnum;
+} strtoidx_data[] = {
+ { "0,1", 0, ",", ",", 0 },
+ { "10", 10, NULL, NULL, 0 },
+ { "-1", 0, NULL, NULL, EINVAL },
+ { "4294967295", 0, NULL, NULL, EINVAL },
+ { "4294967296", 0, NULL, NULL, ERANGE },
+ { "-2147483649", 0, NULL, NULL, ERANGE },
+ { "-2", -2, NULL, NULL, 0 },
+#if SIZEOF_ID_T != SIZEOF_LONG_LONG
+ { "-2", (id_t)4294967294U, NULL, NULL, 0 },
+#endif
+ { "4294967294", (id_t)4294967294U, NULL, NULL, 0 },
+ { NULL, 0, NULL, NULL, 0 }
+};
+
+/*
+ * Simple tests for sudo_strtoidx()
+ */
+int
+main(int argc, char *argv[])
+{
+ int ch, errors = 0, ntests = 0;
+ struct strtoidx_data *d;
+ const char *errstr;
+ char *ep;
+ id_t value;
+
+ initprogname(argc > 0 ? argv[0] : "strtoid_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (d = strtoidx_data; d->idstr != NULL; d++) {
+ ntests++;
+ errstr = "some error";
+ value = sudo_strtoidx(d->idstr, d->sep, &ep, &errstr);
+ if (d->errnum != 0) {
+ if (errstr == NULL) {
+ sudo_warnx_nodebug("FAIL: %s: missing errstr for errno %d",
+ d->idstr, d->errnum);
+ errors++;
+ } else if (value != 0) {
+ sudo_warnx_nodebug("FAIL: %s should return 0 on error",
+ d->idstr);
+ errors++;
+ } else if (errno != d->errnum) {
+ sudo_warnx_nodebug("FAIL: %s: errno mismatch, %d != %d",
+ d->idstr, errno, d->errnum);
+ errors++;
+ }
+ } else if (errstr != NULL) {
+ sudo_warnx_nodebug("FAIL: %s: %s", d->idstr, errstr);
+ errors++;
+ } else if (value != d->id) {
+ sudo_warnx_nodebug("FAIL: %s != %u", d->idstr, (unsigned int)d->id);
+ errors++;
+ } else if (d->ep != NULL && ep[0] != d->ep[0]) {
+ sudo_warnx_nodebug("FAIL: ep[0] %d != %d", (int)(unsigned char)ep[0],
+ (int)(unsigned char)d->ep[0]);
+ errors++;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/util/regress/strtofoo/strtomode_test.c b/lib/util/regress/strtofoo/strtomode_test.c
new file mode 100644
index 0000000..5e4d862
--- /dev/null
+++ b/lib/util/regress/strtofoo/strtomode_test.c
@@ -0,0 +1,91 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/* sudo_strtomode() tests */
+static struct strtomode_data {
+ const char *mode_str;
+ mode_t mode;
+} strtomode_data[] = {
+ { "755", 0755 },
+ { "007", 007 },
+ { "7", 7 },
+ { "8", (mode_t)-1 },
+ { NULL, 0 }
+};
+
+/*
+ * Simple tests for sudo_strtomode().
+ */
+int
+main(int argc, char *argv[])
+{
+ struct strtomode_data *d;
+ const char *errstr;
+ int ch, errors = 0, ntests = 0;
+ mode_t mode;
+
+ initprogname(argc > 0 ? argv[0] : "strtomode_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (d = strtomode_data; d->mode_str != NULL; d++) {
+ ntests++;
+ errstr = "some error";
+ mode = sudo_strtomode(d->mode_str, &errstr);
+ if (errstr != NULL) {
+ if (d->mode != (mode_t)-1) {
+ sudo_warnx_nodebug("FAIL: %s: %s", d->mode_str, errstr);
+ errors++;
+ }
+ } else if (mode != d->mode) {
+ sudo_warnx_nodebug("FAIL: %s != 0%o", d->mode_str,
+ (unsigned int) d->mode);
+ errors++;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/util/regress/strtofoo/strtonum_test.c b/lib/util/regress/strtofoo/strtonum_test.c
new file mode 100644
index 0000000..8aca97d
--- /dev/null
+++ b/lib/util/regress/strtofoo/strtonum_test.c
@@ -0,0 +1,135 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/* sudo_strtonum() tests */
+static struct strtonum_data {
+ const char *str;
+ long long minval;
+ long long maxval;
+ long long retval;
+ int errnum;
+} strtonum_data[] = {
+ { "0,1", LLONG_MIN, LLONG_MAX, 0, EINVAL },
+ { "0", INT_MAX, INT_MIN, 0, EINVAL },
+ { "", 0, UINT_MAX, 0, EINVAL },
+ { " ", 0, UINT_MAX, 0, EINVAL },
+ { "-1 ", 0, UINT_MAX, 0, EINVAL },
+ { "9223372036854775808X", LLONG_MIN, LLONG_MAX, 0, EINVAL },
+ { "-9223372036854775809X", LLONG_MIN, LLONG_MAX, 0, EINVAL },
+
+ { "10", 0, 255, 10, 0 },
+ { "-1", 0, UINT_MAX, 0, ERANGE },
+
+ { "-40", -100, -50, 0, ERANGE },
+ { "-60", -100, -50, -60, 0 },
+ { "-200", -100, -50, 0, ERANGE },
+
+ { "42", 42, 42, 42, 0 },
+ { "-42", -42, -42, -42, 0 },
+
+ { "4294967295", 0, UINT_MAX, UINT_MAX, 0 },
+ { "4294967295", INT_MIN, INT_MAX, 0, ERANGE },
+ { "4294967296", 0, UINT_MAX, 0, ERANGE },
+
+ { "2147483647", INT_MIN, INT_MAX, INT_MAX, 0 },
+ { "-2147483648", INT_MIN, INT_MAX, INT_MIN, 0 },
+ { "2147483648", INT_MIN, INT_MAX, 0, ERANGE },
+ { "-2147483649", INT_MIN, INT_MAX, 0, ERANGE },
+
+ { "9223372036854775807", LLONG_MIN, LLONG_MAX, LLONG_MAX, 0 },
+ { "-9223372036854775808", LLONG_MIN, LLONG_MAX, LLONG_MIN, 0 },
+ { "9223372036854775808", LLONG_MIN, LLONG_MAX, 0, ERANGE },
+ { "-9223372036854775809", LLONG_MIN, LLONG_MAX, 0, ERANGE },
+
+ { NULL, 0, 0, 0, 0 }
+};
+
+/*
+ * Simple tests for sudo_strtonum()
+ */
+int
+main(int argc, char *argv[])
+{
+ int ch, errors = 0, ntests = 0;
+ struct strtonum_data *d;
+ const char *errstr;
+ long long value;
+
+ initprogname(argc > 0 ? argv[0] : "strtonum_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (d = strtonum_data; d->str != NULL; d++) {
+ ntests++;
+ errstr = "some error";
+ value = sudo_strtonum(d->str, d->minval, d->maxval, &errstr);
+ if (d->errnum != 0) {
+ if (errstr == NULL) {
+ sudo_warnx_nodebug("FAIL: \"%s\": missing errstr for errno %d",
+ d->str, d->errnum);
+ errors++;
+ } else if (value != 0) {
+ sudo_warnx_nodebug("FAIL: %s should return 0 on error",
+ d->str);
+ errors++;
+ } else if (errno != d->errnum) {
+ sudo_warnx_nodebug("FAIL: \"%s\": errno mismatch, %d != %d",
+ d->str, errno, d->errnum);
+ errors++;
+ }
+ } else if (errstr != NULL) {
+ sudo_warnx_nodebug("FAIL: \"%s\": %s", d->str, errstr);
+ errors++;
+ } else if (value != d->retval) {
+ sudo_warnx_nodebug("FAIL: %s != %lld", d->str, d->retval);
+ errors++;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/util/regress/sudo_conf/conf_test.c b/lib/util/regress/sudo_conf/conf_test.c
new file mode 100644
index 0000000..1d9c1b6
--- /dev/null
+++ b/lib/util/regress/sudo_conf/conf_test.c
@@ -0,0 +1,125 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2022 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+static void sudo_conf_dump(void);
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/* Awful hack for macOS where the default group source is dynamic. */
+#ifdef __APPLE__
+# undef GROUP_SOURCE_ADAPTIVE
+# define GROUP_SOURCE_ADAPTIVE GROUP_SOURCE_DYNAMIC
+#endif
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-v] conf_file\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Simple test driver for sudo_conf().
+ * Parses the given configuration file and dumps the resulting
+ * sudo_conf_data struct to the standard output.
+ */
+int
+main(int argc, char *argv[])
+{
+ int ch;
+
+ initprogname(argc > 0 ? argv[0] : "conf_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ sudo_conf_clear_paths();
+ if (sudo_conf_read(argv[0], SUDO_CONF_ALL) == -1)
+ return EXIT_FAILURE;
+ sudo_conf_dump();
+
+ return EXIT_SUCCESS;
+}
+
+static void
+sudo_conf_dump(void)
+{
+ struct plugin_info_list *plugins = sudo_conf_plugins();
+ struct sudo_conf_debug_list *debug_list = sudo_conf_debugging();
+ struct sudo_conf_debug *debug_spec;
+ struct sudo_debug_file *debug_file;
+ struct plugin_info *info;
+
+ printf("Set disable_coredump %s\n",
+ sudo_conf_disable_coredump() ? "true" : "false");
+ printf("Set group_source %s\n",
+ sudo_conf_group_source() == GROUP_SOURCE_ADAPTIVE ? "adaptive" :
+ sudo_conf_group_source() == GROUP_SOURCE_STATIC ? "static" : "dynamic");
+ printf("Set max_groups %d\n", sudo_conf_max_groups());
+ printf("Set probe_interfaces %s\n",
+ sudo_conf_probe_interfaces() ? "true" : "false");
+ if (sudo_conf_askpass_path() != NULL)
+ printf("Path askpass %s\n", sudo_conf_askpass_path());
+ if (sudo_conf_sesh_path() != NULL)
+ printf("Path sesh %s\n", sudo_conf_sesh_path());
+ if (sudo_conf_intercept_path() != NULL)
+ printf("Path intercept %s\n", sudo_conf_intercept_path());
+ if (sudo_conf_noexec_path() != NULL)
+ printf("Path noexec %s\n", sudo_conf_noexec_path());
+ if (sudo_conf_plugin_dir_path() != NULL)
+ printf("Path plugin_dir %s\n", sudo_conf_plugin_dir_path());
+ TAILQ_FOREACH(info, plugins, entries) {
+ printf("Plugin %s %s", info->symbol_name, info->path);
+ if (info->options) {
+ char * const * op;
+ for (op = info->options; *op != NULL; op++)
+ printf(" %s", *op);
+ }
+ putchar('\n');
+ }
+ TAILQ_FOREACH(debug_spec, debug_list, entries) {
+ TAILQ_FOREACH(debug_file, &debug_spec->debug_files, entries) {
+ printf("Debug %s %s %s\n", debug_spec->progname,
+ debug_file->debug_file, debug_file->debug_flags);
+ }
+ }
+}
diff --git a/lib/util/regress/sudo_conf/test1.in b/lib/util/regress/sudo_conf/test1.in
new file mode 100644
index 0000000..bc9f626
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test1.in
@@ -0,0 +1,73 @@
+#
+# Sample /etc/sudo.conf file
+#
+# Format:
+# Plugin plugin_name plugin_path plugin_options ...
+# Path askpass /path/to/askpass
+# Path noexec /path/to/sudo_noexec.so
+# Debug sudo /var/log/sudo_debug all@warn
+# Set disable_coredump true
+#
+# Sudo plugins:
+#
+# The plugin_path is relative to ${prefix}/libexec unless fully qualified.
+# The plugin_name corresponds to a global symbol in the plugin
+# that contains the plugin interface structure.
+# The plugin_options are optional.
+#
+# The sudoers plugin is used by default if no Plugin lines are present.
+Plugin sudoers_policy sudoers.so
+Plugin sudoers_io sudoers.so
+
+#
+# Sudo askpass:
+#
+# An askpass helper program may be specified to provide a graphical
+# password prompt for "sudo -A" support. Sudo does not ship with its
+# own askpass program but can use the OpenSSH askpass.
+#
+# Use the OpenSSH askpass
+Path askpass /usr/X11R6/bin/ssh-askpass
+#
+# Use the Gnome OpenSSH askpass
+#Path askpass /usr/libexec/openssh/gnome-ssh-askpass
+
+#
+# Sudo noexec:
+#
+# Path to a shared library containing replacements for the execv(),
+# execve() and fexecve() library functions that just return an error.
+# This is used to implement the "noexec" functionality on systems that
+# support LD_PRELOAD or its equivalent.
+# The compiled-in value is usually sufficient and should only be changed
+# if you rename or move the sudo_noexec.so file.
+#
+Path noexec /usr/local/libexec/sudo_noexec.so
+Path noexec /usr/libexec/sudo_noexec.so
+
+#
+# Core dumps:
+#
+# By default, sudo disables core dumps while it is executing (they
+# are re-enabled for the command that is run).
+# To aid in debugging sudo problems, you may wish to enable core
+# dumps by setting "disable_coredump" to false.
+#
+Set disable_coredump false
+
+#
+# User groups:
+#
+# Sudo passes the user's group list to the policy plugin.
+# If the user is a member of the maximum number of groups (usually 16),
+# sudo will query the group database directly to be sure to include
+# the full list of groups.
+#
+# On some systems, this can be expensive so the behavior is configurable.
+# The "group_source" setting has three possible values:
+# static - use the user's list of groups returned by the kernel.
+# dynamic - query the group database to find the list of groups.
+# adaptive - if user is in less than the maximum number of groups.
+# use the kernel list, else query the group database.
+#
+Set group_source static
diff --git a/lib/util/regress/sudo_conf/test1.out.ok b/lib/util/regress/sudo_conf/test1.out.ok
new file mode 100644
index 0000000..d5b784c
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test1.out.ok
@@ -0,0 +1,8 @@
+Set disable_coredump false
+Set group_source static
+Set max_groups -1
+Set probe_interfaces true
+Path askpass /usr/X11R6/bin/ssh-askpass
+Path noexec /usr/libexec/sudo_noexec.so
+Plugin sudoers_policy sudoers.so
+Plugin sudoers_io sudoers.so
diff --git a/lib/util/regress/sudo_conf/test2.in b/lib/util/regress/sudo_conf/test2.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test2.in
diff --git a/lib/util/regress/sudo_conf/test2.out.ok b/lib/util/regress/sudo_conf/test2.out.ok
new file mode 100644
index 0000000..cfd8a08
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test2.out.ok
@@ -0,0 +1,4 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups -1
+Set probe_interfaces true
diff --git a/lib/util/regress/sudo_conf/test3.in b/lib/util/regress/sudo_conf/test3.in
new file mode 100644
index 0000000..b111a23
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test3.in
@@ -0,0 +1,2 @@
+Plugin sudoers_policy sudoers.so sudoers_file=/etc/sudoers sudoers_mode=0400 sudoers_gid=0 sudoers_uid=0
+Plugin sudoers_io sudoers.so
diff --git a/lib/util/regress/sudo_conf/test3.out.ok b/lib/util/regress/sudo_conf/test3.out.ok
new file mode 100644
index 0000000..3ff2284
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test3.out.ok
@@ -0,0 +1,6 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups -1
+Set probe_interfaces true
+Plugin sudoers_policy sudoers.so sudoers_file=/etc/sudoers sudoers_mode=0400 sudoers_gid=0 sudoers_uid=0
+Plugin sudoers_io sudoers.so
diff --git a/lib/util/regress/sudo_conf/test4.err.ok b/lib/util/regress/sudo_conf/test4.err.ok
new file mode 100644
index 0000000..2d68831
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test4.err.ok
@@ -0,0 +1 @@
+conf_test: invalid value for disable_coredump "foo" in regress/sudo_conf/test4.in, line 1
diff --git a/lib/util/regress/sudo_conf/test4.in b/lib/util/regress/sudo_conf/test4.in
new file mode 100644
index 0000000..a60236a
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test4.in
@@ -0,0 +1 @@
+Set disable_coredump foo
diff --git a/lib/util/regress/sudo_conf/test4.out.ok b/lib/util/regress/sudo_conf/test4.out.ok
new file mode 100644
index 0000000..cfd8a08
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test4.out.ok
@@ -0,0 +1,4 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups -1
+Set probe_interfaces true
diff --git a/lib/util/regress/sudo_conf/test5.err.ok b/lib/util/regress/sudo_conf/test5.err.ok
new file mode 100644
index 0000000..85ef46b
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test5.err.ok
@@ -0,0 +1 @@
+conf_test: invalid max groups "0" in regress/sudo_conf/test5.in, line 1
diff --git a/lib/util/regress/sudo_conf/test5.in b/lib/util/regress/sudo_conf/test5.in
new file mode 100644
index 0000000..3a20495
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test5.in
@@ -0,0 +1 @@
+Set max_groups 0
diff --git a/lib/util/regress/sudo_conf/test5.out.ok b/lib/util/regress/sudo_conf/test5.out.ok
new file mode 100644
index 0000000..cfd8a08
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test5.out.ok
@@ -0,0 +1,4 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups -1
+Set probe_interfaces true
diff --git a/lib/util/regress/sudo_conf/test6.in b/lib/util/regress/sudo_conf/test6.in
new file mode 100644
index 0000000..537fa57
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test6.in
@@ -0,0 +1 @@
+Set max_groups 16
diff --git a/lib/util/regress/sudo_conf/test6.out.ok b/lib/util/regress/sudo_conf/test6.out.ok
new file mode 100644
index 0000000..674ae38
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test6.out.ok
@@ -0,0 +1,4 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups 16
+Set probe_interfaces true
diff --git a/lib/util/regress/sudo_conf/test7.in b/lib/util/regress/sudo_conf/test7.in
new file mode 100644
index 0000000..7438131
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test7.in
@@ -0,0 +1,4 @@
+Debug sudo /var/log/sudo_debug all@info
+Debug sudo /var/log/sudo_debug util@debug
+Debug visudo /var/log/sudo_debug match@debug
+Debug sudoers.so /var/log/sudoers_debug match@debug,nss@info
diff --git a/lib/util/regress/sudo_conf/test7.out.ok b/lib/util/regress/sudo_conf/test7.out.ok
new file mode 100644
index 0000000..7ec856d
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test7.out.ok
@@ -0,0 +1,8 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups -1
+Set probe_interfaces true
+Debug sudo /var/log/sudo_debug all@info
+Debug sudo /var/log/sudo_debug util@debug
+Debug visudo /var/log/sudo_debug match@debug
+Debug sudoers.so /var/log/sudoers_debug match@debug,nss@info
diff --git a/lib/util/regress/sudo_parseln/parseln_test.c b/lib/util/regress/sudo_parseln/parseln_test.c
new file mode 100644
index 0000000..8cd7294
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/parseln_test.c
@@ -0,0 +1,64 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Simple test driver for sudo_parseln().
+ * Behaves similarly to "cat -n" but with comment removal
+ * and line continuation.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ unsigned int lineno = 0;
+ size_t linesize = 0;
+ char *line = NULL;
+ int ch;
+
+ initprogname(argc > 0 ? argv[0] : "parseln_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ while (sudo_parseln(&line, &linesize, &lineno, stdin, 0) != -1)
+ printf("%6u\t%s\n", lineno, line);
+ free(line);
+ return EXIT_SUCCESS;
+}
diff --git a/lib/util/regress/sudo_parseln/test1.in b/lib/util/regress/sudo_parseln/test1.in
new file mode 100644
index 0000000..8f417dd
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test1.in
@@ -0,0 +1,72 @@
+#
+# Sample /etc/sudo.conf file
+#
+# Format:
+# Plugin plugin_name plugin_path plugin_options ...
+# Path askpass /path/to/askpass
+# Path noexec /path/to/sudo_noexec.so
+# Debug sudo /var/log/sudo_debug all@warn
+# Set disable_coredump true
+#
+# Sudo plugins:
+#
+# The plugin_path is relative to ${prefix}/libexec unless fully qualified.
+# The plugin_name corresponds to a global symbol in the plugin
+# that contains the plugin interface structure.
+# The plugin_options are optional.
+#
+# The sudoers plugin is used by default if no Plugin lines are present.
+Plugin sudoers_policy sudoers.so
+Plugin sudoers_io sudoers.so
+
+#
+# Sudo askpass:
+#
+# An askpass helper program may be specified to provide a graphical
+# password prompt for "sudo -A" support. Sudo does not ship with its
+# own askpass program but can use the OpenSSH askpass.
+#
+# Use the OpenSSH askpass
+#Path askpass /usr/X11R6/bin/ssh-askpass
+#
+# Use the Gnome OpenSSH askpass
+#Path askpass /usr/libexec/openssh/gnome-ssh-askpass
+
+#
+# Sudo noexec:
+#
+# Path to a shared library containing replacements for the execv(),
+# execve() and fexecve() library functions that just return an error.
+# This is used to implement the "noexec" functionality on systems that
+# support LD_PRELOAD or its equivalent.
+# The compiled-in value is usually sufficient and should only be changed
+# if you rename or move the sudo_noexec.so file.
+#
+#Path noexec /usr/libexec/sudo_noexec.so
+
+#
+# Core dumps:
+#
+# By default, sudo disables core dumps while it is executing (they
+# are re-enabled for the command that is run).
+# To aid in debugging sudo problems, you may wish to enable core
+# dumps by setting "disable_coredump" to false.
+#
+#Set disable_coredump false
+
+#
+# User groups:
+#
+# Sudo passes the user's group list to the policy plugin.
+# If the user is a member of the maximum number of groups (usually 16),
+# sudo will query the group database directly to be sure to include
+# the full list of groups.
+#
+# On some systems, this can be expensive so the behavior is configurable.
+# The "group_source" setting has three possible values:
+# static - use the user's list of groups returned by the kernel.
+# dynamic - query the group database to find the list of groups.
+# adaptive - if user is in less than the maximum number of groups.
+# use the kernel list, else query the group database.
+#
+#Set group_source static
diff --git a/lib/util/regress/sudo_parseln/test1.out.ok b/lib/util/regress/sudo_parseln/test1.out.ok
new file mode 100644
index 0000000..c98ca77
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test1.out.ok
@@ -0,0 +1,72 @@
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19 Plugin sudoers_policy sudoers.so
+ 20 Plugin sudoers_io sudoers.so
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
diff --git a/lib/util/regress/sudo_parseln/test2.in b/lib/util/regress/sudo_parseln/test2.in
new file mode 100644
index 0000000..49166ee
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test2.in
@@ -0,0 +1,8 @@
+this \
+is all \
+one line
+# this is a comment, and does not get continued\
+trim the \
+ leading \
+ white \
+space
diff --git a/lib/util/regress/sudo_parseln/test2.out.ok b/lib/util/regress/sudo_parseln/test2.out.ok
new file mode 100644
index 0000000..d921968
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test2.out.ok
@@ -0,0 +1,3 @@
+ 3 this is all one line
+ 4
+ 8 trim the leading white space
diff --git a/lib/util/regress/sudo_parseln/test3.in b/lib/util/regress/sudo_parseln/test3.in
new file mode 100644
index 0000000..e372c07
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test3.in
@@ -0,0 +1 @@
+line continuation at EOF \
diff --git a/lib/util/regress/sudo_parseln/test3.out.ok b/lib/util/regress/sudo_parseln/test3.out.ok
new file mode 100644
index 0000000..2e8d16d
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test3.out.ok
@@ -0,0 +1 @@
+ 1 line continuation at EOF
diff --git a/lib/util/regress/sudo_parseln/test4.in b/lib/util/regress/sudo_parseln/test4.in
new file mode 100644
index 0000000..3583f3b
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test4.in
@@ -0,0 +1,4 @@
+line contin\
+uation raw
+line contin\
+ uation indented
diff --git a/lib/util/regress/sudo_parseln/test4.out.ok b/lib/util/regress/sudo_parseln/test4.out.ok
new file mode 100644
index 0000000..38afbeb
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test4.out.ok
@@ -0,0 +1,2 @@
+ 2 line continuation raw
+ 4 line continuation indented
diff --git a/lib/util/regress/sudo_parseln/test5.in b/lib/util/regress/sudo_parseln/test5.in
new file mode 100644
index 0000000..57ddad2
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test5.in
@@ -0,0 +1 @@
+\
diff --git a/lib/util/regress/sudo_parseln/test5.out.ok b/lib/util/regress/sudo_parseln/test5.out.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test5.out.ok
diff --git a/lib/util/regress/sudo_parseln/test6.in b/lib/util/regress/sudo_parseln/test6.in
new file mode 100644
index 0000000..95cac84
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test6.in
@@ -0,0 +1,3 @@
+ leading and trailing white space
+ # a comment
+\
diff --git a/lib/util/regress/sudo_parseln/test6.out.ok b/lib/util/regress/sudo_parseln/test6.out.ok
new file mode 100644
index 0000000..340765e
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test6.out.ok
@@ -0,0 +1,2 @@
+ 1 leading and trailing white space
+ 2
diff --git a/lib/util/regress/tailq/hltq_test.c b/lib/util/regress/tailq/hltq_test.c
new file mode 100644
index 0000000..2a1b2bb
--- /dev/null
+++ b/lib/util/regress/tailq/hltq_test.c
@@ -0,0 +1,205 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_queue.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Note: HLTQ_ENTRY is intentionally in the middle of the struct
+ * to catch bad assumptions in the PREV/NEXT macros.
+ */
+struct test_data {
+ int a;
+ HLTQ_ENTRY(test_data) entries;
+ char b;
+};
+
+TAILQ_HEAD(test_data_list, test_data);
+
+/*
+ * Simple tests for headless tail queue macros.
+ */
+int
+main(int argc, char *argv[])
+{
+ struct test_data d1, d2, d3;
+ struct test_data *hltq;
+ struct test_data_list tq;
+ int ch, errors = 0, ntests = 0;
+
+ initprogname(argc > 0 ? argv[0] : "hltq_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /*
+ * Initialize three data elements and concatenate them in order.
+ */
+ HLTQ_INIT(&d1, entries);
+ d1.a = 1;
+ d1.b = 'a';
+ if (HLTQ_FIRST(&d1) != &d1) {
+ sudo_warnx_nodebug("FAIL: HLTQ_FIRST(1 entry) doesn't return first element: got %p, expected %p", HLTQ_FIRST(&d1), &d1);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_LAST(&d1, test_data, entries) != &d1) {
+ sudo_warnx_nodebug("FAIL: HLTQ_LAST(1 entry) doesn't return first element: got %p, expected %p", HLTQ_LAST(&d1, test_data, entries), &d1);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_PREV(&d1, test_data, entries) != NULL) {
+ sudo_warnx_nodebug("FAIL: HLTQ_PREV(1 entry) doesn't return NULL: got %p", HLTQ_PREV(&d1, test_data, entries));
+ errors++;
+ }
+ ntests++;
+
+ HLTQ_INIT(&d2, entries);
+ d2.a = 2;
+ d2.b = 'b';
+
+ HLTQ_INIT(&d3, entries);
+ d3.a = 3;
+ d3.b = 'c';
+
+ HLTQ_CONCAT(&d1, &d2, entries);
+ HLTQ_CONCAT(&d1, &d3, entries);
+ hltq = &d1;
+
+ /*
+ * Verify that HLTQ_FIRST, HLTQ_LAST, HLTQ_NEXT, HLTQ_PREV
+ * work as expected.
+ */
+ if (HLTQ_FIRST(hltq) != &d1) {
+ sudo_warnx_nodebug("FAIL: HLTQ_FIRST(3 entries) doesn't return first element: got %p, expected %p", HLTQ_FIRST(hltq), &d1);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_LAST(hltq, test_data, entries) != &d3) {
+ sudo_warnx_nodebug("FAIL: HLTQ_LAST(3 entries) doesn't return third element: got %p, expected %p", HLTQ_LAST(hltq, test_data, entries), &d3);
+ errors++;
+ }
+ ntests++;
+
+ if (HLTQ_NEXT(&d1, entries) != &d2) {
+ sudo_warnx_nodebug("FAIL: HLTQ_NEXT(&d1) doesn't return &d2: got %p, expected %p", HLTQ_NEXT(&d1, entries), &d2);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_NEXT(&d2, entries) != &d3) {
+ sudo_warnx_nodebug("FAIL: HLTQ_NEXT(&d2) doesn't return &d3: got %p, expected %p", HLTQ_NEXT(&d2, entries), &d3);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_NEXT(&d3, entries) != NULL) {
+ sudo_warnx_nodebug("FAIL: HLTQ_NEXT(&d3) doesn't return NULL: got %p", HLTQ_NEXT(&d3, entries));
+ errors++;
+ }
+ ntests++;
+
+ if (HLTQ_PREV(&d1, test_data, entries) != NULL) {
+ sudo_warnx_nodebug("FAIL: HLTQ_PREV(&d1) doesn't return NULL: got %p", HLTQ_PREV(&d1, test_data, entries));
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_PREV(&d2, test_data, entries) != &d1) {
+ sudo_warnx_nodebug("FAIL: HLTQ_PREV(&d2) doesn't return &d1: got %p, expected %p", HLTQ_PREV(&d2, test_data, entries), &d1);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_PREV(&d3, test_data, entries) != &d2) {
+ sudo_warnx_nodebug("FAIL: HLTQ_PREV(&d3) doesn't return &d2: got %p, expected %p", HLTQ_PREV(&d3, test_data, entries), &d2);
+ errors++;
+ }
+ ntests++;
+
+ /* Test conversion to TAILQ. */
+ HLTQ_TO_TAILQ(&tq, hltq, entries);
+
+ if (TAILQ_FIRST(&tq) != &d1) {
+ sudo_warnx_nodebug("FAIL: TAILQ_FIRST(&tq) doesn't return first element: got %p, expected %p", TAILQ_FIRST(&tq), &d1);
+ errors++;
+ }
+ ntests++;
+ if (TAILQ_LAST(&tq, test_data_list) != &d3) {
+ sudo_warnx_nodebug("FAIL: TAILQ_LAST(&tq) doesn't return third element: got %p, expected %p", TAILQ_LAST(&tq, test_data_list), &d3);
+ errors++;
+ }
+ ntests++;
+
+ if (TAILQ_NEXT(&d1, entries) != &d2) {
+ sudo_warnx_nodebug("FAIL: TAILQ_NEXT(&d1) doesn't return &d2: got %p, expected %p", TAILQ_NEXT(&d1, entries), &d2);
+ errors++;
+ }
+ ntests++;
+ if (TAILQ_NEXT(&d2, entries) != &d3) {
+ sudo_warnx_nodebug("FAIL: TAILQ_NEXT(&d2) doesn't return &d3: got %p, expected %p", TAILQ_NEXT(&d2, entries), &d3);
+ errors++;
+ }
+ ntests++;
+ if (TAILQ_NEXT(&d3, entries) != NULL) {
+ sudo_warnx_nodebug("FAIL: TAILQ_NEXT(&d3) doesn't return NULL: got %p", TAILQ_NEXT(&d3, entries));
+ errors++;
+ }
+ ntests++;
+
+ if (TAILQ_PREV(&d1, test_data_list, entries) != NULL) {
+ sudo_warnx_nodebug("FAIL: TAILQ_PREV(&d1) doesn't return NULL: got %p", TAILQ_PREV(&d1, test_data_list, entries));
+ errors++;
+ }
+ ntests++;
+ if (TAILQ_PREV(&d2, test_data_list, entries) != &d1) {
+ sudo_warnx_nodebug("FAIL: TAILQ_PREV(&d2) doesn't return &d1: got %p, expected %p", TAILQ_PREV(&d2, test_data_list, entries), &d1);
+ errors++;
+ }
+ ntests++;
+ if (TAILQ_PREV(&d3, test_data_list, entries) != &d2) {
+ sudo_warnx_nodebug("FAIL: TAILQ_PREV(&d3) doesn't return &d2: got %p, expected %p", TAILQ_PREV(&d3, test_data_list, entries), &d2);
+ errors++;
+ }
+ ntests++;
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ exit(errors);
+}
diff --git a/lib/util/regress/uuid/uuid_test.c b/lib/util/regress/uuid/uuid_test.c
new file mode 100644
index 0000000..37ef417
--- /dev/null
+++ b/lib/util/regress/uuid/uuid_test.c
@@ -0,0 +1,105 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <string.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_uuid_create() generates a variant 1, version 4 uuid.
+ */
+
+/* From RFC 4122. */
+struct uuid {
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_hi_and_version;
+ uint8_t clock_seq_hi_and_reserved;
+ uint8_t clock_seq_low;
+ uint8_t node[6];
+};
+
+int
+main(int argc, char *argv[])
+{
+ int ch, errors = 0, ntests = 0;
+ union {
+ struct uuid id;
+ unsigned char u8[16];
+ } uuid;
+
+ initprogname(argc > 0 ? argv[0] : "uuid_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignore */
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-v]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Do 16 passes. */
+ for (ntests = 0; ntests < 16; ntests++) {
+ sudo_uuid_create(uuid.u8);
+
+ /* Variant: two most significant bits (6 and 7) are 0 and 1. */
+ if (ISSET(uuid.id.clock_seq_hi_and_reserved, (1 << 6))) {
+ sudo_warnx("uuid bit 6 set, should be clear");
+ errors++;
+ continue;
+ }
+ if (!ISSET(uuid.id.clock_seq_hi_and_reserved, (1 << 7))) {
+ sudo_warnx("uuid bit 7 clear, should be set");
+ errors++;
+ continue;
+ }
+
+ /* Version: bits 12-15 are 0010. */
+ if ((uuid.id.time_hi_and_version & 0xf000) != 0x4000) {
+ sudo_warnx("bad version: 0x%x", uuid.id.time_hi_and_version & 0xf000);
+ errors++;
+ continue;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ return errors;
+}
diff --git a/lib/util/roundup.c b/lib/util/roundup.c
new file mode 100644
index 0000000..0e7e8fd
--- /dev/null
+++ b/lib/util/roundup.c
@@ -0,0 +1,48 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * Round 32-bit unsigned length to the next highest power of two.
+ * Always returns at least 64.
+ * Algorithm from bit twiddling hacks.
+ */
+unsigned int
+sudo_pow2_roundup_v1(unsigned int len)
+{
+ if (len < 64)
+ return 64;
+ len--;
+ len |= len >> 1;
+ len |= len >> 2;
+ len |= len >> 4;
+ len |= len >> 8;
+ len |= len >> 16;
+ len++;
+ return len;
+}
diff --git a/lib/util/secure_path.c b/lib/util/secure_path.c
new file mode 100644
index 0000000..89d21de
--- /dev/null
+++ b/lib/util/secure_path.c
@@ -0,0 +1,145 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2012, 2014-2022 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_debug.h"
+
+/*
+ * Verify that path is the right type and not writable by other users.
+ */
+static int
+sudo_check_secure(struct stat *sb, unsigned int type, uid_t uid, gid_t gid)
+{
+ int ret = SUDO_PATH_SECURE;
+ debug_decl(sudo_check_secure, SUDO_DEBUG_UTIL);
+
+ if ((sb->st_mode & S_IFMT) != type) {
+ ret = SUDO_PATH_BAD_TYPE;
+ } else if (uid != (uid_t)-1 && sb->st_uid != uid) {
+ ret = SUDO_PATH_WRONG_OWNER;
+ } else if (sb->st_mode & S_IWOTH) {
+ ret = SUDO_PATH_WORLD_WRITABLE;
+ } else if (ISSET(sb->st_mode, S_IWGRP) &&
+ (gid == (gid_t)-1 || sb->st_gid != gid)) {
+ ret = SUDO_PATH_GROUP_WRITABLE;
+ }
+
+ debug_return_int(ret);
+}
+
+/*
+ * Verify that path is the right type and not writable by other users.
+ */
+static int
+sudo_secure_path(const char *path, unsigned int type, uid_t uid, gid_t gid,
+ struct stat *sb)
+{
+ int ret = SUDO_PATH_MISSING;
+ struct stat stat_buf;
+ debug_decl(sudo_secure_path, SUDO_DEBUG_UTIL);
+
+ if (sb == NULL)
+ sb = &stat_buf;
+
+ if (path != NULL && stat(path, sb) == 0)
+ ret = sudo_check_secure(sb, type, uid, gid);
+
+ debug_return_int(ret);
+}
+
+/*
+ * Verify that path is a regular file and not writable by other users.
+ * Not currently used.
+ */
+int
+sudo_secure_file_v1(const char *path, uid_t uid, gid_t gid, struct stat *sb)
+{
+ return sudo_secure_path(path, S_IFREG, uid, gid, sb);
+}
+
+/*
+ * Verify that path is a directory and not writable by other users.
+ */
+int
+sudo_secure_dir_v1(const char *path, uid_t uid, gid_t gid, struct stat *sb)
+{
+ return sudo_secure_path(path, S_IFDIR, uid, gid, sb);
+}
+
+/*
+ * Open path read-only as long as it is not writable by other users.
+ * Returns an open file descriptor on success, else -1.
+ * Sets error to SUDO_PATH_SECURE on success, and a value < 0 on failure.
+ */
+static int
+sudo_secure_open(const char *path, int type, uid_t uid, gid_t gid,
+ struct stat *sb, int *error)
+{
+ struct stat stat_buf;
+ int fd;
+ debug_decl(sudo_secure_open, SUDO_DEBUG_UTIL);
+
+ if (sb == NULL)
+ sb = &stat_buf;
+
+ fd = open(path, O_RDONLY|O_NONBLOCK);
+ if (fd == -1 || fstat(fd, sb) != 0) {
+ if (fd != -1)
+ close(fd);
+ *error = SUDO_PATH_MISSING;
+ debug_return_int(-1);
+ }
+
+ *error = sudo_check_secure(sb, type, uid, gid);
+ if (*error == SUDO_PATH_SECURE) {
+ (void)fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);
+ } else {
+ /* Not secure, caller can check error flag. */
+ close(fd);
+ fd = -1;
+ }
+
+ debug_return_int(fd);
+}
+
+int
+sudo_secure_open_file_v1(const char *path, uid_t uid, gid_t gid,
+ struct stat *sb, int *error)
+{
+ return sudo_secure_open(path, S_IFREG, uid, gid, sb, error);
+}
+
+int
+sudo_secure_open_dir_v1(const char *path, uid_t uid, gid_t gid,
+ struct stat *sb, int *error)
+{
+ return sudo_secure_open(path, S_IFDIR, uid, gid, sb, error);
+}
diff --git a/lib/util/setgroups.c b/lib/util/setgroups.c
new file mode 100644
index 0000000..f424ddb
--- /dev/null
+++ b/lib/util/setgroups.c
@@ -0,0 +1,52 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011-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 <unistd.h>
+#include <errno.h>
+#include <grp.h>
+#include <limits.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+int
+sudo_setgroups_v1(int ngids, const GETGROUPS_T *gids)
+{
+ int maxgids, ret;
+ debug_decl(sudo_setgroups, SUDO_DEBUG_UTIL);
+
+ ret = setgroups(ngids, (GETGROUPS_T *)gids);
+ if (ret == -1 && errno == EINVAL) {
+ /* Too many groups, try again with fewer. */
+ maxgids = (int)sysconf(_SC_NGROUPS_MAX);
+ if (maxgids == -1)
+ maxgids = NGROUPS_MAX;
+ if (ngids > maxgids)
+ ret = setgroups(maxgids, (GETGROUPS_T *)gids);
+ }
+ debug_return_int(ret);
+}
diff --git a/lib/util/sha2.c b/lib/util/sha2.c
new file mode 100644
index 0000000..a9e3857
--- /dev/null
+++ b/lib/util/sha2.c
@@ -0,0 +1,515 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+/*
+ * Implementation of SHA-224, SHA-256, SHA-384 and SHA-512
+ * as per FIPS 180-4: Secure Hash Standard (SHS)
+ * http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf
+ *
+ * Derived from the public domain SHA-1 and SHA-2 implementations
+ * by Steve Reid and Wei Dai respectively.
+ */
+
+#include <config.h>
+#include <string.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#if defined(HAVE_ENDIAN_H)
+# include <endian.h>
+#elif defined(HAVE_SYS_ENDIAN_H)
+# include <sys/endian.h>
+#elif defined(HAVE_MACHINE_ENDIAN_H)
+# include <machine/endian.h>
+#else
+# include "compat/endian.h"
+#endif
+
+#include "sudo_compat.h"
+#include "compat/sha2.h"
+
+/*
+ * SHA-2 operates on 32-bit and 64-bit words in big endian byte order.
+ * The following macros convert between character arrays and big endian words.
+ */
+#define BE8TO32(x, y) do { \
+ (x) = (((uint32_t)((y)[0] & 255) << 24) | \
+ ((uint32_t)((y)[1] & 255) << 16) | \
+ ((uint32_t)((y)[2] & 255) << 8) | \
+ ((uint32_t)((y)[3] & 255))); \
+} while (0)
+
+#define BE8TO64(x, y) do { \
+ (x) = (((uint64_t)((y)[0] & 255) << 56) | \
+ ((uint64_t)((y)[1] & 255) << 48) | \
+ ((uint64_t)((y)[2] & 255) << 40) | \
+ ((uint64_t)((y)[3] & 255) << 32) | \
+ ((uint64_t)((y)[4] & 255) << 24) | \
+ ((uint64_t)((y)[5] & 255) << 16) | \
+ ((uint64_t)((y)[6] & 255) << 8) | \
+ ((uint64_t)((y)[7] & 255))); \
+} while (0)
+
+#define BE32TO8(x, y) do { \
+ (x)[0] = (uint8_t)(((y) >> 24) & 255); \
+ (x)[1] = (uint8_t)(((y) >> 16) & 255); \
+ (x)[2] = (uint8_t)(((y) >> 8) & 255); \
+ (x)[3] = (uint8_t)((y) & 255); \
+} while (0)
+
+#define BE64TO8(x, y) do { \
+ (x)[0] = (uint8_t)(((y) >> 56) & 255); \
+ (x)[1] = (uint8_t)(((y) >> 48) & 255); \
+ (x)[2] = (uint8_t)(((y) >> 40) & 255); \
+ (x)[3] = (uint8_t)(((y) >> 32) & 255); \
+ (x)[4] = (uint8_t)(((y) >> 24) & 255); \
+ (x)[5] = (uint8_t)(((y) >> 16) & 255); \
+ (x)[6] = (uint8_t)(((y) >> 8) & 255); \
+ (x)[7] = (uint8_t)((y) & 255); \
+} while (0)
+
+#define rotrFixed(x,y) (y ? ((x>>y) | (x<<(sizeof(x)*8-y))) : x)
+
+#define blk0(i) (W[i])
+#define blk2(i) (W[i&15]+=s1(W[(i-2)&15])+W[(i-7)&15]+s0(W[(i-15)&15]))
+
+#define Ch(x,y,z) (z^(x&(y^z)))
+#define Maj(x,y,z) (y^((x^y)&(y^z)))
+
+#define a(i) T[(0-i)&7]
+#define b(i) T[(1-i)&7]
+#define c(i) T[(2-i)&7]
+#define d(i) T[(3-i)&7]
+#define e(i) T[(4-i)&7]
+#define f(i) T[(5-i)&7]
+#define g(i) T[(6-i)&7]
+#define h(i) T[(7-i)&7]
+
+void
+SHA224Init(SHA2_CTX *ctx)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->state.st32[0] = 0xc1059ed8UL;
+ ctx->state.st32[1] = 0x367cd507UL;
+ ctx->state.st32[2] = 0x3070dd17UL;
+ ctx->state.st32[3] = 0xf70e5939UL;
+ ctx->state.st32[4] = 0xffc00b31UL;
+ ctx->state.st32[5] = 0x68581511UL;
+ ctx->state.st32[6] = 0x64f98fa7UL;
+ ctx->state.st32[7] = 0xbefa4fa4UL;
+}
+
+void
+SHA224Transform(uint32_t state[8], const uint8_t buffer[SHA224_BLOCK_LENGTH])
+{
+ SHA256Transform(state, buffer);
+}
+
+void
+SHA224Update(SHA2_CTX *ctx, const uint8_t *data, size_t len)
+{
+ SHA256Update(ctx, data, len);
+}
+
+void
+SHA224Pad(SHA2_CTX *ctx)
+{
+ SHA256Pad(ctx);
+}
+
+void
+SHA224Final(uint8_t digest[SHA224_DIGEST_LENGTH], SHA2_CTX *ctx)
+{
+ SHA256Pad(ctx);
+ if (digest != NULL) {
+#if BYTE_ORDER == BIG_ENDIAN
+ memcpy(digest, ctx->state.st32, SHA224_DIGEST_LENGTH);
+#else
+ unsigned int i;
+
+ for (i = 0; i < 7; i++)
+ BE32TO8(digest + (i * 4), ctx->state.st32[i]);
+#endif
+ memset(ctx, 0, sizeof(*ctx));
+ }
+}
+
+static const uint32_t SHA256_K[64] = {
+ 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL,
+ 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL,
+ 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL,
+ 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL,
+ 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL,
+ 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL,
+ 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL,
+ 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL,
+ 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL,
+ 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
+ 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL,
+ 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL,
+ 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL,
+ 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL,
+ 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL,
+ 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL
+};
+
+void
+SHA256Init(SHA2_CTX *ctx)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->state.st32[0] = 0x6a09e667UL;
+ ctx->state.st32[1] = 0xbb67ae85UL;
+ ctx->state.st32[2] = 0x3c6ef372UL;
+ ctx->state.st32[3] = 0xa54ff53aUL;
+ ctx->state.st32[4] = 0x510e527fUL;
+ ctx->state.st32[5] = 0x9b05688cUL;
+ ctx->state.st32[6] = 0x1f83d9abUL;
+ ctx->state.st32[7] = 0x5be0cd19UL;
+}
+
+/* Round macros for SHA256 */
+#define R(i) do { \
+ h(i)+=S1(e(i))+Ch(e(i),f(i),g(i))+SHA256_K[i+j]+(j?blk2(i):blk0(i)); \
+ d(i)+=h(i); \
+ h(i)+=S0(a(i))+Maj(a(i),b(i),c(i)); \
+} while (0)
+
+#define S0(x) (rotrFixed(x,2)^rotrFixed(x,13)^rotrFixed(x,22))
+#define S1(x) (rotrFixed(x,6)^rotrFixed(x,11)^rotrFixed(x,25))
+#define s0(x) (rotrFixed(x,7)^rotrFixed(x,18)^(x>>3))
+#define s1(x) (rotrFixed(x,17)^rotrFixed(x,19)^(x>>10))
+
+void
+SHA256Transform(uint32_t state[8], const uint8_t data[SHA256_BLOCK_LENGTH])
+{
+ uint32_t W[16];
+ uint32_t T[8];
+ unsigned int j;
+
+ /* Copy context state to working vars. */
+ memcpy(T, state, sizeof(T));
+ /* Copy data to W in big endian format. */
+#if BYTE_ORDER == BIG_ENDIAN
+ memcpy(W, data, sizeof(W));
+#else
+ for (j = 0; j < 16; j++) {
+ BE8TO32(W[j], data);
+ data += 4;
+ }
+#endif
+ /* 64 operations, partially loop unrolled. */
+ for (j = 0; j < 64; j += 16)
+ {
+ R( 0); R( 1); R( 2); R( 3);
+ R( 4); R( 5); R( 6); R( 7);
+ R( 8); R( 9); R(10); R(11);
+ R(12); R(13); R(14); R(15);
+ }
+ /* Add the working vars back into context state. */
+ state[0] += a(0);
+ state[1] += b(0);
+ state[2] += c(0);
+ state[3] += d(0);
+ state[4] += e(0);
+ state[5] += f(0);
+ state[6] += g(0);
+ state[7] += h(0);
+ /* Cleanup */
+ explicit_bzero(T, sizeof(T));
+ explicit_bzero(W, sizeof(W));
+}
+
+#undef S0
+#undef S1
+#undef s0
+#undef s1
+#undef R
+
+void
+SHA256Update(SHA2_CTX *ctx, const uint8_t *data, size_t len)
+{
+ size_t i = 0, j;
+
+ j = (size_t)((ctx->count[0] >> 3) & (SHA256_BLOCK_LENGTH - 1));
+ ctx->count[0] += ((uint64_t)len << 3);
+ if ((j + len) > SHA256_BLOCK_LENGTH - 1) {
+ memcpy(&ctx->buffer[j], data, (i = SHA256_BLOCK_LENGTH - j));
+ SHA256Transform(ctx->state.st32, ctx->buffer);
+ for ( ; i + SHA256_BLOCK_LENGTH - 1 < len; i += SHA256_BLOCK_LENGTH)
+ SHA256Transform(ctx->state.st32, (uint8_t *)&data[i]);
+ j = 0;
+ }
+ memcpy(&ctx->buffer[j], &data[i], len - i);
+}
+
+void
+SHA256Pad(SHA2_CTX *ctx)
+{
+ uint8_t finalcount[8];
+
+ /* Store unpadded message length in bits in big endian format. */
+ BE64TO8(finalcount, ctx->count[0]);
+
+ /* Append a '1' bit (0x80) to the message. */
+ SHA256Update(ctx, (uint8_t *)"\200", 1);
+
+ /* Pad message such that the resulting length modulo 512 is 448. */
+ while ((ctx->count[0] & 511) != 448)
+ SHA256Update(ctx, (uint8_t *)"\0", 1);
+
+ /* Append length of message in bits and do final SHA256Transform(). */
+ SHA256Update(ctx, finalcount, sizeof(finalcount));
+}
+
+void
+SHA256Final(uint8_t digest[SHA256_DIGEST_LENGTH], SHA2_CTX *ctx)
+{
+ SHA256Pad(ctx);
+ if (digest != NULL) {
+#if BYTE_ORDER == BIG_ENDIAN
+ memcpy(digest, ctx->state.st32, SHA256_DIGEST_LENGTH);
+#else
+ unsigned int i;
+
+ for (i = 0; i < 8; i++)
+ BE32TO8(digest + (i * 4), ctx->state.st32[i]);
+#endif
+ memset(ctx, 0, sizeof(*ctx));
+ }
+}
+
+void
+SHA384Init(SHA2_CTX *ctx)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->state.st64[0] = 0xcbbb9d5dc1059ed8ULL;
+ ctx->state.st64[1] = 0x629a292a367cd507ULL;
+ ctx->state.st64[2] = 0x9159015a3070dd17ULL;
+ ctx->state.st64[3] = 0x152fecd8f70e5939ULL;
+ ctx->state.st64[4] = 0x67332667ffc00b31ULL;
+ ctx->state.st64[5] = 0x8eb44a8768581511ULL;
+ ctx->state.st64[6] = 0xdb0c2e0d64f98fa7ULL;
+ ctx->state.st64[7] = 0x47b5481dbefa4fa4ULL;
+}
+
+void
+SHA384Transform(uint64_t state[8], const uint8_t data[SHA384_BLOCK_LENGTH])
+{
+ SHA512Transform(state, data);
+}
+
+void
+SHA384Update(SHA2_CTX *ctx, const uint8_t *data, size_t len)
+{
+ SHA512Update(ctx, data, len);
+}
+
+void
+SHA384Pad(SHA2_CTX *ctx)
+{
+ SHA512Pad(ctx);
+}
+
+void
+SHA384Final(uint8_t digest[SHA384_DIGEST_LENGTH], SHA2_CTX *ctx)
+{
+ SHA384Pad(ctx);
+ if (digest != NULL) {
+#if BYTE_ORDER == BIG_ENDIAN
+ memcpy(digest, ctx->state.st64, SHA384_DIGEST_LENGTH);
+#else
+ unsigned int i;
+
+ for (i = 0; i < 6; i++)
+ BE64TO8(digest + (i * 8), ctx->state.st64[i]);
+#endif
+ memset(ctx, 0, sizeof(*ctx));
+ }
+}
+
+static const uint64_t SHA512_K[80] = {
+ 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL,
+ 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,
+ 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
+ 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
+ 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL,
+ 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+ 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL,
+ 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
+ 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
+ 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,
+ 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL,
+ 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+ 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL,
+ 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,
+ 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
+ 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
+ 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL,
+ 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+ 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL,
+ 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
+ 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
+ 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,
+ 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL,
+ 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+ 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL,
+ 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,
+ 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
+ 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
+ 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL,
+ 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+ 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL,
+ 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
+ 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,
+ 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,
+ 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL,
+ 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+ 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL,
+ 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,
+ 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
+ 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL
+};
+
+void
+SHA512Init(SHA2_CTX *ctx)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->state.st64[0] = 0x6a09e667f3bcc908ULL;
+ ctx->state.st64[1] = 0xbb67ae8584caa73bULL;
+ ctx->state.st64[2] = 0x3c6ef372fe94f82bULL;
+ ctx->state.st64[3] = 0xa54ff53a5f1d36f1ULL;
+ ctx->state.st64[4] = 0x510e527fade682d1ULL;
+ ctx->state.st64[5] = 0x9b05688c2b3e6c1fULL;
+ ctx->state.st64[6] = 0x1f83d9abfb41bd6bULL;
+ ctx->state.st64[7] = 0x5be0cd19137e2179ULL;
+}
+
+/* Round macros for SHA512 */
+#define R(i) do { \
+ h(i)+=S1(e(i))+Ch(e(i),f(i),g(i))+SHA512_K[i+j]+(j?blk2(i):blk0(i)); \
+ d(i)+=h(i); \
+ h(i)+=S0(a(i))+Maj(a(i),b(i),c(i)); \
+} while (0)
+
+#define S0(x) (rotrFixed(x,28)^rotrFixed(x,34)^rotrFixed(x,39))
+#define S1(x) (rotrFixed(x,14)^rotrFixed(x,18)^rotrFixed(x,41))
+#define s0(x) (rotrFixed(x,1)^rotrFixed(x,8)^(x>>7))
+#define s1(x) (rotrFixed(x,19)^rotrFixed(x,61)^(x>>6))
+
+void
+SHA512Transform(uint64_t state[8], const uint8_t data[SHA512_BLOCK_LENGTH])
+{
+ uint64_t W[16];
+ uint64_t T[8];
+ unsigned int j;
+
+ /* Copy context state to working vars. */
+ memcpy(T, state, sizeof(T));
+ /* Copy data to W in big endian format. */
+#if BYTE_ORDER == BIG_ENDIAN
+ memcpy(W, data, sizeof(W));
+#else
+ for (j = 0; j < 16; j++) {
+ BE8TO64(W[j], data);
+ data += 8;
+ }
+#endif
+ /* 80 operations, partially loop unrolled. */
+ for (j = 0; j < 80; j += 16)
+ {
+ R( 0); R( 1); R( 2); R( 3);
+ R( 4); R( 5); R( 6); R( 7);
+ R( 8); R( 9); R(10); R(11);
+ R(12); R(13); R(14); R(15);
+ }
+ /* Add the working vars back into context state. */
+ state[0] += a(0);
+ state[1] += b(0);
+ state[2] += c(0);
+ state[3] += d(0);
+ state[4] += e(0);
+ state[5] += f(0);
+ state[6] += g(0);
+ state[7] += h(0);
+ /* Cleanup. */
+ explicit_bzero(T, sizeof(T));
+ explicit_bzero(W, sizeof(W));
+}
+
+void
+SHA512Update(SHA2_CTX *ctx, const uint8_t *data, size_t len)
+{
+ size_t i = 0, j;
+
+ j = (size_t)((ctx->count[0] >> 3) & (SHA512_BLOCK_LENGTH - 1));
+ ctx->count[0] += ((uint64_t)len << 3);
+ if (ctx->count[0] < ((uint64_t)len << 3))
+ ctx->count[1]++;
+ if ((j + len) > SHA512_BLOCK_LENGTH - 1) {
+ memcpy(&ctx->buffer[j], data, (i = SHA512_BLOCK_LENGTH - j));
+ SHA512Transform(ctx->state.st64, ctx->buffer);
+ for ( ; i + SHA512_BLOCK_LENGTH - 1 < len; i += SHA512_BLOCK_LENGTH)
+ SHA512Transform(ctx->state.st64, (uint8_t *)&data[i]);
+ j = 0;
+ }
+ memcpy(&ctx->buffer[j], &data[i], len - i);
+}
+
+void
+SHA512Pad(SHA2_CTX *ctx)
+{
+ uint8_t finalcount[16];
+
+ /* Store unpadded message length in bits in big endian format. */
+ BE64TO8(finalcount, ctx->count[1]);
+ BE64TO8(finalcount + 8, ctx->count[0]);
+
+ /* Append a '1' bit (0x80) to the message. */
+ SHA512Update(ctx, (uint8_t *)"\200", 1);
+
+ /* Pad message such that the resulting length modulo 1024 is 896. */
+ while ((ctx->count[0] & 1023) != 896)
+ SHA512Update(ctx, (uint8_t *)"\0", 1);
+
+ /* Append length of message in bits and do final SHA512Transform(). */
+ SHA512Update(ctx, finalcount, sizeof(finalcount));
+}
+
+void
+SHA512Final(uint8_t digest[SHA512_DIGEST_LENGTH], SHA2_CTX *ctx)
+{
+ SHA512Pad(ctx);
+ if (digest != NULL) {
+#if BYTE_ORDER == BIG_ENDIAN
+ memcpy(digest, ctx->state.st64, SHA512_DIGEST_LENGTH);
+#else
+ unsigned int i;
+
+ for (i = 0; i < 8; i++)
+ BE64TO8(digest + (i * 8), ctx->state.st64[i]);
+#endif
+ memset(ctx, 0, sizeof(*ctx));
+ }
+}
diff --git a/lib/util/sig2str.c b/lib/util/sig2str.c
new file mode 100644
index 0000000..1d86021
--- /dev/null
+++ b/lib/util/sig2str.c
@@ -0,0 +1,100 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2012-2015, 2017-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * 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>
+
+#ifndef HAVE_SIG2STR
+
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#if !defined(HAVE_SIGABBREV_NP)
+# if defined(HAVE_DECL_SYS_SIGNAME) && HAVE_DECL_SYS_SIGNAME == 1
+# define sigabbrev_np(_x) sys_signame[(_x)]
+# elif defined(HAVE_DECL__SYS_SIGNAME) && HAVE_DECL__SYS_SIGNAME == 1
+# define sigabbrev_np(_x) _sys_signame[(_x)]
+# elif defined(HAVE_SYS_SIGABBREV)
+# define sigabbrev_np(_x) sys_sigabbrev[(_x)]
+# if defined(HAVE_DECL_SYS_SIGABBREV) && HAVE_DECL_SYS_SIGABBREV == 0
+ /* sys_sigabbrev is not declared by glibc */
+ extern const char *const sys_sigabbrev[NSIG];
+# endif
+# else
+# define sigabbrev_np(_x) sudo_sys_signame[(_x)]
+ extern const char *const sudo_sys_signame[NSIG];
+# endif
+#endif /* !HAVE_SIGABBREV_NP */
+
+/*
+ * Translate signal number to name.
+ */
+int
+sudo_sig2str(int signo, char *signame)
+{
+#if defined(SIGRTMIN) && defined(SIGRTMAX)
+ /* Realtime signal support. */
+ if (signo >= SIGRTMIN && signo <= SIGRTMAX) {
+# ifdef _SC_RTSIG_MAX
+ const long rtmax = sysconf(_SC_RTSIG_MAX);
+# else
+ const long rtmax = SIGRTMAX - SIGRTMIN;
+# endif
+ if (rtmax > 0) {
+ if (signo == SIGRTMIN) {
+ strlcpy(signame, "RTMIN", SIG2STR_MAX);
+ } else if (signo == SIGRTMAX) {
+ strlcpy(signame, "RTMAX", SIG2STR_MAX);
+ } else if (signo <= SIGRTMIN + (rtmax / 2) - 1) {
+ (void)snprintf(signame, SIG2STR_MAX, "RTMIN+%d",
+ (signo - SIGRTMIN));
+ } else {
+ (void)snprintf(signame, SIG2STR_MAX, "RTMAX-%d",
+ (SIGRTMAX - signo));
+ }
+ }
+ return 0;
+ }
+#endif
+ if (signo > 0 && signo < NSIG) {
+ const char *cp = sigabbrev_np(signo);
+ if (cp != NULL) {
+ strlcpy(signame, cp, SIG2STR_MAX);
+ /* Make sure we always return an upper case signame. */
+ if (islower((unsigned char)signame[0])) {
+ int i;
+ for (i = 0; signame[i] != '\0'; i++)
+ signame[i] = toupper((unsigned char)signame[i]);
+ }
+ return 0;
+ }
+ }
+ errno = EINVAL;
+ return -1;
+}
+#endif /* HAVE_SIG2STR */
diff --git a/lib/util/siglist.in b/lib/util/siglist.in
new file mode 100644
index 0000000..aa57a7a
--- /dev/null
+++ b/lib/util/siglist.in
@@ -0,0 +1,56 @@
+#
+# List of signals used to build sys_siglist (see mksiglist.c)
+# Adapted from pdksh; public domain
+#
+# Note that if a system has multiple defines for the same signal
+# (eg, SIGABRT vs SIGIOT, SIGCHLD vs SIGCLD), only the first one
+# will be seen, so the order in this list is important.
+#
+ HUP Hangup
+ INT Interrupt
+ QUIT Quit
+ ILL Illegal instruction
+ TRAP Trace trap
+# before IOT (ABRT is posix and ABRT is sometimes the same as IOT)
+ ABRT Abort
+ IOT IOT instruction
+ EMT EMT trap
+ FPE Floating point exception
+ KILL Killed
+ BUS Bus error
+ SEGV Memory fault
+# before UNUSED (SYS and UNUSED share the same value in musl libc).
+ SYS Bad system call
+ UNUSED Unused
+ PIPE Broken pipe
+ ALRM Alarm clock
+ TERM Terminated
+ STKFLT Stack fault
+# before POLL (POLL is sometimes the same as IO)
+ IO I/O possible
+ XCPU CPU time limit exceeded
+ XFSZ File size limit exceeded
+ VTALRM Virtual timer expired
+ PROF Profiling timer expired
+ WINCH Window size change
+ LOST File lock lost
+ USR1 User defined signal 1
+ USR2 User defined signal 2
+ PWR Power-fail/Restart
+ POLL Pollable event occurred
+ STOP Stopped (signal)
+ TSTP Stopped
+ CONT Continued
+# before CLD (CHLD is posix and CHLD is sometimes the same as CLD)
+ CHLD Child exited
+ CLD Child exited
+ TTIN Stopped (tty input)
+ TTOU Stopped (tty output)
+ INFO Information request
+ URG Urgent I/O condition
+# Solaris (svr4?) signals
+ WAITING No runnable LWPs
+ LWP Inter-LWP signal
+ FREEZE Checkpoint freeze
+ THAW Checkpoint thaw
+ CANCEL Thread cancellation
diff --git a/lib/util/snprintf.c b/lib/util/snprintf.c
new file mode 100644
index 0000000..8c75ba5
--- /dev/null
+++ b/lib/util/snprintf.c
@@ -0,0 +1,1549 @@
+/* $OpenBSD: vfprintf.c,v 1.67 2014/12/21 00:23:30 daniel Exp $ */
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1999-2005, 2008, 2010-2016
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * From: @(#)vfprintf.c 8.1 (Berkeley) 6/4/93
+ */
+
+/*
+ * v?snprintf/v?asprintf based on OpenBSD vfprintf.c.
+ */
+
+#include <config.h>
+
+#if !defined(HAVE_VSNPRINTF) || !defined(HAVE_SNPRINTF) || \
+ !defined(HAVE_VASPRINTF) || !defined(HAVE_ASPRINTF) || \
+ defined(PREFER_PORTABLE_SNPRINTF)
+
+#include <errno.h>
+#ifdef HAVE_NL_LANGINFO
+# include <langinfo.h>
+#endif
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stddef.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef PRINTF_WIDE_CHAR
+# include <wchar.h>
+#endif
+#include <fcntl.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+/* Avoid printf format attacks by ignoring the %n escape. */
+#define NO_PRINTF_PERCENT_N
+
+union arg {
+ int intarg;
+ unsigned int uintarg;
+ long longarg;
+ unsigned long ulongarg;
+ long long longlongarg;
+ unsigned long long ulonglongarg;
+ ptrdiff_t ptrdiffarg;
+ size_t sizearg;
+ ssize_t ssizearg;
+ intmax_t intmaxarg;
+ uintmax_t uintmaxarg;
+ void *pvoidarg;
+ char *pchararg;
+ signed char *pschararg;
+ short *pshortarg;
+ int *pintarg;
+ long *plongarg;
+ long long *plonglongarg;
+ ptrdiff_t *pptrdiffarg;
+ ssize_t *pssizearg;
+ intmax_t *pintmaxarg;
+#ifdef FLOATING_POINT
+ double doublearg;
+ long double longdoublearg;
+#endif
+#ifdef PRINTF_WIDE_CHAR
+ wint_t wintarg;
+ wchar_t *pwchararg;
+#endif
+};
+
+static int __find_arguments(const char *fmt0, va_list ap, union arg **argtable);
+static int __grow_type_table(unsigned char **typetable, int *tablesize);
+static int xxxprintf(char **, size_t, int, const char *, va_list);
+
+#ifdef PRINTF_WIDE_CHAR
+/*
+ * Convert a wide character string argument for the %ls format to a multibyte
+ * string representation. If not -1, prec specifies the maximum number of
+ * bytes to output, and also means that we can't assume that the wide char
+ * string is null-terminated.
+ */
+static char *
+__wcsconv(wchar_t *wcsarg, int prec)
+{
+ mbstate_t mbs;
+ char buf[MB_LEN_MAX];
+ wchar_t *p;
+ char *convbuf;
+ size_t clen, nbytes;
+
+ /* Allocate space for the maximum number of bytes we could output. */
+ if (prec < 0) {
+ memset(&mbs, 0, sizeof(mbs));
+ p = wcsarg;
+ nbytes = wcsrtombs(NULL, (const wchar_t **)&p, 0, &mbs);
+ if (nbytes == (size_t)-1)
+ return NULL;
+ } else {
+ /*
+ * Optimisation: if the output precision is small enough,
+ * just allocate enough memory for the maximum instead of
+ * scanning the string.
+ */
+ if (prec < 128)
+ nbytes = prec;
+ else {
+ nbytes = 0;
+ p = wcsarg;
+ memset(&mbs, 0, sizeof(mbs));
+ for (;;) {
+ clen = wcrtomb(buf, *p++, &mbs);
+ if (clen == 0 || clen == (size_t)-1 ||
+ nbytes + clen > (size_t)prec)
+ break;
+ nbytes += clen;
+ }
+ if (clen == (size_t)-1)
+ return NULL;
+ }
+ }
+ if ((convbuf = malloc(nbytes + 1)) == NULL)
+ return NULL;
+
+ /* Fill the output buffer. */
+ p = wcsarg;
+ memset(&mbs, 0, sizeof(mbs));
+ if ((nbytes = wcsrtombs(convbuf, (const wchar_t **)&p,
+ nbytes, &mbs)) == (size_t)-1) {
+ free(convbuf);
+ return NULL;
+ }
+ convbuf[nbytes] = '\0';
+ return convbuf;
+}
+#endif
+
+#ifdef FLOATING_POINT
+#include <float.h>
+#include <locale.h>
+#include <math.h>
+#include "floatio.h"
+#include "gdtoa.h"
+
+#define DEFPREC 6
+
+static int exponent(char *, int, int);
+#endif /* FLOATING_POINT */
+
+/*
+ * The size of the buffer we use as scratch space for integer
+ * conversions, among other things. Technically, we would need the
+ * most space for base 10 conversions with thousands' grouping
+ * characters between each pair of digits. 100 bytes is a
+ * conservative overestimate even for a 128-bit uintmax_t.
+ */
+#define BUF 100
+
+#define STATIC_ARG_TBL_SIZE 8 /* Size of static argument table. */
+
+
+/*
+ * Macros for converting digits to letters and vice versa
+ */
+#define to_digit(c) ((c) - '0')
+#define is_digit(c) ((unsigned int)to_digit(c) <= 9)
+#define to_char(n) ((n) + '0')
+
+/*
+ * Flags used during conversion.
+ */
+#define ALT 0x0001 /* alternate form */
+#define LADJUST 0x0004 /* left adjustment */
+#define LONGDBL 0x0008 /* long double */
+#define LONGINT 0x0010 /* long integer */
+#define LLONGINT 0x0020 /* long long integer */
+#define SHORTINT 0x0040 /* short integer */
+#define ZEROPAD 0x0080 /* zero (as opposed to blank) pad */
+#define FPT 0x0100 /* Floating point number */
+#define PTRINT 0x0200 /* (unsigned) ptrdiff_t */
+#define SIZEINT 0x0400 /* (signed) size_t */
+#define CHARINT 0x0800 /* 8 bit integer */
+#undef MAXINT /* Also defined by HP-UX param.h... */
+#define MAXINT 0x1000 /* largest integer size (intmax_t) */
+
+/*
+ * Actual printf innards.
+ */
+static int
+xxxprintf(char **strp, size_t strsize, int alloc, const char *fmt0, va_list ap)
+{
+ char *fmt; /* format string */
+ int ch; /* character from fmt */
+ int n; /* handy integers (short term usage) */
+ char *cp; /* handy char pointer (short term usage) */
+ int flags; /* flags as above */
+ int ret; /* return value accumulator */
+ int width; /* width from format (%8d), or 0 */
+ int prec; /* precision from format; <0 for N/A */
+ char sign; /* sign prefix (' ', '+', '-', or \0) */
+#ifdef FLOATING_POINT
+ /*
+ * We can decompose the printed representation of floating
+ * point numbers into several parts, some of which may be empty:
+ *
+ * [+|-| ] [0x|0X] MMM . NNN [e|E|p|P] [+|-] ZZ
+ * A B ---C--- D E F
+ *
+ * A: 'sign' holds this value if present; '\0' otherwise
+ * B: ox[1] holds the 'x' or 'X'; '\0' if not hexadecimal
+ * C: cp points to the string MMMNNN. Leading and trailing
+ * zeros are not in the string and must be added.
+ * D: expchar holds this character; '\0' if no exponent, e.g. %f
+ * F: at least two digits for decimal, at least one digit for hex
+ */
+#ifdef HAVE_NL_LANGINFO
+ const char *decimal_point = NULL;
+#else
+ const char *decimal_point = ".";
+#endif
+ int signflag; /* true if float is negative */
+ union { /* floating point arguments %[aAeEfFgG] */
+ double dbl;
+ long double ldbl;
+ } fparg;
+ int expt; /* integer value of exponent */
+ char expchar; /* exponent character: [eEpP\0] */
+ char *dtoaend; /* pointer to end of converted digits */
+ int expsize; /* character count for expstr */
+ int lead; /* sig figs before decimal or group sep */
+ int ndig; /* actual number of digits returned by dtoa */
+ char expstr[MAXEXPDIG+2]; /* buffer for exponent string: e+ZZZ */
+ char *dtoaresult = NULL;
+#endif
+
+ uintmax_t _umax; /* integer arguments %[diouxX] */
+ enum { OCT, DEC, HEX } base; /* base for %[diouxX] conversion */
+ int dprec; /* a copy of prec if %[diouxX], 0 otherwise */
+ int realsz; /* field size expanded by dprec */
+ int size; /* size of converted field or string */
+ const char *xdigs = ""; /* digits for %[xX] conversion */
+#define NIOV 8
+ char buf[BUF]; /* buffer with space for digits of uintmax_t */
+ char ox[2]; /* space for 0x; ox[1] is either x, X, or \0 */
+ char *str; /* pointer to string to fill */
+ char *estr; /* pointer to last char in str */
+ union arg *argtable; /* args, built due to positional arg */
+ union arg statargtable[STATIC_ARG_TBL_SIZE];
+ int nextarg; /* 1-based argument index */
+ va_list orgap; /* original argument pointer */
+#ifdef PRINTF_WIDE_CHAR
+ char *convbuf; /* buffer for wide to multi-byte conversion */
+#endif
+
+ /*
+ * Choose PADSIZE to trade efficiency vs. size. If larger printf
+ * fields occur frequently, increase PADSIZE and make the initialisers
+ * below longer.
+ */
+#define PADSIZE 16 /* pad chunk size */
+ static char blanks[PADSIZE] =
+ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
+ static char zeroes[PADSIZE] =
+ {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
+
+ static const char xdigs_lower[16] = "0123456789abcdef";
+ static const char xdigs_upper[16] = "0123456789ABCDEF";
+
+ /*
+ * BEWARE, these `goto done' on error, and PAD uses `n'.
+ */
+ /* Print chars to "str", (allocate as needed if alloc is set). */
+#define PRINT(ptr, len) do { \
+ const char *p = ptr; \
+ const char *endp = ptr + len; \
+ while (p < endp && (str < estr || alloc)) { \
+ if (alloc && str >= estr) { \
+ char *t; \
+ strsize = (strsize << 1) + 1; \
+ if (!(t = realloc(*strp, strsize))) { \
+ free(str); \
+ *strp = NULL; \
+ ret = -1; \
+ goto done; \
+ } \
+ str = t + (str - *strp); \
+ estr = t + strsize - 1; \
+ *strp = t; \
+ } \
+ *str++ = *p++; \
+ } \
+} while (0)
+
+#define PAD(plen, pstr) do { \
+ if ((n = (plen)) > 0) { \
+ while (n > PADSIZE) { \
+ PRINT(pstr, PADSIZE); \
+ n -= PADSIZE; \
+ } \
+ PRINT(pstr, n); \
+ } \
+} while (0)
+#define PRINTANDPAD(p, ep, len, with) do { \
+ int n2 = (ep) - (p); \
+ if (n2 > (len)) \
+ n2 = (len); \
+ if (n2 > 0) \
+ PRINT((p), n2); \
+ PAD((len) - (n2 > 0 ? n2 : 0), (with)); \
+} while(0)
+
+ /*
+ * To extend shorts properly, we need both signed and unsigned
+ * argument extraction methods.
+ */
+#define SARG() \
+ ((intmax_t)(flags&MAXINT ? GETARG(intmax_t) : \
+ flags&LLONGINT ? GETARG(long long) : \
+ flags&LONGINT ? GETARG(long) : \
+ flags&PTRINT ? GETARG(ptrdiff_t) : \
+ flags&SIZEINT ? GETARG(ssize_t) : \
+ flags&SHORTINT ? (short)GETARG(int) : \
+ flags&CHARINT ? (signed char)GETARG(int) : \
+ GETARG(int)))
+#define UARG() \
+ ((uintmax_t)(flags&MAXINT ? GETARG(uintmax_t) : \
+ flags&LLONGINT ? GETARG(unsigned long long) : \
+ flags&LONGINT ? GETARG(unsigned long) : \
+ flags&PTRINT ? (uintptr_t)GETARG(ptrdiff_t) : /* XXX */ \
+ flags&SIZEINT ? GETARG(size_t) : \
+ flags&SHORTINT ? (unsigned short)GETARG(int) : \
+ flags&CHARINT ? (unsigned char)GETARG(int) : \
+ GETARG(unsigned int)))
+
+ /*
+ * Append a digit to a value and check for overflow.
+ */
+#define APPEND_DIGIT(val, dig) do { \
+ if ((val) > INT_MAX / 10) \
+ goto overflow; \
+ (val) *= 10; \
+ if ((val) > INT_MAX - to_digit((dig))) \
+ goto overflow; \
+ (val) += to_digit((dig)); \
+} while (0)
+
+ /*
+ * Get * arguments, including the form *nn$. Preserve the nextarg
+ * that the argument can be gotten once the type is determined.
+ */
+#define GETASTER(val) do { \
+ int n2 = 0; \
+ cp = fmt; \
+ while (is_digit(*cp)) { \
+ APPEND_DIGIT(n2, *cp); \
+ cp++; \
+ } \
+ if (*cp == '$') { \
+ int hold = nextarg; \
+ if (argtable == NULL) { \
+ argtable = statargtable; \
+ if (__find_arguments(fmt0, orgap, &argtable) == -1) { \
+ ret = -1; \
+ goto done; \
+ } \
+ } \
+ nextarg = n2; \
+ val = GETARG(int); \
+ nextarg = hold; \
+ fmt = ++cp; \
+ } else { \
+ val = GETARG(int); \
+ } \
+} while (0)
+
+/*
+* Get the argument indexed by nextarg. If the argument table is
+* built, use it to get the argument. If its not, get the next
+* argument (and arguments must be gotten sequentially).
+*/
+#define GETARG(type) \
+ ((argtable != NULL) ? *((type*)(&argtable[nextarg++])) : \
+ (nextarg++, va_arg(ap, type)))
+
+ fmt = (char *)fmt0;
+ argtable = NULL;
+ nextarg = 1;
+ va_copy(orgap, ap);
+ ret = 0;
+#ifdef PRINTF_WIDE_CHAR
+ convbuf = NULL;
+#endif
+
+ if (alloc) {
+ strsize = 128;
+ *strp = str = malloc(strsize);
+ if (str == NULL) {
+ ret = -1;
+ goto done;
+ }
+ estr = str + 127;
+ } else {
+ str = *strp;
+ if (strsize)
+ estr = str + strsize - 1;
+ else
+ estr = NULL;
+ }
+
+ /*
+ * Scan the format for conversions (`%' character).
+ */
+ for (;;) {
+ for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++)
+ continue;
+
+ if (fmt != cp) {
+ ptrdiff_t m = fmt - cp;
+ if (m < 0 || m > INT_MAX - ret)
+ goto overflow;
+ PRINT(cp, m);
+ ret += m;
+ }
+ if (ch == '\0')
+ goto done;
+ fmt++; /* skip over '%' */
+
+ flags = 0;
+ dprec = 0;
+ width = 0;
+ prec = -1;
+ sign = '\0';
+ ox[1] = '\0';
+
+rflag: ch = *fmt++;
+reswitch: switch (ch) {
+ case ' ':
+ /*
+ * ``If the space and + flags both appear, the space
+ * flag will be ignored.''
+ * -- ANSI X3J11
+ */
+ if (!sign)
+ sign = ' ';
+ goto rflag;
+ case '#':
+ flags |= ALT;
+ goto rflag;
+ case '\'':
+ /* grouping not implemented */
+ goto rflag;
+ case '*':
+ /*
+ * ``A negative field width argument is taken as a
+ * - flag followed by a positive field width.''
+ * -- ANSI X3J11
+ * They don't exclude field widths read from args.
+ */
+ GETASTER(width);
+ if (width >= 0)
+ goto rflag;
+ if (width == INT_MIN)
+ goto overflow;
+ width = -width;
+ FALLTHROUGH;
+ case '-':
+ flags |= LADJUST;
+ goto rflag;
+ case '+':
+ sign = '+';
+ goto rflag;
+ case '.':
+ if ((ch = *fmt++) == '*') {
+ GETASTER(n);
+ prec = n < 0 ? -1 : n;
+ goto rflag;
+ }
+ n = 0;
+ while (is_digit(ch)) {
+ APPEND_DIGIT(n, ch);
+ ch = *fmt++;
+ }
+ if (ch == '$') {
+ nextarg = n;
+ if (argtable == NULL) {
+ argtable = statargtable;
+ if (__find_arguments(fmt0, orgap,
+ &argtable) == -1) {
+ ret = -1;
+ goto done;
+ }
+ }
+ goto rflag;
+ }
+ prec = n;
+ goto reswitch;
+ case '0':
+ /*
+ * ``Note that 0 is taken as a flag, not as the
+ * beginning of a field width.''
+ * -- ANSI X3J11
+ */
+ flags |= ZEROPAD;
+ goto rflag;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = 0;
+ do {
+ APPEND_DIGIT(n, ch);
+ ch = *fmt++;
+ } while (is_digit(ch));
+ if (ch == '$') {
+ nextarg = n;
+ if (argtable == NULL) {
+ argtable = statargtable;
+ if (__find_arguments(fmt0, orgap,
+ &argtable) == -1) {
+ ret = -1;
+ goto done;
+ }
+ }
+ goto rflag;
+ }
+ width = n;
+ goto reswitch;
+#ifdef FLOATING_POINT
+ case 'L':
+ flags |= LONGDBL;
+ goto rflag;
+#endif
+ case 'h':
+ if (*fmt == 'h') {
+ fmt++;
+ flags |= CHARINT;
+ } else {
+ flags |= SHORTINT;
+ }
+ goto rflag;
+ case 'j':
+ flags |= MAXINT;
+ goto rflag;
+ case 'l':
+ if (*fmt == 'l') {
+ fmt++;
+ flags |= LLONGINT;
+ } else {
+ flags |= LONGINT;
+ }
+ goto rflag;
+ case 'q':
+ flags |= LLONGINT;
+ goto rflag;
+ case 't':
+ flags |= PTRINT;
+ goto rflag;
+ case 'z':
+ flags |= SIZEINT;
+ goto rflag;
+ case 'c':
+#ifdef PRINTF_WIDE_CHAR
+ if (flags & LONGINT) {
+ mbstate_t mbs;
+ size_t mbseqlen;
+
+ memset(&mbs, 0, sizeof(mbs));
+ mbseqlen = wcrtomb(buf,
+ (wchar_t)GETARG(wint_t), &mbs);
+ if (mbseqlen == (size_t)-1) {
+ ret = -1;
+ goto done;
+ }
+ cp = buf;
+ size = (int)mbseqlen;
+ } else {
+#endif
+ *(cp = buf) = GETARG(int);
+ size = 1;
+#ifdef PRINTF_WIDE_CHAR
+ }
+#endif
+ sign = '\0';
+ break;
+ case 'D':
+ flags |= LONGINT;
+ FALLTHROUGH;
+ case 'd':
+ case 'i':
+ _umax = SARG();
+ if ((intmax_t)_umax < 0) {
+ _umax = -_umax;
+ sign = '-';
+ }
+ base = DEC;
+ goto number;
+#ifdef FLOATING_POINT
+ case 'a':
+ case 'A':
+ if (ch == 'a') {
+ ox[1] = 'x';
+ xdigs = xdigs_lower;
+ expchar = 'p';
+ } else {
+ ox[1] = 'X';
+ xdigs = xdigs_upper;
+ expchar = 'P';
+ }
+ if (prec >= 0)
+ prec++;
+ if (dtoaresult)
+ __freedtoa(dtoaresult);
+ if (flags & LONGDBL) {
+ fparg.ldbl = GETARG(long double);
+ dtoaresult = cp =
+ __hldtoa(fparg.ldbl, xdigs, prec,
+ &expt, &signflag, &dtoaend);
+ if (dtoaresult == NULL) {
+ errno = ENOMEM;
+ goto done;
+ }
+ } else {
+ fparg.dbl = GETARG(double);
+ dtoaresult = cp =
+ __hdtoa(fparg.dbl, xdigs, prec,
+ &expt, &signflag, &dtoaend);
+ if (dtoaresult == NULL) {
+ errno = ENOMEM;
+ goto done;
+ }
+ }
+ if (prec < 0)
+ prec = dtoaend - cp;
+ if (expt == INT_MAX)
+ ox[1] = '\0';
+ goto fp_common;
+ case 'e':
+ case 'E':
+ expchar = ch;
+ if (prec < 0) /* account for digit before decpt */
+ prec = DEFPREC + 1;
+ else
+ prec++;
+ goto fp_begin;
+ case 'f':
+ case 'F':
+ expchar = '\0';
+ goto fp_begin;
+ case 'g':
+ case 'G':
+ expchar = ch - ('g' - 'e');
+ if (prec == 0)
+ prec = 1;
+fp_begin:
+ if (prec < 0)
+ prec = DEFPREC;
+ if (dtoaresult)
+ __freedtoa(dtoaresult);
+ if (flags & LONGDBL) {
+ fparg.ldbl = GETARG(long double);
+ dtoaresult = cp =
+ __ldtoa(&fparg.ldbl, expchar ? 2 : 3, prec,
+ &expt, &signflag, &dtoaend);
+ if (dtoaresult == NULL) {
+ errno = ENOMEM;
+ goto done;
+ }
+ } else {
+ fparg.dbl = GETARG(double);
+ dtoaresult = cp =
+ __dtoa(fparg.dbl, expchar ? 2 : 3, prec,
+ &expt, &signflag, &dtoaend);
+ if (dtoaresult == NULL) {
+ errno = ENOMEM;
+ goto done;
+ }
+ if (expt == 9999)
+ expt = INT_MAX;
+ }
+fp_common:
+ if (signflag)
+ sign = '-';
+ if (expt == INT_MAX) { /* inf or nan */
+ if (*cp == 'N')
+ cp = (ch >= 'a') ? "nan" : "NAN";
+ else
+ cp = (ch >= 'a') ? "inf" : "INF";
+ size = 3;
+ flags &= ~ZEROPAD;
+ break;
+ }
+ flags |= FPT;
+ ndig = dtoaend - cp;
+ if (ch == 'g' || ch == 'G') {
+ if (expt > -4 && expt <= prec) {
+ /* Make %[gG] smell like %[fF] */
+ expchar = '\0';
+ if (flags & ALT)
+ prec -= expt;
+ else
+ prec = ndig - expt;
+ if (prec < 0)
+ prec = 0;
+ } else {
+ /*
+ * Make %[gG] smell like %[eE], but
+ * trim trailing zeroes if no # flag.
+ */
+ if (!(flags & ALT))
+ prec = ndig;
+ }
+ }
+ if (expchar) {
+ expsize = exponent(expstr, expt - 1, expchar);
+ size = expsize + prec;
+ if (prec > 1 || flags & ALT)
+ ++size;
+ } else {
+ /* space for digits before decimal point */
+ if (expt > 0)
+ size = expt;
+ else /* "0" */
+ size = 1;
+ /* space for decimal pt and following digits */
+ if (prec || flags & ALT)
+ size += prec + 1;
+ lead = expt;
+ }
+ break;
+#endif /* FLOATING_POINT */
+#ifndef NO_PRINTF_PERCENT_N
+ case 'n':
+ if (flags & LLONGINT)
+ *GETARG(long long *) = ret;
+ else if (flags & LONGINT)
+ *GETARG(long *) = ret;
+ else if (flags & SHORTINT)
+ *GETARG(short *) = ret;
+ else if (flags & CHARINT)
+ *GETARG(signed char *) = ret;
+ else if (flags & PTRINT)
+ *GETARG(ptrdiff_t *) = ret;
+ else if (flags & SIZEINT)
+ *GETARG(ssize_t *) = ret;
+ else if (flags & MAXINT)
+ *GETARG(intmax_t *) = ret;
+ else
+ *GETARG(int *) = ret;
+ continue; /* no output */
+#endif /* NO_PRINTF_PERCENT_N */
+ case 'O':
+ flags |= LONGINT;
+ FALLTHROUGH;
+ case 'o':
+ _umax = UARG();
+ base = OCT;
+ goto nosign;
+ case 'p':
+ /*
+ * ``The argument shall be a pointer to void. The
+ * value of the pointer is converted to a sequence
+ * of printable characters, in an implementation-
+ * defined manner.''
+ * -- ANSI X3J11
+ */
+ /* NOSTRICT */
+ _umax = (u_long)GETARG(void *);
+ base = HEX;
+ xdigs = xdigs_lower;
+ ox[1] = 'x';
+ goto nosign;
+ case 's': {
+ size_t len;
+
+#ifdef PRINTF_WIDE_CHAR
+ if (flags & LONGINT) {
+ wchar_t *wcp;
+
+ free(convbuf);
+ convbuf = NULL;
+ if ((wcp = GETARG(wchar_t *)) == NULL) {
+ cp = (char *)"(null)";
+ } else {
+ convbuf = __wcsconv(wcp, prec);
+ if (convbuf == NULL) {
+ ret = -1;
+ goto done;
+ }
+ cp = convbuf;
+ }
+ } else
+#endif /* PRINTF_WIDE_CHAR */
+ if ((cp = GETARG(char *)) == NULL)
+ cp = (char *)"(null)";
+ len = prec >= 0 ? strnlen(cp, prec) : strlen(cp);
+ if (len > INT_MAX)
+ goto overflow;
+ size = (int)len;
+ sign = '\0';
+ }
+ break;
+ case 'U':
+ flags |= LONGINT;
+ FALLTHROUGH;
+ case 'u':
+ _umax = UARG();
+ base = DEC;
+ goto nosign;
+ case 'X':
+ xdigs = xdigs_upper;
+ goto hex;
+ case 'x':
+ xdigs = xdigs_lower;
+hex: _umax = UARG();
+ base = HEX;
+ /* leading 0x/X only if non-zero */
+ if (flags & ALT && _umax != 0)
+ ox[1] = ch;
+
+ /* unsigned conversions */
+nosign: sign = '\0';
+ /*
+ * ``... diouXx conversions ... if a precision is
+ * specified, the 0 flag will be ignored.''
+ * -- ANSI X3J11
+ */
+number: if ((dprec = prec) >= 0)
+ flags &= ~ZEROPAD;
+
+ /*
+ * ``The result of converting a zero value with an
+ * explicit precision of zero is no characters.''
+ * -- ANSI X3J11
+ */
+ cp = buf + BUF;
+ if (_umax != 0 || prec != 0) {
+ /*
+ * Unsigned mod is hard, and unsigned mod
+ * by a constant is easier than that by
+ * a variable; hence this switch.
+ */
+ switch (base) {
+ case OCT:
+ do {
+ *--cp = to_char(_umax & 7);
+ _umax >>= 3;
+ } while (_umax);
+ /* handle octal leading 0 */
+ if (flags & ALT && *cp != '0')
+ *--cp = '0';
+ break;
+
+ case DEC:
+ /* many numbers are 1 digit */
+ while (_umax >= 10) {
+ *--cp = to_char(_umax % 10);
+ _umax /= 10;
+ }
+ *--cp = to_char(_umax);
+ break;
+
+ case HEX:
+ do {
+ *--cp = xdigs[_umax & 15];
+ _umax >>= 4;
+ } while (_umax);
+ break;
+
+ default:
+ cp = (char *)"bug in xxxprintf: bad base";
+ size = strlen(cp);
+ goto skipsize;
+ }
+ }
+ size = buf + BUF - cp;
+ if (size > BUF) /* should never happen */
+ abort();
+ skipsize:
+ break;
+ default: /* "%?" prints ?, unless ? is NUL */
+ if (ch == '\0')
+ goto done;
+ /* pretend it was %c with argument ch */
+ cp = buf;
+ *cp = ch;
+ size = 1;
+ sign = '\0';
+ break;
+ }
+
+ /*
+ * All reasonable formats wind up here. At this point, `cp'
+ * points to a string which (if not flags&LADJUST) should be
+ * padded out to `width' places. If flags&ZEROPAD, it should
+ * first be prefixed by any sign or other prefix; otherwise,
+ * it should be blank padded before the prefix is emitted.
+ * After any left-hand padding and prefixing, emit zeroes
+ * required by a decimal %[diouxX] precision, then print the
+ * string proper, then emit zeroes required by any leftover
+ * floating precision; finally, if LADJUST, pad with blanks.
+ *
+ * Compute actual size, so we know how much to pad.
+ * size excludes decimal prec; realsz includes it.
+ */
+ realsz = dprec > size ? dprec : size;
+ if (sign)
+ realsz++;
+ if (ox[1])
+ realsz+= 2;
+
+ /* right-adjusting blank padding */
+ if ((flags & (LADJUST|ZEROPAD)) == 0)
+ PAD(width - realsz, blanks);
+
+ /* prefix */
+ if (sign)
+ PRINT(&sign, 1);
+ if (ox[1]) { /* ox[1] is either x, X, or \0 */
+ ox[0] = '0';
+ PRINT(ox, 2);
+ }
+
+ /* right-adjusting zero padding */
+ if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD)
+ PAD(width - realsz, zeroes);
+
+ /* leading zeroes from decimal precision */
+ PAD(dprec - size, zeroes);
+
+ /* the string or number proper */
+#ifdef FLOATING_POINT
+ if ((flags & FPT) == 0) {
+ PRINT(cp, size);
+ } else { /* glue together f_p fragments */
+#ifdef HAVE_NL_LANGINFO
+ if (decimal_point == NULL)
+ decimal_point = nl_langinfo(RADIXCHAR);
+#endif
+ if (!expchar) { /* %[fF] or sufficiently short %[gG] */
+ if (expt <= 0) {
+ PRINT(zeroes, 1);
+ if (prec || flags & ALT)
+ PRINT(decimal_point, 1);
+ PAD(-expt, zeroes);
+ /* already handled initial 0's */
+ prec += expt;
+ } else {
+ PRINTANDPAD(cp, dtoaend, lead, zeroes);
+ cp += lead;
+ if (prec || flags & ALT)
+ PRINT(decimal_point, 1);
+ }
+ PRINTANDPAD(cp, dtoaend, prec, zeroes);
+ } else { /* %[eE] or sufficiently long %[gG] */
+ if (prec > 1 || flags & ALT) {
+ buf[0] = *cp++;
+ buf[1] = *decimal_point;
+ PRINT(buf, 2);
+ PRINT(cp, ndig-1);
+ PAD(prec - ndig, zeroes);
+ } else { /* XeYYY */
+ PRINT(cp, 1);
+ }
+ PRINT(expstr, expsize);
+ }
+ }
+#else
+ PRINT(cp, size);
+#endif
+ /* left-adjusting padding (always blank) */
+ if (flags & LADJUST)
+ PAD(width - realsz, blanks);
+
+ /* finally, adjust ret */
+ if (width < realsz)
+ width = realsz;
+ if (width > INT_MAX - ret)
+ goto overflow;
+ ret += width;
+ }
+done:
+ va_end(orgap);
+ if (strsize)
+ *str = '\0';
+ goto finish;
+
+overflow:
+ errno = EOVERFLOW;
+ ret = -1;
+
+finish:
+#ifdef PRINTF_WIDE_CHAR
+ free(convbuf);
+#endif
+#ifdef FLOATING_POINT
+ if (dtoaresult)
+ __freedtoa(dtoaresult);
+#endif
+ if (argtable != NULL && argtable != statargtable) {
+ sudo_mmap_free(argtable);
+ argtable = NULL;
+ }
+ return ret;
+}
+
+/*
+ * Type ids for argument type table.
+ */
+#define T_UNUSED 0
+#define T_SHORT 1
+#define T_U_SHORT 2
+#define TP_SHORT 3
+#define T_INT 4
+#define T_U_INT 5
+#define TP_INT 6
+#define T_LONG 7
+#define T_U_LONG 8
+#define TP_LONG 9
+#define T_LLONG 10
+#define T_U_LLONG 11
+#define TP_LLONG 12
+#define T_DOUBLE 13
+#define T_LONG_DOUBLE 14
+#define TP_CHAR 15
+#define TP_VOID 16
+#define T_PTRINT 17
+#define TP_PTRINT 18
+#define T_SIZEINT 19
+#define T_SSIZEINT 20
+#define TP_SSIZEINT 21
+#define T_MAXINT 22
+#define T_MAXUINT 23
+#define TP_MAXINT 24
+#define T_CHAR 25
+#define T_U_CHAR 26
+#define T_WINT 27
+#define TP_WCHAR 28
+
+/*
+ * Find all arguments when a positional parameter is encountered. Returns a
+ * table, indexed by argument number, of pointers to each arguments. The
+ * initial argument table should be an array of STATIC_ARG_TBL_SIZE entries.
+ * It will be replaced with a mmap-ed one if it overflows (malloc cannot be
+ * used since we are attempting to make snprintf thread safe, and alloca is
+ * problematic since we have nested functions..)
+ */
+static int
+__find_arguments(const char *fmt0, va_list ap, union arg **argtable)
+{
+ char *fmt; /* format string */
+ int ch; /* character from fmt */
+ int n; /* handy integer (short term usage) */
+ char *cp; /* handy char pointer (short term usage) */
+ int flags; /* flags as above */
+ unsigned char *typetable; /* table of types */
+ unsigned char stattypetable[STATIC_ARG_TBL_SIZE];
+ int tablesize; /* current size of type table */
+ int tablemax; /* largest used index in table */
+ int nextarg; /* 1-based argument index */
+ int ret = 0; /* return value */
+
+ /*
+ * Add an argument type to the table, expanding if necessary.
+ */
+#define ADDTYPE(type) \
+ ((nextarg >= tablesize) ? \
+ __grow_type_table(&typetable, &tablesize) : 0, \
+ (nextarg > tablemax) ? tablemax = nextarg : 0, \
+ typetable[nextarg++] = type)
+
+#define ADDSARG() \
+ ((flags&MAXINT) ? ADDTYPE(T_MAXINT) : \
+ ((flags&PTRINT) ? ADDTYPE(T_PTRINT) : \
+ ((flags&SIZEINT) ? ADDTYPE(T_SSIZEINT) : \
+ ((flags&LLONGINT) ? ADDTYPE(T_LLONG) : \
+ ((flags&LONGINT) ? ADDTYPE(T_LONG) : \
+ ((flags&SHORTINT) ? ADDTYPE(T_SHORT) : \
+ ((flags&CHARINT) ? ADDTYPE(T_CHAR) : ADDTYPE(T_INT))))))))
+
+#define ADDUARG() \
+ ((flags&MAXINT) ? ADDTYPE(T_MAXUINT) : \
+ ((flags&PTRINT) ? ADDTYPE(T_PTRINT) : \
+ ((flags&SIZEINT) ? ADDTYPE(T_SIZEINT) : \
+ ((flags&LLONGINT) ? ADDTYPE(T_U_LLONG) : \
+ ((flags&LONGINT) ? ADDTYPE(T_U_LONG) : \
+ ((flags&SHORTINT) ? ADDTYPE(T_U_SHORT) : \
+ ((flags&CHARINT) ? ADDTYPE(T_U_CHAR) : ADDTYPE(T_U_INT))))))))
+
+ /*
+ * Add * arguments to the type array.
+ */
+#define ADDASTER() do { \
+ int n2 = 0; \
+ cp = fmt; \
+ while (is_digit(*cp)) { \
+ APPEND_DIGIT(n2, *cp); \
+ cp++; \
+ } \
+ if (*cp == '$') { \
+ int hold = nextarg; \
+ nextarg = n2; \
+ ADDTYPE(T_INT); \
+ nextarg = hold; \
+ fmt = ++cp; \
+ } else { \
+ ADDTYPE(T_INT); \
+ } \
+} while (0)
+ fmt = (char *)fmt0;
+ typetable = stattypetable;
+ tablesize = STATIC_ARG_TBL_SIZE;
+ tablemax = 0;
+ nextarg = 1;
+ memset(typetable, T_UNUSED, STATIC_ARG_TBL_SIZE);
+
+ /*
+ * Scan the format for conversions (`%' character).
+ */
+ for (;;) {
+ for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++)
+ continue;
+ if (ch == '\0')
+ goto done;
+ fmt++; /* skip over '%' */
+
+ flags = 0;
+
+rflag: ch = *fmt++;
+reswitch: switch (ch) {
+ case ' ':
+ case '#':
+ case '\'':
+ goto rflag;
+ case '*':
+ ADDASTER();
+ goto rflag;
+ case '-':
+ case '+':
+ goto rflag;
+ case '.':
+ if ((ch = *fmt++) == '*') {
+ ADDASTER();
+ goto rflag;
+ }
+ while (is_digit(ch)) {
+ ch = *fmt++;
+ }
+ goto reswitch;
+ case '0':
+ goto rflag;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = 0;
+ do {
+ APPEND_DIGIT(n ,ch);
+ ch = *fmt++;
+ } while (is_digit(ch));
+ if (ch == '$') {
+ nextarg = n;
+ goto rflag;
+ }
+ goto reswitch;
+#ifdef FLOATING_POINT
+ case 'L':
+ flags |= LONGDBL;
+ goto rflag;
+#endif
+ case 'h':
+ if (*fmt == 'h') {
+ fmt++;
+ flags |= CHARINT;
+ } else {
+ flags |= SHORTINT;
+ }
+ goto rflag;
+ case 'j':
+ flags |= MAXINT;
+ goto rflag;
+ case 'l':
+ if (*fmt == 'l') {
+ fmt++;
+ flags |= LLONGINT;
+ } else {
+ flags |= LONGINT;
+ }
+ goto rflag;
+ case 'q':
+ flags |= LLONGINT;
+ goto rflag;
+ case 't':
+ flags |= PTRINT;
+ goto rflag;
+ case 'z':
+ flags |= SIZEINT;
+ goto rflag;
+ case 'c':
+#ifdef PRINTF_WIDE_CHAR
+ if (flags & LONGINT)
+ ADDTYPE(T_WINT);
+ else
+#endif
+ ADDTYPE(T_INT);
+ break;
+ case 'D':
+ flags |= LONGINT;
+ FALLTHROUGH;
+ case 'd':
+ case 'i':
+ ADDSARG();
+ break;
+#ifdef FLOATING_POINT
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ if (flags & LONGDBL)
+ ADDTYPE(T_LONG_DOUBLE);
+ else
+ ADDTYPE(T_DOUBLE);
+ break;
+#endif /* FLOATING_POINT */
+#ifndef NO_PRINTF_PERCENT_N
+ case 'n':
+ if (flags & LLONGINT)
+ ADDTYPE(TP_LLONG);
+ else if (flags & LONGINT)
+ ADDTYPE(TP_LONG);
+ else if (flags & SHORTINT)
+ ADDTYPE(TP_SHORT);
+ else if (flags & PTRINT)
+ ADDTYPE(TP_PTRINT);
+ else if (flags & SIZEINT)
+ ADDTYPE(TP_SSIZEINT);
+ else if (flags & MAXINT)
+ ADDTYPE(TP_MAXINT);
+ else
+ ADDTYPE(TP_INT);
+ continue; /* no output */
+#endif /* NO_PRINTF_PERCENT_N */
+ case 'O':
+ flags |= LONGINT;
+ FALLTHROUGH;
+ case 'o':
+ ADDUARG();
+ break;
+ case 'p':
+ ADDTYPE(TP_VOID);
+ break;
+ case 's':
+#ifdef PRINTF_WIDE_CHAR
+ if (flags & LONGINT)
+ ADDTYPE(TP_WCHAR);
+ else
+#endif
+ ADDTYPE(TP_CHAR);
+ break;
+ case 'U':
+ flags |= LONGINT;
+ FALLTHROUGH;
+ case 'u':
+ case 'X':
+ case 'x':
+ ADDUARG();
+ break;
+ default: /* "%?" prints ?, unless ? is NUL */
+ if (ch == '\0')
+ goto done;
+ break;
+ }
+ }
+done:
+ /*
+ * Build the argument table.
+ */
+ if (tablemax >= STATIC_ARG_TBL_SIZE) {
+ *argtable = sudo_mmap_allocarray(tablemax + 1,
+ sizeof(union arg));
+ if (*argtable == NULL)
+ return -1;
+ }
+
+ for (n = 1; n <= tablemax; n++) {
+ switch (typetable[n]) {
+ case T_UNUSED:
+ case T_CHAR:
+ case T_U_CHAR:
+ case T_SHORT:
+ case T_U_SHORT:
+ case T_INT:
+ (*argtable)[n].intarg = va_arg(ap, int);
+ break;
+ case TP_SHORT:
+ (*argtable)[n].pshortarg = va_arg(ap, short *);
+ break;
+ case T_U_INT:
+ (*argtable)[n].uintarg = va_arg(ap, unsigned int);
+ break;
+ case TP_INT:
+ (*argtable)[n].pintarg = va_arg(ap, int *);
+ break;
+ case T_LONG:
+ (*argtable)[n].longarg = va_arg(ap, long);
+ break;
+ case T_U_LONG:
+ (*argtable)[n].ulongarg = va_arg(ap, unsigned long);
+ break;
+ case TP_LONG:
+ (*argtable)[n].plongarg = va_arg(ap, long *);
+ break;
+ case T_LLONG:
+ (*argtable)[n].longlongarg = va_arg(ap, long long);
+ break;
+ case T_U_LLONG:
+ (*argtable)[n].ulonglongarg = va_arg(ap, unsigned long long);
+ break;
+ case TP_LLONG:
+ (*argtable)[n].plonglongarg = va_arg(ap, long long *);
+ break;
+#ifdef FLOATING_POINT
+ case T_DOUBLE:
+ (*argtable)[n].doublearg = va_arg(ap, double);
+ break;
+ case T_LONG_DOUBLE:
+ (*argtable)[n].longdoublearg = va_arg(ap, long double);
+ break;
+#endif
+ case TP_CHAR:
+ (*argtable)[n].pchararg = va_arg(ap, char *);
+ break;
+ case TP_VOID:
+ (*argtable)[n].pvoidarg = va_arg(ap, void *);
+ break;
+ case T_PTRINT:
+ (*argtable)[n].ptrdiffarg = va_arg(ap, ptrdiff_t);
+ break;
+ case TP_PTRINT:
+ (*argtable)[n].pptrdiffarg = va_arg(ap, ptrdiff_t *);
+ break;
+ case T_SIZEINT:
+ (*argtable)[n].sizearg = va_arg(ap, size_t);
+ break;
+ case T_SSIZEINT:
+ (*argtable)[n].ssizearg = va_arg(ap, ssize_t);
+ break;
+ case TP_SSIZEINT:
+ (*argtable)[n].pssizearg = va_arg(ap, ssize_t *);
+ break;
+ case T_MAXINT:
+ (*argtable)[n].intmaxarg = va_arg(ap, intmax_t);
+ break;
+ case T_MAXUINT:
+ (*argtable)[n].uintmaxarg = va_arg(ap, uintmax_t);
+ break;
+ case TP_MAXINT:
+ (*argtable)[n].pintmaxarg = va_arg(ap, intmax_t *);
+ break;
+#ifdef PRINTF_WIDE_CHAR
+ case T_WINT:
+ (*argtable)[n].wintarg = va_arg(ap, wint_t);
+ break;
+ case TP_WCHAR:
+ (*argtable)[n].pwchararg = va_arg(ap, wchar_t *);
+ break;
+#endif
+ }
+ }
+ goto finish;
+
+overflow:
+ errno = EOVERFLOW;
+ ret = -1;
+
+finish:
+ if (typetable != NULL && typetable != stattypetable) {
+ sudo_mmap_free(typetable);
+ typetable = NULL;
+ }
+ return ret;
+}
+
+/*
+ * Increase the size of the type table.
+ */
+static int
+__grow_type_table(unsigned char **typetable, int *tablesize)
+{
+ unsigned char *oldtable = *typetable;
+ int newsize = *tablesize * 2;
+
+ if (newsize < sysconf(_SC_PAGESIZE))
+ newsize = sysconf(_SC_PAGESIZE);
+
+ if (*tablesize == STATIC_ARG_TBL_SIZE) {
+ *typetable = sudo_mmap_alloc(newsize);
+ if (*typetable == NULL)
+ return -1;
+ memcpy(*typetable, oldtable, *tablesize);
+ } else {
+ unsigned char *new = sudo_mmap_alloc(newsize);
+ if (new == NULL)
+ return -1;
+ memcpy(new, *typetable, *tablesize);
+ sudo_mmap_free(*typetable);
+ *typetable = new;
+ }
+ memset(*typetable + *tablesize, T_UNUSED, (newsize - *tablesize));
+
+ *tablesize = newsize;
+ return 0;
+}
+
+#ifdef FLOATING_POINT
+static int
+exponent(char *p0, int exp, int fmtch)
+{
+ char *p, *t;
+ char expbuf[MAXEXPDIG];
+
+ p = p0;
+ *p++ = fmtch;
+ if (exp < 0) {
+ exp = -exp;
+ *p++ = '-';
+ } else
+ *p++ = '+';
+ t = expbuf + MAXEXPDIG;
+ if (exp > 9) {
+ do {
+ *--t = to_char(exp % 10);
+ } while ((exp /= 10) > 9);
+ *--t = to_char(exp);
+ for (; t < expbuf + MAXEXPDIG; *p++ = *t++)
+ /* nothing */;
+ } else {
+ /*
+ * Exponents for decimal floating point conversions
+ * (%[eEgG]) must be at least two characters long,
+ * whereas exponents for hexadecimal conversions can
+ * be only one character long.
+ */
+ if (fmtch == 'e' || fmtch == 'E')
+ *p++ = '0';
+ *p++ = to_char(exp);
+ }
+ return p - p0;
+}
+#endif /* FLOATING_POINT */
+
+#if !defined(HAVE_VSNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
+int
+sudo_vsnprintf(char *str, size_t n, const char *fmt, va_list ap)
+{
+ if (n > INT_MAX) {
+ errno = EOVERFLOW;
+ *str = '\0';
+ return -1;
+ }
+ return xxxprintf(&str, n, 0, fmt, ap);
+}
+#endif /* !HAVE_VSNPRINTF || PREFER_PORTABLE_SNPRINTF */
+
+#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
+int
+sudo_snprintf(char *str, size_t n, char const *fmt, ...)
+{
+ int ret;
+ va_list ap;
+
+ if (n > INT_MAX) {
+ errno = EOVERFLOW;
+ *str = '\0';
+ return -1;
+ }
+ va_start(ap, fmt);
+ ret = xxxprintf(&str, n, 0, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+#endif /* !HAVE_SNPRINTF || PREFER_PORTABLE_SNPRINTF */
+
+#if !defined(HAVE_VASPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
+int
+sudo_vasprintf(char **str, const char *fmt, va_list ap)
+{
+ int ret;
+
+ ret = xxxprintf(str, 0, 1, fmt, ap);
+ if (ret == -1)
+ *str = NULL;
+ return ret;
+}
+#endif /* !HAVE_VASPRINTF || PREFER_PORTABLE_SNPRINTF */
+
+#if !defined(HAVE_ASPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
+int
+sudo_asprintf(char **str, char const *fmt, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = xxxprintf(str, 0, 1, fmt, ap);
+ va_end(ap);
+ if (ret == -1)
+ *str = NULL;
+ return ret;
+}
+#endif /* !HAVE_ASPRINTF || PREFER_PORTABLE_SNPRINTF */
+
+#endif /* !HAVE_VSNPRINTF || !HAVE_SNPRINTF || !HAVE_VASPRINTF || !HAVE_ASPRINTF || PREFER_PORTABLE_SNPRINTF */
diff --git a/lib/util/str2sig.c b/lib/util/str2sig.c
new file mode 100644
index 0000000..e265ec4
--- /dev/null
+++ b/lib/util/str2sig.c
@@ -0,0 +1,174 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_STR2SIG
+
+#include <errno.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <ctype.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#if !defined(HAVE_SIGABBREV_NP)
+# if defined(HAVE_DECL_SYS_SIGNAME) && HAVE_DECL_SYS_SIGNAME == 1
+# define sigabbrev_np(_x) sys_signame[(_x)]
+# elif defined(HAVE_DECL__SYS_SIGNAME) && HAVE_DECL__SYS_SIGNAME == 1
+# define sigabbrev_np(_x) _sys_signame[(_x)]
+# elif defined(HAVE_SYS_SIGABBREV)
+# define sigabbrev_np(_x) sys_sigabbrev[(_x)]
+# if defined(HAVE_DECL_SYS_SIGABBREV) && HAVE_DECL_SYS_SIGABBREV == 0
+ /* sys_sigabbrev is not declared by glibc */
+ extern const char *const sys_sigabbrev[NSIG];
+# endif
+# else
+# define sigabbrev_np(_x) sudo_sys_signame[(_x)]
+ extern const char *const sudo_sys_signame[NSIG];
+# endif
+#endif /* !HAVE_SIGABBREV_NP */
+
+/*
+ * Many systems use aliases for source backward compatibility.
+ */
+static struct sigalias {
+ const char *name;
+ int number;
+} sigaliases[] = {
+#ifdef SIGABRT
+ { "ABRT", SIGABRT },
+#endif
+#ifdef SIGCLD
+ { "CLD", SIGCLD },
+#endif
+#ifdef SIGIO
+ { "IO", SIGIO },
+#endif
+#ifdef SIGIOT
+ { "IOT", SIGIOT },
+#endif
+#ifdef SIGLOST
+ { "LOST", SIGLOST },
+#endif
+#ifdef SIGPOLL
+ { "POLL", SIGPOLL },
+#endif
+ { NULL, -1 }
+};
+
+/*
+ * Translate signal name to number.
+ */
+int
+sudo_str2sig(const char *signame, int *result)
+{
+ struct sigalias *alias;
+ const char *errstr;
+ int signo;
+
+ /* Could be a signal number encoded as a string. */
+ if (isdigit((unsigned char)signame[0])) {
+ signo = sudo_strtonum(signame, 0, NSIG - 1, &errstr);
+ if (errstr != NULL)
+ return -1;
+ *result = signo;
+ return 0;
+ }
+
+ /* Check real-time signals. */
+#if defined(SIGRTMIN)
+ if (strncmp(signame, "RTMIN", 5) == 0) {
+ if (signame[5] == '\0') {
+ *result = SIGRTMIN;
+ return 0;
+ }
+ if (signame[5] == '+') {
+ if (isdigit((unsigned char)signame[6])) {
+# ifdef _SC_RTSIG_MAX
+ const long rtmax = sysconf(_SC_RTSIG_MAX);
+# else
+ const long rtmax = SIGRTMAX - SIGRTMIN;
+# endif
+ const int off = signame[6] - '0';
+
+ if (rtmax > 0 && off < rtmax / 2) {
+ *result = SIGRTMIN + off;
+ return 0;
+ }
+ }
+ }
+ }
+#endif
+#if defined(SIGRTMAX)
+ if (strncmp(signame, "RTMAX", 5) == 0) {
+ if (signame[5] == '\0') {
+ *result = SIGRTMAX;
+ return 0;
+ }
+ if (signame[5] == '-') {
+ if (isdigit((unsigned char)signame[6])) {
+# ifdef _SC_RTSIG_MAX
+ const long rtmax = sysconf(_SC_RTSIG_MAX);
+# else
+ const long rtmax = SIGRTMAX - SIGRTMIN;
+# endif
+ const int off = signame[6] - '0';
+
+ if (rtmax > 0 && off < rtmax / 2) {
+ *result = SIGRTMAX - off;
+ return 0;
+ }
+ }
+ }
+ }
+#endif
+
+ /* Check aliases. */
+ for (alias = sigaliases; alias->name != NULL; alias++) {
+ if (strcmp(signame, alias->name) == 0) {
+ *result = alias->number;
+ return 0;
+ }
+ }
+
+ for (signo = 1; signo < NSIG; signo++) {
+ const char *cp = sigabbrev_np(signo);
+ if (cp != NULL) {
+ /* On macOS sys_signame[] may contain lower-case names. */
+ if (strcasecmp(signame, cp) == 0) {
+ *result = signo;
+ return 0;
+ }
+ }
+ }
+
+ errno = EINVAL;
+ return -1;
+}
+#endif /* HAVE_STR2SIG */
diff --git a/lib/util/strlcat.c b/lib/util/strlcat.c
new file mode 100644
index 0000000..fa1934a
--- /dev/null
+++ b/lib/util/strlcat.c
@@ -0,0 +1,69 @@
+/* $OpenBSD: strlcat.c,v 1.15 2015/03/02 21:41:08 millert Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1998, 2003-2005, 2010-2011, 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>
+
+#ifndef HAVE_STRLCAT
+
+#include <string.h>
+
+#include "sudo_compat.h"
+
+/*
+ * Appends src to string dst of size dsize (unlike strncat, dsize is the
+ * full size of dst, not space left). At most dsize-1 characters
+ * will be copied. Always NUL terminates (unless dsize <= strlen(dst)).
+ * Returns strlen(src) + MIN(dsize, strlen(initial dst)).
+ * If retval >= dsize, truncation occurred.
+ */
+size_t
+sudo_strlcat(char *dst, const char *src, size_t dsize)
+{
+ const char *odst = dst;
+ const char *osrc = src;
+ size_t n = dsize;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end. */
+ while (n-- != 0 && *dst != '\0')
+ dst++;
+ dlen = dst - odst;
+ n = dsize - dlen;
+
+ if (n-- == 0)
+ return(dlen + strlen(src));
+ while (*src != '\0') {
+ if (n != 0) {
+ *dst++ = *src;
+ n--;
+ }
+ src++;
+ }
+ *dst = '\0';
+
+ return(dlen + (src - osrc)); /* count does not include NUL */
+}
+#endif /* HAVE_STRLCAT */
diff --git a/lib/util/strlcpy.c b/lib/util/strlcpy.c
new file mode 100644
index 0000000..f9e5244
--- /dev/null
+++ b/lib/util/strlcpy.c
@@ -0,0 +1,64 @@
+/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1998, 2003-2005, 2010-2011, 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>
+
+#ifndef HAVE_STRLCPY
+
+#include <string.h>
+
+#include "sudo_compat.h"
+
+/*
+ * Copy string src to buffer dst of size dsize. At most dsize-1
+ * chars will be copied. Always NUL terminates (unless dsize == 0).
+ * Returns strlen(src); if retval >= dsize, truncation occurred.
+ */
+size_t
+sudo_strlcpy(char *dst, const char *src, size_t dsize)
+{
+ const char *osrc = src;
+ size_t nleft = dsize;
+
+ /* Copy as many bytes as will fit. */
+ if (nleft != 0) {
+ while (--nleft != 0) {
+ if ((*dst++ = *src++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src. */
+ if (nleft == 0) {
+ if (dsize != 0)
+ *dst = '\0'; /* NUL-terminate dst */
+ while (*src++)
+ continue;
+ }
+
+ return(src - osrc - 1); /* count does not include NUL */
+}
+#endif /* HAVE_STRLCPY */
diff --git a/lib/util/strndup.c b/lib/util/strndup.c
new file mode 100644
index 0000000..75b92eb
--- /dev/null
+++ b/lib/util/strndup.c
@@ -0,0 +1,51 @@
+/* $OpenBSD: strndup.c,v 1.1 2010/05/18 22:24:55 tedu Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010 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>
+
+#ifndef HAVE_STRNDUP
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+
+char *
+sudo_strndup(const char *str, size_t maxlen)
+{
+ char *copy;
+ size_t len;
+
+ len = strnlen(str, maxlen);
+ copy = malloc(len + 1);
+ if (copy != NULL) {
+ (void)memcpy(copy, str, len);
+ copy[len] = '\0';
+ }
+
+ return copy;
+}
+
+#endif /* HAVE_STRNDUP */
diff --git a/lib/util/strnlen.c b/lib/util/strnlen.c
new file mode 100644
index 0000000..5e0977a
--- /dev/null
+++ b/lib/util/strnlen.c
@@ -0,0 +1,45 @@
+/* $OpenBSD: strnlen.c,v 1.5 2014/06/10 04:17:37 deraadt Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010 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>
+
+#ifndef HAVE_STRNLEN
+
+#include <sys/types.h>
+
+#include "sudo_compat.h"
+
+size_t
+sudo_strnlen(const char *str, size_t maxlen)
+{
+ const char *cp;
+
+ for (cp = str; maxlen != 0 && *cp != '\0'; cp++, maxlen--)
+ continue;
+
+ return (size_t)(cp - str);
+}
+
+#endif /* HAVE_STRNLEN */
diff --git a/lib/util/strsignal.c b/lib/util/strsignal.c
new file mode 100644
index 0000000..76c9378
--- /dev/null
+++ b/lib/util/strsignal.c
@@ -0,0 +1,52 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-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.
+ */
+
+/*
+ * 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>
+
+#ifndef HAVE_STRSIGNAL
+
+#include <signal.h>
+
+#include "sudo_compat.h"
+#include "sudo_gettext.h"
+
+#if defined(HAVE_DECL_SYS_SIGLIST) && HAVE_DECL_SYS_SIGLIST == 1
+# define sudo_sys_siglist sys_siglist
+#elif defined(HAVE_DECL__SYS_SIGLIST) && HAVE_DECL__SYS_SIGLIST == 1
+# define sudo_sys_siglist _sys_siglist
+#else
+extern const char *const sudo_sys_siglist[NSIG];
+#endif
+
+/*
+ * Get signal description string
+ */
+char *
+sudo_strsignal(int signo)
+{
+ if (signo > 0 && signo < NSIG && sudo_sys_siglist[signo] != NULL)
+ return (char *)sudo_sys_siglist[signo];
+ /* XXX - should be "Unknown signal: %d" */
+ return (char *)_("Unknown signal");
+}
+#endif /* HAVE_STRSIGNAL */
diff --git a/lib/util/strsplit.c b/lib/util/strsplit.c
new file mode 100644
index 0000000..7663460
--- /dev/null
+++ b/lib/util/strsplit.c
@@ -0,0 +1,73 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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 "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * Like strtok_r but non-destructive and works w/o a NUL terminator.
+ * TODO: Optimize by storing current char in a variable ch
+ */
+const char *
+sudo_strsplit_v1(const char *str, const char *endstr, const char *sep, const char **last)
+{
+ const char *cp, *s;
+ debug_decl(sudo_strsplit, SUDO_DEBUG_UTIL);
+
+ /* If no str specified, use last ptr (if any). */
+ if (str == NULL)
+ str = *last;
+
+ /* Skip leading separator characters. */
+ while (str < endstr) {
+ for (s = sep; *s != '\0'; s++) {
+ if (*str == *s) {
+ str++;
+ break;
+ }
+ }
+ if (*s == '\0')
+ break;
+ }
+
+ /* Empty string? */
+ if (str >= endstr) {
+ *last = endstr;
+ debug_return_ptr(NULL);
+ }
+
+ /* Scan str until we hit a char from sep. */
+ for (cp = str; cp < endstr; cp++) {
+ for (s = sep; *s != '\0'; s++) {
+ if (*cp == *s)
+ break;
+ }
+ if (*s != '\0')
+ break;
+ }
+ *last = cp;
+ debug_return_const_ptr(str);
+}
diff --git a/lib/util/strtobool.c b/lib/util/strtobool.c
new file mode 100644
index 0000000..3cd1986
--- /dev/null
+++ b/lib/util/strtobool.c
@@ -0,0 +1,77 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+int
+sudo_strtobool_v1(const char *str)
+{
+ debug_decl(sudo_strtobool, SUDO_DEBUG_UTIL);
+
+ switch (*str) {
+ case '0':
+ case '1':
+ if (str[1] == '\0')
+ debug_return_int(*str - '0');
+ break;
+ case 'y':
+ case 'Y':
+ if (strcasecmp(str, "yes") == 0)
+ debug_return_int(1);
+ break;
+ case 't':
+ case 'T':
+ if (strcasecmp(str, "true") == 0)
+ debug_return_int(1);
+ break;
+ case 'o':
+ case 'O':
+ if (strcasecmp(str, "on") == 0)
+ debug_return_int(1);
+ if (strcasecmp(str, "off") == 0)
+ debug_return_int(0);
+ break;
+ case 'n':
+ case 'N':
+ if (strcasecmp(str, "no") == 0)
+ debug_return_int(0);
+ break;
+ case 'f':
+ case 'F':
+ if (strcasecmp(str, "false") == 0)
+ debug_return_int(0);
+ break;
+ }
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "invalid boolean value \"%s\"", str);
+
+ debug_return_int(-1);
+}
diff --git a/lib/util/strtoid.c b/lib/util/strtoid.c
new file mode 100644
index 0000000..fe5b44d
--- /dev/null
+++ b/lib/util/strtoid.c
@@ -0,0 +1,108 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * 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> /* for id_t */
+
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+/*
+ * Make sure that the ID ends with a valid separator char.
+ */
+static bool
+valid_separator(const char *p, const char *ep, const char *sep)
+{
+ bool valid = false;
+
+ if (ep != p) {
+ /* check for valid separator (including '\0') */
+ if (sep == NULL)
+ sep = "";
+ do {
+ if (*ep == *sep)
+ valid = true;
+ } while (*sep++ != '\0');
+ }
+ return valid;
+}
+
+/*
+ * Parse a uid/gid in string form.
+ * If sep is non-NULL, it contains valid separator characters (e.g. comma, space)
+ * If endp is non-NULL it is set to the next char after the ID.
+ * On success, returns the parsed ID and clears errstr.
+ * On error, returns 0 and sets errstr.
+ */
+id_t
+sudo_strtoidx_v1(const char *p, const char *sep, char **endp, const char **errstrp)
+{
+ const char *errstr;
+ char *ep;
+ id_t ret;
+ debug_decl(sudo_strtoid, SUDO_DEBUG_UTIL);
+
+ ret = sudo_strtonumx(p, INT_MIN, UINT_MAX, &ep, &errstr);
+ if (errstr == NULL) {
+ /*
+ * Disallow id -1 (UINT_MAX), which means "no change"
+ * and check for a valid separator (if specified).
+ */
+ if (ret == (id_t)-1 || ret == (id_t)UINT_MAX || !valid_separator(p, ep, sep)) {
+ errstr = N_("invalid value");
+ errno = EINVAL;
+ ret = 0;
+ }
+ }
+ if (errstrp != NULL)
+ *errstrp = errstr;
+ if (endp != NULL)
+ *endp = ep;
+ debug_return_id_t(ret);
+}
+
+/* Backward compatibility */
+id_t
+sudo_strtoid_v1(const char *p, const char *sep, char **endp, const char **errstrp)
+{
+ return sudo_strtoidx_v1(p, sep, endp, errstrp);
+}
+
+/* Simplified interface */
+id_t
+sudo_strtoid_v2(const char *p, const char **errstrp)
+{
+ return sudo_strtoidx_v1(p, NULL, NULL, errstrp);
+}
diff --git a/lib/util/strtomode.c b/lib/util/strtomode.c
new file mode 100644
index 0000000..1e35d34
--- /dev/null
+++ b/lib/util/strtomode.c
@@ -0,0 +1,65 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+/*
+ * Parse an octal file mode in the range [0, 0777].
+ * On success, returns the parsed mode and clears errstr.
+ * On error, returns 0 and sets errstr.
+ */
+int
+sudo_strtomode_v1(const char *cp, const char **errstr)
+{
+ char *ep;
+ long lval;
+ debug_decl(sudo_strtomode, SUDO_DEBUG_UTIL);
+
+ errno = 0;
+ lval = strtol(cp, &ep, 8);
+ if (ep == cp || *ep != '\0') {
+ if (errstr != NULL)
+ *errstr = N_("invalid value");
+ errno = EINVAL;
+ debug_return_int(0);
+ }
+ if (lval < 0 || lval > ACCESSPERMS) {
+ if (errstr != NULL)
+ *errstr = lval < 0 ? N_("value too small") : N_("value too large");
+ errno = ERANGE;
+ debug_return_int(0);
+ }
+ if (errstr != NULL)
+ *errstr = NULL;
+ debug_return_int((int)lval);
+}
diff --git a/lib/util/strtonum.c b/lib/util/strtonum.c
new file mode 100644
index 0000000..12edbbe
--- /dev/null
+++ b/lib/util/strtonum.c
@@ -0,0 +1,193 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015, 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+enum strtonum_err {
+ STN_INITIAL,
+ STN_VALID,
+ STN_INVALID,
+ STN_TOOSMALL,
+ STN_TOOBIG
+};
+
+/*
+ * Convert a string to a number in the range [minval, maxval]
+ * Unlike strtonum(), this returns the first non-digit in endp (if not NULL).
+ */
+long long
+sudo_strtonumx(const char *str, long long minval, long long maxval, char **endp,
+ const char **errstrp)
+{
+ enum strtonum_err errval = STN_INITIAL;
+ long long lastval, result = 0;
+ const char *cp = str;
+ unsigned char ch;
+ int remainder;
+ char sign;
+
+ if (minval > maxval) {
+ errval = STN_INVALID;
+ goto done;
+ }
+
+ /* Trim leading space and check sign, if any. */
+ do {
+ ch = *cp++;
+ } while (isspace(ch));
+ switch (ch) {
+ case '-':
+ sign = '-';
+ ch = *cp++;
+ break;
+ case '+':
+ ch = *cp++;
+ FALLTHROUGH;
+ default:
+ sign = '+';
+ break;
+ }
+
+ /*
+ * To prevent overflow we determine the highest (or lowest in
+ * the case of negative numbers) value result can have *before*
+ * if its multiplied (divided) by 10 as well as the remainder.
+ * If result matches this value and the next digit is larger than
+ * the remainder, we know the result is out of range.
+ * The remainder is always positive since it is compared against
+ * an unsigned digit.
+ */
+ if (sign == '-') {
+ lastval = minval / 10;
+ remainder = -(minval % 10);
+ if (remainder < 0) {
+ lastval += 1;
+ remainder += 10;
+ }
+ for (;; ch = *cp++) {
+ if (!isdigit(ch))
+ break;
+ ch -= '0';
+ if (result < lastval || (result == lastval && ch > remainder)) {
+ /* Skip remaining digits. */
+ do {
+ ch = *cp++;
+ } while (isdigit(ch));
+ errval = STN_TOOSMALL;
+ break;
+ } else {
+ result *= 10;
+ result -= ch;
+ errval = STN_VALID;
+ }
+ }
+ if (result > maxval)
+ errval = STN_TOOBIG;
+ } else {
+ lastval = maxval / 10;
+ remainder = maxval % 10;
+ for (;; ch = *cp++) {
+ if (!isdigit(ch))
+ break;
+ ch -= '0';
+ if (result > lastval || (result == lastval && ch > remainder)) {
+ /* Skip remaining digits. */
+ do {
+ ch = *cp++;
+ } while (isdigit(ch));
+ errval = STN_TOOBIG;
+ break;
+ } else {
+ result *= 10;
+ result += ch;
+ errval = STN_VALID;
+ }
+ }
+ if (result < minval)
+ errval = STN_TOOSMALL;
+ }
+
+done:
+ switch (errval) {
+ case STN_INITIAL:
+ case STN_VALID:
+ if (errstrp != NULL)
+ *errstrp = NULL;
+ break;
+ case STN_INVALID:
+ result = 0;
+ errno = EINVAL;
+ if (errstrp != NULL)
+ *errstrp = N_("invalid value");
+ break;
+ case STN_TOOSMALL:
+ result = 0;
+ errno = ERANGE;
+ if (errstrp != NULL)
+ *errstrp = N_("value too small");
+ break;
+ case STN_TOOBIG:
+ result = 0;
+ errno = ERANGE;
+ if (errstrp != NULL)
+ *errstrp = N_("value too large");
+ break;
+ }
+ if (endp != NULL) {
+ if (errval == STN_INITIAL || errval == STN_INVALID)
+ *endp = (char *)str;
+ else
+ *endp = (char *)(cp - 1);
+ }
+ return result;
+}
+
+/*
+ * Convert a string to a number in the range [minval, maxval]
+ */
+long long
+sudo_strtonum(const char *str, long long minval, long long maxval,
+ const char **errstrp)
+{
+ const char *errstr;
+ char *ep;
+ long long ret;
+
+ ret = sudo_strtonumx(str, minval, maxval, &ep, &errstr);
+ /* Check for empty string and terminating NUL. */
+ if (str == ep || *ep != '\0') {
+ errno = EINVAL;
+ errstr = N_("invalid value");
+ ret = 0;
+ }
+ if (errstrp != NULL)
+ *errstrp = errstr;
+ return ret;
+}
diff --git a/lib/util/sudo_conf.c b/lib/util/sudo_conf.c
new file mode 100644
index 0000000..9c56120
--- /dev/null
+++ b/lib/util/sudo_conf.c
@@ -0,0 +1,776 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <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>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_plugin.h"
+#include "sudo_util.h"
+#include "pathnames.h"
+
+#ifndef _PATH_SUDO_INTERCEPT
+# define _PATH_SUDO_INTERCEPT NULL
+#endif
+#ifndef _PATH_SUDO_NOEXEC
+# define _PATH_SUDO_NOEXEC NULL
+#endif
+
+struct sudo_conf_table {
+ const char *name;
+ unsigned int namelen;
+ int (*parser)(const char *entry, const char *conf_file, unsigned int lineno);
+};
+
+struct sudo_conf_path_table {
+ const char *pname;
+ unsigned int pnamelen;
+ bool dynamic;
+ const char *pval;
+};
+
+struct sudo_conf_settings {
+ bool updated;
+ bool disable_coredump;
+ bool probe_interfaces;
+ int group_source;
+ int max_groups;
+};
+
+static int parse_debug(const char *entry, const char *conf_file, unsigned int lineno);
+static int parse_path(const char *entry, const char *conf_file, unsigned int lineno);
+static int parse_plugin(const char *entry, const char *conf_file, unsigned int lineno);
+static int parse_variable(const char *entry, const char *conf_file, unsigned int lineno);
+
+static struct sudo_conf_table sudo_conf_table[] = {
+ { "Debug", sizeof("Debug") - 1, parse_debug },
+ { "Path", sizeof("Path") - 1, parse_path },
+ { "Plugin", sizeof("Plugin") - 1, parse_plugin },
+ { "Set", sizeof("Set") - 1, parse_variable },
+ { NULL }
+};
+
+static int set_var_disable_coredump(const char *entry, const char *conf_file, unsigned int);
+static int set_var_group_source(const char *entry, const char *conf_file, unsigned int);
+static int set_var_max_groups(const char *entry, const char *conf_file, unsigned int);
+static int set_var_probe_interfaces(const char *entry, const char *conf_file, unsigned int);
+
+static struct sudo_conf_table sudo_conf_var_table[] = {
+ { "disable_coredump", sizeof("disable_coredump") - 1, set_var_disable_coredump },
+ { "group_source", sizeof("group_source") - 1, set_var_group_source },
+ { "max_groups", sizeof("max_groups") - 1, set_var_max_groups },
+ { "probe_interfaces", sizeof("probe_interfaces") - 1, set_var_probe_interfaces },
+ { NULL }
+};
+
+/* Indexes into path_table[] below (order is important). */
+#define SUDO_CONF_PATH_ASKPASS 0
+#define SUDO_CONF_PATH_SESH 1
+#define SUDO_CONF_PATH_INTERCEPT 2
+#define SUDO_CONF_PATH_NOEXEC 3
+#define SUDO_CONF_PATH_PLUGIN_DIR 4
+#define SUDO_CONF_PATH_DEVSEARCH 5
+
+#define SUDO_CONF_PATH_INITIALIZER { \
+ { "askpass", sizeof("askpass") - 1, false, _PATH_SUDO_ASKPASS }, \
+ { "sesh", sizeof("sesh") - 1, false, _PATH_SUDO_SESH }, \
+ { "intercept", sizeof("intercept") - 1, false, _PATH_SUDO_INTERCEPT }, \
+ { "noexec", sizeof("noexec") - 1, false, _PATH_SUDO_NOEXEC }, \
+ { "plugin_dir", sizeof("plugin_dir") - 1, false, _PATH_SUDO_PLUGIN_DIR }, \
+ { "devsearch", sizeof("devsearch") - 1, false, _PATH_SUDO_DEVSEARCH }, \
+ { NULL } \
+}
+
+/*
+ * getgroups(2) on macOS is flakey with respect to non-local groups.
+ * Even with _DARWIN_UNLIMITED_GETGROUPS set we may not get all groups./
+ * See bug #946 for details.
+ */
+#ifdef __APPLE__
+# define GROUP_SOURCE_DEFAULT GROUP_SOURCE_DYNAMIC
+#else
+# define GROUP_SOURCE_DEFAULT GROUP_SOURCE_ADAPTIVE
+#endif
+
+#define SUDO_CONF_SETTINGS_INITIALIZER { \
+ false, /* updated */ \
+ true, /* disable_coredump */ \
+ true, /* probe_interfaces */ \
+ GROUP_SOURCE_DEFAULT, /* group_source */ \
+ -1 /* max_groups */ \
+}
+
+static struct sudo_conf_data {
+ struct sudo_conf_settings settings;
+ struct sudo_conf_debug_list debugging;
+ struct plugin_info_list plugins;
+ struct sudo_conf_path_table path_table[7];
+} sudo_conf_data = {
+ SUDO_CONF_SETTINGS_INITIALIZER,
+ TAILQ_HEAD_INITIALIZER(sudo_conf_data.debugging),
+ TAILQ_HEAD_INITIALIZER(sudo_conf_data.plugins),
+ SUDO_CONF_PATH_INITIALIZER
+};
+
+/*
+ * "Set variable_name value"
+ */
+static int
+parse_variable(const char *entry, const char *conf_file, unsigned int lineno)
+{
+ struct sudo_conf_table *var;
+ int ret;
+ debug_decl(parse_variable, SUDO_DEBUG_UTIL);
+
+ for (var = sudo_conf_var_table; var->name != NULL; var++) {
+ if (strncmp(entry, var->name, var->namelen) == 0 &&
+ isblank((unsigned char)entry[var->namelen])) {
+ entry += var->namelen + 1;
+ while (isblank((unsigned char)*entry))
+ entry++;
+ ret = var->parser(entry, conf_file, lineno);
+ sudo_debug_printf(ret ? SUDO_DEBUG_INFO : SUDO_DEBUG_ERROR,
+ "%s: %s:%u: Set %s %s", __func__, conf_file,
+ lineno, var->name, entry);
+ debug_return_int(ret);
+ }
+ }
+ sudo_debug_printf(SUDO_DEBUG_WARN, "%s: %s:%u: unknown setting %s",
+ __func__, conf_file, lineno, entry);
+ debug_return_int(false);
+}
+
+/*
+ * "Path name /path/to/file"
+ * If path is missing it will be set to the NULL pointer.
+ */
+static int
+parse_path(const char *entry, const char *conf_file, unsigned int lineno)
+{
+ const char *entry_end = entry + strlen(entry);
+ const char *ep, *name, *path;
+ struct sudo_conf_path_table *cur;
+ size_t namelen;
+ debug_decl(parse_path, SUDO_DEBUG_UTIL);
+
+ /* Parse name. */
+ name = sudo_strsplit(entry, entry_end, " \t", &ep);
+ if (name == NULL)
+ goto bad;
+ namelen = (size_t)(ep - name);
+
+ /* Parse path (if present). */
+ path = sudo_strsplit(NULL, entry_end, " \t", &ep);
+
+ /* Match supported paths, ignoring unknown paths. */
+ for (cur = sudo_conf_data.path_table; cur->pname != NULL; cur++) {
+ if (namelen == cur->pnamelen &&
+ strncasecmp(name, cur->pname, cur->pnamelen) == 0) {
+ char *pval = NULL;
+ if (path != NULL) {
+ if ((pval = strdup(path)) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__,
+ U_("unable to allocate memory"));
+ debug_return_int(-1);
+ }
+ }
+ if (cur->dynamic)
+ free((char *)cur->pval);
+ cur->pval = pval;
+ cur->dynamic = true;
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %s:%u: Path %s %s",
+ __func__, conf_file, lineno, cur->pname,
+ pval ? pval : "(none)");
+ debug_return_int(true);
+ }
+ }
+ sudo_debug_printf(SUDO_DEBUG_WARN, "%s: %s:%u: unknown path %s",
+ __func__, conf_file, lineno, entry);
+ debug_return_int(false);
+bad:
+ sudo_warnx(U_("invalid Path value \"%s\" in %s, line %u"),
+ entry, conf_file, lineno);
+ debug_return_int(false);
+}
+
+/*
+ * "Debug program /path/to/log flags,..."
+ */
+static int
+parse_debug(const char *entry, const char *conf_file, unsigned int lineno)
+{
+ struct sudo_conf_debug *debug_spec;
+ struct sudo_debug_file *debug_file = NULL;
+ const char *ep, *path, *progname, *flags;
+ const char *entry_end = entry + strlen(entry);
+ size_t pathlen, prognamelen;
+ debug_decl(parse_debug, SUDO_DEBUG_UTIL);
+
+ /* Parse progname. */
+ progname = sudo_strsplit(entry, entry_end, " \t", &ep);
+ if (progname == NULL)
+ debug_return_int(false); /* not enough fields */
+ prognamelen = (size_t)(ep - progname);
+
+ /* Parse path. */
+ path = sudo_strsplit(NULL, entry_end, " \t", &ep);
+ if (path == NULL)
+ debug_return_int(false); /* not enough fields */
+ pathlen = (size_t)(ep - path);
+
+ /* Remainder is flags (freeform). */
+ flags = sudo_strsplit(NULL, entry_end, " \t", &ep);
+ if (flags == NULL)
+ debug_return_int(false); /* not enough fields */
+
+ /* If progname already exists, use it, else alloc a new one. */
+ TAILQ_FOREACH(debug_spec, &sudo_conf_data.debugging, entries) {
+ if (strncmp(debug_spec->progname, progname, prognamelen) == 0 &&
+ debug_spec->progname[prognamelen] == '\0')
+ break;
+ }
+ if (debug_spec == NULL) {
+ debug_spec = malloc(sizeof(*debug_spec));
+ if (debug_spec == NULL)
+ goto oom;
+ debug_spec->progname = strndup(progname, prognamelen);
+ if (debug_spec->progname == NULL) {
+ free(debug_spec);
+ debug_spec = NULL;
+ goto oom;
+ }
+ TAILQ_INIT(&debug_spec->debug_files);
+ TAILQ_INSERT_TAIL(&sudo_conf_data.debugging, debug_spec, entries);
+ }
+ debug_file = calloc(1, sizeof(*debug_file));
+ if (debug_file == NULL)
+ goto oom;
+ debug_file->debug_file = strndup(path, pathlen);
+ if (debug_file->debug_file == NULL)
+ goto oom;
+ debug_file->debug_flags = strdup(flags);
+ if (debug_file->debug_flags == NULL)
+ goto oom;
+ TAILQ_INSERT_TAIL(&debug_spec->debug_files, debug_file, entries);
+
+ debug_return_int(true);
+oom:
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (debug_file != NULL) {
+ free(debug_file->debug_file);
+ free(debug_file->debug_flags);
+ free(debug_file);
+ }
+ debug_return_int(-1);
+}
+
+/*
+ * "Plugin symbol /path/to/log args..."
+ */
+static int
+parse_plugin(const char *entry, const char *conf_file, unsigned int lineno)
+{
+ struct plugin_info *info = NULL;
+ const char *ep, *path, *symbol;
+ const char *entry_end = entry + strlen(entry);
+ char **options = NULL;
+ size_t pathlen, symlen;
+ unsigned int nopts = 0;
+ debug_decl(parse_plugin, SUDO_DEBUG_UTIL);
+
+ /* Parse symbol. */
+ symbol = sudo_strsplit(entry, entry_end, " \t", &ep);
+ if (symbol == NULL)
+ debug_return_int(false); /* not enough fields */
+ symlen = (size_t)(ep - symbol);
+
+ /* Parse path. */
+ path = sudo_strsplit(NULL, entry_end, " \t", &ep);
+ if (path == NULL)
+ debug_return_int(false); /* not enough fields */
+ pathlen = (size_t)(ep - path);
+
+ /* Split options into an array if present. */
+ while (isblank((unsigned char)*ep))
+ ep++;
+ if (*ep != '\0') {
+ /* Count number of options and allocate array. */
+ const char *cp, *opt = ep;
+
+ /* Count and allocate options array. */
+ for (nopts = 0, cp = sudo_strsplit(opt, entry_end, " \t", &ep);
+ cp != NULL; cp = sudo_strsplit(NULL, entry_end, " \t", &ep)) {
+ nopts++;
+ }
+ options = reallocarray(NULL, nopts + 1, sizeof(*options));
+ if (options == NULL)
+ goto oom;
+
+ /* Fill in options array. */
+ for (nopts = 0, cp = sudo_strsplit(opt, entry_end, " \t", &ep);
+ cp != NULL; cp = sudo_strsplit(NULL, entry_end, " \t", &ep)) {
+ options[nopts] = strndup(cp, (size_t)(ep - cp));
+ if (options[nopts] == NULL)
+ goto oom;
+ nopts++;
+ }
+ options[nopts] = NULL;
+ }
+
+ info = calloc(sizeof(*info), 1);
+ if (info == NULL)
+ goto oom;
+ info->symbol_name = strndup(symbol, symlen);
+ if (info->symbol_name == NULL)
+ goto oom;
+ info->path = strndup(path, pathlen);
+ if (info->path == NULL)
+ goto oom;
+ info->options = options;
+ info->lineno = lineno;
+ TAILQ_INSERT_TAIL(&sudo_conf_data.plugins, info, entries);
+
+ debug_return_int(true);
+oom:
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (options != NULL) {
+ while (nopts--)
+ free(options[nopts]);
+ free(options);
+ }
+ if (info != NULL) {
+ free(info->symbol_name);
+ free(info->path);
+ free(info);
+ }
+ debug_return_int(-1);
+}
+
+static int
+set_var_disable_coredump(const char *strval, const char *conf_file,
+ unsigned int lineno)
+{
+ int val = sudo_strtobool(strval);
+ debug_decl(set_var_disable_coredump, SUDO_DEBUG_UTIL);
+
+ if (val == -1) {
+ sudo_warnx(U_("invalid value for %s \"%s\" in %s, line %u"),
+ "disable_coredump", strval, conf_file, lineno);
+ debug_return_int(false);
+ }
+ sudo_conf_data.settings.disable_coredump = val;
+ debug_return_int(true);
+}
+
+static int
+set_var_group_source(const char *strval, const char *conf_file,
+ unsigned int lineno)
+{
+ debug_decl(set_var_group_source, SUDO_DEBUG_UTIL);
+
+ if (strcasecmp(strval, "adaptive") == 0) {
+ sudo_conf_data.settings.group_source = GROUP_SOURCE_ADAPTIVE;
+ } else if (strcasecmp(strval, "static") == 0) {
+ sudo_conf_data.settings.group_source = GROUP_SOURCE_STATIC;
+ } else if (strcasecmp(strval, "dynamic") == 0) {
+ sudo_conf_data.settings.group_source = GROUP_SOURCE_DYNAMIC;
+ } else {
+ sudo_warnx(U_("unsupported group source \"%s\" in %s, line %u"), strval,
+ conf_file, lineno);
+ debug_return_int(false);
+ }
+ debug_return_int(true);
+}
+
+static int
+set_var_max_groups(const char *strval, const char *conf_file,
+ unsigned int lineno)
+{
+ int max_groups;
+ debug_decl(set_var_max_groups, SUDO_DEBUG_UTIL);
+
+ max_groups = sudo_strtonum(strval, 1, 1024, NULL);
+ if (max_groups <= 0) {
+ sudo_warnx(U_("invalid max groups \"%s\" in %s, line %u"), strval,
+ conf_file, lineno);
+ debug_return_int(false);
+ }
+ sudo_conf_data.settings.max_groups = max_groups;
+ debug_return_int(true);
+}
+
+static int
+set_var_probe_interfaces(const char *strval, const char *conf_file,
+ unsigned int lineno)
+{
+ int val = sudo_strtobool(strval);
+ debug_decl(set_var_probe_interfaces, SUDO_DEBUG_UTIL);
+
+ if (val == -1) {
+ sudo_warnx(U_("invalid value for %s \"%s\" in %s, line %u"),
+ "probe_interfaces", strval, conf_file, lineno);
+ debug_return_int(false);
+ }
+ sudo_conf_data.settings.probe_interfaces = val;
+ debug_return_int(true);
+}
+
+const char *
+sudo_conf_askpass_path_v1(void)
+{
+ return sudo_conf_data.path_table[SUDO_CONF_PATH_ASKPASS].pval;
+}
+
+const char *
+sudo_conf_sesh_path_v1(void)
+{
+ return sudo_conf_data.path_table[SUDO_CONF_PATH_SESH].pval;
+}
+
+const char *
+sudo_conf_intercept_path_v1(void)
+{
+ return sudo_conf_data.path_table[SUDO_CONF_PATH_INTERCEPT].pval;
+}
+
+const char *
+sudo_conf_noexec_path_v1(void)
+{
+ return sudo_conf_data.path_table[SUDO_CONF_PATH_NOEXEC].pval;
+}
+
+const char *
+sudo_conf_plugin_dir_path_v1(void)
+{
+ return sudo_conf_data.path_table[SUDO_CONF_PATH_PLUGIN_DIR].pval;
+}
+
+const char *
+sudo_conf_devsearch_path_v1(void)
+{
+ return sudo_conf_data.path_table[SUDO_CONF_PATH_DEVSEARCH].pval;
+}
+
+int
+sudo_conf_group_source_v1(void)
+{
+ return sudo_conf_data.settings.group_source;
+}
+
+int
+sudo_conf_max_groups_v1(void)
+{
+ return sudo_conf_data.settings.max_groups;
+}
+
+struct plugin_info_list *
+sudo_conf_plugins_v1(void)
+{
+ return &sudo_conf_data.plugins;
+}
+
+struct sudo_conf_debug_list *
+sudo_conf_debugging_v1(void)
+{
+ return &sudo_conf_data.debugging;
+}
+
+/* Return the debug files list for a program, or NULL if none. */
+struct sudo_conf_debug_file_list *
+sudo_conf_debug_files_v1(const char *progname)
+{
+ struct sudo_conf_debug *debug_spec;
+ const char *progbase;
+ debug_decl(sudo_conf_debug_files, SUDO_DEBUG_UTIL);
+
+ /* Determine basename if program is fully qualified (like for plugins). */
+ progbase = progname[0] == '/' ? sudo_basename(progname) : progname;
+
+ /* Convert sudoedit -> sudo. */
+ if (strcmp(progbase, "sudoedit") == 0)
+ progbase = "sudo";
+
+ TAILQ_FOREACH(debug_spec, &sudo_conf_data.debugging, entries) {
+ const char *prog = progbase;
+
+ if (debug_spec->progname[0] == '/') {
+ /* Match fully-qualified name, if possible. */
+ prog = progname;
+ }
+ if (strcmp(debug_spec->progname, prog) == 0)
+ debug_return_ptr(&debug_spec->debug_files);
+ }
+ debug_return_ptr(NULL);
+}
+
+bool
+sudo_conf_developer_mode_v1(void)
+{
+ /* Developer mode was removed in sudo 1.9.13. */
+ return false;
+}
+
+bool
+sudo_conf_disable_coredump_v1(void)
+{
+ return sudo_conf_data.settings.disable_coredump;
+}
+
+bool
+sudo_conf_probe_interfaces_v1(void)
+{
+ return sudo_conf_data.settings.probe_interfaces;
+}
+
+/*
+ * Free dynamically allocated parts of sudo_conf_data and
+ * reset to initial values.
+ */
+static void
+sudo_conf_init(int conf_types)
+{
+ struct sudo_conf_debug *debug_spec;
+ struct sudo_debug_file *debug_file;
+ struct plugin_info *plugin_info;
+ int i;
+ debug_decl(sudo_conf_init, SUDO_DEBUG_UTIL);
+
+ /* Free and reset paths. */
+ if (ISSET(conf_types, SUDO_CONF_PATHS)) {
+ const struct sudo_conf_path_table path_table[] = SUDO_CONF_PATH_INITIALIZER;
+ sudo_conf_clear_paths();
+ memcpy(sudo_conf_data.path_table, path_table,
+ sizeof(sudo_conf_data.path_table));
+ }
+
+ /* Free and reset debug settings. */
+ if (ISSET(conf_types, SUDO_CONF_DEBUG)) {
+ while ((debug_spec = TAILQ_FIRST(&sudo_conf_data.debugging))) {
+ TAILQ_REMOVE(&sudo_conf_data.debugging, debug_spec, entries);
+ free(debug_spec->progname);
+ while ((debug_file = TAILQ_FIRST(&debug_spec->debug_files))) {
+ TAILQ_REMOVE(&debug_spec->debug_files, debug_file, entries);
+ free(debug_file->debug_file);
+ free(debug_file->debug_flags);
+ free(debug_file);
+ }
+ free(debug_spec);
+ }
+ }
+
+ /* Free and reset plugins. */
+ if (ISSET(conf_types, SUDO_CONF_PLUGINS)) {
+ while ((plugin_info = TAILQ_FIRST(&sudo_conf_data.plugins))) {
+ TAILQ_REMOVE(&sudo_conf_data.plugins, plugin_info, entries);
+ free(plugin_info->symbol_name);
+ free(plugin_info->path);
+ if (plugin_info->options != NULL) {
+ for (i = 0; plugin_info->options[i] != NULL; i++)
+ free(plugin_info->options[i]);
+ free(plugin_info->options);
+ }
+ free(plugin_info);
+ }
+ }
+
+ /* Set initial values. */
+ if (ISSET(conf_types, SUDO_CONF_SETTINGS)) {
+ const struct sudo_conf_settings settings = SUDO_CONF_SETTINGS_INITIALIZER;
+ sudo_conf_data.settings = settings;
+ }
+
+ debug_return;
+}
+
+/*
+ * Read in /etc/sudo.conf and populates sudo_conf_data.
+ */
+int
+sudo_conf_read_v1(const char *conf_file, int conf_types)
+{
+ FILE *fp = NULL;
+ int fd, ret = false;
+ char *prev_locale, *line = NULL;
+ unsigned int conf_lineno = 0;
+ size_t linesize = 0;
+ debug_decl(sudo_conf_read, SUDO_DEBUG_UTIL);
+
+ if ((prev_locale = setlocale(LC_ALL, NULL)) == NULL) {
+ sudo_warn("setlocale(LC_ALL, NULL)");
+ debug_return_int(-1);
+ }
+ if ((prev_locale = strdup(prev_locale)) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_int(-1);
+ }
+
+ /* Parse sudo.conf in the "C" locale. */
+ if (prev_locale[0] != 'C' || prev_locale[1] != '\0')
+ setlocale(LC_ALL, "C");
+
+#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ if (conf_file == NULL) {
+ struct stat sb;
+ int error;
+
+ conf_file = _PATH_SUDO_CONF;
+ fd = sudo_secure_open_file(conf_file, ROOT_UID, -1, &sb, &error);
+ if (fd == -1) {
+ switch (error) {
+ case SUDO_PATH_MISSING:
+ /* Root should always be able to read sudo.conf. */
+ if (errno != ENOENT && geteuid() == ROOT_UID)
+ sudo_warn(U_("unable to open %s"), conf_file);
+ break;
+ case SUDO_PATH_BAD_TYPE:
+ sudo_warnx(U_("%s is not a regular file"), conf_file);
+ break;
+ case SUDO_PATH_WRONG_OWNER:
+ sudo_warnx(U_("%s is owned by uid %u, should be %u"),
+ conf_file, (unsigned int) sb.st_uid, ROOT_UID);
+ break;
+ case SUDO_PATH_WORLD_WRITABLE:
+ sudo_warnx(U_("%s is world writable"), conf_file);
+ break;
+ case SUDO_PATH_GROUP_WRITABLE:
+ sudo_warnx(U_("%s is group writable"), conf_file);
+ break;
+ default:
+ sudo_warnx("%s: internal error, unexpected error %d",
+ __func__, error);
+ break;
+ }
+ goto done;
+ }
+ } else
+#else
+ if (conf_file == NULL)
+ conf_file = _PATH_SUDO_CONF;
+#endif /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
+ {
+ fd = open(conf_file, O_RDONLY);
+ if (fd == -1) {
+ sudo_warn(U_("unable to open %s"), conf_file);
+ goto done;
+ }
+ }
+
+ if ((fp = fdopen(fd, "r")) == NULL) {
+ if (errno != ENOENT && geteuid() == ROOT_UID)
+ sudo_warn(U_("unable to open %s"), conf_file);
+ close(fd);
+ goto done;
+ }
+
+ /* Reset to initial values if necessary. */
+ if (sudo_conf_data.settings.updated)
+ sudo_conf_init(conf_types);
+
+ while (sudo_parseln(&line, &linesize, &conf_lineno, fp, 0) != -1) {
+ struct sudo_conf_table *cur;
+ unsigned int i;
+ char *cp;
+
+ if (*(cp = line) == '\0')
+ continue; /* empty line or comment */
+
+ for (i = 0, cur = sudo_conf_table; cur->name != NULL; i++, cur++) {
+ if (strncasecmp(cp, cur->name, cur->namelen) == 0 &&
+ isblank((unsigned char)cp[cur->namelen])) {
+ if (ISSET(conf_types, (1 << i))) {
+ cp += cur->namelen;
+ while (isblank((unsigned char)*cp))
+ cp++;
+ ret = cur->parser(cp, conf_file, conf_lineno);
+ switch (ret) {
+ case true:
+ sudo_conf_data.settings.updated = true;
+ break;
+ case false:
+ break;
+ default:
+ goto done;
+ }
+ }
+ break;
+ }
+ }
+ if (cur->name == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_WARN,
+ "%s: %s:%u: unsupported entry: %s", __func__, conf_file,
+ conf_lineno, line);
+ }
+ }
+ ret = true;
+
+done:
+ if (fp != NULL)
+ fclose(fp);
+ free(line);
+
+ /* Restore locale if needed. */
+ if (prev_locale[0] != 'C' || prev_locale[1] != '\0')
+ setlocale(LC_ALL, prev_locale);
+ free(prev_locale);
+ debug_return_int(ret);
+}
+
+/*
+ * Used by the sudo_conf regress test to clear compile-time path settings.
+ */
+void
+sudo_conf_clear_paths_v1(void)
+{
+ struct sudo_conf_path_table *cur;
+ debug_decl(sudo_conf_clear_paths, SUDO_DEBUG_UTIL);
+
+ for (cur = sudo_conf_data.path_table; cur->pname != NULL; cur++) {
+ if (cur->dynamic)
+ free((char *)cur->pval);
+ cur->pval = NULL;
+ cur->dynamic = false;
+ }
+}
diff --git a/lib/util/sudo_debug.c b/lib/util/sudo_debug.c
new file mode 100644
index 0000000..c06ba8c
--- /dev/null
+++ b/lib/util/sudo_debug.c
@@ -0,0 +1,1167 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011-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/stat.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <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 <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_plugin.h"
+#include "sudo_util.h"
+
+#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+/*
+ * The debug priorities and subsystems are currently hard-coded.
+ * In the future we might consider allowing plugins to register their
+ * own subsystems and provide direct access to the debugging API.
+ */
+
+/* Note: this must match the order in sudo_debug.h */
+static const char *const sudo_debug_priorities[] = {
+ "crit",
+ "err",
+ "warn",
+ "notice",
+ "diag",
+ "info",
+ "trace",
+ "debug",
+ NULL
+};
+
+/* Note: this must match the order in sudo_debug.h */
+static const char *const sudo_debug_default_subsystems[] = {
+ "args",
+ "conv",
+ "edit",
+ "event",
+ "exec",
+ "hooks",
+ "main",
+ "netif",
+ "pcomm",
+ "plugin",
+ "pty",
+ "selinux",
+ "util",
+ "utmp",
+ NULL
+};
+
+#define NUM_DEF_SUBSYSTEMS (nitems(sudo_debug_default_subsystems) - 1)
+
+/*
+ * For multiple programs/plugins there is a per-program instance
+ * and one or more outputs (files).
+ */
+struct sudo_debug_output {
+ SLIST_ENTRY(sudo_debug_output) entries;
+ char *filename;
+ int *settings;
+ int fd;
+};
+SLIST_HEAD(sudo_debug_output_list, sudo_debug_output);
+struct sudo_debug_instance {
+ char *program;
+ const char *const *subsystems;
+ const unsigned int *subsystem_ids;
+ unsigned int max_subsystem;
+ unsigned int refcnt;
+ struct sudo_debug_output_list outputs;
+};
+
+/* Support up to 10 instances. */
+#define SUDO_DEBUG_INSTANCE_MAX 10
+static struct sudo_debug_instance *sudo_debug_instances[SUDO_DEBUG_INSTANCE_MAX];
+static int sudo_debug_last_instance = -1;
+
+static char sudo_debug_pidstr[(((sizeof(int) * 8) + 2) / 3) + 3];
+static size_t sudo_debug_pidlen;
+
+#define round_nfds(_n) (((_n) + (4 * NBBY) - 1) & ~((4 * NBBY) - 1))
+static int sudo_debug_fds_size;
+static unsigned char *sudo_debug_fds;
+static int sudo_debug_max_fd = -1;
+
+/* Default instance index to use for common utility functions. */
+static int sudo_debug_active_instance = -1;
+
+/*
+ * Free the specified output structure.
+ */
+static void
+sudo_debug_free_output(struct sudo_debug_output *output)
+{
+ free(output->filename);
+ free(output->settings);
+ if (output->fd != -1)
+ close(output->fd);
+ free(output);
+}
+
+/*
+ * Create a new output file for the specified debug instance.
+ * Returns NULL if the file cannot be opened or memory cannot be allocated.
+ * XXX - check for duplicates
+ */
+static struct sudo_debug_output *
+sudo_debug_new_output(struct sudo_debug_instance *instance,
+ struct sudo_debug_file *debug_file, int minfd)
+{
+ char *buf, *cp, *last, *subsys, *pri;
+ struct sudo_debug_output *output;
+ unsigned int j;
+ int i;
+
+ /* Create new output for the instance. */
+ /* XXX - reuse fd for existing filename? */
+ output = calloc(1, sizeof(*output));
+ if (output == NULL)
+ goto oom;
+ output->fd = -1;
+ output->settings = reallocarray(NULL, instance->max_subsystem + 1,
+ sizeof(int));
+ if (output->settings == NULL)
+ goto oom;
+ output->filename = strdup(debug_file->debug_file);
+ if (output->filename == NULL)
+ goto oom;
+
+ /* Init per-subsystems settings to -1 since 0 is a valid priority. */
+ for (j = 0; j <= instance->max_subsystem; j++)
+ output->settings[j] = -1;
+
+ /* Open debug file. */
+ output->fd = open(output->filename, O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR);
+ if (output->fd == -1) {
+ /* Create debug file as needed and set group ownership. */
+ if (errno == ENOENT) {
+ output->fd = open(output->filename, O_WRONLY|O_APPEND|O_CREAT,
+ S_IRUSR|S_IWUSR);
+ }
+ if (output->fd == -1) {
+ sudo_warn_nodebug("%s", output->filename);
+ goto bad;
+ }
+ ignore_result(fchown(output->fd, (uid_t)-1, 0));
+ }
+ if (output->fd < minfd) {
+ int newfd = fcntl(output->fd, F_DUPFD, minfd);
+ if (newfd == -1) {
+ sudo_warn_nodebug("%s", output->filename);
+ goto bad;
+ }
+ close(output->fd);
+ output->fd = newfd;
+ }
+ if (fcntl(output->fd, F_SETFD, FD_CLOEXEC) == -1) {
+ sudo_warn_nodebug("%s", output->filename);
+ goto bad;
+ }
+ if (sudo_debug_fds_size < output->fd) {
+ /* Bump fds size to the next multiple of 4 * NBBY. */
+ const int old_size = sudo_debug_fds_size / NBBY;
+ const int new_size = round_nfds(output->fd + 1) / NBBY;
+ unsigned char *new_fds;
+
+ new_fds = realloc(sudo_debug_fds, new_size);
+ if (new_fds == NULL)
+ goto oom;
+ memset(new_fds + old_size, 0, new_size - old_size);
+ sudo_debug_fds = new_fds;
+ sudo_debug_fds_size = new_size * NBBY;
+ }
+ sudo_setbit(sudo_debug_fds, output->fd);
+ if (output->fd > sudo_debug_max_fd)
+ sudo_debug_max_fd = output->fd;
+
+ /* Parse Debug conf string. */
+ buf = strdup(debug_file->debug_flags);
+ if (buf == NULL)
+ goto oom;
+ for ((cp = strtok_r(buf, ",", &last)); cp != NULL; (cp = strtok_r(NULL, ",", &last))) {
+ /* Should be in the form subsys@pri. */
+ subsys = cp;
+ if ((pri = strchr(cp, '@')) == NULL)
+ continue;
+ *pri++ = '\0';
+
+ /* Look up priority and subsystem, fill in sudo_debug_settings[]. */
+ for (i = 0; sudo_debug_priorities[i] != NULL; i++) {
+ if (strcasecmp(pri, sudo_debug_priorities[i]) == 0) {
+ for (j = 0; instance->subsystems[j] != NULL; j++) {
+ if (strcasecmp(subsys, "all") == 0) {
+ const unsigned int idx = instance->subsystem_ids ?
+ SUDO_DEBUG_SUBSYS(instance->subsystem_ids[j]) : j;
+ if (i > output->settings[idx])
+ output->settings[idx] = i;
+ continue;
+ }
+ if (strcasecmp(subsys, instance->subsystems[j]) == 0) {
+ const unsigned int idx = instance->subsystem_ids ?
+ SUDO_DEBUG_SUBSYS(instance->subsystem_ids[j]) : j;
+ if (i > output->settings[idx])
+ output->settings[idx] = i;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ free(buf);
+
+ return output;
+oom:
+ // -V:sudo_warn_nodebug:575, 618
+ sudo_warn_nodebug(NULL);
+bad:
+ if (output != NULL)
+ sudo_debug_free_output(output);
+ return NULL;
+}
+
+/*
+ * Register a program/plugin with the debug framework,
+ * parses settings string from sudo.conf and opens debug_files.
+ * If subsystem names are specified they override the default values.
+ * NOTE: subsystems must not be freed by caller unless deregistered.
+ * Sets the active instance to the newly registered instance.
+ * Returns instance index on success, SUDO_DEBUG_INSTANCE_INITIALIZER
+ * if no debug files are specified and SUDO_DEBUG_INSTANCE_ERROR
+ * on error.
+ */
+int
+sudo_debug_register_v2(const char *program, const char *const subsystems[],
+ unsigned int ids[], struct sudo_conf_debug_file_list *debug_files,
+ int minfd)
+{
+ struct sudo_debug_instance *instance = NULL;
+ struct sudo_debug_output *output;
+ struct sudo_debug_file *debug_file;
+ int idx, free_idx = -1;
+ debug_decl_func(sudo_debug_register);
+
+ if (debug_files == NULL)
+ return SUDO_DEBUG_INSTANCE_INITIALIZER;
+
+ /* Use default subsystem names if none are provided. */
+ if (subsystems == NULL) {
+ subsystems = sudo_debug_default_subsystems;
+ } else if (ids == NULL) {
+ /* If subsystems are specified we must have ids[] too. */
+ return SUDO_DEBUG_INSTANCE_ERROR;
+ }
+
+ /* Search for existing instance. */
+ for (idx = 0; idx <= sudo_debug_last_instance; idx++) {
+ if (sudo_debug_instances[idx] == NULL) {
+ free_idx = idx;
+ continue;
+ }
+ if (sudo_debug_instances[idx]->subsystems == subsystems &&
+ strcmp(sudo_debug_instances[idx]->program, program) == 0) {
+ instance = sudo_debug_instances[idx];
+ break;
+ }
+ }
+
+ if (instance == NULL) {
+ unsigned int i, j, max_id = NUM_DEF_SUBSYSTEMS - 1;
+
+ /* Fill in subsystem name -> id mapping as needed. */
+ if (ids != NULL) {
+ for (i = 0; subsystems[i] != NULL; i++) {
+ /* Check default subsystems. */
+ for (j = 0; j < NUM_DEF_SUBSYSTEMS; j++) {
+ if (strcmp(subsystems[i], sudo_debug_default_subsystems[j]) == 0)
+ break;
+ }
+ if (j == NUM_DEF_SUBSYSTEMS)
+ j = ++max_id;
+ ids[i] = ((j + 1) << 6);
+ }
+ }
+
+ if (free_idx != -1)
+ idx = free_idx;
+ if (idx == SUDO_DEBUG_INSTANCE_MAX) {
+ /* XXX - realloc? */
+ sudo_warnx_nodebug("too many debug instances (max %d)", SUDO_DEBUG_INSTANCE_MAX);
+ return SUDO_DEBUG_INSTANCE_ERROR;
+ }
+ if (idx != sudo_debug_last_instance + 1 && idx != free_idx) {
+ sudo_warnx_nodebug("%s: instance number mismatch: expected %d or %d, got %d", __func__, sudo_debug_last_instance + 1, free_idx, idx);
+ return SUDO_DEBUG_INSTANCE_ERROR;
+ }
+ if ((instance = malloc(sizeof(*instance))) == NULL)
+ return SUDO_DEBUG_INSTANCE_ERROR;
+ if ((instance->program = strdup(program)) == NULL) {
+ free(instance);
+ return SUDO_DEBUG_INSTANCE_ERROR;
+ }
+ instance->subsystems = subsystems;
+ instance->subsystem_ids = ids;
+ instance->max_subsystem = max_id;
+ instance->refcnt = 1;
+ SLIST_INIT(&instance->outputs);
+ sudo_debug_instances[idx] = instance;
+ if (idx != free_idx)
+ sudo_debug_last_instance++;
+ } else {
+ /* Check for matching instance but different ids[]. */
+ if (ids != NULL && instance->subsystem_ids != ids) {
+ unsigned int i;
+
+ for (i = 0; subsystems[i] != NULL; i++)
+ ids[i] = instance->subsystem_ids[i];
+ }
+ instance->refcnt++;
+ }
+
+ TAILQ_FOREACH(debug_file, debug_files, entries) {
+ output = sudo_debug_new_output(instance, debug_file, minfd);
+ if (output != NULL)
+ SLIST_INSERT_HEAD(&instance->outputs, output, entries);
+ }
+
+ /* Set active instance. */
+ sudo_debug_active_instance = idx;
+
+ /* Stash the pid string so we only have to format it once. */
+ if (sudo_debug_pidlen == 0) {
+ (void)snprintf(sudo_debug_pidstr, sizeof(sudo_debug_pidstr), "[%d] ",
+ (int)getpid());
+ sudo_debug_pidlen = strlen(sudo_debug_pidstr);
+ }
+
+ return idx;
+}
+
+int
+sudo_debug_register_v1(const char *program, const char *const subsystems[],
+ unsigned int ids[], struct sudo_conf_debug_file_list *debug_files)
+{
+ return sudo_debug_register_v2(program, subsystems, ids, debug_files, -1);
+}
+
+/*
+ * De-register the specified instance from the debug subsystem
+ * and free up any associated data structures.
+ * Returns the number of remaining references for the instance or -1 on error.
+ */
+int
+sudo_debug_deregister_v1(int idx)
+{
+ struct sudo_debug_instance *instance;
+ struct sudo_debug_output *output, *next;
+ debug_decl_func(sudo_debug_deregister);
+
+ if (idx < 0 || idx > sudo_debug_last_instance) {
+ sudo_warnx_nodebug("%s: invalid instance ID %d, max %d",
+ __func__, idx, sudo_debug_last_instance);
+ return -1;
+ }
+ /* Reset active instance as needed. */
+ if (sudo_debug_active_instance == idx)
+ sudo_debug_active_instance = -1;
+
+ instance = sudo_debug_instances[idx];
+ if (instance == NULL)
+ return -1; /* already deregistered */
+
+ if (--instance->refcnt != 0)
+ return instance->refcnt; /* ref held by other caller */
+
+ /* Free up instance data, note that subsystems[] is owned by caller. */
+ sudo_debug_instances[idx] = NULL;
+ SLIST_FOREACH_SAFE(output, &instance->outputs, entries, next) {
+ close(output->fd);
+ free(output->filename);
+ free(output->settings);
+ free(output);
+ }
+ free(instance->program);
+ free(instance);
+
+ if (idx == sudo_debug_last_instance)
+ sudo_debug_last_instance--;
+
+ return 0;
+}
+
+/*
+ * Parse the "filename flags,..." debug_flags entry from sudo.conf
+ * and insert a new sudo_debug_file struct into the list.
+ * Returns 0 on success, 1 on parse error or -1 on malloc failure.
+ */
+int
+sudo_debug_parse_flags_v1(struct sudo_conf_debug_file_list *debug_files,
+ const char *entry)
+{
+ struct sudo_debug_file *debug_file;
+ const char *filename, *flags;
+ size_t namelen;
+
+ /* Only process new-style debug flags: filename flags,... */
+ filename = entry;
+ if (*filename != '/' || (flags = strpbrk(filename, " \t")) == NULL)
+ return 1;
+ namelen = (size_t)(flags - filename);
+ while (isblank((unsigned char)*flags))
+ flags++;
+ if (*flags != '\0') {
+ if ((debug_file = calloc(1, sizeof(*debug_file))) == NULL)
+ goto oom;
+ if ((debug_file->debug_file = strndup(filename, namelen)) == NULL)
+ goto oom;
+ if ((debug_file->debug_flags = strdup(flags)) == NULL)
+ goto oom;
+ TAILQ_INSERT_TAIL(debug_files, debug_file, entries);
+ }
+ return 0;
+oom:
+ if (debug_file != NULL) {
+ free(debug_file->debug_file);
+ free(debug_file->debug_flags);
+ free(debug_file);
+ }
+ return -1;
+}
+
+int
+sudo_debug_get_instance_v1(const char *program)
+{
+ int idx;
+
+ for (idx = 0; idx <= sudo_debug_last_instance; idx++) {
+ if (sudo_debug_instances[idx] == NULL)
+ continue;
+ if (strcmp(sudo_debug_instances[idx]->program, program) == 0)
+ return idx;
+ }
+ return SUDO_DEBUG_INSTANCE_INITIALIZER;
+}
+
+pid_t
+sudo_debug_fork_v1(void)
+{
+ pid_t pid;
+
+ if ((pid = fork()) == 0) {
+ (void)snprintf(sudo_debug_pidstr, sizeof(sudo_debug_pidstr), "[%d] ",
+ (int)getpid());
+ sudo_debug_pidlen = strlen(sudo_debug_pidstr);
+ }
+
+ return pid;
+}
+
+void
+sudo_debug_enter_v1(const char *func, const char *file, int line,
+ int subsys)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "-> %s @ %s:%d", func, file, line);
+}
+
+void
+sudo_debug_exit_v1(const char *func, const char *file, int line,
+ int subsys)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d", func, file, line);
+}
+
+void
+sudo_debug_exit_int_v1(const char *func, const char *file, int line,
+ int subsys, int ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %d", func, file, line, ret);
+}
+
+void
+sudo_debug_exit_long_v1(const char *func, const char *file, int line,
+ int subsys, long ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %ld", func, file, line, ret);
+}
+
+void
+sudo_debug_exit_id_t_v1(const char *func, const char *file, int line,
+ int subsys, id_t ret)
+{
+#if SIZEOF_ID_T == 8
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %lld", func, file, line, (long long)ret);
+#else
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %d", func, file, line, (int)ret);
+#endif
+}
+
+void
+sudo_debug_exit_size_t_v1(const char *func, const char *file, int line,
+ int subsys, size_t ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %zu", func, file, line, ret);
+}
+
+void
+sudo_debug_exit_ssize_t_v1(const char *func, const char *file, int line,
+ int subsys, ssize_t ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %zd", func, file, line, ret);
+}
+
+void
+sudo_debug_exit_time_t_v1(const char *func, const char *file, int line,
+ int subsys, time_t ret)
+{
+#if SIZEOF_TIME_T == 8
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %lld", func, file, line, (long long)ret);
+#else
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %d", func, file, line, (int)ret);
+#endif
+}
+
+void
+sudo_debug_exit_bool_v1(const char *func, const char *file, int line,
+ int subsys, bool ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %s", func, file, line, ret ? "true" : "false");
+}
+
+void
+sudo_debug_exit_str_v1(const char *func, const char *file, int line,
+ int subsys, const char *ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %s", func, file, line, ret ? ret : "(null)");
+}
+
+void
+sudo_debug_exit_str_masked_v1(const char *func, const char *file, int line,
+ int subsys, const char *ret)
+{
+ static const char stars[] = "********************************************************************************";
+ int len = ret ? strlen(ret) : sizeof("(null)") - 1;
+
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %.*s", func, file, line, len, ret ? stars : "(null)");
+}
+
+void
+sudo_debug_exit_ptr_v1(const char *func, const char *file, int line,
+ int subsys, const void *ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %p", func, file, line, ret);
+}
+
+void
+sudo_debug_write2_v1(int fd, const char *func, const char *file, int lineno,
+ const char *str, int len, int errnum)
+{
+ char numbuf[(((sizeof(int) * 8) + 2) / 3) + 2];
+ char timebuf[64];
+ struct timeval tv;
+ struct iovec iov[12];
+ int iovcnt = 3;
+
+ /* Cannot use sudo_gettime_real() here since it calls sudo_debug. */
+ timebuf[0] = '\0';
+ if (gettimeofday(&tv, NULL) != -1) {
+ time_t now = tv.tv_sec;
+ struct tm tm;
+ size_t tlen;
+ if (localtime_r(&now, &tm) != NULL) {
+ timebuf[sizeof(timebuf) - 1] = '\0';
+ tlen = strftime(timebuf, sizeof(timebuf), "%b %e %H:%M:%S", &tm);
+ if (tlen == 0 || timebuf[sizeof(timebuf) - 1] != '\0') {
+ /* contents are undefined on error */
+ timebuf[0] = '\0';
+ } else {
+ (void)snprintf(timebuf + tlen, sizeof(timebuf) - tlen,
+ ".%03d ", (int)tv.tv_usec / 1000);
+ }
+ }
+ }
+ iov[0].iov_base = timebuf;
+ iov[0].iov_len = strlen(timebuf);
+
+ /* Prepend program name and pid with a trailing space. */
+ iov[1].iov_base = (char *)getprogname();
+ iov[1].iov_len = strlen(iov[1].iov_base);
+ iov[2].iov_base = sudo_debug_pidstr;
+ iov[2].iov_len = sudo_debug_pidlen;
+
+ /* Add string, trimming any trailing newlines. */
+ while (len > 0 && str[len - 1] == '\n')
+ len--;
+ if (len > 0) {
+ iov[iovcnt].iov_base = (char *)str;
+ iov[iovcnt].iov_len = len;
+ iovcnt++;
+ }
+
+ /* Append error string if errno is specified. */
+ if (errnum) {
+ if (len > 0) {
+ iov[iovcnt].iov_base = (char *)": ";
+ iov[iovcnt].iov_len = 2;
+ iovcnt++;
+ }
+ iov[iovcnt].iov_base = strerror(errnum);
+ iov[iovcnt].iov_len = strlen(iov[iovcnt].iov_base);
+ iovcnt++;
+ }
+
+ /* If function, file and lineno are specified, append them. */
+ if (func != NULL && file != NULL && lineno != 0) {
+ iov[iovcnt].iov_base = (char *)" @ ";
+ iov[iovcnt].iov_len = 3;
+ iovcnt++;
+
+ iov[iovcnt].iov_base = (char *)func;
+ iov[iovcnt].iov_len = strlen(func);
+ iovcnt++;
+
+ iov[iovcnt].iov_base = (char *)"() ";
+ iov[iovcnt].iov_len = 3;
+ iovcnt++;
+
+ iov[iovcnt].iov_base = (char *)file;
+ iov[iovcnt].iov_len = strlen(file);
+ iovcnt++;
+
+ (void)snprintf(numbuf, sizeof(numbuf), ":%d", lineno);
+ iov[iovcnt].iov_base = numbuf;
+ iov[iovcnt].iov_len = strlen(numbuf);
+ iovcnt++;
+ }
+
+ /* Append newline. */
+ iov[iovcnt].iov_base = (char *)"\n";
+ iov[iovcnt].iov_len = 1;
+ iovcnt++;
+
+ /* Write message in a single syscall */
+ ignore_result(writev(fd, iov, iovcnt));
+}
+
+bool
+sudo_debug_needed_v1(int level)
+{
+ unsigned int subsys;
+ int pri;
+ struct sudo_debug_instance *instance;
+ struct sudo_debug_output *output;
+ bool result = false;
+
+ if (sudo_debug_active_instance == -1)
+ goto out;
+
+ /* Extract priority and subsystem from level. */
+ pri = SUDO_DEBUG_PRI(level);
+ subsys = (unsigned int)SUDO_DEBUG_SUBSYS(level);
+
+ if (sudo_debug_active_instance > sudo_debug_last_instance)
+ goto out;
+
+ instance = sudo_debug_instances[sudo_debug_active_instance];
+ if (instance == NULL)
+ goto out;
+
+ if (subsys <= instance->max_subsystem) {
+ SLIST_FOREACH(output, &instance->outputs, entries) {
+ if (output->settings[subsys] >= pri) {
+ result = true;
+ break;
+ }
+ }
+ }
+out:
+ return result;
+}
+
+void
+sudo_debug_vprintf2_v1(const char *func, const char *file, int lineno, int level,
+ const char *fmt, va_list ap)
+{
+ int buflen, pri, saved_errno = errno;
+ unsigned int subsys;
+ char static_buf[1024], *buf = static_buf;
+ struct sudo_debug_instance *instance;
+ struct sudo_debug_output *output;
+ debug_decl_func(sudo_debug_vprintf2);
+
+ if (sudo_debug_active_instance == -1)
+ goto out;
+
+ /* Extract priority and subsystem from level. */
+ pri = SUDO_DEBUG_PRI(level);
+ subsys = SUDO_DEBUG_SUBSYS(level);
+
+ /* Find matching instance. */
+ if (sudo_debug_active_instance > sudo_debug_last_instance) {
+ sudo_warnx_nodebug("%s: invalid instance ID %d, max %d",
+ __func__, sudo_debug_active_instance, sudo_debug_last_instance);
+ goto out;
+ }
+ instance = sudo_debug_instances[sudo_debug_active_instance];
+ if (instance == NULL) {
+ sudo_warnx_nodebug("%s: unregistered instance index %d", __func__,
+ sudo_debug_active_instance);
+ goto out;
+ }
+
+ SLIST_FOREACH(output, &instance->outputs, entries) {
+ /* Make sure we want debug info at this level. */
+ if (subsys <= instance->max_subsystem && output->settings[subsys] >= pri) {
+ va_list ap2;
+
+ /* Operate on a copy of ap to support multiple outputs. */
+ va_copy(ap2, ap);
+ buflen = fmt ? vsnprintf(static_buf, sizeof(static_buf), fmt, ap2) : 0;
+ va_end(ap2);
+ if (buflen >= ssizeof(static_buf)) {
+ va_list ap3;
+
+ /* Not enough room in static buf, allocate dynamically. */
+ va_copy(ap3, ap);
+ buflen = vasprintf(&buf, fmt, ap3);
+ va_end(ap3);
+ }
+ if (buflen != -1) {
+ int errcode = ISSET(level, SUDO_DEBUG_ERRNO) ? saved_errno : 0;
+ if (ISSET(level, SUDO_DEBUG_LINENO))
+ sudo_debug_write2(output->fd, func, file, lineno, buf, buflen, errcode);
+ else
+ sudo_debug_write2(output->fd, NULL, NULL, 0, buf, buflen, errcode);
+ if (buf != static_buf) {
+ free(buf);
+ buf = static_buf;
+ }
+ }
+ }
+ }
+out:
+ errno = saved_errno;
+}
+
+#ifdef NO_VARIADIC_MACROS
+void
+sudo_debug_printf_nvm_v1(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ sudo_debug_vprintf2(NULL, NULL, 0, pri, fmt, ap);
+ va_end(ap);
+}
+#endif /* NO_VARIADIC_MACROS */
+
+void
+sudo_debug_printf2_v1(const char *func, const char *file, int lineno, int level,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ sudo_debug_vprintf2(func, file, lineno, level, fmt, ap);
+ va_end(ap);
+}
+
+#define EXEC_PREFIX "exec "
+
+void
+sudo_debug_execve2_v1(int level, const char *path, char *const argv[], char *const envp[])
+{
+ int buflen, pri, saved_errno = errno;
+ unsigned int subsys;
+ struct sudo_debug_instance *instance;
+ struct sudo_debug_output *output;
+ char * const *av;
+ char *cp, static_buf[4096], *buf = static_buf;
+ size_t plen;
+ debug_decl_func(sudo_debug_execve2);
+
+ if (sudo_debug_active_instance == -1 || path == NULL)
+ goto out;
+
+ /* Extract priority and subsystem from level. */
+ pri = SUDO_DEBUG_PRI(level);
+ subsys = SUDO_DEBUG_SUBSYS(level);
+
+ /* Find matching instance. */
+ if (sudo_debug_active_instance > sudo_debug_last_instance) {
+ sudo_warnx_nodebug("%s: invalid instance ID %d, max %d",
+ __func__, sudo_debug_active_instance, sudo_debug_last_instance);
+ goto out;
+ }
+ instance = sudo_debug_instances[sudo_debug_active_instance];
+ if (instance == NULL) {
+ sudo_warnx_nodebug("%s: unregistered instance index %d", __func__,
+ sudo_debug_active_instance);
+ goto out;
+ }
+ if (subsys > instance->max_subsystem)
+ goto out;
+
+ SLIST_FOREACH(output, &instance->outputs, entries) {
+ bool log_envp = false;
+
+ /* Make sure we want debug info at this level. */
+ if (output->settings[subsys] < pri)
+ continue;
+
+ /* Log envp for debug level "debug". */
+ if (output->settings[subsys] >= SUDO_DEBUG_DEBUG - 1 && envp != NULL)
+ log_envp = true;
+
+ /* Alloc and build up buffer. */
+ plen = strlen(path);
+ buflen = sizeof(EXEC_PREFIX) -1 + plen;
+ if (argv != NULL && argv[0] != NULL) {
+ buflen += sizeof(" []") - 1;
+ for (av = argv; *av; av++)
+ buflen += strlen(*av) + 1;
+ buflen--;
+ }
+ if (log_envp && envp[0] != NULL) {
+ buflen += sizeof(" []") - 1;
+ for (av = envp; *av; av++)
+ buflen += strlen(*av) + 1;
+ buflen--;
+ }
+ if (buflen >= ssizeof(static_buf)) {
+ buf = malloc(buflen + 1);
+ if (buf == NULL)
+ goto out;
+ }
+
+ /* Copy prefix and command. */
+ memcpy(buf, EXEC_PREFIX, sizeof(EXEC_PREFIX) - 1);
+ cp = buf + sizeof(EXEC_PREFIX) - 1;
+ memcpy(cp, path, plen);
+ cp += plen;
+
+ /* Copy argv. */
+ if (argv != NULL && argv[0] != NULL) {
+ *cp++ = ' ';
+ *cp++ = '[';
+ for (av = argv; *av; av++) {
+ size_t avlen = strlen(*av);
+ memcpy(cp, *av, avlen);
+ cp += avlen;
+ *cp++ = ' ';
+ }
+ cp[-1] = ']';
+ }
+
+ if (log_envp && envp[0] != NULL) {
+ *cp++ = ' ';
+ *cp++ = '[';
+ for (av = envp; *av; av++) {
+ size_t avlen = strlen(*av);
+ memcpy(cp, *av, avlen);
+ cp += avlen;
+ *cp++ = ' ';
+ }
+ cp[-1] = ']';
+ }
+
+ *cp = '\0';
+
+ sudo_debug_write(output->fd, buf, buflen, 0);
+ if (buf != static_buf) {
+ free(buf);
+ buf = static_buf;
+ }
+ }
+out:
+ errno = saved_errno;
+}
+
+/*
+ * Returns the active instance or SUDO_DEBUG_INSTANCE_INITIALIZER
+ * if no instance is active.
+ */
+int
+sudo_debug_get_active_instance_v1(void)
+{
+ return sudo_debug_active_instance;
+}
+
+/*
+ * Sets a new active instance, returning the old one.
+ * Note that the old instance may be SUDO_DEBUG_INSTANCE_INITIALIZER
+ * if this is the only instance.
+ */
+int
+sudo_debug_set_active_instance_v1(int idx)
+{
+ const int old_idx = sudo_debug_active_instance;
+
+ if (idx >= -1 && idx <= sudo_debug_last_instance)
+ sudo_debug_active_instance = idx;
+ return old_idx;
+}
+
+/*
+ * Replace the ofd with nfd in all outputs if present.
+ * Also updates sudo_debug_fds.
+ */
+void
+sudo_debug_update_fd_v1(int ofd, int nfd)
+{
+ int idx;
+
+ if (ofd <= sudo_debug_max_fd && sudo_isset(sudo_debug_fds, ofd)) {
+ /* Update sudo_debug_fds. */
+ sudo_clrbit(sudo_debug_fds, ofd);
+ sudo_setbit(sudo_debug_fds, nfd);
+
+ /* Update the outputs. */
+ for (idx = 0; idx <= sudo_debug_last_instance; idx++) {
+ struct sudo_debug_instance *instance;
+ struct sudo_debug_output *output;
+
+ instance = sudo_debug_instances[idx];
+ if (instance == NULL)
+ continue;
+ SLIST_FOREACH(output, &instance->outputs, entries) {
+ if (output->fd == ofd)
+ output->fd = nfd;
+ }
+ }
+ }
+}
+
+/*
+ * Returns the highest debug output fd or -1 if no debug files open.
+ * Fills in fds with the value of sudo_debug_fds.
+ */
+int
+sudo_debug_get_fds_v1(unsigned char **fds)
+{
+ *fds = sudo_debug_fds;
+ return sudo_debug_max_fd;
+}
+#else /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
+int
+sudo_debug_register_v2(const char *program, const char *const subsystems[],
+ unsigned int ids[], struct sudo_conf_debug_file_list *debug_files,
+ int minfd)
+{
+ return SUDO_DEBUG_INSTANCE_INITIALIZER;
+}
+
+int
+sudo_debug_register_v1(const char *program, const char *const subsystems[],
+ unsigned int ids[], struct sudo_conf_debug_file_list *debug_files)
+{
+ return SUDO_DEBUG_INSTANCE_INITIALIZER;
+}
+
+int
+sudo_debug_deregister_v1(int idx)
+{
+ return -1;
+}
+
+int
+sudo_debug_parse_flags_v1(struct sudo_conf_debug_file_list *debug_files,
+ const char *entry)
+{
+ return 0;
+}
+
+int
+sudo_debug_get_instance_v1(const char *program)
+{
+ return SUDO_DEBUG_INSTANCE_INITIALIZER;
+}
+
+pid_t
+sudo_debug_fork_v1(void)
+{
+ return fork();
+}
+
+void
+sudo_debug_enter_v1(const char *func, const char *file, int line,
+ int subsys)
+{
+}
+
+void
+sudo_debug_exit_v1(const char *func, const char *file, int line,
+ int subsys)
+{
+}
+
+void
+sudo_debug_exit_int_v1(const char *func, const char *file, int line,
+ int subsys, int ret)
+{
+}
+
+void
+sudo_debug_exit_long_v1(const char *func, const char *file, int line,
+ int subsys, long ret)
+{
+}
+
+void
+sudo_debug_exit_id_t_v1(const char *func, const char *file, int line,
+ int subsys, id_t ret)
+{
+}
+
+void
+sudo_debug_exit_size_t_v1(const char *func, const char *file, int line,
+ int subsys, size_t ret)
+{
+}
+
+void
+sudo_debug_exit_ssize_t_v1(const char *func, const char *file, int line,
+ int subsys, ssize_t ret)
+{
+}
+
+void
+sudo_debug_exit_time_t_v1(const char *func, const char *file, int line,
+ int subsys, time_t ret)
+{
+}
+
+void
+sudo_debug_exit_bool_v1(const char *func, const char *file, int line,
+ int subsys, bool ret)
+{
+}
+
+void
+sudo_debug_exit_str_v1(const char *func, const char *file, int line,
+ int subsys, const char *ret)
+{
+}
+
+void
+sudo_debug_exit_str_masked_v1(const char *func, const char *file, int line,
+ int subsys, const char *ret)
+{
+}
+
+void
+sudo_debug_exit_ptr_v1(const char *func, const char *file, int line,
+ int subsys, const void *ret)
+{
+}
+
+void
+sudo_debug_write2_v1(int fd, const char *func, const char *file, int lineno,
+ const char *str, int len, int errnum)
+{
+}
+
+bool
+sudo_debug_needed_v1(int level)
+{
+ return false;
+}
+
+void
+sudo_debug_vprintf2_v1(const char *func, const char *file, int lineno, int level,
+ const char *fmt, va_list ap)
+{
+}
+
+#ifdef NO_VARIADIC_MACROS
+void
+sudo_debug_printf_nvm_v1(int pri, const char *fmt, ...)
+{
+}
+#endif /* NO_VARIADIC_MACROS */
+
+void
+sudo_debug_printf2_v1(const char *func, const char *file, int lineno, int level,
+ const char *fmt, ...)
+{
+}
+
+void
+sudo_debug_execve2_v1(int level, const char *path, char *const argv[], char *const envp[])
+{
+}
+
+int
+sudo_debug_get_active_instance_v1(void)
+{
+ return SUDO_DEBUG_INSTANCE_INITIALIZER;
+}
+
+int
+sudo_debug_set_active_instance_v1(int idx)
+{
+ return SUDO_DEBUG_INSTANCE_INITIALIZER;
+}
+
+void
+sudo_debug_update_fd_v1(int ofd, int nfd)
+{
+}
+
+int
+sudo_debug_get_fds_v1(unsigned char **fds)
+{
+ return -1;
+}
+#endif /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
diff --git a/lib/util/sudo_dso.c b/lib/util/sudo_dso.c
new file mode 100644
index 0000000..d38fedf
--- /dev/null
+++ b/lib/util/sudo_dso.c
@@ -0,0 +1,432 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010, 2012-2014, 2021-2022 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifdef __linux__
+# include <sys/stat.h>
+# include <sys/utsname.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if defined(HAVE_SHL_LOAD)
+# include <dl.h>
+#elif defined(HAVE_DLOPEN)
+# include <dlfcn.h>
+#endif
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_dso.h"
+#include "sudo_util.h"
+
+/*
+ * Pointer for statically compiled symbols.
+ */
+static struct sudo_preload_table *preload_table;
+
+void
+sudo_dso_preload_table_v1(struct sudo_preload_table *table)
+{
+ preload_table = table;
+}
+
+#if defined(HAVE_SHL_LOAD)
+
+# ifndef DYNAMIC_PATH
+# define DYNAMIC_PATH 0
+# endif
+
+void *
+sudo_dso_load_v1(const char *path, int mode)
+{
+ struct sudo_preload_table *pt;
+ int flags = DYNAMIC_PATH | BIND_VERBOSE;
+
+ if (mode == 0)
+ mode = SUDO_DSO_LAZY; /* default behavior */
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->path != NULL && strcmp(path, pt->path) == 0)
+ return pt->handle;
+ }
+ }
+
+ /* We don't support SUDO_DSO_GLOBAL or SUDO_DSO_LOCAL yet. */
+ if (ISSET(mode, SUDO_DSO_LAZY))
+ flags |= BIND_DEFERRED;
+ if (ISSET(mode, SUDO_DSO_NOW))
+ flags |= BIND_IMMEDIATE;
+
+ return (void *)shl_load(path, flags, 0L);
+}
+
+int
+sudo_dso_unload_v1(void *handle)
+{
+ struct sudo_preload_table *pt;
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle)
+ return 0;
+ }
+ }
+
+ return shl_unload((shl_t)handle);
+}
+
+void *
+sudo_dso_findsym_v1(void *vhandle, const char *symbol)
+{
+ struct sudo_preload_table *pt;
+ shl_t handle = vhandle;
+ void *value = NULL;
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle) {
+ struct sudo_preload_symbol *sym;
+ for (sym = pt->symbols; sym->name != NULL; sym++) {
+ if (strcmp(sym->name, symbol) == 0)
+ return sym->addr;
+ }
+ errno = ENOENT;
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * Note that the behavior of of SUDO_DSO_NEXT and SUDO_DSO_SELF
+ * differs from most implementations when called from
+ * a shared library.
+ */
+ if (vhandle == SUDO_DSO_NEXT) {
+ /* Iterate over all shared libs looking for symbol. */
+ shl_t myhandle = PROG_HANDLE;
+ struct shl_descriptor *desc;
+ int idx = 0;
+
+ /* Find program's real handle. */
+ if (shl_gethandle(PROG_HANDLE, &desc) == 0)
+ myhandle = desc->handle;
+ while (shl_get(idx++, &desc) == 0) {
+ if (desc->handle == myhandle)
+ continue;
+ if (shl_findsym(&desc->handle, symbol, TYPE_UNDEFINED, &value) == 0)
+ break;
+ }
+ } else {
+ if (vhandle == SUDO_DSO_DEFAULT)
+ handle = NULL;
+ else if (vhandle == SUDO_DSO_SELF)
+ handle = PROG_HANDLE;
+ (void)shl_findsym(&handle, symbol, TYPE_UNDEFINED, &value);
+ }
+
+ return value;
+}
+
+char *
+sudo_dso_strerror_v1(void)
+{
+ return strerror(errno);
+}
+
+#elif defined(HAVE_DLOPEN)
+
+# ifndef RTLD_GLOBAL
+# define RTLD_GLOBAL 0
+# endif
+
+/* Default member names for AIX when dlopen()ing an ar (.a) file. */
+# ifdef RTLD_MEMBER
+# ifdef __LP64__
+# define SUDO_DSO_MEMBER "shr_64.o"
+# else
+# define SUDO_DSO_MEMBER "shr.o"
+# endif
+# endif
+
+# if defined(__linux__)
+/*
+ * On Linux systems that use multi-arch, the actual DSO may be
+ * in a machine-specific subdirectory. If the specified path
+ * contains /lib/ or /libexec/, insert a multi-arch directory
+ * after it.
+ */
+static void *
+dlopen_multi_arch(const char *path, int flags)
+{
+ void *ret = NULL;
+ struct stat sb;
+ char *newpath;
+
+ /* Only try multi-arch if the original path does not exist. */
+ if (stat(path, &sb) == -1 && errno == ENOENT) {
+ newpath = sudo_stat_multiarch(path, &sb);
+ if (newpath != NULL) {
+ ret = dlopen(newpath, flags);
+ free(newpath);
+ }
+ }
+ return ret;
+}
+# else
+static void *
+dlopen_multi_arch(const char *path, int flags)
+{
+ return NULL;
+}
+# endif /* __linux__ */
+
+void *
+sudo_dso_load_v1(const char *path, int mode)
+{
+ struct sudo_preload_table *pt;
+ int flags = 0;
+ void *ret;
+# ifdef RTLD_MEMBER
+ char *cp;
+ size_t pathlen;
+# endif
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->path != NULL && strcmp(path, pt->path) == 0)
+ return pt->handle;
+ }
+ }
+
+ /* Map SUDO_DSO_* -> RTLD_* */
+ if (ISSET(mode, SUDO_DSO_LAZY))
+ SET(flags, RTLD_LAZY);
+ if (ISSET(mode, SUDO_DSO_NOW))
+ SET(flags, RTLD_NOW);
+ if (ISSET(mode, SUDO_DSO_GLOBAL))
+ SET(flags, RTLD_GLOBAL);
+ if (ISSET(mode, SUDO_DSO_LOCAL))
+ SET(flags, RTLD_LOCAL);
+
+# ifdef RTLD_MEMBER
+ /* Check for AIX shlib.a(member) syntax and dlopen() with RTLD_MEMBER. */
+ pathlen = strlen(path);
+ if (pathlen > 2 && path[pathlen - 1] == ')') {
+ cp = strrchr(path, '(');
+ if (cp != NULL && cp > path + 2 && cp[-2] == '.' && cp[-1] == 'a') {
+ /* Only for archive files (e.g. sudoers.a). */
+ SET(flags, RTLD_MEMBER);
+ }
+ }
+# endif /* RTLD_MEMBER */
+ ret = dlopen(path, flags);
+# if defined(RTLD_MEMBER)
+ /* Special fallback handling for AIX shared objects. */
+ if (ret == NULL && !ISSET(flags, RTLD_MEMBER)) {
+ switch (errno) {
+ case ENOEXEC:
+ /*
+ * If we try to dlopen() an AIX .a file without an explicit member
+ * it will fail with ENOEXEC. Try again using the default member.
+ */
+ if (pathlen > 2 && strcmp(&path[pathlen - 2], ".a") == 0) {
+ int len = asprintf(&cp, "%s(%s)", path, SUDO_DSO_MEMBER);
+ if (len != -1) {
+ ret = dlopen(cp, flags|RTLD_MEMBER);
+ free(cp);
+ }
+ if (ret == NULL) {
+ /* Retry with the original path to get the correct error. */
+ ret = dlopen(path, flags);
+ }
+ }
+ break;
+ case ENOENT:
+ /*
+ * If the .so file is missing but the .a file exists, try to
+ * dlopen() the AIX .a file using the .so name as the member.
+ * This is for compatibility with versions of sudo that use
+ * SVR4-style shared libs, not AIX-style shared libs.
+ */
+ if (pathlen > 3 && strcmp(&path[pathlen - 3], ".so") == 0) {
+ int len = asprintf(&cp, "%.*s.a(%s)", (int)(pathlen - 3),
+ path, sudo_basename(path));
+ if (len != -1) {
+ ret = dlopen(cp, flags|RTLD_MEMBER);
+ free(cp);
+ }
+ if (ret == NULL) {
+ /* Retry with the original path to get the correct error. */
+ ret = dlopen(path, flags);
+ }
+ }
+ break;
+ }
+ }
+# endif /* RTLD_MEMBER */
+ /* On failure, try again with a multi-arch path where possible. */
+ if (ret == NULL)
+ ret = dlopen_multi_arch(path, flags);
+
+ return ret;
+}
+
+int
+sudo_dso_unload_v1(void *handle)
+{
+ struct sudo_preload_table *pt;
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle)
+ return 0;
+ }
+ }
+
+ return dlclose(handle);
+}
+
+void *
+sudo_dso_findsym_v1(void *handle, const char *symbol)
+{
+ struct sudo_preload_table *pt;
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle) {
+ struct sudo_preload_symbol *sym;
+ for (sym = pt->symbols; sym->name != NULL; sym++) {
+ if (strcmp(sym->name, symbol) == 0)
+ return sym->addr;
+ }
+ errno = ENOENT;
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * Not all implementations support the special handles.
+ */
+ if (handle == SUDO_DSO_NEXT) {
+# ifdef RTLD_NEXT
+ handle = RTLD_NEXT;
+# else
+ errno = ENOENT;
+ return NULL;
+# endif
+ } else if (handle == SUDO_DSO_DEFAULT) {
+# ifdef RTLD_DEFAULT
+ handle = RTLD_DEFAULT;
+# else
+ errno = ENOENT;
+ return NULL;
+# endif
+ } else if (handle == SUDO_DSO_SELF) {
+# ifdef RTLD_SELF
+ handle = RTLD_SELF;
+# else
+ errno = ENOENT;
+ return NULL;
+# endif
+ }
+
+ return dlsym(handle, symbol);
+}
+
+char *
+sudo_dso_strerror_v1(void)
+{
+ return dlerror();
+}
+
+#else /* !HAVE_SHL_LOAD && !HAVE_DLOPEN */
+
+/*
+ * Emulate dlopen() using a static list of symbols compiled into sudo.
+ */
+void *
+sudo_dso_load_v1(const char *path, int mode)
+{
+ struct sudo_preload_table *pt;
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->path != NULL && strcmp(path, pt->path) == 0)
+ return pt->handle;
+ }
+ }
+ return NULL;
+}
+
+int
+sudo_dso_unload_v1(void *handle)
+{
+ struct sudo_preload_table *pt;
+
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle)
+ return 0;
+ }
+ }
+ return -1;
+}
+
+void *
+sudo_dso_findsym_v1(void *handle, const char *symbol)
+{
+ struct sudo_preload_table *pt;
+
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle) {
+ struct sudo_preload_symbol *sym;
+ for (sym = pt->symbols; sym->name != NULL; sym++) {
+ if (strcmp(sym->name, symbol) == 0)
+ return sym->addr;
+ }
+ }
+ }
+ }
+ errno = ENOENT;
+ return NULL;
+}
+
+char *
+sudo_dso_strerror_v1(void)
+{
+ return strerror(errno);
+}
+#endif /* !HAVE_SHL_LOAD && !HAVE_DLOPEN */
diff --git a/lib/util/sys_siglist.h b/lib/util/sys_siglist.h
new file mode 100644
index 0000000..f2ed2b2
--- /dev/null
+++ b/lib/util/sys_siglist.h
@@ -0,0 +1,182 @@
+/* public domain */
+
+#include <config.h>
+#include <sys/types.h>
+#include <signal.h>
+#include "sudo_compat.h"
+
+int sudo_end_of_headers;
+static char *sudo_sys_siglist[NSIG];
+
+#ifdef SIGHUP
+ if (sudo_sys_siglist[SIGHUP] == NULL)
+ sudo_sys_siglist[SIGHUP] = "Hangup";
+#endif
+#ifdef SIGINT
+ if (sudo_sys_siglist[SIGINT] == NULL)
+ sudo_sys_siglist[SIGINT] = "Interrupt";
+#endif
+#ifdef SIGQUIT
+ if (sudo_sys_siglist[SIGQUIT] == NULL)
+ sudo_sys_siglist[SIGQUIT] = "Quit";
+#endif
+#ifdef SIGILL
+ if (sudo_sys_siglist[SIGILL] == NULL)
+ sudo_sys_siglist[SIGILL] = "Illegal instruction";
+#endif
+#ifdef SIGTRAP
+ if (sudo_sys_siglist[SIGTRAP] == NULL)
+ sudo_sys_siglist[SIGTRAP] = "Trace trap";
+#endif
+#ifdef SIGABRT
+ if (sudo_sys_siglist[SIGABRT] == NULL)
+ sudo_sys_siglist[SIGABRT] = "Abort";
+#endif
+#ifdef SIGIOT
+ if (sudo_sys_siglist[SIGIOT] == NULL)
+ sudo_sys_siglist[SIGIOT] = "IOT instruction";
+#endif
+#ifdef SIGEMT
+ if (sudo_sys_siglist[SIGEMT] == NULL)
+ sudo_sys_siglist[SIGEMT] = "EMT trap";
+#endif
+#ifdef SIGFPE
+ if (sudo_sys_siglist[SIGFPE] == NULL)
+ sudo_sys_siglist[SIGFPE] = "Floating point exception";
+#endif
+#ifdef SIGKILL
+ if (sudo_sys_siglist[SIGKILL] == NULL)
+ sudo_sys_siglist[SIGKILL] = "Killed";
+#endif
+#ifdef SIGBUS
+ if (sudo_sys_siglist[SIGBUS] == NULL)
+ sudo_sys_siglist[SIGBUS] = "Bus error";
+#endif
+#ifdef SIGSEGV
+ if (sudo_sys_siglist[SIGSEGV] == NULL)
+ sudo_sys_siglist[SIGSEGV] = "Memory fault";
+#endif
+#ifdef SIGSYS
+ if (sudo_sys_siglist[SIGSYS] == NULL)
+ sudo_sys_siglist[SIGSYS] = "Bad system call";
+#endif
+#ifdef SIGUNUSED
+ if (sudo_sys_siglist[SIGUNUSED] == NULL)
+ sudo_sys_siglist[SIGUNUSED] = "Unused";
+#endif
+#ifdef SIGPIPE
+ if (sudo_sys_siglist[SIGPIPE] == NULL)
+ sudo_sys_siglist[SIGPIPE] = "Broken pipe";
+#endif
+#ifdef SIGALRM
+ if (sudo_sys_siglist[SIGALRM] == NULL)
+ sudo_sys_siglist[SIGALRM] = "Alarm clock";
+#endif
+#ifdef SIGTERM
+ if (sudo_sys_siglist[SIGTERM] == NULL)
+ sudo_sys_siglist[SIGTERM] = "Terminated";
+#endif
+#ifdef SIGSTKFLT
+ if (sudo_sys_siglist[SIGSTKFLT] == NULL)
+ sudo_sys_siglist[SIGSTKFLT] = "Stack fault";
+#endif
+#ifdef SIGIO
+ if (sudo_sys_siglist[SIGIO] == NULL)
+ sudo_sys_siglist[SIGIO] = "I/O possible";
+#endif
+#ifdef SIGXCPU
+ if (sudo_sys_siglist[SIGXCPU] == NULL)
+ sudo_sys_siglist[SIGXCPU] = "CPU time limit exceeded";
+#endif
+#ifdef SIGXFSZ
+ if (sudo_sys_siglist[SIGXFSZ] == NULL)
+ sudo_sys_siglist[SIGXFSZ] = "File size limit exceeded";
+#endif
+#ifdef SIGVTALRM
+ if (sudo_sys_siglist[SIGVTALRM] == NULL)
+ sudo_sys_siglist[SIGVTALRM] = "Virtual timer expired";
+#endif
+#ifdef SIGPROF
+ if (sudo_sys_siglist[SIGPROF] == NULL)
+ sudo_sys_siglist[SIGPROF] = "Profiling timer expired";
+#endif
+#ifdef SIGWINCH
+ if (sudo_sys_siglist[SIGWINCH] == NULL)
+ sudo_sys_siglist[SIGWINCH] = "Window size change";
+#endif
+#ifdef SIGLOST
+ if (sudo_sys_siglist[SIGLOST] == NULL)
+ sudo_sys_siglist[SIGLOST] = "File lock lost";
+#endif
+#ifdef SIGUSR1
+ if (sudo_sys_siglist[SIGUSR1] == NULL)
+ sudo_sys_siglist[SIGUSR1] = "User defined signal 1";
+#endif
+#ifdef SIGUSR2
+ if (sudo_sys_siglist[SIGUSR2] == NULL)
+ sudo_sys_siglist[SIGUSR2] = "User defined signal 2";
+#endif
+#ifdef SIGPWR
+ if (sudo_sys_siglist[SIGPWR] == NULL)
+ sudo_sys_siglist[SIGPWR] = "Power-fail/Restart";
+#endif
+#ifdef SIGPOLL
+ if (sudo_sys_siglist[SIGPOLL] == NULL)
+ sudo_sys_siglist[SIGPOLL] = "Pollable event occurred";
+#endif
+#ifdef SIGSTOP
+ if (sudo_sys_siglist[SIGSTOP] == NULL)
+ sudo_sys_siglist[SIGSTOP] = "Stopped (signal)";
+#endif
+#ifdef SIGTSTP
+ if (sudo_sys_siglist[SIGTSTP] == NULL)
+ sudo_sys_siglist[SIGTSTP] = "Stopped";
+#endif
+#ifdef SIGCONT
+ if (sudo_sys_siglist[SIGCONT] == NULL)
+ sudo_sys_siglist[SIGCONT] = "Continued";
+#endif
+#ifdef SIGCHLD
+ if (sudo_sys_siglist[SIGCHLD] == NULL)
+ sudo_sys_siglist[SIGCHLD] = "Child exited";
+#endif
+#ifdef SIGCLD
+ if (sudo_sys_siglist[SIGCLD] == NULL)
+ sudo_sys_siglist[SIGCLD] = "Child exited";
+#endif
+#ifdef SIGTTIN
+ if (sudo_sys_siglist[SIGTTIN] == NULL)
+ sudo_sys_siglist[SIGTTIN] = "Stopped (tty input)";
+#endif
+#ifdef SIGTTOU
+ if (sudo_sys_siglist[SIGTTOU] == NULL)
+ sudo_sys_siglist[SIGTTOU] = "Stopped (tty output)";
+#endif
+#ifdef SIGINFO
+ if (sudo_sys_siglist[SIGINFO] == NULL)
+ sudo_sys_siglist[SIGINFO] = "Information request";
+#endif
+#ifdef SIGURG
+ if (sudo_sys_siglist[SIGURG] == NULL)
+ sudo_sys_siglist[SIGURG] = "Urgent I/O condition";
+#endif
+#ifdef SIGWAITING
+ if (sudo_sys_siglist[SIGWAITING] == NULL)
+ sudo_sys_siglist[SIGWAITING] = "No runnable LWPs";
+#endif
+#ifdef SIGLWP
+ if (sudo_sys_siglist[SIGLWP] == NULL)
+ sudo_sys_siglist[SIGLWP] = "Inter-LWP signal";
+#endif
+#ifdef SIGFREEZE
+ if (sudo_sys_siglist[SIGFREEZE] == NULL)
+ sudo_sys_siglist[SIGFREEZE] = "Checkpoint freeze";
+#endif
+#ifdef SIGTHAW
+ if (sudo_sys_siglist[SIGTHAW] == NULL)
+ sudo_sys_siglist[SIGTHAW] = "Checkpoint thaw";
+#endif
+#ifdef SIGCANCEL
+ if (sudo_sys_siglist[SIGCANCEL] == NULL)
+ sudo_sys_siglist[SIGCANCEL] = "Thread cancellation";
+#endif
diff --git a/lib/util/sys_signame.h b/lib/util/sys_signame.h
new file mode 100644
index 0000000..3616df3
--- /dev/null
+++ b/lib/util/sys_signame.h
@@ -0,0 +1,182 @@
+/* public domain */
+
+#include <config.h>
+#include <sys/types.h>
+#include <signal.h>
+#include "sudo_compat.h"
+
+int sudo_end_of_headers;
+static char *sudo_sys_signame[NSIG];
+
+#ifdef SIGHUP
+ if (sudo_sys_signame[SIGHUP] == NULL)
+ sudo_sys_signame[SIGHUP] = "HUP";
+#endif
+#ifdef SIGINT
+ if (sudo_sys_signame[SIGINT] == NULL)
+ sudo_sys_signame[SIGINT] = "INT";
+#endif
+#ifdef SIGQUIT
+ if (sudo_sys_signame[SIGQUIT] == NULL)
+ sudo_sys_signame[SIGQUIT] = "QUIT";
+#endif
+#ifdef SIGILL
+ if (sudo_sys_signame[SIGILL] == NULL)
+ sudo_sys_signame[SIGILL] = "ILL";
+#endif
+#ifdef SIGTRAP
+ if (sudo_sys_signame[SIGTRAP] == NULL)
+ sudo_sys_signame[SIGTRAP] = "TRAP";
+#endif
+#ifdef SIGABRT
+ if (sudo_sys_signame[SIGABRT] == NULL)
+ sudo_sys_signame[SIGABRT] = "ABRT";
+#endif
+#ifdef SIGIOT
+ if (sudo_sys_signame[SIGIOT] == NULL)
+ sudo_sys_signame[SIGIOT] = "IOT";
+#endif
+#ifdef SIGEMT
+ if (sudo_sys_signame[SIGEMT] == NULL)
+ sudo_sys_signame[SIGEMT] = "EMT";
+#endif
+#ifdef SIGFPE
+ if (sudo_sys_signame[SIGFPE] == NULL)
+ sudo_sys_signame[SIGFPE] = "FPE";
+#endif
+#ifdef SIGKILL
+ if (sudo_sys_signame[SIGKILL] == NULL)
+ sudo_sys_signame[SIGKILL] = "KILL";
+#endif
+#ifdef SIGBUS
+ if (sudo_sys_signame[SIGBUS] == NULL)
+ sudo_sys_signame[SIGBUS] = "BUS";
+#endif
+#ifdef SIGSEGV
+ if (sudo_sys_signame[SIGSEGV] == NULL)
+ sudo_sys_signame[SIGSEGV] = "SEGV";
+#endif
+#ifdef SIGSYS
+ if (sudo_sys_signame[SIGSYS] == NULL)
+ sudo_sys_signame[SIGSYS] = "SYS";
+#endif
+#ifdef SIGUNUSED
+ if (sudo_sys_signame[SIGUNUSED] == NULL)
+ sudo_sys_signame[SIGUNUSED] = "UNUSED";
+#endif
+#ifdef SIGPIPE
+ if (sudo_sys_signame[SIGPIPE] == NULL)
+ sudo_sys_signame[SIGPIPE] = "PIPE";
+#endif
+#ifdef SIGALRM
+ if (sudo_sys_signame[SIGALRM] == NULL)
+ sudo_sys_signame[SIGALRM] = "ALRM";
+#endif
+#ifdef SIGTERM
+ if (sudo_sys_signame[SIGTERM] == NULL)
+ sudo_sys_signame[SIGTERM] = "TERM";
+#endif
+#ifdef SIGSTKFLT
+ if (sudo_sys_signame[SIGSTKFLT] == NULL)
+ sudo_sys_signame[SIGSTKFLT] = "STKFLT";
+#endif
+#ifdef SIGIO
+ if (sudo_sys_signame[SIGIO] == NULL)
+ sudo_sys_signame[SIGIO] = "IO";
+#endif
+#ifdef SIGXCPU
+ if (sudo_sys_signame[SIGXCPU] == NULL)
+ sudo_sys_signame[SIGXCPU] = "XCPU";
+#endif
+#ifdef SIGXFSZ
+ if (sudo_sys_signame[SIGXFSZ] == NULL)
+ sudo_sys_signame[SIGXFSZ] = "XFSZ";
+#endif
+#ifdef SIGVTALRM
+ if (sudo_sys_signame[SIGVTALRM] == NULL)
+ sudo_sys_signame[SIGVTALRM] = "VTALRM";
+#endif
+#ifdef SIGPROF
+ if (sudo_sys_signame[SIGPROF] == NULL)
+ sudo_sys_signame[SIGPROF] = "PROF";
+#endif
+#ifdef SIGWINCH
+ if (sudo_sys_signame[SIGWINCH] == NULL)
+ sudo_sys_signame[SIGWINCH] = "WINCH";
+#endif
+#ifdef SIGLOST
+ if (sudo_sys_signame[SIGLOST] == NULL)
+ sudo_sys_signame[SIGLOST] = "LOST";
+#endif
+#ifdef SIGUSR1
+ if (sudo_sys_signame[SIGUSR1] == NULL)
+ sudo_sys_signame[SIGUSR1] = "USR1";
+#endif
+#ifdef SIGUSR2
+ if (sudo_sys_signame[SIGUSR2] == NULL)
+ sudo_sys_signame[SIGUSR2] = "USR2";
+#endif
+#ifdef SIGPWR
+ if (sudo_sys_signame[SIGPWR] == NULL)
+ sudo_sys_signame[SIGPWR] = "PWR";
+#endif
+#ifdef SIGPOLL
+ if (sudo_sys_signame[SIGPOLL] == NULL)
+ sudo_sys_signame[SIGPOLL] = "POLL";
+#endif
+#ifdef SIGSTOP
+ if (sudo_sys_signame[SIGSTOP] == NULL)
+ sudo_sys_signame[SIGSTOP] = "STOP";
+#endif
+#ifdef SIGTSTP
+ if (sudo_sys_signame[SIGTSTP] == NULL)
+ sudo_sys_signame[SIGTSTP] = "TSTP";
+#endif
+#ifdef SIGCONT
+ if (sudo_sys_signame[SIGCONT] == NULL)
+ sudo_sys_signame[SIGCONT] = "CONT";
+#endif
+#ifdef SIGCHLD
+ if (sudo_sys_signame[SIGCHLD] == NULL)
+ sudo_sys_signame[SIGCHLD] = "CHLD";
+#endif
+#ifdef SIGCLD
+ if (sudo_sys_signame[SIGCLD] == NULL)
+ sudo_sys_signame[SIGCLD] = "CLD";
+#endif
+#ifdef SIGTTIN
+ if (sudo_sys_signame[SIGTTIN] == NULL)
+ sudo_sys_signame[SIGTTIN] = "TTIN";
+#endif
+#ifdef SIGTTOU
+ if (sudo_sys_signame[SIGTTOU] == NULL)
+ sudo_sys_signame[SIGTTOU] = "TTOU";
+#endif
+#ifdef SIGINFO
+ if (sudo_sys_signame[SIGINFO] == NULL)
+ sudo_sys_signame[SIGINFO] = "INFO";
+#endif
+#ifdef SIGURG
+ if (sudo_sys_signame[SIGURG] == NULL)
+ sudo_sys_signame[SIGURG] = "URG";
+#endif
+#ifdef SIGWAITING
+ if (sudo_sys_signame[SIGWAITING] == NULL)
+ sudo_sys_signame[SIGWAITING] = "WAITING";
+#endif
+#ifdef SIGLWP
+ if (sudo_sys_signame[SIGLWP] == NULL)
+ sudo_sys_signame[SIGLWP] = "LWP";
+#endif
+#ifdef SIGFREEZE
+ if (sudo_sys_signame[SIGFREEZE] == NULL)
+ sudo_sys_signame[SIGFREEZE] = "FREEZE";
+#endif
+#ifdef SIGTHAW
+ if (sudo_sys_signame[SIGTHAW] == NULL)
+ sudo_sys_signame[SIGTHAW] = "THAW";
+#endif
+#ifdef SIGCANCEL
+ if (sudo_sys_signame[SIGCANCEL] == NULL)
+ sudo_sys_signame[SIGCANCEL] = "CANCEL";
+#endif
diff --git a/lib/util/term.c b/lib/util/term.c
new file mode 100644
index 0000000..41d2725
--- /dev/null
+++ b/lib/util/term.c
@@ -0,0 +1,315 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011-2015, 2017-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * 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/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/* TCSASOFT is a BSD extension that ignores control flags and speed. */
+#ifndef TCSASOFT
+# define TCSASOFT 0
+#endif
+
+/* Non-standard termios input flags */
+#ifndef IUCLC
+# define IUCLC 0
+#endif
+#ifndef IMAXBEL
+# define IMAXBEL 0
+#endif
+#ifndef IUTF8
+# define IUTF8 0
+#endif
+
+/* Non-standard termios output flags */
+#ifndef OLCUC
+# define OLCUC 0
+#endif
+#ifndef ONLCR
+# define ONLCR 0
+#endif
+#ifndef OCRNL
+# define OCRNL 0
+#endif
+#ifndef ONOCR
+# define ONOCR 0
+#endif
+#ifndef ONLRET
+# define ONLRET 0
+#endif
+
+/* Non-standard termios local flags */
+#ifndef XCASE
+# define XCASE 0
+#endif
+#ifndef IEXTEN
+# define IEXTEN 0
+#endif
+#ifndef ECHOCTL
+# define ECHOCTL 0
+#endif
+#ifndef ECHOKE
+# define ECHOKE 0
+#endif
+#ifndef PENDIN
+# define PENDIN 0
+#endif
+
+static struct termios oterm;
+static int changed;
+
+/* tgetpass() needs to know the erase and kill chars for cbreak mode. */
+sudo_dso_public int sudo_term_eof;
+sudo_dso_public int sudo_term_erase;
+sudo_dso_public int sudo_term_kill;
+
+static volatile sig_atomic_t got_sigttou;
+
+/*
+ * SIGTTOU signal handler for term_restore that just sets a flag.
+ */
+static void
+sigttou(int signo)
+{
+ got_sigttou = 1;
+}
+
+/*
+ * Like tcsetattr() 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.
+ */
+static int
+tcsetattr_nobg(int fd, int flags, struct termios *tp)
+{
+ struct sigaction sa, osa;
+ int rc;
+ debug_decl(tcsetattr_nobg, SUDO_DEBUG_UTIL);
+
+ /*
+ * If we receive SIGTTOU from tcsetattr() it means we are
+ * not in the foreground process group.
+ * This should be less racy than using tcgetpgrp().
+ */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = sigttou;
+ got_sigttou = 0;
+ sigaction(SIGTTOU, &sa, &osa);
+ do {
+ rc = tcsetattr(fd, flags, tp);
+ } while (rc != 0 && errno == EINTR && !got_sigttou);
+ sigaction(SIGTTOU, &osa, NULL);
+
+ debug_return_int(rc);
+}
+
+/*
+ * Restore saved terminal settings if we are in the foreground process group.
+ * Returns true on success or false on failure.
+ */
+bool
+sudo_term_restore_v1(int fd, bool flush)
+{
+ debug_decl(sudo_term_restore, SUDO_DEBUG_UTIL);
+
+ if (changed) {
+ const int flags = flush ? (TCSASOFT|TCSAFLUSH) : (TCSASOFT|TCSADRAIN);
+ if (tcsetattr_nobg(fd, flags, &oterm) != 0)
+ debug_return_bool(false);
+ changed = 0;
+ }
+ debug_return_bool(true);
+}
+
+/*
+ * Disable terminal echo.
+ * Returns true on success or false on failure.
+ */
+bool
+sudo_term_noecho_v1(int fd)
+{
+ struct termios term;
+ debug_decl(sudo_term_noecho, SUDO_DEBUG_UTIL);
+
+ if (!changed && tcgetattr(fd, &oterm) != 0)
+ debug_return_bool(false);
+ (void) memcpy(&term, &oterm, sizeof(term));
+ CLR(term.c_lflag, ECHO|ECHONL);
+#ifdef VSTATUS
+ term.c_cc[VSTATUS] = _POSIX_VDISABLE;
+#endif
+ if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
+ changed = 1;
+ debug_return_bool(true);
+ }
+ debug_return_bool(false);
+}
+
+/*
+ * Set terminal to raw mode with optional terminal signals.
+ * Returns true on success or false on failure.
+ */
+bool
+sudo_term_raw_v1(int fd, int isig)
+{
+ struct termios term;
+ debug_decl(sudo_term_raw, SUDO_DEBUG_UTIL);
+
+ if (!changed && tcgetattr(fd, &oterm) != 0)
+ debug_return_bool(false);
+ (void) memcpy(&term, &oterm, sizeof(term));
+ /* Set terminal to raw mode but optionally enable terminal signals. */
+ cfmakeraw(&term);
+ if (isig)
+ SET(term.c_lflag, ISIG);
+ if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
+ changed = 1;
+ debug_return_bool(true);
+ }
+ debug_return_bool(false);
+}
+
+/*
+ * Set terminal to cbreak mode.
+ * Returns true on success or false on failure.
+ */
+bool
+sudo_term_cbreak_v1(int fd)
+{
+ struct termios term;
+ debug_decl(sudo_term_cbreak, SUDO_DEBUG_UTIL);
+
+ if (!changed && tcgetattr(fd, &oterm) != 0)
+ debug_return_bool(false);
+ (void) memcpy(&term, &oterm, sizeof(term));
+ /* Set terminal to half-cooked mode */
+ term.c_cc[VMIN] = 1;
+ term.c_cc[VTIME] = 0;
+ /* cppcheck-suppress redundantAssignment */
+ CLR(term.c_lflag, ECHO | ECHONL | ICANON | IEXTEN);
+ /* cppcheck-suppress redundantAssignment */
+ SET(term.c_lflag, ISIG);
+#ifdef VSTATUS
+ term.c_cc[VSTATUS] = _POSIX_VDISABLE;
+#endif
+ if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
+ sudo_term_eof = term.c_cc[VEOF];
+ sudo_term_erase = term.c_cc[VERASE];
+ sudo_term_kill = term.c_cc[VKILL];
+ changed = 1;
+ debug_return_bool(true);
+ }
+ debug_return_bool(false);
+}
+
+/* Termios flags to copy between terminals. */
+#define INPUT_FLAGS (IGNPAR|PARMRK|INPCK|ISTRIP|INLCR|IGNCR|ICRNL|IUCLC|IXON|IXANY|IXOFF|IMAXBEL|IUTF8)
+#define OUTPUT_FLAGS (OPOST|OLCUC|ONLCR|OCRNL|ONOCR|ONLRET)
+#define CONTROL_FLAGS (CS7|CS8|PARENB|PARODD)
+#define LOCAL_FLAGS (ISIG|ICANON|XCASE|ECHO|ECHOE|ECHOK|ECHONL|NOFLSH|TOSTOP|IEXTEN|ECHOCTL|ECHOKE|PENDIN)
+
+/*
+ * Copy terminal settings from one descriptor to another.
+ * We cannot simply copy the struct termios as src and dst may be
+ * different terminal types (pseudo-tty vs. console or glass tty).
+ * Returns true on success or false on failure.
+ */
+bool
+sudo_term_copy_v1(int src, int dst)
+{
+ struct termios tt_src, tt_dst;
+ struct winsize wsize;
+ speed_t speed;
+ int i;
+ debug_decl(sudo_term_copy, SUDO_DEBUG_UTIL);
+
+ if (tcgetattr(src, &tt_src) != 0 || tcgetattr(dst, &tt_dst) != 0)
+ debug_return_bool(false);
+
+ /* Clear select input, output, control and local flags. */
+ CLR(tt_dst.c_iflag, INPUT_FLAGS);
+ CLR(tt_dst.c_oflag, OUTPUT_FLAGS);
+ CLR(tt_dst.c_cflag, CONTROL_FLAGS);
+ CLR(tt_dst.c_lflag, LOCAL_FLAGS);
+
+ /* Copy select input, output, control and local flags. */
+ SET(tt_dst.c_iflag, (tt_src.c_iflag & INPUT_FLAGS));
+ SET(tt_dst.c_oflag, (tt_src.c_oflag & OUTPUT_FLAGS));
+ SET(tt_dst.c_cflag, (tt_src.c_cflag & CONTROL_FLAGS));
+ SET(tt_dst.c_lflag, (tt_src.c_lflag & LOCAL_FLAGS));
+
+ /* Copy special chars from src verbatim. */
+ for (i = 0; i < NCCS; i++)
+ tt_dst.c_cc[i] = tt_src.c_cc[i];
+
+ /* Copy speed from src (zero output speed closes the connection). */
+ if ((speed = cfgetospeed(&tt_src)) == B0)
+ speed = B38400;
+ cfsetospeed(&tt_dst, speed);
+ speed = cfgetispeed(&tt_src);
+ cfsetispeed(&tt_dst, speed);
+
+ if (tcsetattr_nobg(dst, TCSASOFT|TCSAFLUSH, &tt_dst) == -1)
+ debug_return_bool(false);
+
+ if (ioctl(src, TIOCGWINSZ, &wsize) == 0)
+ (void)ioctl(dst, TIOCSWINSZ, &wsize);
+
+ debug_return_bool(true);
+}
+
+/*
+ * Returns true if fd refers to a tty in raw mode, else false.
+ */
+bool
+sudo_term_is_raw_v1(int fd)
+{
+ struct termios term;
+ debug_decl(sudo_term_is_raw, SUDO_DEBUG_UTIL);
+
+ if (tcgetattr(fd, &term) != 0)
+ debug_return_bool(false);
+
+ if (term.c_cc[VMIN] != 1 || term.c_cc[VTIME] != 0)
+ debug_return_bool(false);
+
+ if (ISSET(term.c_oflag, OPOST))
+ debug_return_bool(false);
+
+ if (ISSET(term.c_oflag, ECHO|ECHONL|ICANON))
+ debug_return_bool(false);
+
+ debug_return_bool(true);
+}
diff --git a/lib/util/timegm.c b/lib/util/timegm.c
new file mode 100644
index 0000000..968d829
--- /dev/null
+++ b/lib/util/timegm.c
@@ -0,0 +1,99 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2017, 2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_TIMEGM
+
+#include <stdio.h>
+#include <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+
+/*
+ * Returns the offset from GMT in seconds (algorithm taken from sendmail).
+ */
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+static long
+get_gmtoff(time_t *when)
+{
+ struct tm local;
+
+ if (localtime_r(when, &local) == NULL)
+ return 0;
+
+ /* Adjust for DST. */
+ if (local.tm_isdst != 0)
+ local.tm_gmtoff -= local.tm_isdst * 3600;
+
+ return local.tm_gmtoff;
+}
+#else
+static long
+get_gmtoff(time_t *when)
+{
+ struct tm gmt, local;
+ long offset;
+
+ if (gmtime_r(when, &gmt) == NULL)
+ return 0;
+ if (localtime_r(when, &local) == NULL)
+ return 0;
+
+ offset = (local.tm_sec - gmt.tm_sec) +
+ ((local.tm_min - gmt.tm_min) * 60) +
+ ((local.tm_hour - gmt.tm_hour) * 3600);
+
+ /* Timezone may cause year rollover to happen on a different day. */
+ if (local.tm_year < gmt.tm_year)
+ offset -= 24 * 3600;
+ else if (local.tm_year > gmt.tm_year)
+ offset -= 24 * 3600;
+ else if (local.tm_yday < gmt.tm_yday)
+ offset -= 24 * 3600;
+ else if (local.tm_yday > gmt.tm_yday)
+ offset += 24 * 3600;
+
+ /* Adjust for DST. */
+ if (local.tm_isdst != 0)
+ offset -= local.tm_isdst * 3600;
+
+ return offset;
+}
+#endif /* HAVE_TM_GMTOFF */
+
+time_t
+sudo_timegm(struct tm *tm)
+{
+ time_t result;
+
+ tm->tm_isdst = 0;
+ result = mktime(tm);
+ if (result != -1)
+ result += get_gmtoff(&result);
+
+ return result;
+}
+
+#endif /* HAVE_TIMEGM */
diff --git a/lib/util/ttyname_dev.c b/lib/util/ttyname_dev.c
new file mode 100644
index 0000000..08b9777
--- /dev/null
+++ b/lib/util/ttyname_dev.c
@@ -0,0 +1,311 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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>
+
+#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>
+#else
+# include <sys/param.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <dirent.h>
+
+#include "pathnames.h"
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_conf.h"
+#include "sudo_util.h"
+
+#if defined(HAVE_DEVNAME)
+/*
+ * Like ttyname() but uses a dev_t instead of an open fd.
+ * Returns name on success and NULL on failure, setting errno.
+ * The BSD version uses devname().
+ */
+char *
+sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen)
+{
+ char *dev;
+ debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
+
+ /* Some versions of devname() return NULL on failure, others do not. */
+ dev = devname(tdev, S_IFCHR);
+ if (dev != NULL && *dev != '?' && *dev != '#') {
+ if (strlcpy(name, _PATH_DEV, namelen) < namelen &&
+ strlcat(name, dev, namelen) < namelen)
+ debug_return_str(name);
+ errno = ERANGE;
+ } else {
+ /* Not all versions of devname() set errno. */
+ errno = ENOENT;
+ }
+ debug_return_str(NULL);
+}
+#elif defined(HAVE__TTYNAME_DEV)
+extern char *_ttyname_dev(dev_t rdev, char *buffer, size_t buflen);
+
+/*
+ * Like ttyname() but uses a dev_t instead of an open fd.
+ * Returns name on success and NULL on failure, setting errno.
+ * This version is just a wrapper around _ttyname_dev().
+ */
+char *
+sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen)
+{
+ int serrno = errno;
+ debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
+
+ /*
+ * _ttyname_dev() sets errno to ERANGE if namelen is too small
+ * but does not modify it if tdev is not found.
+ */
+ errno = ENOENT;
+ if (_ttyname_dev(tdev, name, namelen) == NULL)
+ debug_return_str(NULL);
+ errno = serrno;
+
+ debug_return_str(name);
+}
+#else
+/*
+ * Device nodes to ignore.
+ */
+static const char *ignore_devs[] = {
+ _PATH_DEV "stdin",
+ _PATH_DEV "stdout",
+ _PATH_DEV "stderr",
+ NULL
+};
+
+/*
+ * Do a scan of a directory looking for the specified device.
+ * Does not descend into subdirectories.
+ * Returns name on success and NULL on failure, setting errno.
+ */
+static char *
+sudo_ttyname_scan(const char *dir, dev_t rdev, char *name, size_t namelen)
+{
+ size_t sdlen;
+ char pathbuf[PATH_MAX];
+ char *ret = NULL;
+ struct dirent *dp;
+ struct stat sb;
+ unsigned int i;
+ DIR *d = NULL;
+ debug_decl(sudo_ttyname_scan, SUDO_DEBUG_UTIL);
+
+ if (dir[0] == '\0') {
+ errno = ENOENT;
+ goto done;
+ }
+ if ((d = opendir(dir)) == NULL)
+ goto done;
+
+ if (fstat(dirfd(d), &sb) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to fstat %s", dir);
+ goto done;
+ }
+ if ((sb.st_mode & S_IWOTH) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "ignoring world-writable directory %s", dir);
+ errno = ENOENT;
+ goto done;
+ }
+
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "scanning for dev %u in %s", (unsigned int)rdev, dir);
+
+ sdlen = strlen(dir);
+ while (sdlen > 0 && dir[sdlen - 1] == '/')
+ sdlen--;
+ if (sdlen + 1 >= sizeof(pathbuf)) {
+ errno = ERANGE;
+ goto done;
+ }
+ memcpy(pathbuf, dir, sdlen);
+ pathbuf[sdlen++] = '/';
+
+ while ((dp = readdir(d)) != NULL) {
+ /* Skip anything starting with "." */
+ if (dp->d_name[0] == '.')
+ continue;
+
+ pathbuf[sdlen] = '\0';
+ if (strlcat(pathbuf, dp->d_name, sizeof(pathbuf)) >= sizeof(pathbuf)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s%s is too big to fit in pathbuf", pathbuf, dp->d_name);
+ continue;
+ }
+
+ /* Ignore device nodes listed in ignore_devs[]. */
+ for (i = 0; ignore_devs[i] != NULL; i++) {
+ if (strcmp(pathbuf, ignore_devs[i]) == 0)
+ break;
+ }
+ if (ignore_devs[i] != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "ignoring %s", pathbuf);
+ continue;
+ }
+
+# if defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ /*
+ * Avoid excessive stat() calls by checking dp->d_type.
+ */
+ switch (dp->d_type) {
+ case DT_CHR:
+ case DT_LNK:
+ case DT_UNKNOWN:
+ break;
+ default:
+ /* Not a character device or link, skip it. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "skipping non-device %s", pathbuf);
+ continue;
+ }
+# endif
+ if (stat(pathbuf, &sb) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to stat %s", pathbuf);
+ continue;
+ }
+ if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "resolved dev %u as %s", (unsigned int)rdev, pathbuf);
+ if (strlcpy(name, pathbuf, namelen) < namelen) {
+ ret = name;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to store %s, have %zu, need %zu",
+ pathbuf, namelen, strlen(pathbuf) + 1);
+ errno = ERANGE;
+ }
+ goto done;
+ }
+ }
+
+done:
+ if (d != NULL)
+ closedir(d);
+ debug_return_str(ret);
+}
+
+static char *
+sudo_dev_check(dev_t rdev, const char *devname, char *buf, size_t buflen)
+{
+ struct stat sb;
+ debug_decl(sudo_dev_check, SUDO_DEBUG_UTIL);
+
+ if (stat(devname, &sb) == 0) {
+ if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "comparing dev %u to %s: match!",
+ (unsigned int)rdev, devname);
+ if (strlcpy(buf, devname, buflen) < buflen)
+ debug_return_str(buf);
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to store %s, have %zu, need %zu",
+ devname, buflen, strlen(devname) + 1);
+ errno = ERANGE;
+ }
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "comparing dev %u to %s: no", (unsigned int)rdev, devname);
+ debug_return_str(NULL);
+}
+
+/*
+ * Like ttyname() but uses a dev_t instead of an open fd.
+ * Returns name on success and NULL on failure, setting errno.
+ * Generic version.
+ */
+char *
+sudo_ttyname_dev_v1(dev_t rdev, char *buf, size_t buflen)
+{
+ const char *devsearch, *devsearch_end;
+ char path[PATH_MAX], *ret;
+ const char *cp, *ep;
+ size_t len;
+ debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
+
+ /*
+ * First, check /dev/console.
+ */
+ ret = sudo_dev_check(rdev, _PATH_DEV "console", buf, buflen);
+ if (ret != NULL)
+ goto done;
+
+ /*
+ * Then check the device search path.
+ */
+ devsearch = sudo_conf_devsearch_path();
+ devsearch_end = devsearch + strlen(devsearch);
+ for (cp = sudo_strsplit(devsearch, devsearch_end, ":", &ep);
+ cp != NULL; cp = sudo_strsplit(NULL, devsearch_end, ":", &ep)) {
+
+ len = (size_t)(ep - cp);
+ if (len >= sizeof(path)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "devsearch entry %.*s too long", (int)len, cp);
+ continue;
+ }
+ memcpy(path, cp, len);
+ path[len] = '\0';
+
+ if (strcmp(path, _PATH_DEV "pts") == 0) {
+ /* Special case /dev/pts */
+ len = (size_t)snprintf(path, sizeof(path), "%spts/%u",
+ _PATH_DEV, (unsigned int)minor(rdev));
+ if (len >= sizeof(path)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "devsearch entry %spts/%u too long",
+ _PATH_DEV, (unsigned int)minor(rdev));
+ continue;
+ }
+ ret = sudo_dev_check(rdev, path, buf, buflen);
+ if (ret != NULL)
+ goto done;
+ } else {
+ /* Scan path, looking for rdev. */
+ ret = sudo_ttyname_scan(path, rdev, buf, buflen);
+ if (ret != NULL || errno == ENOMEM)
+ goto done;
+ }
+ }
+
+done:
+ debug_return_str(ret);
+}
+#endif
diff --git a/lib/util/ttysize.c b/lib/util/ttysize.c
new file mode 100644
index 0000000..221ea92
--- /dev/null
+++ b/lib/util/ttysize.c
@@ -0,0 +1,71 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010-2012, 2014-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/ioctl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <termios.h> /* for struct winsize on HP-UX */
+#include <limits.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+static int
+get_ttysize_ioctl(int *rowp, int *colp)
+{
+ struct winsize wsize;
+ debug_decl(get_ttysize_ioctl, SUDO_DEBUG_UTIL);
+
+ if (ioctl(STDERR_FILENO, TIOCGWINSZ, &wsize) == 0 &&
+ wsize.ws_row != 0 && wsize.ws_col != 0) {
+ *rowp = wsize.ws_row;
+ *colp = wsize.ws_col;
+ debug_return_int(0);
+ }
+ debug_return_int(-1);
+}
+
+void
+sudo_get_ttysize_v1(int *rowp, int *colp)
+{
+ debug_decl(sudo_get_ttysize, SUDO_DEBUG_UTIL);
+
+ if (get_ttysize_ioctl(rowp, colp) == -1) {
+ char *p;
+
+ /* Fall back on $LINES and $COLUMNS. */
+ if ((p = getenv("LINES")) == NULL ||
+ (*rowp = sudo_strtonum(p, 1, INT_MAX, NULL)) <= 0) {
+ *rowp = 24;
+ }
+ if ((p = getenv("COLUMNS")) == NULL ||
+ (*colp = sudo_strtonum(p, 1, INT_MAX, NULL)) <= 0) {
+ *colp = 80;
+ }
+ }
+
+ debug_return;
+}
diff --git a/lib/util/unlinkat.c b/lib/util/unlinkat.c
new file mode 100644
index 0000000..f1d590e
--- /dev/null
+++ b/lib/util/unlinkat.c
@@ -0,0 +1,60 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_UNLINKAT
+int
+sudo_unlinkat(int dfd, const char *path, int flag)
+{
+ int odfd, ret;
+
+ if (dfd == AT_FDCWD)
+ return unlink(path);
+
+ /* Save cwd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ return -1;
+
+ if (fchdir(dfd) == -1) {
+ close(odfd);
+ return -1;
+ }
+
+ ret = unlink(path);
+
+ /* Restore cwd */
+ if (fchdir(odfd) == -1) {
+ /* Should not happen */
+ ret = -1;
+ }
+ close(odfd);
+
+ return ret;
+}
+#endif /* HAVE_UNLINKAT */
diff --git a/lib/util/util.exp.in b/lib/util/util.exp.in
new file mode 100644
index 0000000..6c8130a
--- /dev/null
+++ b/lib/util/util.exp.in
@@ -0,0 +1,166 @@
+@COMPAT_EXP@initprogname
+initprogname2
+sudo_basename_v1
+sudo_conf_askpass_path_v1
+sudo_conf_clear_paths_v1
+sudo_conf_debug_files_v1
+sudo_conf_debugging_v1
+sudo_conf_developer_mode_v1
+sudo_conf_devsearch_path_v1
+sudo_conf_disable_coredump_v1
+sudo_conf_group_source_v1
+sudo_conf_intercept_path_v1
+sudo_conf_max_groups_v1
+sudo_conf_noexec_path_v1
+sudo_conf_plugin_dir_path_v1
+sudo_conf_plugins_v1
+sudo_conf_probe_interfaces_v1
+sudo_conf_read_v1
+sudo_conf_sesh_path_v1
+sudo_debug_deregister_v1
+sudo_debug_enter_v1
+sudo_debug_execve2_v1
+sudo_debug_exit_bool_v1
+sudo_debug_exit_id_t_v1
+sudo_debug_exit_int_v1
+sudo_debug_exit_long_v1
+sudo_debug_exit_ptr_v1
+sudo_debug_exit_size_t_v1
+sudo_debug_exit_ssize_t_v1
+sudo_debug_exit_str_masked_v1
+sudo_debug_exit_str_v1
+sudo_debug_exit_time_t_v1
+sudo_debug_exit_v1
+sudo_debug_fork_v1
+sudo_debug_get_active_instance_v1
+sudo_debug_get_fds_v1
+sudo_debug_get_instance_v1
+sudo_debug_needed_v1
+sudo_debug_parse_flags_v1
+sudo_debug_printf2_v1
+sudo_debug_register_v1
+sudo_debug_register_v2
+sudo_debug_set_active_instance_v1
+sudo_debug_update_fd_v1
+sudo_debug_vprintf2_v1
+sudo_debug_write2_v1
+sudo_digest_alloc_v1
+sudo_digest_final_v1
+sudo_digest_free_v1
+sudo_digest_getlen_v1
+sudo_digest_reset_v1
+sudo_digest_update_v1
+sudo_dso_findsym_v1
+sudo_dso_load_v1
+sudo_dso_preload_table_v1
+sudo_dso_strerror_v1
+sudo_dso_unload_v1
+sudo_ev_add_v1
+sudo_ev_add_v2
+sudo_ev_alloc_v1
+sudo_ev_base_alloc_v1
+sudo_ev_base_free_v1
+sudo_ev_base_setdef_v1
+sudo_ev_del_v1
+sudo_ev_dispatch_v1
+sudo_ev_free_v1
+sudo_ev_get_timeleft_v1
+sudo_ev_get_timeleft_v2
+sudo_ev_got_break_v1
+sudo_ev_got_exit_v1
+sudo_ev_loop_v1
+sudo_ev_loopbreak_v1
+sudo_ev_loopcontinue_v1
+sudo_ev_loopexit_v1
+sudo_ev_pending_v1
+sudo_ev_set_v1
+sudo_fatal_callback_deregister_v1
+sudo_fatal_callback_register_v1
+sudo_fatal_nodebug_v1
+sudo_fatalx_nodebug_v1
+sudo_gai_fatal_nodebug_v1
+sudo_gai_vfatal_nodebug_v1
+sudo_gai_vwarn_nodebug_v1
+sudo_gai_warn_nodebug_v1
+sudo_get_ttysize_v1
+sudo_getgrouplist2_v1
+sudo_gethostname_v1
+sudo_gettime_awake_v1
+sudo_gettime_mono_v1
+sudo_gettime_real_v1
+sudo_hexchar_v1
+sudo_json_add_value_as_object_v1
+sudo_json_add_value_v1
+sudo_json_close_array_v1
+sudo_json_close_object_v1
+sudo_json_free_v1
+sudo_json_get_buf_v1
+sudo_json_get_len_v1
+sudo_json_init_v1
+sudo_json_init_v2
+sudo_json_open_array_v1
+sudo_json_open_object_v1
+sudo_lbuf_append_esc_v1
+sudo_lbuf_append_quoted_v1
+sudo_lbuf_append_v1
+sudo_lbuf_clearerr_v1
+sudo_lbuf_destroy_v1
+sudo_lbuf_error_v1
+sudo_lbuf_init_v1
+sudo_lbuf_print_v1
+sudo_lock_file_v1
+sudo_lock_region_v1
+sudo_logfac2str_v1
+sudo_logpri2str_v1
+sudo_mkdir_parents_v1
+sudo_mmap_alloc_v1
+sudo_mmap_allocarray_v1
+sudo_mmap_free_v1
+sudo_mmap_protect_v1
+sudo_mmap_strdup_v1
+sudo_new_key_val_v1
+sudo_open_parent_dir_v1
+sudo_parse_gids_v1
+sudo_parseln_v1
+sudo_parseln_v2
+sudo_pow2_roundup_v1
+sudo_rcstr_addref
+sudo_rcstr_alloc
+sudo_rcstr_delref
+sudo_rcstr_dup
+sudo_regex_compile_v1
+sudo_secure_dir_v1
+sudo_secure_file_v1
+sudo_secure_open_dir_v1
+sudo_secure_open_file_v1
+sudo_setgroups_v1
+sudo_stat_multiarch_v1
+sudo_str2logfac_v1
+sudo_str2logpri_v1
+sudo_strsplit_v1
+sudo_strtobool_v1
+sudo_strtoid_v1
+sudo_strtoid_v2
+sudo_strtoidx_v1
+sudo_strtomode_v1
+sudo_strtonum
+sudo_term_cbreak_v1
+sudo_term_copy_v1
+sudo_term_eof
+sudo_term_erase
+sudo_term_is_raw_v1
+sudo_term_kill
+sudo_term_noecho_v1
+sudo_term_raw_v1
+sudo_term_restore_v1
+sudo_ttyname_dev_v1
+sudo_uuid_create_v1
+sudo_uuid_to_string_v1
+sudo_vfatal_nodebug_v1
+sudo_vfatalx_nodebug_v1
+sudo_vwarn_nodebug_v1
+sudo_vwarnx_nodebug_v1
+sudo_warn_nodebug_v1
+sudo_warn_set_conversation_v1
+sudo_warn_set_locale_func_v1
+sudo_warnx_nodebug_v1
diff --git a/lib/util/utimens.c b/lib/util/utimens.c
new file mode 100644
index 0000000..c6b8ee2
--- /dev/null
+++ b/lib/util/utimens.c
@@ -0,0 +1,200 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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.
+ */
+
+/*
+ * 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>
+
+#if !defined(HAVE_FUTIMENS) || !defined(HAVE_UTIMENSAT)
+
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <time.h>
+#if !defined(HAVE_UTIMES) || defined(HAVE_FUTIME)
+# include <utime.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#if !defined(HAVE_FUTIMES) && defined(HAVE_FUTIMESAT)
+# define futimes(_f, _tv) futimesat(_f, NULL, _tv)
+# define HAVE_FUTIMES
+#endif
+
+#if defined(HAVE_ST_MTIM)
+# ifdef HAVE_ST__TIM
+# define ATIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_atim.st__tim)
+# define MTIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_mtim.st__tim)
+# else
+# define ATIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_atim)
+# define MTIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_mtim)
+# endif
+#elif defined(HAVE_ST_MTIMESPEC)
+# define ATIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_atimespec)
+# define MTIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_mtimespec)
+#elif defined(HAVE_ST_NMTIME)
+# define ATIME_TO_TIMEVAL(_x, _y) do { (_x)->tv_sec = (_y)->st_atime; (_x)->tv_usec = (_y)->st_natime; } while (0)
+# define MTIME_TO_TIMEVAL(_x, _y) do { (_x)->tv_sec = (_y)->st_mtime; (_x)->tv_usec = (_y)->st_nmtime; } while (0)
+#else
+# define ATIME_TO_TIMEVAL(_x, _y) do { (_x)->tv_sec = (_y)->st_atime; (_x)->tv_usec = 0; } while (0)
+# define MTIME_TO_TIMEVAL(_x, _y) do { (_x)->tv_sec = (_y)->st_mtime; (_x)->tv_usec = 0; } while (0)
+#endif /* HAVE_ST_MTIM */
+
+/*
+ * Convert the pair of timespec structs passed to futimens() / utimensat()
+ * to a pair of timeval structs, handling UTIME_OMIT and UTIME_NOW.
+ * Returns 0 on success and -1 on failure (setting errno).
+ */
+static int
+utimens_ts_to_tv(int fd, const char *file, const struct timespec *ts,
+ struct timeval *tv)
+{
+ TIMESPEC_TO_TIMEVAL(&tv[0], &ts[0]);
+ TIMESPEC_TO_TIMEVAL(&tv[1], &ts[1]);
+ if (ts[0].tv_nsec == UTIME_OMIT || ts[1].tv_nsec == UTIME_OMIT) {
+ struct stat sb;
+
+ if (fd != -1) {
+ /* For futimens() */
+ if (fstat(fd, &sb) == -1)
+ return -1;
+ } else {
+ /* For utimensat() */
+ if (stat(file, &sb) == -1)
+ return -1;
+ }
+ if (ts[0].tv_nsec == UTIME_OMIT)
+ ATIME_TO_TIMEVAL(&tv[0], &sb);
+ if (ts[1].tv_nsec == UTIME_OMIT)
+ MTIME_TO_TIMEVAL(&tv[1], &sb);
+ }
+ if (ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) {
+ struct timeval now;
+
+ if (gettimeofday(&now, NULL) == -1)
+ return -1;
+ if (ts[0].tv_nsec == UTIME_NOW)
+ tv[0] = now;
+ if (ts[1].tv_nsec == UTIME_NOW)
+ tv[1] = now;
+ }
+ return 0;
+}
+
+#if defined(HAVE_FUTIMES)
+/*
+ * Emulate futimens() via futimes()
+ */
+int
+sudo_futimens(int fd, const struct timespec *ts)
+{
+ struct timeval tv[2], *times = NULL;
+
+ if (ts != NULL) {
+ if (utimens_ts_to_tv(fd, NULL, ts, tv) == -1)
+ return -1;
+ times = tv;
+ }
+ return futimes(fd, times);
+}
+#elif defined(HAVE_FUTIME)
+/*
+ * Emulate futimens() via futime()
+ */
+int
+sudo_futimens(int fd, const struct timespec *ts)
+{
+ struct utimbuf utb, *times = NULL;
+
+ if (ts != NULL) {
+ struct timeval tv[2];
+
+ if (utimens_ts_to_tv(fd, NULL, ts, tv) == -1)
+ return -1;
+ utb.actime = (time_t)(tv[0].tv_sec + tv[0].tv_usec / 1000000);
+ utb.modtime = (time_t)(tv[1].tv_sec + tv[1].tv_usec / 1000000);
+ times = &utb;
+ }
+ return futime(fd, times);
+}
+#else
+/*
+ * Nothing to do but fail.
+ */
+int
+sudo_futimens(int fd, const struct timespec *ts)
+{
+ errno = ENOSYS;
+ return -1;
+}
+#endif /* HAVE_FUTIMES */
+
+#if defined(HAVE_UTIMES)
+/*
+ * Emulate utimensat() via utimes()
+ */
+int
+sudo_utimensat(int fd, const char *file, const struct timespec *ts, int flag)
+{
+ struct timeval tv[2], *times = NULL;
+
+ if (fd != AT_FDCWD || flag != 0) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (ts != NULL) {
+ if (utimens_ts_to_tv(-1, file, ts, tv) == -1)
+ return -1;
+ times = tv;
+ }
+ return utimes(file, times);
+}
+#else
+/*
+ * Emulate utimensat() via utime()
+ */
+int
+sudo_utimensat(int fd, const char *file, const struct timespec *ts, int flag)
+{
+ struct utimbuf utb, *times = NULL;
+
+ if (fd != AT_FDCWD || flag != 0) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (ts != NULL) {
+ struct timeval tv[2];
+
+ if (utimens_ts_to_tv(-1, file, ts, tv) == -1)
+ return -1;
+ utb.actime = (time_t)(tv[0].tv_sec + tv[0].tv_usec / 1000000);
+ utb.modtime = (time_t)(tv[1].tv_sec + tv[1].tv_usec / 1000000);
+ times = &utb;
+ }
+ return utime(file, times);
+}
+#endif /* !HAVE_UTIMES */
+
+#endif /* !HAVE_FUTIMENS && !HAVE_UTIMENSAT */
diff --git a/lib/util/uuid.c b/lib/util/uuid.c
new file mode 100644
index 0000000..77efdfe
--- /dev/null
+++ b/lib/util/uuid.c
@@ -0,0 +1,99 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_rand.h"
+
+struct uuid {
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_hi_and_version;
+ uint8_t clock_seq_hi_and_reserved;
+ uint8_t clock_seq_low;
+ uint8_t node[6];
+};
+
+/*
+ * Create a type 4 (random), variant 1 universally unique identifier (UUID).
+ * As per RFC 4122 section 4.4.
+ */
+void
+sudo_uuid_create_v1(unsigned char uuid_out[16])
+{
+ struct uuid uuid;
+
+ arc4random_buf(&uuid, sizeof(uuid));
+
+ /* Set version to 4 (random), 4 most significant bits (12-15) are 0010. */
+ uuid.time_hi_and_version &= 0x0fff;
+ uuid.time_hi_and_version |= 0x4000;
+
+ /* Set variant to 1: two most significant bits (6 and 7) are 01. */
+ uuid.clock_seq_hi_and_reserved &= 0x3f;
+ uuid.clock_seq_hi_and_reserved |= 0x80;
+
+ memcpy(uuid_out, &uuid, 16);
+}
+
+/*
+ * Format a uuid as a 36-byte string (plus one for the NUL).
+ */
+char *
+sudo_uuid_to_string_v1(unsigned char uuid[16], char *dst, size_t dstsiz)
+{
+ const char hex[] = "0123456789abcdef";
+ char *cp = dst;
+ int i;
+
+ if (dstsiz < sizeof("123e4567-e89b-12d3-a456-426655440000"))
+ return NULL;
+
+ for (i = 0; i < 16; i++) {
+ *cp++ = hex[uuid[i] >> 4];
+ *cp++ = hex[uuid[i] & 0x0f];
+
+ switch (i) {
+ case 4:
+ case 6:
+ case 8:
+ case 10:
+ *cp++ = '-';
+ break;
+ }
+ }
+ *cp = '\0';
+
+ return dst;
+}