diff options
Diffstat (limited to 'lib/util')
158 files changed, 27266 insertions, 0 deletions
diff --git a/lib/util/Makefile.in b/lib/util/Makefile.in new file mode 100644 index 0000000..05455f7 --- /dev/null +++ b/lib/util/Makefile.in @@ -0,0 +1,1730 @@ +# +# 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@ +adminconfdir = @adminconfdir@ +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=\"@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 digest_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 + +DIGEST_TEST_OBJS = digest_test.lo @DIGEST@ + +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@ @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@ @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) + +digest_test: $(DIGEST_TEST_OBJS) libsudo_util.la + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(DIGEST_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS) @LIBCRYPTO@ + +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)" || \ + rm -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 $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + fi; \ + ./digest_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + if test -f fnm_test; then \ + ./fnm_test $(TEST_VERBOSE) $(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 $(TEST_VERBOSE) $(srcdir)/regress/glob/globtest.in || rval=`expr $$rval + $$?`; \ + rm -rf fake; \ + fi; \ + if test -f getdelim_test; then \ + ./getdelim_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + fi; \ + if test -f mktemp_test; then \ + ./mktemp_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + fi; \ + if test -f strsig_test; then \ + ./strsig_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + fi; \ + ./getgrouplist_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./hexchar_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./hltq_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./json_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./multiarch_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./open_parent_dir_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./parse_gids_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./progname_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + rm -f ./progname_test2 $(TEST_VERBOSE); ln -s ./progname_test ./progname_test2; \ + ./progname_test2 $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + rm -f ./progname_test2; \ + ./regex_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./strsplit_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./strtobool_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./strtoid_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./strtomode_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./strtonum_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + ./uuid_test $(TEST_VERBOSE) || rval=`expr $$rval + $$?`; \ + 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 $@ +digest_test.lo: $(srcdir)/regress/digest/digest_test.c \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_digest.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/digest/digest_test.c +digest_test.i: $(srcdir)/regress/digest/digest_test.c \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_digest.h $(incdir)/sudo_fatal.h \ + $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +digest_test.plog: digest_test.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/digest/digest_test.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 $@ +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_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_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 \ + $(incdir)/sudo_util.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 \ + $(incdir)/sudo_util.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 $@ +realpath.lo: $(srcdir)/realpath.c $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/realpath.c +realpath.i: $(srcdir)/realpath.c $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +realpath.plog: realpath.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/realpath.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..3751f58 --- /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, ®istry, 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..20ff4e1 --- /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..c0fbe70 --- /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..ad3c5aa --- /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..a2c2c39 --- /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..f0dd41e --- /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..ef8e20c --- /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((unsigned int)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 = (int)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..b3a3b87 --- /dev/null +++ b/lib/util/digest.c @@ -0,0 +1,172 @@ +/* + * 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 size_t 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(unsigned int digest_type) +{ + debug_decl(sudo_digest_alloc, SUDO_DEBUG_UTIL); + struct digest_function *func = NULL; + struct sudo_digest *dig; + unsigned 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; +} + +size_t +sudo_digest_getlen_v2(unsigned int digest_type) +{ + debug_decl(sudo_digest_getlen, SUDO_DEBUG_UTIL); + unsigned int i; + + for (i = 0; digest_functions[i].digest_len != 0; i++) { + if (digest_type == i) + debug_return_size_t(digest_functions[i].digest_len); + } + + debug_return_size_t(0); +} + +int +sudo_digest_getlen_v1(unsigned int digest_type) +{ + size_t len = sudo_digest_getlen_v2(digest_type); + return len ? (int)len : -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..7deae2d --- /dev/null +++ b/lib/util/digest_gcrypt.c @@ -0,0 +1,167 @@ +/* + * 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(unsigned 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(unsigned 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; +} + +size_t +sudo_digest_getlen_v2(unsigned 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_size_t(0); + + debug_return_size_t(gcry_md_get_algo_dlen(gcry_digest_type)); +} + +int +sudo_digest_getlen_v1(unsigned int digest_type) +{ + size_t len = sudo_digest_getlen_v2(digest_type); + return len ? (int)len : -1; +} + +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..712b20b --- /dev/null +++ b/lib/util/digest_openssl.c @@ -0,0 +1,159 @@ +/* + * 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(unsigned 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(unsigned 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; +} + +size_t +sudo_digest_getlen_v2(unsigned 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_size_t(0); + + debug_return_size_t((size_t)EVP_MD_size(md)); +} + +int +sudo_digest_getlen_v1(unsigned int digest_type) +{ + size_t len = sudo_digest_getlen_v2(digest_type); + return len ? (int)len : -1; +} + +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..c3ab141 --- /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..e49f657 --- /dev/null +++ b/lib/util/event.c @@ -0,0 +1,881 @@ +/* + * 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, int 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; + unsigned 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) +{ + unsigned 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; + unsigned 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, int 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_v2(struct sudo_event *ev, int fd, int 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)); + 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); +} + +int +sudo_ev_set_v1(struct sudo_event *ev, int fd, short events, + sudo_ev_callback_t callback, void *closure) +{ + return sudo_ev_set_v2(ev, fd, events, callback, closure); +} + +struct sudo_event * +sudo_ev_alloc_v2(int fd, int 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); +} + +struct sudo_event * +sudo_ev_alloc_v1(int fd, short events, sudo_ev_callback_t callback, void *closure) +{ + return sudo_ev_alloc_v2(fd, events, callback, closure); +} + +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, unsigned 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_v2(struct sudo_event *ev, int 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); +} + +int +sudo_ev_pending_v1(struct sudo_event *ev, short events, struct timespec *ts) +{ + return sudo_ev_pending_v2(ev, events, ts); +} diff --git a/lib/util/event_poll.c b/lib/util/event_poll.c new file mode 100644 index 0000000..245fe59 --- /dev/null +++ b/lib/util/event_poll.c @@ -0,0 +1,236 @@ +/* + * 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 <limits.h> +#include <poll.h> +#include <stdlib.h> +#include <time.h> + +#include <sudo_compat.h> +#include <sudo_util.h> +#include <sudo_fatal.h> +#include <sudo_debug.h> +#include <sudo_event.h> + +#if defined(OPEN_MAX) && OPEN_MAX > 256 +# define SUDO_OPEN_MAX OPEN_MAX +#else +# define SUDO_OPEN_MAX 256 +#endif + +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, (size_t)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 = (int)rlim.rlim_cur; + } else { + nofile_max = SUDO_OPEN_MAX; + } + } + + /* 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, (size_t)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 = (short)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, unsigned 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, (nfds_t)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 = (short)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..74e6e2f --- /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 = (size_t)o * sizeof(fd_mask); + const size_t new_bytes = (size_t)(n - o) * sizeof(fd_mask); + fd_set *rfds_in, *wfds_in, *rfds_out, *wfds_out; + + rfds_in = reallocarray(base->readfds_in, (size_t)n, sizeof(fd_mask)); + wfds_in = reallocarray(base->writefds_in, (size_t)n, sizeof(fd_mask)); + rfds_out = reallocarray(base->readfds_out, (size_t)n, sizeof(fd_mask)); + wfds_out = reallocarray(base->writefds_out, (size_t)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, unsigned 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 = (size_t)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) { + short 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 %hd, 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..3d0ee1f --- /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..bdee6a1 --- /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 * restrict errstr, const char * restrict 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); + } +} + +sudo_noreturn void +sudo_fatal_nodebug_v1(const char * restrict fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + warning(strerror(errno), fmt, ap); + va_end(ap); + do_cleanup(); + exit(EXIT_FAILURE); +} + +sudo_noreturn void +sudo_fatalx_nodebug_v1(const char * restrict fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + warning(NULL, fmt, ap); + va_end(ap); + do_cleanup(); + exit(EXIT_FAILURE); +} + +sudo_noreturn void +sudo_vfatal_nodebug_v1(const char * restrict fmt, va_list ap) +{ + warning(strerror(errno), fmt, ap); + do_cleanup(); + exit(EXIT_FAILURE); +} + +sudo_noreturn void +sudo_vfatalx_nodebug_v1(const char * restrict fmt, va_list ap) +{ + warning(NULL, fmt, ap); + do_cleanup(); + exit(EXIT_FAILURE); +} + +void +sudo_warn_nodebug_v1(const char * restrict fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + warning(strerror(errno), fmt, ap); + va_end(ap); +} + +void +sudo_warnx_nodebug_v1(const char * restrict fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + warning(NULL, fmt, ap); + va_end(ap); +} + +void +sudo_vwarn_nodebug_v1(const char * restrict fmt, va_list ap) +{ + warning(strerror(errno), fmt, ap); +} + +void +sudo_vwarnx_nodebug_v1(const char * restrict fmt, va_list ap) +{ + warning(NULL, fmt, ap); +} + +sudo_noreturn void +sudo_gai_fatal_nodebug_v1(int errnum, const char * restrict fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + warning(gai_strerror(errnum), fmt, ap); + va_end(ap); + do_cleanup(); + exit(EXIT_FAILURE); +} + +sudo_noreturn void +sudo_gai_vfatal_nodebug_v1(int errnum, const char * restrict 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 * restrict 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 * restrict fmt, va_list ap) +{ + warning(gai_strerror(errnum), fmt, ap); +} + +static void +warning(const char * restrict errstr, const char * restrict 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)) { + /* Not enough room in static buf, allocate dynamically. */ + if (vasprintf(&buf, fmt, ap) == -1) + buf = static_buf; + } + if (buflen > 0) { + 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..39d294e --- /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..035f239 --- /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..729d546 --- /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..dae48b5 --- /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..983085d --- /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..4d8256e --- /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; + size_t 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/getdelim.c b/lib/util/getdelim.c new file mode 100644 index 0000000..1c95705 --- /dev/null +++ b/lib/util/getdelim.c @@ -0,0 +1,83 @@ +/* + * 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 ** restrict buf, size_t * restrict bufsize, int delim, + FILE * restrict 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..5daf0f7 --- /dev/null +++ b/lib/util/getentropy.c @@ -0,0 +1,649 @@ +/* + * 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, (int)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; + size_t ret = (size_t)read(fd, (char *)buf + i, wanted); + + if (ret == (size_t)-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; + long 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, faster = 0; + int ret = -1; + static size_t cnt; + unsigned int repeat; + size_t pgs; + 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 (len == 0) + return 0; + pgs = (size_t)sysconf(_SC_PAGESIZE); + if (pgs == (size_t)-1) + return -1; + if ((ctx = sudo_digest_alloc(SUDO_DIGEST_SHA512)) == NULL) + return -1; + digest_len = sudo_digest_getlen(SUDO_DIGEST_SHA512); + if (digest_len == 0 || (results = malloc(digest_len)) == NULL) + goto done; + + pid = getpid(); + if (lastpid == pid) { + faster = 1; + repeat = 2; + } else { + faster = 0; + lastpid = pid; + repeat = REPEAT; + } + i = 0; + do { + unsigned int j; + for (j = 0; j < repeat; j++) { + HX((e = gettimeofday(&tv, NULL)) == -1, tv); + if (e != -1) { + cnt += (size_t)tv.tv_sec; + cnt += (size_t)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 += (size_t)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 += (size_t)ts.tv_nsec; + } +#endif /* HAVE_CLOCK_GETTIME */ + + HX((e = getrusage(RUSAGE_SELF, + &ru)) == -1, ru); + if (e != -1) { + cnt += (size_t)ru.ru_utime.tv_sec; + cnt += (size_t)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 += (size_t)ru.ru_utime.tv_sec; + cnt += (size_t)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 += (size_t)tv.tv_sec; + cnt += (size_t)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); + } while (i < len); + 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..48d1fbe --- /dev/null +++ b/lib/util/getgrouplist.c @@ -0,0 +1,530 @@ +/* + * 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 + long grpsize; + int 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 = sysconf(_SC_NGROUPS_MAX); + if (grpsize < 0 || grpsize > INT_MAX) + 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, (size_t)grpsize, sizeof(*groups)); + if (groups == NULL) + debug_return_int(-1); + ngroups = (int)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..34f714b --- /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..04f6d47 --- /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..54fbcb3 --- /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..05bbc44 --- /dev/null +++ b/lib/util/getusershell.c @@ -0,0 +1,145 @@ +/* + * 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_util.h> + +#ifdef TESTSUDOERS +# include <tsgetgrpw.h> +#endif + +static const char *shellfile = "/etc/shell"; +static char **allowed_shells, * const *current_shell; +static const 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(shellfile, "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++); +} + +#ifdef TESTSUDOERS +void +testsudoers_setshellfile(const char *file) +{ + testsudoers_endusershell(); + shellfile = file; +} +#endif /* TESTSUDOERS */ diff --git a/lib/util/gidlist.c b/lib/util/gidlist.c new file mode 100644 index 0000000..efcb7df --- /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, (size_t)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..d546837 --- /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)fputc(CHAR(*p), stdout); + (void)fputc('\n', stdout); + for (p = s; *p; p++) + (void)fputc(*p & M_PROTECT ? '"' : ' ', stdout); + (void)fputc('\n', stdout); + for (p = s; *p; p++) + (void)fputc(ismeta(*p) ? '_' : ' ', stdout); + (void)fputc('\n', stdout); +} +#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..11e2651 --- /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..0650843 --- /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]; + unsigned 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..e456d6e --- /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 * restrict src, char * restrict 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 || (socklen_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 * restrict src, char * restrict 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 & 1)) << 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 * restrict src, char * restrict 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..d3b4b4b --- /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..cc099c0 --- /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..e0bd286 --- /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) +{ + unsigned 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 += (unsigned int)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"; + 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((unsigned char)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, unsigned 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, unsigned 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[STRLEN_MAX_SIGNED(long long) + 1]; + 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..89ba4ac --- /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..6bc74ee --- /dev/null +++ b/lib/util/lbuf.c @@ -0,0 +1,496 @@ +/* + * 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 <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <string.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, + unsigned int indent, const char *continuation, int cols) +{ + debug_decl(sudo_lbuf_init, SUDO_DEBUG_UTIL); + + if (cols < 0) + cols = 0; + + lbuf->output = output; + lbuf->continuation = continuation; + lbuf->indent = indent; + lbuf->cols = (unsigned short)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) { + const size_t size = lbuf->len + extra + 1; + size_t new_size = sudo_pow2_roundup(size); + char *new_buf; + + if (new_size > UINT_MAX || 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 = (unsigned int)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 unsigned int +escape(char ch, char *buf) +{ + unsigned char uch = (unsigned char)ch; + const unsigned int len = uch < 0100 ? (uch < 010 ? 3 : 4) : 5; + + /* Work backwards from the least significant digit to most significant. */ + switch (len) { + case 5: + buf[4] = (uch & 7) + '0'; + uch >>= 3; + FALLTHROUGH; + case 4: + buf[3] = (uch & 7) + '0'; + uch >>= 3; + FALLTHROUGH; + case 3: + buf[2] = (uch & 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 * restrict 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 * restrict fmt, ...) +{ + unsigned int saved_len = lbuf->len; + bool ret = false; + const char *cp, *s; + va_list ap; + unsigned 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 = (unsigned 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 = (unsigned int)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, %n$s and %% escapes are supported. + */ +bool +sudo_lbuf_append_v1(struct sudo_lbuf *lbuf, const char * restrict fmt, ...) +{ + unsigned int saved_len = lbuf->len; + bool ret = false; + va_list ap; + const char *s; + unsigned 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] == '%' && isdigit((unsigned char)fmt[1])) { + const char *num_start = fmt + 1; + const char *num_end = num_start; + int arg_num; + /* Find the end of the numeric part */ + while (isdigit((unsigned char)*num_end)) + num_end++; + if (num_end[0] == '$' && num_end[1] == 's' && num_end > num_start) { + /* Convert the numeric part to an integer */ + char numbuf[STRLEN_MAX_SIGNED(int) + 1]; + len = (unsigned int)(num_end - num_start); + if (len >= sizeof(numbuf)) { + errno = EINVAL; + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "integer overflow parsing $n"); + lbuf->error = 1; + goto done; + } + memcpy(numbuf, num_start, len); + numbuf[len] = '\0'; + arg_num = atoi(numbuf); + if (arg_num > 0) { + va_list arg_copy; + va_copy(arg_copy, ap); + for (int i = 1; i < arg_num; i++) { + (void)va_arg(arg_copy, char *); + } + if ((s = va_arg(arg_copy, char *)) == NULL) + s = "(NULL)"; + len = (unsigned int)strlen(s); + if (!sudo_lbuf_expand(lbuf, len)) { + va_end(arg_copy); + goto done; + } + memcpy(lbuf->buf + lbuf->len, s, len); + lbuf->len += len; + fmt = num_end + 2; + va_end(arg_copy); + continue; + } + } + } + if (fmt[0] == '%' && fmt[1] == 's') { + if ((s = va_arg(ap, char *)) == NULL) + s = "(NULL)"; + len = (unsigned int)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, size_t len) +{ + char *cp, save; + size_t i, have, contlen = 0; + unsigned 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; + size_t need = len - (size_t)(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 = (size_t)(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; + size_t 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 - (size_t)(cp - lbuf->buf); + if ((ep = memchr(cp, '\n', len)) != NULL) + len = (size_t)(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..180c890 --- /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..a6bce60 --- /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..cb9c3ce --- /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..3fd1f6c --- /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..0a042e4 --- /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..b483748 --- /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, (int)(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, (int)(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..18649b1 --- /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..6495e22 --- /dev/null +++ b/lib/util/mksiglist.c @@ -0,0 +1,57 @@ +/* + * 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" + + /* + * For portability we must not use %zu below. + * This program is compiled with the host C compiler, + * so it cannot use any of the functions in libsudo_util. + */ + puts("const char *const sudo_sys_siglist[] = {"); + for (i = 0; i < NSIG; i++) { + if (sudo_sys_siglist[i] != NULL) { + printf(" \"%s\",\n", sudo_sys_siglist[i]); + } else { + printf(" \"Signal %u\",\n", i); + } + } + puts("};"); + + return EXIT_SUCCESS; +} diff --git a/lib/util/mksigname.c b/lib/util/mksigname.c new file mode 100644 index 0000000..1b51199 --- /dev/null +++ b/lib/util/mksigname.c @@ -0,0 +1,57 @@ +/* + * 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" + + /* + * For portability we must not use %zu below. + * This program is compiled with the host C compiler, + * so it cannot use any of the functions in libsudo_util. + */ + puts("const char *const sudo_sys_signame[] = {"); + for (i = 0; i < NSIG; i++) { + if (sudo_sys_signame[i] != NULL) { + printf(" \"%s\",\n", sudo_sys_signame[i]); + } else { + printf(" \"Signal %u\",\n", i); + } + } + puts("};"); + + return EXIT_SUCCESS; +} diff --git a/lib/util/mktemp.c b/lib/util/mktemp.c new file mode 100644 index 0000000..08f289a --- /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]; + size_t 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..b587338 --- /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..0b79de5 --- /dev/null +++ b/lib/util/multiarch.c @@ -0,0 +1,103 @@ +/* + * 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; + size_t 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/. */ + if (asprintf(&newpath, "%.*s%s%s-linux-gnu%s", + (int)(lib - path), path, newlib, unamebuf.machine, slash) == -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..84c41b8 --- /dev/null +++ b/lib/util/nanosleep.c @@ -0,0 +1,74 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2009-2011, 2013, 2017-2018, 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> + +#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 *timeout, struct timespec *remainder) +{ + struct timespec endtime, now; + struct timeval tv; + int rval; + + if (timeout->tv_sec == 0 && timeout->tv_nsec < 1000) { + tv.tv_sec = 0; + tv.tv_usec = 1; + } else { + TIMESPEC_TO_TIMEVAL(&tv, timeout); + } + if (remainder != NULL) { + if (sudo_gettime_real(&endtime) == -1) + return -1; + sudo_timespecadd(&endtime, timeout, &endtime); + } + rval = select(0, NULL, NULL, NULL, &tv); + if (remainder != NULL) { + if (rval == 0) { + /* Timeout expired, no remaining time. */ + sudo_timespecclear(remainder); + } else if (errno == EINTR) { + /* Interrupted, compute remaining time. */ + if (sudo_gettime_real(&now) == -1) + return -1; + sudo_timespecsub(&endtime, &now, remainder); + if (remainder->tv_sec < 0 || remainder->tv_nsec < 0) + sudo_timespecclear(remainder); + } + } + return rval; +} +#endif /* HAVE_NANOSLEEP */ diff --git a/lib/util/openat.c b/lib/util/openat.c new file mode 100644 index 0000000..b748be9 --- /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..ec10d9d --- /dev/null +++ b/lib/util/parseln.c @@ -0,0 +1,130 @@ +/* + * 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 <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.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) +{ + ssize_t len, total = 0; + size_t bufsize, linesize = 0; + 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--; + + bufsize = (size_t)(total + len + 1); + if (*bufp == NULL || bufsize > *bufsizep) { + const size_t newsize = sudo_pow2_roundup(bufsize); + void *newbuf; + + if (newsize < bufsize) { + /* overflow */ + errno = ENOMEM; + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate memory"); + len = -1; + total = 0; + break; + } + if ((newbuf = realloc(*bufp, newsize)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate memory"); + len = -1; + total = 0; + break; + } + *bufp = newbuf; + *bufsizep = newsize; + } + memcpy(*bufp + total, cp, (size_t)(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..1ffcad0 --- /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..a8a4774 --- /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..9355b6a --- /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; + size_t 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..d643b5b --- /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..24dba6c --- /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..1fefbad --- /dev/null +++ b/lib/util/rcstr.c @@ -0,0 +1,103 @@ +/* + * 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[]; +}; + +/* + * 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); + + rcs = malloc(sizeof(struct rcstr) + len + 1); + 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..63d7882 --- /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/realpath.c b/lib/util/realpath.c new file mode 100644 index 0000000..9e945af --- /dev/null +++ b/lib/util/realpath.c @@ -0,0 +1,205 @@ +/* + * Copyright (c) 1989, 1991, 1993, 1995 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Jan-Simon Pendry. + * + * 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. + * + * @(#)getcwd.c 8.5 (Berkeley) 2/7/95 + */ + +#include <config.h> + +#ifndef HAVE_REALPATH + +#include <sys/types.h> +#include <sys/stat.h> + +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* + * char *realpath(const char *path, char *resolved); + * + * Find the real name of path, by removing all ".", ".." and symlink + * components. Returns (resolved) on success, or (NULL) on failure, + * in which case the path which caused trouble is left in (resolved). + */ +char * +sudo_realpath(const char * restrict path, char * restrict resolved) +{ + struct stat sb; + int idx = 0, nlnk = 0; + const char *q; + char *p, wbuf[2][PATH_MAX], *fres = NULL; + static long symloop_max; + size_t len; + ssize_t n; + + if (path == NULL) { + errno = EINVAL; + return NULL; + } + + if (symloop_max == 0) { + if ((symloop_max = sysconf(_SC_SYMLOOP_MAX)) <= 0) + symloop_max = 8; /* POSIX */ + } + + if (resolved == NULL) { + fres = resolved = malloc(PATH_MAX); + if (resolved == NULL) + return NULL; + } + + + /* + * Build real path one by one with paying an attention to ., + * .. and symbolic link. + */ + + /* + * `p' is where we'll put a new component with prepending + * a delimiter. + */ + p = resolved; + + if (*path == '\0') { + *p = '\0'; + errno = ENOENT; + goto out; + } + + /* If relative path, start from current working directory. */ + if (*path != '/') { + if (getcwd(resolved, PATH_MAX) == NULL) { + p[0] = '.'; + p[1] = '\0'; + goto out; + } + len = strlen(resolved); + if (len > 1) + p += len; + } + +loop: + /* Skip any slash. */ + while (*path == '/') + path++; + + if (*path == '\0') { + if (p == resolved) + *p++ = '/'; + *p = '\0'; + return resolved; + } + + /* Find the end of this component. */ + q = path; + do { + q++; + } while (*q != '/' && *q != '\0'); + + /* Test . or .. */ + if (path[0] == '.') { + if (q - path == 1) { + path = q; + goto loop; + } + if (path[1] == '.' && q - path == 2) { + /* Trim the last component. */ + if (p != resolved) + while (*--p != '/') + continue; + path = q; + goto loop; + } + } + + /* Append this component. */ + if (p - resolved + 1 + q - path + 1 > PATH_MAX) { + errno = ENAMETOOLONG; + if (p == resolved) + *p++ = '/'; + *p = '\0'; + goto out; + } + p[0] = '/'; + memcpy(&p[1], path, (size_t)(q - path)); + p[1 + q - path] = '\0'; + + /* + * If this component is a symlink, toss it and prepend link + * target to unresolved path. + */ + if (lstat(resolved, &sb) == -1) + goto out; + + if (S_ISLNK(sb.st_mode)) { + if (nlnk++ >= symloop_max) { + errno = ELOOP; + goto out; + } + n = readlink(resolved, wbuf[idx], sizeof(wbuf[0]) - 1); + if (n < 0) + goto out; + if (n == 0) { + errno = ENOENT; + goto out; + } + + /* Append unresolved path to link target and switch to it. */ + if ((size_t)n + (len = strlen(q)) + 1 > sizeof(wbuf[0])) { + errno = ENAMETOOLONG; + goto out; + } + memcpy(&wbuf[idx][n], q, len + 1); + path = wbuf[idx]; + idx ^= 1; + + /* If absolute symlink, start from root. */ + if (*path == '/') + p = resolved; + goto loop; + } + if (*q == '/' && !S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + goto out; + } + + /* Advance both resolved and unresolved path. */ + p += 1 + q - path; + path = q; + goto loop; +out: + free(fres); + return NULL; +} +#endif /* HAVE_REALPATH */ diff --git a/lib/util/regex.c b/lib/util/regex.c new file mode 100644 index 0000000..1f91b4d --- /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 int lastval = INT_MAX / 10; + const int remainder = INT_MAX % 10; + int result = 0; + char ch; + + while ((ch = *str++) != '\0') { + if (ch == '\\' && isdigit((unsigned char)str[0])) + ch = *str++; + else if (!isdigit((unsigned char)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..750c951 --- /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/digest/digest_test.c b/lib/util/regress/digest/digest_test.c new file mode 100644 index 0000000..69740a9 --- /dev/null +++ b/lib/util/regress/digest/digest_test.c @@ -0,0 +1,1179 @@ +/* + * 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. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define SUDO_ERROR_WRAP 0 + +#include <sudo_compat.h> +#include <sudo_digest.h> +#include <sudo_util.h> +#include <sudo_fatal.h> + +sudo_dso_public int main(int argc, char *argv[]); + +static int errors, ntests; +static const char hex[] = "0123456789abcdef"; + +struct test_vector { + size_t len; /* length in bytes */ + const char *str; /* input string (hex bytes) */ + const char *md; /* message digest */ +}; + +/* SHA224 short messages test vectors. */ +static struct test_vector sha224_vectors[] = { + { + 0, + "", + "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f" + }, { + 1, + "\x84", + "3cd36921df5d6963e73739cf4d20211e2d8877c19cff087ade9d0e3a" + }, { + 2, + "\x5c\x7b", + "daff9bce685eb831f97fc1225b03c275a6c112e2d6e76f5faf7a36e6" + }, { + 3, + "\x51\xca\x3d", + "2c8959023515476e38388abb43599a29876b4b33d56adc06032de3a2" + }, { + 4, + "\x60\x84\x34\x7e", + "ae57c0a6d49739ba338adfa53bdae063e5c09122b77604780a8eeaa3" + }, { + 5, + "\x49\x3e\x14\x62\x3c", + "7f631f295e024e74552083245ca8f988a3fb65680ae97c3040d2e65c" + }, { + 6, + "\xd7\x29\xd8\xcd\x16\x31", + "342e8e6b23c1c6a54910631f098e08e836259c57e49c1b1d023d166d" + }, { + 7, + "\xcb\xf2\x06\x1e\x10\xfa\xa5", + "3aa702b1b66dc57d7aec3ccdbdfbd88592d7520f843ba5d0fa481168" + }, { + 8, + "\x5f\x77\xb3\x66\x48\x23\xc3\x3e", + "bdf21ff325f754157ccf417f4855360a72e8fd117d28c8fe7da3ea38" + }, { + 9, + "\x10\x71\x3b\x89\x4d\xe4\xa7\x34\xc0", + "03842600c86f5cd60c3a2147a067cb962a05303c3488b05cb45327bd" + }, { + 10, + "\x00\x64\x70\xd5\x7d\xad\x98\x93\xdc\x03", + "c90026cda5ad24115059c62ae9add57793ade445d4742273288bbce7" + }, { + 11, + "\x6f\x29\xca\x27\x41\x90\x40\x07\x20\xbb\xa2", + "ac53157947aa4b2a19089182382a4363d182dd8e4ca79cd8571390be" + }, { + 12, + "\x17\xe8\x55\x61\x76\xfc\xca\x2a\xdd\xbd\xde\x29", + "cc6ad0488db0222066f740557b5758a19b30372b302332295d8c3aff" + }, { + 13, + "\xdb\xf1\x63\x60\x1d\xb9\xa1\x22\xa4\x02\x68\x24\xde", + "9849845f4e47e1ece9a1c1e01a0d896ffea61c6c8894a75a11ce5f49" + }, { + 14, + "\x5e\x1e\xf2\xad\x86\xce\xaf\x54\x39\xfe\x87\xd2\xec\x9b", + "223c5d5d4a0116b32cea044f9af0fe44babea1c5ab201502591bcd5f" + }, { + 15, + "\x65\xf3\xb9\x86\x6f\xb8\x00\x2b\x53\xcf\xaf\x80\x6f\x70\x2f", + "b1e0806a218d593821fde8e9eacc44ab5287c32209a94f011ab66b75" + }, { + 16, + "\xb7\x76\x70\x8f\xfb\x91\xb3\x51\x5a\xc4\x65\x98\xab\x9f\xa7\x96", + "427311b1d7ab2488791c4deeb4251d783fe5f9806bfdfb5188c5443d" + }, { + 17, + "\xa4\xbc\x10\xb1\xa6\x2c\x96\xd4\x59\xfb\xaf\x3a\x5a\xa3\xfa\xce\x73", + "d7e6634723ac25cb1879bdb1508da05313530419013fe255967a39e1" + }, { + 18, + "\x9e\x8f\x3c\x66\x45\xc1\x74\x9b\x55\xc5\x0d\x20\x18\xce\x40\xdc\x24\x27", + "2f5a583bf588c8988a572d128a95bea5ef1b66780a7d4be9c29efc31" + }, { + 19, + "\x2d\xb6\xd2\x07\xc0\xb7\xd9\x11\x7f\x24\xd7\x8e\xe5\x9a\xbf\x2f\x31\x69\x78", + "35681fce28307cae19522c23cbd4a77969347f7d8ee4a3088ba90ada" + }, { + 20, + "\x3d\xf5\xe7\xf3\x99\xf6\xdd\x61\xa1\x2a\x9d\x4e\x94\x64\xfc\x49\x97\xc1\xf3\x7b", + "a3e68076e30751085a843a6cbfbf0f3dee63d9c4219c914372e50b28" + }, { + 21, + "\x65\x78\x1d\x01\x8f\x27\xca\x0c\x72\xa9\xfa\x9a\xb4\x64\x8e\xd3\x69\x64\x6d\xd3\xce", + "d15ef0d872d02da6427b8d0349dea2f204e67133b7365b4b150efc3c" + }, { + 22, + "\xaf\x48\xee\xdd\xd9\x3f\xee\x69\xd1\xbd\x7d\xe4\x28\xa6\x39\x86\x01\x1d\x10\x94\x5e\xaf", + "b89d428ee42e397cf11029ecbb27baddd036c8938f51c8ab56b875ac" + }, { + 23, + "\xdf\x2b\xf0\xd5\xf9\xc9\x94\xac\x69\xd7\x8b\xaa\x0d\x51\x2e\xce\xb7\x4d\x8a\x04\x75\x31\xc1", + "db8e1ce68c8c6b84d6db755c2b8bf54f3c4b081a881efcddaf303294" + }, { + 24, + "\x48\xd2\xf2\x09\x55\xea\x2d\x13\x43\x3c\x20\xbc\x04\x04\xeb\x2e\x6a\xd7\x9e\xd2\x8f\x7c\xb4\xc0", + "3617cc3179f8b59adce181eebeed5e2763f62650949224a67e53694b" + }, { + 25, + "\x21\x8f\x74\xa4\x2d\x3a\x47\xef\x3b\x80\x66\x01\xfb\xa0\x24\xb0\x78\xcb\xff\x4e\x4b\x85\x77\x2e\x0e", + "b5f40b95dcc363b97e9d00b67c5d7c37f17ab563297d2d67a4df20c9" + }, { + 26, + "\xef\x55\xb1\xe7\x97\x00\x0b\x04\xfc\xdb\x9b\x30\x21\xb0\x93\x27\xe3\xb4\xe2\x69\xd2\x0c\xab\xdf\x41\x8f", + "827b223d51240c2e3271c534c19c5637b6fe10083e85bcf06761ef21" + }, { + 27, + "\x96\xdf\x43\x87\xdc\x2c\x40\x29\x70\x43\xbe\xa3\x64\x83\xf6\x5e\x4e\xb1\xe0\x7e\x93\x35\x9c\xb7\xe6\x86\x10", + "98e430a63fcdedafc9419010f7f59a4d816a45b4f973beb62530ff8c" + }, { + 28, + "\x3e\xc0\xaa\x8d\x30\xd5\xed\x82\x5b\x77\xdc\x70\x95\xf4\x21\xb1\xe6\x08\x15\x87\x97\xa3\x77\xff\x8b\xed\x64\x1b", + "3108321eb7ff857f6aae69101b937f32a51ea279a6c14ba5232ac8c1" + }, { + 29, + "\x8b\x02\x39\x71\x20\x39\xf0\x77\xce\x32\x3b\x35\xf4\xe3\x06\x78\x7b\x9b\x35\x27\x00\x96\xe5\x77\x35\xcf\xf4\x5d\x84", + "a5c740d3ce46bb2e0a048488f2b0605c6d0ca0ea2f382d043d13db97" + }, { + 30, + "\x04\x4b\xe3\x01\x67\xa9\x75\x8c\x46\xc7\x27\x92\x1d\xc4\xeb\x4e\x0d\xcb\x96\x56\x23\x42\x3e\x6f\xdd\x44\xe7\xa4\xea\x52", + "6eb78313c743ea8769d8340f284dda6ded64a1db64392f21abb82c5c" + }, { + 31, + "\x57\xf6\x11\x8b\xac\xce\x47\xec\xc3\x1c\xe8\xb0\xc0\x83\xd3\xc9\x21\x9e\x0d\xbe\x9e\x4f\xbe\xa1\x54\x53\x7c\x41\x23\x1a\xcc", + "0dbb53c866d63af44c222c76c825df0e379dcedfb958db03b6fd29a5" + }, { + 32, + "\xfe\x1f\x0f\xb0\x2c\x90\x11\xf4\xc8\xc5\x90\x59\x34\xed\x15\x13\x67\x71\x73\x7c\xe3\x1c\x58\x59\xe6\x7f\x23\x5f\xe5\x94\xf5\xf6", + "bbeaacc632c2a3db2a9b47f157ab54aa27776c6e74cf0bcaa91b06d5" + }, { + 33, + "\x14\xfb\x01\xae\x9d\x60\x15\xec\xb3\xe5\x6d\x6e\xcd\xfa\x4b\xc0\x53\x31\x86\xad\xf8\x45\x7f\x5e\x4a\x5c\x57\xc6\x87\x89\x5f\x3d\xb3", + "178272c7d7cc71b15074c27e3b7997d4a3ba99626986a1a16cf30030" + }, { + 34, + "\xff\x6c\x49\x71\x2f\x04\x4f\x40\x63\xc1\x41\x25\xc0\xcd\xfb\xa1\x8e\xd8\xb7\x13\x84\x53\x76\x8a\x45\xdf\xa2\xd8\x2a\x05\xf1\xe8\x42\x27", + "403284c888a7280bc8bfc25f0c34182cd378306a21a1404d4e1c40cf" + }, { + 35, + "\xf9\x00\xbd\x7e\x01\x17\x24\x7f\x97\xc8\xfc\x7a\x66\x5c\x76\xa3\x5f\x57\x1c\x33\x66\x57\x1d\x6c\x4a\x3e\xe5\xd7\xfb\x93\xf1\xd1\xf7\x26\xe2", + "48235b9820d66d8885faabf6a9ede63ba2a21b6177e987a33242373e" + }, { + 36, + "\x42\xd3\x81\x88\xac\x49\x44\x0c\xfe\xfb\x77\xdb\x97\x5e\x08\x3e\x6b\x22\x34\x8c\x4c\x67\xf0\xf8\x69\x2e\x88\xad\x14\x0d\x86\x1d\xc8\x28\xd5\x95", + "615344f890e5bcf71b5efe39de1fc942ba1fe30dd9e9146adb6a41bf" + }, { + 37, + "\x74\xfd\xd7\xd9\x58\xb8\xae\x7c\x2c\x3c\x5c\xff\x42\x66\xdf\xb2\xb3\xb8\x42\xc9\xf5\x9e\xcb\xbc\xaf\xf5\x75\xed\xcb\xcd\xa0\x8c\xcd\x6e\x08\xb7\x64", + "66d7d6c54fc7775a0ba845ba3e11719fa535b9289f20b098c5f7a342" + }, { + 38, + "\x93\x44\x16\xdd\x05\x81\xe2\x2f\x2b\xfb\xec\xe7\xbb\x64\xaf\xe8\x20\x45\x1f\xa2\x13\x42\xdf\x7e\x6f\x9f\xb3\x7c\x41\x03\x38\x1a\x1f\x7c\xd3\x79\xbc\xc4", + "fae8f1aa22def4dbaa814c5b0babdec43394951792c937050d2963a6" + }, { + 39, + "\x10\x24\x01\xc8\x4a\x71\x6a\xe7\x25\x79\xc6\xae\x79\xc3\x59\xea\x30\x9f\xfd\x95\xab\xff\xae\x4c\x61\x88\x4c\x03\xc9\xe9\x9d\xf7\x7b\x6c\x92\xe4\x92\xca\xcb", + "8f34812d57a16ef8a51ad987660c5f8623e0fa9d89846e28d46d14d9" + }, { + 40, + "\x79\xbc\x8f\xb6\x0f\x85\xd1\x5a\x23\x86\x56\x6e\x3e\x73\x14\xdf\x28\x45\x33\x08\x5a\xdd\x1c\x7b\xb6\xea\xd3\xff\x76\x0c\x86\xd5\x63\x3a\x66\x40\x47\x61\xb5\x44", + "65c54014cfa30f0bc27d1c6efa96ae8481f4c2505bff272956eab0df" + }, { + 41, + "\xdb\x31\x21\xea\x71\x29\x49\x83\xb1\x85\x20\x7a\x9d\x8d\xe3\xe4\x84\xa6\x6c\x04\x31\xbf\x07\xc9\x62\xeb\x82\x97\x7c\x4f\x83\x4b\x7c\x3f\x1e\x79\x31\xa4\xa7\xf7\xa9", + "9316d2f021c2913d63a7e66924c87c161c3cfde0ea7ba07f54772862" + }, { + 42, + "\x0d\xd5\x1a\xa6\x60\xc5\xcb\x4b\x7f\x78\xc4\x68\x52\xc1\xdb\x87\x07\xab\x45\x1c\x13\x67\xb6\x18\x73\x88\xc8\xbb\x38\x73\xa1\xaa\x42\x10\xd0\x41\x4c\xc6\x79\x2a\x29\xa7", + "31989e7a62a5132a5070d77250d8904bb82d457dc63469d06b50185e" + }, { + 43, + "\x48\x7f\xd2\xe5\xb6\x94\xb7\x07\x1d\x37\x89\xa2\x58\xa5\x1e\x86\x04\xdc\x0d\x3e\x8f\x5d\x62\xf3\x91\x31\x96\x8e\x60\x2a\xbe\x1d\xdf\x6b\x02\x78\x96\x2a\x51\x24\x08\xb5\x53", + "e798683438284626d710877d9eea3a0e02f349fc43acb7f9f8f9e81c" + }, { + 44, + "\x11\x18\x3b\xde\xbf\xef\x58\xe4\xda\x5b\x1c\xb7\x3b\xe0\xd3\x0b\x20\xda\x30\x4d\x86\x59\xd9\x21\xda\x2e\x27\x0f\xd1\x46\x26\x79\x95\x37\xe4\xd1\x21\x19\xe8\x09\xee\x97\x00\x4a", + "96870657d6cb668be3995aa8bd31df77840d1d1915d72482e83b6b2c" + }, { + 45, + "\xa2\x39\xde\x5c\x8e\x26\x44\xe8\xf0\x30\xd9\x4d\x98\xf1\xa3\x06\x64\xe6\xfd\x96\x1d\xc2\x97\x7a\x9c\x08\xbe\x5c\x31\xd8\xde\x89\x45\x09\x45\xa5\x3d\x79\x29\x9e\xa2\xa1\xed\xde\x7f", + "e99743d4fd26c8800c36a67b6762247c29da6b62794123c59de06dc0" + }, { + 46, + "\x91\x7c\x45\x77\xaa\x6b\x0f\x9d\xf4\x99\x99\xfc\x1c\x95\x8c\xb0\x9b\x7f\xd5\xfc\x80\xbe\x94\x96\x70\xf0\x35\x45\xeb\x27\xdc\xae\xd0\x52\x07\x6b\x24\xf9\x6f\x5e\x0f\x2e\x2f\x45\x27\xc0", + "7ecd693d4d9cf43929464698efa0bac33c2e1424f816edc769260978" + }, { + 47, + "\xc3\xf1\xe7\x35\xa6\x74\x1a\xa4\x81\xad\x57\x7a\x98\xdb\xac\x1f\x03\xcc\x80\xea\x0d\xae\x1b\x94\xdb\x23\x69\xed\x4e\x93\xfa\xcd\x29\xc6\x4e\x4e\x77\xb2\x50\x38\x27\x91\x20\xbd\xfa\x37\x15", + "86f0d89d8e14fd8b6606412d71a7a54a347b304ea5d49c208f2266ab" + }, { + 48, + "\xde\x4f\xbf\xd5\x53\xcd\xf3\x70\x19\xf2\x5a\xfa\x82\xdc\x6b\x99\x70\xf4\xbb\x1e\xbb\xc3\x7f\x80\xd3\x08\x4c\x88\xa7\x07\x22\xcd\xc5\x23\xa9\xe3\xc2\xaf\xba\xd0\xdc\x02\x21\xbf\xde\xc9\xa2\xf9", + "4c5262acb4a2a44eaa9bc6757024fb202ef4d5a7a16fa37252a422b5" + }, { + 49, + "\xdb\x2e\x2e\xb6\x36\x61\x0c\xf4\x2e\x9b\x33\x43\x3a\xcc\xe1\xb3\xb9\x25\x94\x9f\x29\x7d\xd8\x31\x99\xf4\x5d\x28\x61\xd6\x4c\xd9\x10\xc2\xdb\x74\xa6\x0b\x20\x89\x04\x5e\x22\xcb\xa0\xa5\x36\x13\x7d", + "16bf4e45bcdc60447c68dcb30e6b08f55ce9f4124a29cf1f9a9d065d" + }, { + 50, + "\xa8\xe7\x29\xd3\x36\xd5\xd6\xac\x50\xe1\xe2\x2f\x0b\x19\x3b\x66\xe2\x60\x42\xfc\x64\x59\x21\x41\x29\x87\x5e\x74\x0a\xb2\xb1\x42\x91\x8c\x13\x8a\xaf\x94\x18\x63\xad\x3b\x7e\x60\x65\x45\x06\x13\xb2\x73", + "452bf2e5ebfc4e451cc434bc09e2a10032eed0b7627cf55e7e5ed0e2" + }, { + 51, + "\xd0\x53\x17\xd4\xb5\x35\xf9\xd1\x0f\x73\x9d\x0c\x2d\xed\xf3\xff\xb0\x90\xc1\xad\x9d\x20\x50\x89\xb1\x34\x66\x93\xf5\x82\x73\xc4\x92\x5c\x0f\xac\xe5\x7b\xa4\x5a\xd6\xfc\x68\x7c\x66\xa8\x8f\xc7\x88\x78\xbe", + "4f03c439e097b51b00e314f675937c4d911505859fb7ab16adc65e44" + }, { + 52, + "\x26\xbb\x4e\xd4\xf0\x42\x4c\x60\xfe\x42\x12\xff\x8c\x95\x5e\x89\xe2\xf5\x53\xa7\xd7\x70\x1b\xe5\x94\x16\xd2\x08\x9a\xf5\x9f\xa1\x07\x47\x24\xe2\x14\xe9\x19\xb1\xe3\x0f\x33\xfb\x78\x37\x4b\x4b\x05\x5b\xbc\x9b", + "e7c899e27009d4dc77c2d300f191b757e52c9e7eac4b023bfab2b52a" + }, { + 53, + "\xf0\x15\xec\x83\x94\x4f\x03\x29\x24\x63\xc4\x34\x5f\xdb\x1c\x26\xd1\xea\x07\x64\x5f\xac\xbc\x95\x20\xae\x24\x4b\x6e\xb1\x91\xe5\x3d\xab\xad\xb4\xac\x0f\xb1\x5c\xda\x4e\xd7\x7d\xfb\x9e\x11\x93\xab\xfa\xfb\x1b\x81", + "459e40b3fbd612912f0217c60099379ce077cd02505871b0c9c14e7a" + }, { + 54, + "\x07\x86\x70\x6f\x68\x0c\x27\xb7\x92\xd0\x54\xfa\xa6\x3f\x49\x9a\x8e\x6b\x5d\xdb\x90\x50\x29\x46\x23\x5b\xf7\x4c\x02\x2d\x77\x2c\x80\x9c\xb4\x17\x1b\xfa\x47\x91\x53\x9a\xca\x1a\xbd\x91\x90\x0e\x53\xba\x93\xca\x0e\xfd", + "fadebab7c3d0fb8e97e429b79083087735e4ab385a789521260ef3ad" + }, { + 55, + "\x44\x5e\x86\x98\xee\xb8\xac\xcb\xaa\xc4\xff\xa7\xd9\x34\xff\xfd\x16\x01\x4a\x43\x0e\xf7\x0f\x3a\x91\x74\xc6\xcf\xe9\x6d\x1e\x3f\x6a\xb1\x37\x7f\x4a\x72\x12\xdb\xb3\x01\x46\xdd\x17\xd9\xf4\x70\xc4\xdf\xfc\x45\xb8\xe8\x71", + "4c7ae028c0fe61f2a9cada61fae30685b77f04c6442576e912af9fa6" + }, { + 56, + "\x52\x83\x9f\x2f\x08\x53\xa3\x0d\xf1\x4e\xc8\x97\xa1\x91\x4c\x68\x5c\x1a\xc2\x14\x70\xd0\x06\x54\xc8\xc3\x76\x63\xbf\xb6\x5f\xa7\x32\xdb\xb6\x94\xd9\xdd\x09\xce\xd7\x23\xb4\x8d\x8f\x54\x58\x46\xba\x16\x89\x88\xb6\x1c\xc7\x24", + "2f755a57674b49d5c25cb37348f35b6fd2de2552c749f2645ba63d20" + }, { + 57, + "\x5f\xe8\xc2\x07\x2d\x89\x00\x28\x7c\xca\xf0\x7f\x3f\x66\xb0\xc2\x2a\xcd\x3e\x0b\xb9\x1d\x95\x73\x75\x4e\x19\xe3\x73\xac\x35\x27\x1d\x8b\x43\x44\x34\x36\xac\x0c\x16\x28\x50\xef\x3d\x7f\x28\x14\x09\xad\x29\xa9\xbf\x71\x6c\x77\xd1", + "42909757f6e229f69f04cc7a863c4e70e48c7c3575057b455c959775" + }, { + 58, + "\xe8\x06\x4d\x83\xf3\xd6\x43\xaf\x87\x18\xc8\x7e\x3c\xcd\x6a\x97\x33\x68\x5e\xac\x61\xd5\x72\xa2\x2a\xb9\x43\xf2\x32\xfc\xb0\x4f\x70\x85\x8e\x89\x84\x44\x9d\xb1\x4a\x76\xbb\x7e\xaf\x24\x58\xef\xc3\xed\x2a\x32\x10\x06\x22\xc5\x2b\x7f", + "1a1d8ed54cb45c97bc970754b43eb93d9eabde4c7b07f76ad82d8ede" + }, { + 59, + "\x87\xc9\xa5\x17\xe2\x8d\x1b\xb5\x4a\xd2\x0f\xca\x76\x46\x0e\xfd\x89\x4d\x77\x86\xe6\x8e\xe8\xd7\x46\xb2\xf6\x82\x08\x68\x21\x57\xc8\xad\x06\xcc\x32\x4a\xd7\xa3\x18\x9e\x09\xc6\xc3\x9d\x4c\x76\x87\x19\xc0\xa4\x9a\x41\x66\x9f\x27\x67\xd5", + "605977cf87b9b309bbddaaa64e528ace66b04df9f72c0e7ec88be1da" + }, { + 60, + "\x59\xfd\xac\x3b\x6b\x32\x03\x92\x91\x80\x1c\x7d\x6f\x46\xed\xe8\xd2\x6d\xc5\xb7\xa1\x92\xe0\x07\x11\x67\x39\xb6\x17\x56\x9f\x25\x23\x68\x0b\x3c\x0b\x66\x31\xaf\x45\x3e\x55\x80\x5a\xa7\x60\xc6\x97\x08\x33\xac\x06\x96\x3b\xbc\x9d\xbd\x45\x5e", + "e9f0cb1dc8337e906385892f2348a8ba4412318ecad9b96e3711531f" + }, { + 61, + "\x30\x35\x0a\x4d\xf0\xb5\x8f\xf4\x9c\x0f\xa0\x9e\x42\x6f\xcd\x70\x07\xb2\x90\xc7\x60\xc8\x25\xc1\x85\x5d\x9b\x00\x23\xb8\x2c\xaa\x51\xe3\xca\xb4\xc6\x0c\xfa\x61\x49\x2b\xe5\x05\x68\xe5\xac\x0f\x6d\xb0\xfd\x46\x8e\x39\xe4\x53\x64\x03\xe3\x80\x9f", + "776cc6636c02408fbf65ace73ae80017108b917c16c5a912fd860241" + }, { + 62, + "\xef\x79\x7a\x0d\x43\xc3\x0b\x4f\xe1\x01\x4b\xdb\x94\x20\x87\x9c\x2f\xf8\x45\xd2\x7e\x73\xd5\x5a\x7d\xf2\x29\x30\xc8\xec\xe7\x32\x53\xd8\xbb\x26\x5b\x4e\xf2\xff\x9c\x69\x45\x5c\xc5\x6f\xf2\x52\x29\xb4\x12\x6b\xb7\xbb\x26\xee\x2c\x9f\xf3\x61\x87\xb1", + "f5b9ffb102affac352a4a535a00f89b06c268cf4881d712668906025" + }, { + 63, + "\x71\x69\x44\xde\x41\x71\x0c\x29\xb6\x59\xbe\x10\x48\x0b\xb2\x5a\x35\x1a\x39\xe5\x77\xee\x30\xe8\xf4\x22\xd5\x7c\xf6\x2a\xd9\x5b\xda\x39\xb6\xe7\x0c\x61\x42\x6e\x33\xfd\x84\xac\xa8\x4c\xc7\x91\x2d\x5e\xee\x45\xdc\x34\x07\x6a\x5d\x23\x23\xa1\x5c\x79\x64", + "61645ac748db567ac862796b8d06a47afebfa2e1783d5c5f3bcd81e2" + }, { + 64, + "\xa3\x31\x0b\xa0\x64\xbe\x2e\x14\xad\x32\x27\x6e\x18\xcd\x03\x10\xc9\x33\xa6\xe6\x50\xc3\xc7\x54\xd0\x24\x3c\x6c\x61\x20\x78\x65\xb4\xb6\x52\x48\xf6\x6a\x08\xed\xf6\xe0\x83\x26\x89\xa9\xdc\x3a\x2e\x5d\x20\x95\xee\xea\x50\xbd\x86\x2b\xac\x88\xc8\xbd\x31\x8d", + "b2a5586d9cbf0baa999157b4af06d88ae08d7c9faab4bc1a96829d65" + }, { + 0, + NULL, + NULL + } +}; + +/* SHA256 short messages test vectors. */ +static struct test_vector sha256_vectors[] = { + { + 0, + "", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, { + 1, + "\xd3", + "28969cdfa74a12c82f3bad960b0b000aca2ac329deea5c2328ebc6f2ba9802c1" + }, { + 2, + "\x11\xaf", + "5ca7133fa735326081558ac312c620eeca9970d1e70a4b95533d956f072d1f98" + }, { + 3, + "\xb4\x19\x0e", + "dff2e73091f6c05e528896c4c831b9448653dc2ff043528f6769437bc7b975c2" + }, { + 4, + "\x74\xba\x25\x21", + "b16aa56be3880d18cd41e68384cf1ec8c17680c45a02b1575dc1518923ae8b0e" + }, { + 5, + "\xc2\x99\x20\x96\x82", + "f0887fe961c9cd3beab957e8222494abb969b1ce4c6557976df8b0f6d20e9166" + }, { + 6, + "\xe1\xdc\x72\x4d\x56\x21", + "eca0a060b489636225b4fa64d267dabbe44273067ac679f20820bddc6b6a90ac" + }, { + 7, + "\x06\xe0\x76\xf5\xa4\x42\xd5", + "3fd877e27450e6bbd5d74bb82f9870c64c66e109418baa8e6bbcff355e287926" + }, { + 8, + "\x57\x38\xc9\x29\xc4\xf4\xcc\xb6", + "963bb88f27f512777aab6c8b1a02c70ec0ad651d428f870036e1917120fb48bf" + }, { + 9, + "\x33\x34\xc5\x80\x75\xd3\xf4\x13\x9e", + "078da3d77ed43bd3037a433fd0341855023793f9afd08b4b08ea1e5597ceef20" + }, { + 10, + "\x74\xcb\x93\x81\xd8\x9f\x5a\xa7\x33\x68", + "73d6fad1caaa75b43b21733561fd3958bdc555194a037c2addec19dc2d7a52bd" + }, { + 11, + "\x76\xed\x24\xa0\xf4\x0a\x41\x22\x1e\xbf\xcf", + "044cef802901932e46dc46b2545e6c99c0fc323a0ed99b081bda4216857f38ac" + }, { + 12, + "\x9b\xaf\x69\xcb\xa3\x17\xf4\x22\xfe\x26\xa9\xa0", + "fe56287cd657e4afc50dba7a3a54c2a6324b886becdcd1fae473b769e551a09b" + }, { + 13, + "\x68\x51\x1c\xdb\x2d\xbb\xf3\x53\x0d\x7f\xb6\x1c\xbc", + "af53430466715e99a602fc9f5945719b04dd24267e6a98471f7a7869bd3b4313" + }, { + 14, + "\xaf\x39\x7a\x8b\x8d\xd7\x3a\xb7\x02\xce\x8e\x53\xaa\x9f", + "d189498a3463b18e846b8ab1b41583b0b7efc789dad8a7fb885bbf8fb5b45c5c" + }, { + 15, + "\x29\x4a\xf4\x80\x2e\x5e\x92\x5e\xb1\xc6\xcc\x9c\x72\x4f\x09", + "dcbaf335360de853b9cddfdafb90fa75567d0d3d58af8db9d764113aef570125" + }, { + 16, + "\x0a\x27\x84\x7c\xdc\x98\xbd\x6f\x62\x22\x0b\x04\x6e\xdd\x76\x2b", + "80c25ec1600587e7f28b18b1b18e3cdc89928e39cab3bc25e4d4a4c139bcedc4" + }, { + 17, + "\x1b\x50\x3f\xb9\xa7\x3b\x16\xad\xa3\xfc\xf1\x04\x26\x23\xae\x76\x10", + "d5c30315f72ed05fe519a1bf75ab5fd0ffec5ac1acb0daf66b6b769598594509" + }, { + 18, + "\x59\xeb\x45\xbb\xbe\xb0\x54\xb0\xb9\x73\x34\xd5\x35\x80\xce\x03\xf6\x99", + "32c38c54189f2357e96bd77eb00c2b9c341ebebacc2945f97804f59a93238288" + }, { + 19, + "\x58\xe5\xa3\x25\x9c\xb0\xb6\xd1\x2c\x83\xf7\x23\x37\x9e\x35\xfd\x29\x8b\x60", + "9b5b37816de8fcdf3ec10b745428708df8f391c550ea6746b2cafe019c2b6ace" + }, { + 20, + "\xc1\xef\x39\xce\xe5\x8e\x78\xf6\xfc\xdc\x12\xe0\x58\xb7\xf9\x02\xac\xd1\xa9\x3b", + "6dd52b0d8b48cc8146cebd0216fbf5f6ef7eeafc0ff2ff9d1422d6345555a142" + }, { + 21, + "\x9c\xab\x7d\x7d\xca\xec\x98\xcb\x3a\xc6\xc6\x4d\xd5\xd4\x47\x0d\x0b\x10\x3a\x81\x0c", + "44d34809fc60d1fcafa7f37b794d1d3a765dd0d23194ebbe340f013f0c39b613" + }, { + 22, + "\xea\x15\x7c\x02\xeb\xaf\x1b\x22\xde\x22\x1b\x53\xf2\x35\x39\x36\xd2\x35\x9d\x1e\x1c\x97", + "9df5c16a3f580406f07d96149303d8c408869b32053b726cf3defd241e484957" + }, { + 23, + "\xda\x99\x9b\xc1\xf9\xc7\xac\xff\x32\x82\x8a\x73\xe6\x72\xd0\xa4\x92\xf6\xee\x89\x5c\x68\x67", + "672b54e43f41ee77584bdf8bf854d97b6252c918f7ea2d26bc4097ea53a88f10" + }, { + 24, + "\x47\x99\x13\x01\x15\x6d\x1d\x97\x7c\x03\x38\xef\xbc\xad\x41\x00\x41\x33\xae\xfb\xca\x6b\xcf\x7e", + "feeb4b2b59fec8fdb1e55194a493d8c871757b5723675e93d3ac034b380b7fc9" + }, { + 25, + "\x2e\x7e\xa8\x4d\xa4\xbc\x4d\x7c\xfb\x46\x3e\x3f\x2c\x86\x47\x05\x7a\xff\xf3\xfb\xec\xec\xa1\xd2\x00", + "76e3acbc718836f2df8ad2d0d2d76f0cfa5fea0986be918f10bcee730df441b9" + }, { + 26, + "\x47\xc7\x70\xeb\x45\x49\xb6\xef\xf6\x38\x1d\x62\xe9\xbe\xb4\x64\xcd\x98\xd3\x41\xcc\x1c\x09\x98\x1a\x7a", + "6733809c73e53666c735b3bd3daf87ebc77c72756150a616a194108d71231272" + }, { + 27, + "\xac\x4c\x26\xd8\xb4\x3b\x85\x79\xd8\xf6\x1c\x98\x07\x02\x6e\x83\xe9\xb5\x86\xe1\x15\x9b\xd4\x3b\x85\x19\x37", + "0e6e3c143c3a5f7f38505ed6adc9b48c18edf6dedf11635f6e8f9ac73c39fe9e" + }, { + 28, + "\x07\x77\xfc\x1e\x1c\xa4\x73\x04\xc2\xe2\x65\x69\x28\x38\x10\x9e\x26\xaa\xb9\xe5\xc4\xae\x4e\x86\x00\xdf\x4b\x1f", + "ffb4fc03e054f8ecbc31470fc023bedcd4a406b9dd56c71da1b660dcc4842c65" + }, { + 29, + "\x1a\x57\x25\x1c\x43\x1d\x4e\x6c\x2e\x06\xd6\x52\x46\xa2\x96\x91\x50\x71\xa5\x31\x42\x5e\xcf\x25\x59\x89\x42\x2a\x66", + "c644612cd326b38b1c6813b1daded34448805aef317c35f548dfb4a0d74b8106" + }, { + 30, + "\x9b\x24\x5f\xda\xd9\xba\xeb\x89\x0d\x9c\x0d\x0e\xff\x81\x6e\xfb\x4c\xa1\x38\x61\x0b\xc7\xd7\x8c\xb1\xa8\x01\xed\x32\x73", + "c0e29eeeb0d3a7707947e623cdc7d1899adc70dd7861205ea5e5813954fb7957" + }, { + 31, + "\x95\xa7\x65\x80\x9c\xaf\x30\xad\xa9\x0a\xd6\xd6\x1c\x2b\x4b\x30\x25\x0d\xf0\xa7\xce\x23\xb7\x75\x3c\x91\x87\xf4\x31\x9c\xe2", + "a4139b74b102cf1e2fce229a6cd84c87501f50afa4c80feacf7d8cf5ed94f042" + }, { + 32, + "\x09\xfc\x1a\xcc\xc2\x30\xa2\x05\xe4\xa2\x08\xe6\x4a\x8f\x20\x42\x91\xf5\x81\xa1\x27\x56\x39\x2d\xa4\xb8\xc0\xcf\x5e\xf0\x2b\x95", + "4f44c1c7fbebb6f9601829f3897bfd650c56fa07844be76489076356ac1886a4" + }, { + 33, + "\x05\x46\xf7\xb8\x68\x2b\x5b\x95\xfd\x32\x38\x5f\xaf\x25\x85\x4c\xb3\xf7\xb4\x0c\xc8\xfa\x22\x9f\xbd\x52\xb1\x69\x34\xaa\xb3\x88\xa7", + "b31ad3cd02b10db282b3576c059b746fb24ca6f09fef69402dc90ece7421cbb7" + }, { + 34, + "\xb1\x2d\xb4\xa1\x02\x55\x29\xb3\xb7\xb1\xe4\x5c\x6d\xbc\x7b\xaa\x88\x97\xa0\x57\x6e\x66\xf6\x4b\xf3\xf8\x23\x61\x13\xa6\x27\x6e\xe7\x7d", + "1c38bf6bbfd32292d67d1d651fd9d5b623b6ec1e854406223f51d0df46968712" + }, { + 35, + "\xe6\x8c\xb6\xd8\xc1\x86\x6c\x0a\x71\xe7\x31\x3f\x83\xdc\x11\xa5\x80\x9c\xf5\xcf\xbe\xed\x1a\x58\x7c\xe9\xc2\xc9\x2e\x02\x2a\xbc\x16\x44\xbb", + "c2684c0dbb85c232b6da4fb5147dd0624429ec7e657991edd95eda37a587269e" + }, { + 36, + "\x4e\x3d\x8a\xc3\x6d\x61\xd9\xe5\x14\x80\x83\x11\x55\xb2\x53\xb3\x79\x69\xfe\x7e\xf4\x9d\xb3\xb3\x99\x26\xf3\xa0\x0b\x69\xa3\x67\x74\x36\x60\x00", + "bf9d5e5b5393053f055b380baed7e792ae85ad37c0ada5fd4519542ccc461cf3" + }, { + 37, + "\x03\xb2\x64\xbe\x51\xe4\xb9\x41\x86\x4f\x9b\x70\xb4\xc9\x58\xf5\x35\x5a\xac\x29\x4b\x4b\x87\xcb\x03\x7f\x11\xf8\x5f\x07\xeb\x57\xb3\xf0\xb8\x95\x50", + "d1f8bd684001ac5a4b67bbf79f87de524d2da99ac014dec3e4187728f4557471" + }, { + 38, + "\xd0\xfe\xfd\x96\x78\x7c\x65\xff\xa7\xf9\x10\xd6\xd0\xad\xa6\x3d\x64\xd5\xc4\x67\x99\x60\xe7\xf0\x6a\xeb\x8c\x70\xdf\xef\x95\x4f\x8e\x39\xef\xdb\x62\x9b", + "49ba38db85c2796f85ffd57dd5ec337007414528ae33935b102d16a6b91ba6c1" + }, { + 39, + "\xb7\xc7\x9d\x7e\x5f\x1e\xec\xcd\xfe\xdf\x0e\x7b\xf4\x3e\x73\x0d\x44\x7e\x60\x7d\x8d\x14\x89\x82\x3d\x09\xe1\x12\x01\xa0\xb1\x25\x80\x39\xe7\xbd\x48\x75\xb1", + "725e6f8d888ebaf908b7692259ab8839c3248edd22ca115bb13e025808654700" + }, { + 40, + "\x64\xcd\x36\x3e\xcc\xe0\x5f\xdf\xda\x24\x86\xd0\x11\xa3\xdb\x95\xb5\x20\x6a\x19\xd3\x05\x40\x46\x81\x9d\xd0\xd3\x67\x83\x95\x5d\x7e\x5b\xf8\xba\x18\xbf\x73\x8a", + "32caef024f84e97c30b4a7b9d04b678b3d8a6eb2259dff5b7f7c011f090845f8" + }, { + 41, + "\x6a\xc6\xc6\x3d\x61\x8e\xaf\x00\xd9\x1c\x5e\x28\x07\xe8\x3c\x09\x39\x12\xb8\xe2\x02\xf7\x8e\x13\x97\x03\x49\x8a\x79\xc6\x06\x7f\x54\x49\x7c\x61\x27\xa2\x39\x10\xa6", + "4bb33e7c6916e08a9b3ed6bcef790aaaee0dcf2e7a01afb056182dea2dad7d63" + }, { + 42, + "\xd2\x68\x26\xdb\x9b\xae\xaa\x89\x26\x91\xb6\x89\x00\xb9\x61\x63\x20\x8e\x80\x6a\x1d\xa0\x77\x42\x9e\x45\x4f\xa0\x11\x84\x09\x51\xa0\x31\x32\x7e\x60\x5a\xb8\x2e\xcc\xe2", + "3ac7ac6bed82fdc8cd15b746f0ee7489158192c238f371c1883c9fe90b3e2831" + }, { + 43, + "\x3f\x7a\x05\x9b\x65\xd6\xcb\x02\x49\x20\x4a\xac\x10\xb9\xf1\xa4\xac\x9e\x58\x68\xad\xeb\xbe\x93\x5a\x9e\xb5\xb9\x01\x9e\x1c\x93\x8b\xfc\x4e\x5c\x53\x78\x99\x7a\x39\x47\xf2", + "bfce809534eefe871273964d32f091fe756c71a7f512ef5f2300bcd57f699e74" + }, { + 44, + "\x60\xff\xcb\x23\xd6\xb8\x8e\x48\x5b\x92\x0a\xf8\x1d\x10\x83\xf6\x29\x1d\x06\xac\x8c\xa3\xa9\x65\xb8\x59\x14\xbc\x2a\xdd\x40\x54\x4a\x02\x7f\xca\x93\x6b\xbd\xe8\xf3\x59\x05\x1c", + "1d26f3e04f89b4eaa9dbed9231bb051eef2e8311ad26fe53d0bf0b821eaf7567" + }, { + 45, + "\x9e\xcd\x07\xb6\x84\xbb\x9e\x0e\x66\x92\xe3\x20\xce\xc4\x51\x0c\xa7\x9f\xcd\xb3\xa2\x21\x2c\x26\xd9\x0d\xf6\x5d\xb3\x3e\x69\x2d\x07\x3c\xc1\x74\x84\x0d\xb7\x97\x50\x4e\x48\x2e\xef", + "0ffeb644a49e787ccc6970fe29705a4f4c2bfcfe7d19741c158333ff6982cc9c" + }, { + 46, + "\x9d\x64\xde\x71\x61\x89\x58\x84\xe7\xfa\x3d\x6e\x9e\xb9\x96\xe7\xeb\xe5\x11\xb0\x1f\xe1\x9c\xd4\xa6\xb3\x32\x2e\x80\xaa\xf5\x2b\xf6\x44\x7e\xd1\x85\x4e\x71\x00\x1f\x4d\x54\xf8\x93\x1d", + "d048ee1524014adf9a56e60a388277de194c694cc787fc5a1b554ea9f07abfdf" + }, { + 47, + "\xc4\xad\x3c\x5e\x78\xd9\x17\xec\xb0\xcb\xbc\xd1\xc4\x81\xfc\x2a\xaf\x23\x2f\x7e\x28\x97\x79\xf4\x0e\x50\x4c\xc3\x09\x66\x2e\xe9\x6f\xec\xbd\x20\x64\x7e\xf0\x0e\x46\x19\x9f\xbc\x48\x2f\x46", + "50dbf40066f8d270484ee2ef6632282dfa300a85a8530eceeb0e04275e1c1efd" + }, { + 48, + "\x4e\xef\x51\x07\x45\x9b\xdd\xf8\xf2\x4f\xc7\x65\x6f\xd4\x89\x6d\xa8\x71\x1d\xb5\x04\x00\xc0\x16\x48\x47\xf6\x92\xb8\x86\xce\x8d\x7f\x4d\x67\x39\x50\x90\xb3\x53\x4e\xfd\x7b\x0d\x29\x8d\xa3\x4b", + "7c5d14ed83dab875ac25ce7feed6ef837d58e79dc601fb3c1fca48d4464e8b83" + }, { + 49, + "\x04\x7d\x27\x58\xe7\xc2\xc9\x62\x3f\x9b\xdb\x93\xb6\x59\x7c\x5e\x84\xa0\xcd\x34\xe6\x10\x01\x4b\xcb\x25\xb4\x9e\xd0\x5c\x7e\x35\x6e\x98\xc7\xa6\x72\xc3\xdd\xdc\xae\xb8\x43\x17\xef\x61\x4d\x34\x2f", + "7d53eccd03da37bf58c1962a8f0f708a5c5c447f6a7e9e26137c169d5bdd82e4" + }, { + 50, + "\x3d\x83\xdf\x37\x17\x2c\x81\xaf\xd0\xde\x11\x51\x39\xfb\xf4\x39\x0c\x22\xe0\x98\xc5\xaf\x4c\x5a\xb4\x85\x24\x06\x51\x0b\xc0\xe6\xcf\x74\x17\x69\xf4\x44\x30\xc5\x27\x0f\xda\xe0\xcb\x84\x9d\x71\xcb\xab", + "99dc772e91ea02d9e421d552d61901016b9fd4ad2df4a8212c1ec5ba13893ab2" + }, { + 51, + "\x33\xfd\x9b\xc1\x7e\x2b\x27\x1f\xa0\x4c\x6b\x93\xc0\xbd\xea\xe9\x86\x54\xa7\x68\x2d\x31\xd9\xb4\xda\xb7\xe6\xf3\x2c\xd5\x8f\x2f\x14\x8a\x68\xfb\xe7\xa8\x8c\x5a\xb1\xd8\x8e\xdc\xcd\xde\xb3\x0a\xb2\x1e\x5e", + "cefdae1a3d75e792e8698d5e71f177cc761314e9ad5df9602c6e60ae65c4c267" + }, { + 52, + "\x77\xa8\x79\xcf\xa1\x1d\x7f\xca\xc7\xa8\x28\x2c\xc3\x8a\x43\xdc\xf3\x76\x43\xcc\x90\x98\x37\x21\x3b\xd6\xfd\x95\xd9\x56\xb2\x19\xa1\x40\x6c\xbe\x73\xc5\x2c\xd5\x6c\x60\x0e\x55\xb7\x5b\xc3\x7e\xa6\x96\x41\xbc", + "c99d64fa4dadd4bc8a389531c68b4590c6df0b9099c4d583bc00889fb7b98008" + }, { + 53, + "\x45\xa3\xe6\xb8\x65\x27\xf2\x0b\x45\x37\xf5\xaf\x96\xcf\xc5\xad\x87\x77\xa2\xdd\xe6\xcf\x75\x11\x88\x6c\x55\x90\xec\xe2\x4f\xc6\x1b\x22\x67\x39\xd2\x07\xda\xbf\xe3\x2b\xa6\xef\xd9\xff\x4c\xd5\xdb\x1b\xd5\xea\xd3", + "4d12a849047c6acd4b2eee6be35fa9051b02d21d50d419543008c1d82c427072" + }, { + 54, + "\x25\x36\x2a\x4b\x9d\x74\xbd\xe6\x12\x8c\x4f\xdc\x67\x23\x05\x90\x09\x47\xbc\x3a\xda\x9d\x9d\x31\x6e\xbc\xf1\x66\x7a\xd4\x36\x31\x89\x93\x72\x51\xf1\x49\xc7\x2e\x06\x4a\x48\x60\x8d\x94\x0b\x75\x74\xb1\x7f\xef\xc0\xdf", + "f8e4ccab6c979229f6066cc0cb0cfa81bb21447c16c68773be7e558e9f9d798d" + }, { + 55, + "\x3e\xbf\xb0\x6d\xb8\xc3\x8d\x5b\xa0\x37\xf1\x36\x3e\x11\x85\x50\xaa\xd9\x46\x06\xe2\x68\x35\xa0\x1a\xf0\x50\x78\x53\x3c\xc2\x5f\x2f\x39\x57\x3c\x04\xb6\x32\xf6\x2f\x68\xc2\x94\xab\x31\xf2\xa3\xe2\xa1\xa0\xd8\xc2\xbe\x51", + "6595a2ef537a69ba8583dfbf7f5bec0ab1f93ce4c8ee1916eff44a93af5749c4" + }, { + 56, + "\x2d\x52\x44\x7d\x12\x44\xd2\xeb\xc2\x86\x50\xe7\xb0\x56\x54\xba\xd3\x5b\x3a\x68\xee\xdc\x7f\x85\x15\x30\x6b\x49\x6d\x75\xf3\xe7\x33\x85\xdd\x1b\x00\x26\x25\x02\x4b\x81\xa0\x2f\x2f\xd6\xdf\xfb\x6e\x6d\x56\x1c\xb7\xd0\xbd\x7a", + "cfb88d6faf2de3a69d36195acec2e255e2af2b7d933997f348e09f6ce5758360" + }, { + 57, + "\x4c\xac\xe4\x22\xe4\xa0\x15\xa7\x54\x92\xb3\xb3\xbb\xfb\xdf\x37\x58\xea\xff\x4f\xe5\x04\xb4\x6a\x26\xc9\x0d\xac\xc1\x19\xfa\x90\x50\xf6\x03\xd2\xb5\x8b\x39\x8c\xad\x6d\x6d\x9f\xa9\x22\xa1\x54\xd9\xe0\xbc\x43\x89\x96\x82\x74\xb0", + "4d54b2d284a6794581224e08f675541c8feab6eefa3ac1cfe5da4e03e62f72e4" + }, { + 58, + "\x86\x20\xb8\x6f\xbc\xaa\xce\x4f\xf3\xc2\x92\x1b\x84\x66\xdd\xd7\xba\xca\xe0\x7e\xef\xef\x69\x3c\xf1\x77\x62\xdc\xab\xb8\x9a\x84\x01\x0f\xc9\xa0\xfb\x76\xce\x1c\x26\x59\x3a\xd6\x37\xa6\x12\x53\xf2\x24\xd1\xb1\x4a\x05\xad\xdc\xca\xbe", + "dba490256c9720c54c612a5bd1ef573cd51dc12b3e7bd8c6db2eabe0aacb846b" + }, { + 59, + "\xd1\xbe\x3f\x13\xfe\xba\xfe\xfc\x14\x41\x4d\x9f\xb7\xf6\x93\xdb\x16\xdc\x1a\xe2\x70\xc5\xb6\x47\xd8\x0d\xa8\x58\x35\x87\xc1\xad\x8c\xb8\xcb\x01\x82\x43\x24\x41\x1c\xa5\xac\xe3\xca\x22\xe1\x79\xa4\xff\x49\x86\xf3\xf2\x11\x90\xf3\xd7\xf3", + "02804978eba6e1de65afdbc6a6091ed6b1ecee51e8bff40646a251de6678b7ef" + }, { + 60, + "\xf4\x99\xcc\x3f\x6e\x3c\xf7\xc3\x12\xff\xdf\xba\x61\xb1\x26\x0c\x37\x12\x9c\x1a\xfb\x39\x10\x47\x19\x33\x67\xb7\xb2\xed\xeb\x57\x92\x53\xe5\x1d\x62\xba\x6d\x91\x1e\x7b\x81\x8c\xca\xe1\x55\x3f\x61\x46\xea\x78\x0f\x78\xe2\x21\x9f\x62\x93\x09", + "0b66c8b4fefebc8dc7da0bbedc1114f228aa63c37d5c30e91ab500f3eadfcec5" + }, { + 61, + "\x6d\xd6\xef\xd6\xf6\xca\xa6\x3b\x72\x9a\xa8\x18\x6e\x30\x8b\xc1\xbd\xa0\x63\x07\xc0\x5a\x2c\x0a\xe5\xa3\x68\x4e\x6e\x46\x08\x11\x74\x86\x90\xdc\x2b\x58\x77\x59\x67\xcf\xcc\x64\x5f\xd8\x20\x64\xb1\x27\x9f\xdc\xa7\x71\x80\x3d\xb9\xdc\xa0\xff\x53", + "c464a7bf6d180de4f744bb2fe5dc27a3f681334ffd54a9814650e60260a478e3" + }, { + 62, + "\x65\x11\xa2\x24\x2d\xdb\x27\x31\x78\xe1\x9a\x82\xc5\x7c\x85\xcb\x05\xa6\x88\x7f\xf2\x01\x4c\xf1\xa3\x1c\xb9\xba\x5d\xf1\x69\x5a\xad\xb2\x5c\x22\xb3\xc5\xed\x51\xc1\x0d\x04\x7d\x25\x6b\x8e\x34\x42\x84\x2a\xe4\xe6\xc5\x25\xf8\xd7\xa5\xa9\x44\xaf\x2a", + "d6859c0b5a0b66376a24f56b2ab104286ed0078634ba19112ace0d6d60a9c1ae" + }, { + 63, + "\xe2\xf7\x6e\x97\x60\x6a\x87\x2e\x31\x74\x39\xf1\xa0\x3f\xcd\x92\xe6\x32\xe5\xbd\x4e\x7c\xbc\x4e\x97\xf1\xaf\xc1\x9a\x16\xfd\xe9\x2d\x77\xcb\xe5\x46\x41\x6b\x51\x64\x0c\xdd\xb9\x2a\xf9\x96\x53\x4d\xfd\x81\xed\xb1\x7c\x44\x24\xcf\x1a\xc4\xd7\x5a\xce\xeb", + "18041bd4665083001fba8c5411d2d748e8abbfdcdfd9218cb02b68a78e7d4c23" + }, { + 64, + "\x5a\x86\xb7\x37\xea\xea\x8e\xe9\x76\xa0\xa2\x4d\xa6\x3e\x7e\xd7\xee\xfa\xd1\x8a\x10\x1c\x12\x11\xe2\xb3\x65\x0c\x51\x87\xc2\xa8\xa6\x50\x54\x72\x08\x25\x1f\x6d\x42\x37\xe6\x61\xc7\xbf\x4c\x77\xf3\x35\x39\x03\x94\xc3\x7f\xa1\xa9\xf9\xbe\x83\x6a\xc2\x85\x09", + "42e61e174fbb3897d6dd6cef3dd2802fe67b331953b06114a65c772859dfc1aa" + }, { + 0, + NULL, + NULL + } +}; + +/* SHA512 short messages test vectors. */ +static struct test_vector sha512_vectors[] = { + { + 0, + "", + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" + }, { + 1, + "\x21", + "3831a6a6155e509dee59a7f451eb35324d8f8f2df6e3708894740f98fdee23889f4de5adb0c5010dfb555cda77c8ab5dc902094c52de3278f35a75ebc25f093a" + }, { + 2, + "\x90\x83", + "55586ebba48768aeb323655ab6f4298fc9f670964fc2e5f2731e34dfa4b0c09e6e1e12e3d7286b3145c61c2047fb1a2a1297f36da64160b31fa4c8c2cddd2fb4" + }, { + 3, + "\x0a\x55\xdb", + "7952585e5330cb247d72bae696fc8a6b0f7d0804577e347d99bc1b11e52f384985a428449382306a89261ae143c2f3fb613804ab20b42dc097e5bf4a96ef919b" + }, { + 4, + "\x23\xbe\x86\xd5", + "76d42c8eadea35a69990c63a762f330614a4699977f058adb988f406fb0be8f2ea3dce3a2bbd1d827b70b9b299ae6f9e5058ee97b50bd4922d6d37ddc761f8eb" + }, { + 5, + "\xeb\x0c\xa9\x46\xc1", + "d39ecedfe6e705a821aee4f58bfc489c3d9433eb4ac1b03a97e321a2586b40dd0522f40fa5aef36afff591a78c916bfc6d1ca515c4983dd8695b1ec7951d723e" + }, { + 6, + "\x38\x66\x7f\x39\x27\x7b", + "85708b8ff05d974d6af0801c152b95f5fa5c06af9a35230c5bea2752f031f9bd84bd844717b3add308a70dc777f90813c20b47b16385664eefc88449f04f2131" + }, { + 7, + "\xb3\x9f\x71\xaa\xa8\xa1\x08", + "258b8efa05b4a06b1e63c7a3f925c5ef11fa03e3d47d631bf4d474983783d8c0b09449009e842fc9fa15de586c67cf8955a17d790b20f41dadf67ee8cdcdfce6" + }, { + 8, + "\x6f\x8d\x58\xb7\xca\xb1\x88\x8c", + "a3941def2803c8dfc08f20c06ba7e9a332ae0c67e47ae57365c243ef40059b11be22c91da6a80c2cff0742a8f4bcd941bdee0b861ec872b215433ce8dcf3c031" + }, { + 9, + "\x16\x2b\x0c\xf9\xb3\x75\x0f\x94\x38", + "ade217305dc34392aa4b8e57f64f5a3afdd27f1fa969a9a2608353f82b95cfb4ae84598d01575a578a1068a59b34b5045ff6d5299c5cb7ee17180701b2d1d695" + }, { + 10, + "\xba\xd7\xc6\x18\xf4\x5b\xe2\x07\x97\x5e", + "5886828959d1f82254068be0bd14b6a88f59f534061fb20376a0541052dd3635edf3c6f0ca3d08775e13525df9333a2113c0b2af76515887529910b6c793c8a5" + }, { + 11, + "\x62\x13\xe1\x0a\x44\x20\xe0\xd9\xb7\x70\x37", + "9982dc2a04dff165567f276fd463efef2b369fa2fbca8cee31ce0de8a79a2eb0b53e437f7d9d1f41c71d725cabb949b513075bad1740c9eefbf6a5c6633400c7" + }, { + 12, + "\x63\x32\xc3\xc2\xa0\xa6\x25\xa6\x1d\xf7\x18\x58", + "9d60375d9858d9f2416fb86fa0a2189ee4213e8710314fd1ebed0fd158b043e6e7c9a76d62c6ba1e1d411a730902309ec676dd491433c6ef66c8f116233d6ce7" + }, { + 13, + "\xf4\x7b\xe3\xa2\xb0\x19\xd1\xbe\xed\xed\xf5\xb8\x0c", + "b94292625caa28c7be24a0997eb7328062a76d9b529c0f1d568f850df6d569b5e84df07e9e246be232033ffac3adf2d18f92ab9dacfc0ecf08aff7145f0b833b" + }, { + 14, + "\xb1\x71\x5f\x78\x2f\xf0\x2c\x6b\x88\x93\x7f\x05\x41\x16", + "ee1a56ee78182ec41d2c3ab33d4c41871d437c5c1ca060ee9e219cb83689b4e5a4174dfdab5d1d1096a31a7c8d3abda75c1b5e6da97e1814901c505b0bc07f25" + }, { + 15, + "\x9b\xcd\x52\x62\x86\x8c\xd9\xc8\xa9\x6c\x9e\x82\x98\x7f\x03", + "2e07662a001b9755ae922c8e8a95756db5341dc0f2e62ae1cf827038f33ce055f63ad5c00b65391428434ddc01e5535e7fecbf53db66d93099b8e0b7e44e4b25" + }, { + 16, + "\xcd\x67\xbd\x40\x54\xaa\xa3\xba\xa0\xdb\x17\x8c\xe2\x32\xfd\x5a", + "0d8521f8f2f3900332d1a1a55c60ba81d04d28dfe8c504b6328ae787925fe0188f2ba91c3a9f0c1653c4bf0ada356455ea36fd31f8e73e3951cad4ebba8c6e04" + }, { + 17, + "\x6b\xa0\x04\xfd\x17\x67\x91\xef\xb3\x81\xb8\x62\xe2\x98\xc6\x7b\x08", + "112e19144a9c51a223a002b977459920e38afd4ca610bd1c532349e9fa7c0d503215c01ad70e1b2ac5133cf2d10c9e8c1a4c9405f291da2dc45f706761c5e8fe" + }, { + 18, + "\xc6\xa1\x70\x93\x65\x68\x65\x10\x20\xed\xfe\x15\xdf\x80\x12\xac\xda\x8d", + "c36c100cdb6c8c45b072f18256d63a66c9843acb4d07de62e0600711d4fbe64c8cf314ec3457c90308147cb7ac7e4d073ba10f0ced78ea724a474b32dae71231" + }, { + 19, + "\x61\xbe\x0c\x9f\x5c\xf6\x27\x45\xc7\xda\x47\xc1\x04\x59\x71\x94\xdb\x24\x5c", + "b379249a3ca5f14c29456710114ba6f6136b34c3fc9f6fb91b59d491af782d6b237eb71aaffdd38079461cf690a46d9a4ddd602d19808ab6235d1d8aa01e8200" + }, { + 20, + "\xe0\x70\x56\xd4\xf7\x27\x7b\xc5\x48\x09\x95\x77\x72\x0a\x58\x1e\xec\x94\x14\x1d", + "59f1856303ff165e2ab5683dddeb6e8ad81f15bb578579b999eb5746680f22cfec6dba741e591ca4d9e53904837701b374be74bbc0847a92179ac2b67496d807" + }, { + 21, + "\x67\xeb\xda\x0a\x35\x73\xa9\xa5\x87\x51\xd4\x16\x9e\x10\xc7\xe8\x66\x3f\xeb\xb3\xa8", + "13963f81cfabfca71de4739fd24a10ce3897bba1d716907fc0a28490c192a7fc3ccb8db1f91af7a2d250d6617f0dfd1519d221d618a02e3e3fa9041cf35ed1ea" + }, { + 22, + "\x63\xe0\x9d\xb9\x9e\xb4\xcd\x62\x38\x67\x78\x59\xa5\x67\xdf\x31\x3c\x85\x20\xd8\x45\xb4", + "9083e5348b08eb9810b2d15781d8265845410de54fe61750d4b93853690649adc6e72490bc2b7c365e2390573d9414becc0939719e0cb78eca6b2c80c2fda920" + }, { + 23, + "\xf3\xe0\x6b\x4b\xd7\x9e\x38\x0a\x65\xcb\x67\x9a\x98\xcc\xd7\x32\x56\x3c\xc5\xeb\xe8\x92\xe2", + "6b315f106b07c59eedc5ab1df813b3c0b903060e7217cc010e9070278512a885008dac8b2472a521e77835a7f4deadc1d591aa23b624b69948a99bb60121c54e" + }, { + 24, + "\x16\xb1\x70\x74\xd3\xe3\xd9\x75\x57\xf9\xed\x77\xd9\x20\xb4\xb1\xbf\xf4\xe8\x45\xb3\x45\xa9\x22", + "6884134582a760046433abcbd53db8ff1a89995862f305b887020f6da6c7b903a314721e972bf438483f452a8b09596298a576c903c91df4a414c7bd20fd1d07" + }, { + 25, + "\x3e\xdf\x93\x25\x13\x49\xd2\x28\x06\xbe\xd2\x53\x45\xfd\x5c\x19\x0a\xac\x96\xd6\xcd\xb2\xd7\x58\xb8", + "299e0daf6605e5b0c30e1ec8bb98e7a3bd7b33b388bdb457452dab509594406c8e7b841e6f4e75c8d6fbd614d5eb9e56c359bfafb4285754787ab72b46dd33f0" + }, { + 26, + "\xb2\xd5\xa1\x4f\x01\xe6\xb7\x78\x88\x8c\x56\x2a\x05\x9e\xc8\x19\xad\x89\x99\x2d\x16\xa0\x9f\x7a\x54\xb4", + "ab2e7d745d8ad393439af2a3fbc9cdc25510d4a04e78b526e12b1c0be3b22966872ebe652e2f46ed5c5acecd2f233a9175dd295ebeb3a0706fc66fa1b137042b" + }, { + 27, + "\x84\x4b\x66\xf1\x2b\xa0\xc5\xf9\xe9\x27\x31\xf5\x71\x53\x9d\x1e\xef\x33\x2e\x15\x49\xa4\x9d\xbf\xa4\xc6\xde", + "c3f9c5781925774783ae9d839772d7513dfcea8c5af8da262c196f9fe80135b2b0c8c6ca0a1604e0a3460247620de20b299f2db7871982d27c2176ae5fa7ad65" + }, { + 28, + "\x6b\x6c\xc6\x92\xd3\x98\x60\xb1\xf3\x02\x03\x65\x3e\x25\xd0\x9c\x01\xe6\xa8\x04\x3c\x1a\x9c\xb8\xb2\x49\xa4\x1e", + "2e5263d9a4f21b210e0e161ed39df44102864325788647261a6e70ea4b1ee0abb57b57499bc82158d82336dd53f1ef4464c6a08126e138b2cc0892f765f6af85" + }, { + 29, + "\xab\x1f\xc9\xee\x84\x5e\xeb\x20\x5e\xc1\x37\x25\xda\xf1\xfb\x1f\x5d\x50\x62\x9b\x14\xea\x9a\x22\x35\xa9\x35\x0a\x88", + "72d188a9df5f3b00057bca22c92c0f8228422d974302d22d4b322e7a6c8fc3b2b50ec74c6842781f29f7075c3d4bd065878648846c39bb3e4e2692c0f053f7ed" + }, { + 30, + "\x59\x4e\xd8\x2a\xcf\xc0\x3c\x0e\x35\x9c\xc5\x60\xb8\xe4\xb8\x5f\x6e\xe7\x7e\xe5\x9a\x70\x02\x3c\x2b\x3d\x5b\x32\x85\xb2", + "5ef322cb4014ecbb713a13659612a222225984d31c187debc4459ba7901f03dac775400acfe3510b306b79894fb0e8437b412150c9193ee5a2164306ebb78301" + }, { + 31, + "\xf2\xc6\x6e\xfb\xf2\xa7\x6c\x5b\x04\x18\x60\xea\x57\x61\x03\xcd\x8c\x6b\x25\xe5\x0e\xca\x9f\xf6\xa2\xfa\x88\x08\x3f\xe9\xac", + "7978f93ef7ed02c4a24abecba124d14dd214e1492ff1e168304c0eab89637da0f7a569c43dc4562bdb9404a018b6314fe0eebaccfb25ba76506aa7e9dcd956a7" + }, { + 32, + "\x8c\xcb\x08\xd2\xa1\xa2\x82\xaa\x8c\xc9\x99\x02\xec\xaf\x0f\x67\xa9\xf2\x1c\xff\xe2\x80\x05\xcb\x27\xfc\xf1\x29\xe9\x63\xf9\x9d", + "4551def2f9127386eea8d4dae1ea8d8e49b2add0509f27ccbce7d9e950ac7db01d5bca579c271b9f2d806730d88f58252fd0c2587851c3ac8a0e72b4e1dc0da6" + }, { + 33, + "\x9f\x8c\x49\x32\x0a\xf9\x37\x0c\xd3\xdb\x20\xe9\xb5\x0d\x3e\xaa\x59\xa6\x23\x2d\x7a\x86\xfb\x7d\x47\x2f\x12\x45\x08\xd7\x96\x8b\x05", + "81b002f15c4d48be8517f7ed89df302fb1435c9435efefed58f3eb8ea11910623f1eb9028a66e02121a7f08a7c604226f2324f483e91548dbbd2c441ab704ce5" + }, { + 34, + "\x4a\xb9\xaa\x06\x94\x75\xe5\x4b\x25\xe5\x68\x8a\x52\xdd\x4a\xcd\x13\x41\x69\xc8\x58\x10\x5f\x01\xa0\xa1\xb1\x34\xc7\x2d\x4a\xf5\x1f\x8e", + "48ba5a63aba7e7bd8e420475331125a947928c67fdb00f65c4080d9a0b99c0672424e76a1ba6bd76dfe492c730f6f9adccaee7bb11571aadb31f6bb628cfa933" + }, { + 35, + "\xf0\xc1\xd3\x40\x7d\xe9\x2e\xf7\x42\x1e\x42\xdf\x5c\x9a\xb3\x1d\x2e\xc0\xa7\x50\xa9\x52\x28\x69\xcb\xe4\xca\xbd\x66\x90\x8d\x58\x23\xec\x04", + "9e75c5bca2c2af1d7739787f46e1d981c4f98e493d0724b5252c2fbae3c526719f1d27e6ccd0d705240281e8fbf3db75b9b3205c1413436d3b5d140004b8cca1" + }, { + 36, + "\xae\x8c\x9f\x8f\xb4\x1b\x51\x9b\x6d\x94\x38\x33\xfe\x1c\x32\xd1\xc4\x29\x2f\xb1\xdd\xf1\xdb\xe2\xeb\x22\x7d\x9e\x14\xd3\x1e\xd7\x4e\xba\xef\x12", + "042f9fd0a4ed3d9fec3655ae11011c6f2bc7e457e8812b6d8be2cd45fc6c432a94558c88f22c01439618865e8e49e509c448b342ca914b120344aaf7bcbdca18" + }, { + 37, + "\xda\x39\xfb\x86\x23\x7f\x00\x30\x38\x44\xe6\x1f\xc6\xcf\xe7\x79\xe4\x2a\xf5\x33\x49\x83\x95\x90\xbc\xd2\xf0\xe4\xcb\xbc\x27\x9e\xc0\xb7\xe8\x85\xd1", + "ecb43de8c233a731b38e30c5696f8876761b7ea72efe283fd07bedf20029f47c6d2a4427823e100fb087abaf22d7eff42a951c97c3dd05f48a20163fa4367cba" + }, { + 38, + "\x3e\x72\x71\xd2\x07\x0e\xf0\x95\x39\x46\x20\xc4\xb0\x16\x57\x6c\x15\x0f\x34\xbe\xa6\x07\x84\x61\x3a\x0f\x66\x0d\x7f\xa5\xae\x56\x87\x2b\x88\xc5\x83\x98", + "8154d0da634ab2266061acc123acb407650ffe9164a22de3fe29bf05393b2aece92cf4db00ea5b4341c31ddb7de151683c8a71b5a44d5c3175790feac67d18ee" + }, { + 39, + "\x31\x1f\xb6\x7f\x6a\x07\x84\xbb\x01\xa2\xd5\xa3\xf3\x09\x2c\x40\x7a\x9d\x33\x22\x31\x9d\xff\x9a\x79\xf8\x94\x29\x1c\x5f\xac\x37\x31\x9f\xb4\x08\x40\x2e\x18", + "1870fe913abb0a4b4f53b6581ae18322cd05328514556607f3f4d7b6a2ac8e9185d94d947d8b9c88e0efa66d89b59f7439c75fdadd1816f7412306ab2b59d664" + }, { + 40, + "\x76\x51\xab\x49\x1b\x8f\xa8\x6f\x96\x9d\x42\x97\x7d\x09\xdf\x5f\x8b\xee\x3e\x58\x99\x18\x0b\x52\xc9\x68\xb0\xdb\x05\x7a\x6f\x02\xa8\x86\xad\x61\x7a\x84\x91\x5a", + "f35e50e2e02b8781345f8ceb2198f068ba103476f715cfb487a452882c9f0de0c720b2a088a39d06a8a6b64ce4d6470dfeadc4f65ae06672c057e29f14c4daf9" + }, { + 41, + "\xdb\xe5\xdb\x68\x5e\xd7\xcb\x84\x8c\x09\x45\x24\xc1\x72\x35\x19\xd4\x9d\xc6\x6e\xf9\xfe\x6d\x57\xe6\x86\x2a\x64\x35\x75\x0b\xfa\x0a\x70\xf1\x04\xf5\xd3\x96\xe6\x1a", + "2fa6e5b2c443a68050f093e7fb713bd6b18f6274c061ed61d79bf0688a61dba1940bcc30998276860943ab038902896d0fbf59b88b07c80de927037097150c40" + }, { + 42, + "\x9f\xa8\x3e\x96\xb2\xa6\xdf\x23\xfb\x37\x28\x95\x01\x56\x78\xe0\xb2\xc9\xcd\x18\xa8\x54\x2c\x3e\xaa\x2c\x43\x5a\x76\xae\x4d\xc9\xbd\x51\x36\xd9\x70\xda\xff\x93\x3a\xcf", + "3a2c0ec88a3e5347cf0ea9c078838300ef7356f9a6c342063277c106b880a00ed2be205c13064097bd372fde38007bc306561eb4e74bba2bb20bd354aa690ca6" + }, { + 43, + "\x8a\x5a\x45\xe3\x98\xba\xc1\xd9\xb8\x96\xb5\xa2\xb4\xe3\x56\x6b\x91\xd8\x0a\xd2\x0c\x97\x7e\xa7\x45\x0f\xf2\xef\xb5\x21\xd8\x2f\x65\x01\x9e\xe7\x62\xe0\xc8\x5c\x6c\xc8\x41", + "3c704620f4066d79c1ff67752980f39ef3d9c1023fa5a213a5265376b14a15166ffe069b51df7710d8907fef9406bf375d502ce086ac82aff17229aaa7a5a334" + }, { + 44, + "\x49\xcf\xff\xda\xf4\xd0\x31\xe3\x3b\x1d\x28\xa4\x47\x45\x05\x45\xf6\xc4\x29\x3b\x38\xd5\xaf\xbc\xb9\x88\x39\x76\xc0\x14\xf0\x80\x57\x6e\xc6\x91\xac\x1b\xff\x70\xb7\x42\xef\xab", + "8bcc4f1ea2b7862ef1591bfa73916665de8faf65439ddf5cc1be43cebfd5f60f205e835a2b186b675b041258c5cff42669316ce25b46a2f4d4218e102f0f5d6f" + }, { + 45, + "\x2f\xf8\x45\xd8\x5e\xfb\xc4\xfa\x56\x37\xe9\x44\x8d\x95\x04\x96\xf1\x9d\x8d\x57\xda\x99\xb7\xbd\x3d\xf7\x47\x48\x22\xf0\xa7\x90\x58\x67\x36\x41\x67\x14\xe3\x64\xc6\xe1\xfa\xe0\x4e", + "236f6f4ed6e858c02d51787e60c578f731f694f8e52b5df4ecd5b04dff14c78e56bad1028d6f626c29d85aeee151a2a2846d3eed5cfafa9854a69fea8af6d04a" + }, { + 46, + "\xcf\xca\x05\xfd\x89\x3c\x0f\x00\x5f\x5f\xf7\x96\xf4\xda\x19\xba\x27\xa1\xe7\x29\x95\x6b\x8b\x71\x5e\x67\xce\x4b\x2d\x2a\x38\x2a\x72\xec\x78\x14\xf2\xf5\x07\xb1\x82\x52\x09\xa2\x0f\xcc", + "d80969284a4565add4dad6ab9b3bdf53446142f84aaf92d4b23dd22ee7241e6c81489ac8b246edcb6df9bd7b23d91a0c517f546feba4ed5790a2be6e165c1709" + }, { + 47, + "\xcf\xc4\x25\x75\x9a\x9c\x36\xbb\x9f\x4b\x32\xee\xd7\x76\x7a\xf6\x56\x6f\x68\xde\xd0\xad\xea\xe2\x5c\x7a\x70\xca\x78\xec\x09\x77\x4d\x16\xc8\xbc\x35\x7f\x6d\x6f\x7b\xd4\x41\xbf\x62\xd9\x42", + "b587a785cdf455cc9c544e756c1e306300aa3c59f8725012e68ab4d54020b6d227a164d9f83c905e86f8cebeef708a69f976d6e7b18b9bf78e9b98cc4a5cd1b6" + }, { + 48, + "\x09\x7c\x9d\xb9\x19\x51\x52\x42\xc9\x9d\x97\x3a\xcb\x1d\xc4\xed\x48\x27\x68\xf9\x74\xeb\x83\xb4\x65\xf9\xf6\xc8\x25\x03\x37\x20\x06\xe4\x49\x08\x35\xe2\xec\x8f\x92\x30\x11\x30\xbf\xb7\x90\xb2", + "ff5a376f938e73014caef7fe3962944a7230d020b7087869ebe7ec70302721cd06fcdc981c893a425d05e2f99fe198e4db50a088aee2bf1263212110efed422c" + }, { + 49, + "\x77\xe7\x3d\x38\x7e\x7b\xc8\x04\x19\xeb\xf5\x48\x2b\x61\xd5\x25\x5c\xaf\x81\x9f\xb5\x92\x51\xff\x6a\x38\x4e\x75\xf6\x01\xea\x02\x6d\x83\xef\x95\x0e\xd0\xb6\x75\x18\xfb\x99\xde\xe0\xd8\xaa\xef\x1f", + "c4c89cd882ec945cc888fb9a0127d35e585ecc14a75e4b5b3d8330538d22da28cf6af1ebec96dc247f109cd2aaab9756e6946a3d80db8363a4da3e6ddbb510a1" + }, { + 50, + "\x31\x7e\x5d\x9a\xc7\x3e\xd0\x63\x3f\xa1\x8e\xbe\xbb\xca\x79\x09\xec\x3a\x5e\xf7\x90\x47\x8f\x9c\x38\xca\xce\xc4\x4f\x19\x6d\x89\x58\x35\xb4\x25\x77\x44\x83\x04\x33\x41\x38\x1e\x7a\xf2\xd3\x83\xe5\x1a", + "b10bb04491b9c0c334709b407cda1d503efb6b63ee944f2d366b6855e6e63e5b80115be4be7ff63edecdfb5923792e68123976d79212b3884dec2179d1fcf382" + }, { + 51, + "\x20\x94\x61\xf2\x06\x66\xa3\x46\xfe\xdf\x4a\x53\x0f\x41\xa6\xfa\x28\x0c\x43\x66\x57\x67\xbe\x92\x3b\xc1\xd8\x0b\xbc\xb8\xc9\xf8\xf9\x3a\xd7\x57\x82\xea\x26\x89\xc8\xc5\xd2\x11\xd2\x05\x3b\x99\x31\x45\xa0", + "67b7a328d9444056a52ca2f695c5d3f3baafb625a14fb32eee8ff26a40ccb296bec1771a826b55f7ddb6170d4caf7795b612448e66a0f19356fe505927149b47" + }, { + 52, + "\x5d\x61\xaa\x45\xc4\x46\xf3\xbf\x93\x60\x4b\x05\x11\x31\x3b\x4e\x2f\x30\x6d\x6b\x04\x6f\xbd\x94\x79\x7b\x92\x67\x46\x83\x6f\x2e\x1d\xbd\xc5\x61\x24\x06\x0c\x6c\xa9\xc9\x11\xb1\x12\x21\x92\xd1\x12\x42\x08\x27", + "d3931bde2bde8271ed18ca0b9148b12f6f16161e637e376fc961f65bc33bcacf2f6addf26a3eaa81b196653cc37e8a739ec5b3df870d8c38c8f28691c22a39bb" + }, { + 53, + "\x92\x88\xc7\x95\xbb\x0b\x86\xc0\x41\x9d\x9c\x56\x37\xdc\xc3\x7b\x39\xbf\xa1\x8d\x44\x1e\x3f\xbf\xca\x75\xbc\x03\x06\xe5\x43\x2e\x8e\x7b\x3a\x56\x27\xb5\xbc\x7f\xdc\x42\x4a\x77\x52\x0a\xbd\xff\x56\x6e\x7f\x2b\xb8", + "e363d0e95d8cd18c384016ebeed6d99c4fa2768e2bd58fca019c5108b9cde1cb46f3f884028a55ce282ec310a10037faa1b16b4a6a669957f0b00f350bbd63d0" + }, { + 54, + "\x78\x04\x27\xdc\x16\x4b\x2f\x69\xb8\xc7\xd5\x69\x26\x6f\x46\x1e\x2d\x30\xc8\x8c\x4c\xd6\x05\x7f\xb0\x30\xa6\xcf\x63\x6f\x24\xe3\xc0\xd0\xdb\x74\x2a\x7b\x61\x93\xfd\xaa\x15\xee\xc5\x0d\xfb\x4f\xae\x6e\xc7\x65\x3c\x91", + "2964b009fb1bf996de12e030b9d6e0608ae8b9dbf2acfb9beb76fc5361cc104ee85c2a46fb7b4cee90848312da302de49afe61c546477e2b25d223d5e3d33560" + }, { + 55, + "\xec\x2a\x92\xe4\x7f\x69\x2b\x53\xc1\x35\x54\x75\xc7\x1c\xef\xf0\xb0\x95\x2a\x8b\x35\x41\xb2\x93\x82\x70\x24\x7d\x44\xe7\xc5\xcc\x04\xe1\x72\x36\xb3\x53\xda\x02\x86\x74\xea\xb4\x04\x7d\x89\xec\x5d\xad\x86\x8c\xfd\x91\xce", + "c83aca6147bfcbbc72c377efa8d53654ba0830c5a6a89e1d2a19b713e68fb534640deb833ca512247166dd273b5897e57d526f88eef58f6ff97baee0b4ee5644" + }, { + 56, + "\xc9\x9e\x31\xad\x4e\x23\xac\x68\xe1\x5e\x60\x5d\x0b\x02\x43\x7f\x81\x47\xc4\x4f\x54\x45\xa5\x5b\x68\xa1\x09\x05\x27\x6c\xce\x86\x76\x48\x1c\x33\xe8\xcd\x3e\xfe\x32\x2b\xb1\x3f\xe0\x10\x7b\xb5\x46\xcc\xbe\xc7\xb8\xb3\x8d\x10", + "52992d45a88221d972958e9f2854adaa9a21d2bf7051e1f1019ae78004da50c5b55c144a02afffe539d753949a2b056534f5b4c21f248a05baa52a6c38c7f5dd" + }, { + 57, + "\x9a\xa3\xe8\xad\x92\x77\x7d\xfe\xb1\x21\xa6\x46\xce\x2e\x91\x8d\x1e\x12\xb3\x07\x54\xbc\x09\x47\x0d\x6d\xa4\xaf\x6c\xc9\x64\x2b\x01\x2f\x04\x1f\xf0\x46\x56\x9d\x4f\xd8\xd0\xdc\xcf\xe4\x48\xe5\x9f\xee\xfc\x90\x8d\x9a\xd5\xaf\x6f", + "994d1cda4de40aff4713237cf9f78f7033af83369ac9c64e504091ea2f1caff6c5152d6a0c5608f82886c0093b3d7fbadd49dfd1f9e0f85accf23bc7dad48904" + }, { + 58, + "\x58\x42\x51\x2c\x37\x31\x25\x11\xa3\xd8\xae\x41\xf5\x80\x1d\xf6\x0c\xd6\x82\xd5\x8b\x4a\x99\x73\x42\xb6\xe7\x17\xe9\x40\x06\xc2\x14\x81\x3e\x6c\x63\xe7\x55\x91\xf9\x57\xa7\xec\x30\x17\x79\x83\x8b\xec\x8a\xe3\xed\x7f\xeb\xad\x08\x05", + "9763c43331ad0eb279d704c5f6e97e02da8724115026827f889e9fcda21f60fd230894ab35abb719890f3afa51afd31bc6852183b9c51059910af460abd2474d" + }, { + 59, + "\xca\x14\xe2\xea\x2f\x37\xc7\x8f\x78\xef\x28\x0f\x58\x70\x7e\xc5\x49\xa3\x1a\x94\x36\x10\x73\xe3\x77\x01\xbf\xe5\x03\xe4\xc0\x1e\xe1\xf2\xe1\x23\xe0\x0e\x81\xa1\x88\xf0\x8f\xa0\x50\x82\x57\x09\x12\x8a\x9b\x66\xbb\x8a\xe6\xea\x47\xe4\x1d", + "4600e022a02258739f67fdd367cc1e662631fb087918768352062b9b3c8de8dbca0e9ec751b91f284694fbddb8d325c0637bccb21dd2efa92e48dbab2e5e9c26" + }, { + 60, + "\x64\x76\x29\xc7\x79\xb2\x4c\x1e\x76\xf4\x17\x44\xab\xa1\x71\x59\x48\x75\x32\xa0\x15\x6a\x7d\x82\x64\xdb\x50\xd6\x45\xe9\x59\x5f\xf8\x1e\x0c\x96\xa8\x50\xf2\xaa\x56\xc8\x44\xc6\x13\xa4\xb8\x92\x72\x7a\x9b\xfc\x3d\x3e\x20\x38\x67\x66\xf8\x05", + "5bc842fc2d3b7eb31d2d3044df3ec32af114feaa7cfc27ebc8630f46ab6f0c543f59b812e776e5303861d17da3f1f16097641f3b808d4d5cb3e483946409746c" + }, { + 61, + "\x1c\x5d\xc0\xd1\xdd\x2e\x4c\x71\x76\x35\xff\x3e\x9b\x67\xca\xf9\x57\xae\xc0\xf8\xf6\x3c\x1b\x1e\x22\x1e\x80\x0a\x4c\x14\x84\x8f\x4e\xa0\x6e\x64\x4e\x5d\x3e\x1d\xe5\x92\xef\x5a\x80\x07\xfa\x3f\x07\x17\x1b\x24\xbd\x07\x57\x8d\x68\x96\x3e\x5c\xb1", + "cbf1ea86fa5b3dbf67be82fac41e84cccd0d296c757169b37837d273ccc015eecd102b9ce1cff68fdc7f05d22f2b774734f62ded54c8ee0bf57a5a82010d74f5" + }, { + 62, + "\x8a\x55\x5e\x75\x47\x7d\x06\x5b\x3a\xf7\xe6\x15\x47\x5f\x37\xc0\xa6\x67\xf7\x3a\x4c\x7a\xf5\xe4\xa6\x9f\x28\xa6\x8d\x9f\x44\x34\x77\x6a\x8f\x90\xea\xb7\xf1\xd1\x37\xeb\x4b\x22\x64\x3c\x0a\x0d\x6a\x16\xfc\xfa\xa1\xbd\x62\xf2\x78\x35\x46\xa9\x69\x5f", + "c088e4a3d7da2f6f99a8f3f717361108872b8ffef921b383c24b8061d4e7c27fc56f4f20dc8f952a14043c5650b5a9e777c49c41cfeb3f2de97ee2e16b2c3924" + }, { + 63, + "\xeb\xb3\xe2\xad\x78\x03\x50\x8b\xa4\x6e\x81\xe2\x20\xb1\xcf\xf3\x3e\xa8\x38\x15\x04\x11\x0e\x9f\x80\x92\xef\x08\x5a\xfe\xf8\x4d\xb0\xd4\x36\x93\x1d\x08\x5d\x0e\x1b\x06\xbd\x21\x8c\xf5\x71\xc7\x93\x38\xda\x31\xa8\x3b\x4c\xb1\xec\x6c\x06\xd6\xb9\x87\x68", + "f33428d8fc67aa2cc1adcb2822f37f29cbd72abff68190483e415824f0bcecd447cb4f05a9c47031b9c50e0411c552f31cd04c30cea2bc64bcf825a5f8a66028" + }, { + 64, + "\xc1\xca\x70\xae\x12\x79\xba\x0b\x91\x81\x57\x55\x8b\x49\x20\xd6\xb7\xfb\xa8\xa0\x6b\xe5\x15\x17\x0f\x20\x2f\xaf\xd3\x6f\xb7\xf7\x9d\x69\xfa\xd7\x45\xdb\xa6\x15\x05\x68\xdb\x1e\x2b\x72\x85\x04\x11\x3e\xea\xc3\x4f\x52\x7f\xc8\x2f\x22\x00\xb4\x62\xec\xbf\x5d", + "046e46623912b3932b8d662ab42583423843206301b58bf20ab6d76fd47f1cbbcf421df536ecd7e56db5354e7e0f98822d2129c197f6f0f222b8ec5231f3967d" + }, { + 65, + "\xd3\xdd\xdd\xf8\x05\xb1\x67\x8a\x02\xe3\x92\x00\xf6\x44\x00\x47\xac\xbb\x06\x2e\x4a\x2f\x04\x6a\x3c\xa7\xf1\xdd\x6e\xb0\x3a\x18\xbe\x00\xcd\x1e\xb1\x58\x70\x6a\x64\xaf\x58\x34\xc6\x8c\xf7\xf1\x05\xb4\x15\x19\x46\x05\x22\x2c\x99\xa2\xcb\xf7\x2c\x50\xcb\x14\xbf", + "bae7c5d590bf25a493d8f48b8b4638ccb10541c67996e47287b984322009d27d1348f3ef2999f5ee0d38e112cd5a807a57830cdc318a1181e6c4653cdb8cf122" + }, { + 66, + "\x8e\x8e\xf8\xaa\x33\x6b\x3b\x98\x89\x4c\x31\x26\xc7\x18\x78\x91\x06\x18\x83\x8c\x00\xac\x85\x90\x17\x3c\x91\x74\x99\x72\xff\x3d\x42\xa6\x11\x37\x02\x9a\xd7\x45\x01\x68\x4f\x75\xe1\xb8\xd1\xd7\x43\x36\xaa\x90\x8c\x44\x08\x2a\xe9\xeb\x16\x2e\x90\x18\x67\xf5\x49\x05", + "41672931558a93762522b1d55389ecf1b8c0feb8b88f4587fbd417ca809055b0cb630d8bea133ab7f6cf1f21c6b35e2e25c0d19583258808e6c23e1a75336103" + }, { + 67, + "\x52\x76\x1e\x1d\xac\x0e\xae\xa8\x98\xe0\xb0\x7c\xd2\x4f\x4b\x2e\x6b\xb7\xbc\x20\x0e\xa4\xb0\x52\x88\x42\xf1\x7b\x87\x15\x45\x59\xa2\xea\x94\x45\x9a\x0e\x48\x0a\xe0\xbd\xf9\xf7\x57\xdd\x4a\x33\x5a\xed\x0e\x51\x01\x38\xb0\x24\xa0\x4e\xd1\xd5\x91\xb4\x32\x32\x34\xdb\xd5", + "b826fe80494e19c51b42f2582b2d080ba6b90512f35f2db67dd7fd5ee532eaa16498afba08b4996cbcfdf8d1a2df6b1da939e8265115a48aefa42f38205db436" + }, { + 68, + "\x38\x04\xeb\xc4\x3c\xbe\xa8\x0c\x2b\xd7\xe4\xfd\xa5\xc5\x51\x55\x00\xcd\x2d\x2b\x84\x6a\x13\x78\xdb\xf2\x18\xd5\xc3\x77\x13\x86\x06\xeb\x3c\xb8\xac\x88\xf9\x07\x6f\x6f\xf4\x43\x6f\x90\x71\x74\x27\xc9\xdf\x1b\xa0\x52\xac\xbb\xe4\x58\x5e\x98\xb6\xe8\xe0\xbf\x80\x0f\x19\x46", + "17dd6d87bc6773051e52047fd444996afa8124b0483fe121877f98553448772bd0e7751fc655e9cc2d29830211015d310f191474ca6adc0477a187c03b8fe252" + }, { + 69, + "\x22\x49\xd6\x98\xc4\xd8\x07\xa8\xe7\xb4\xde\x21\xc4\x85\x73\x89\x59\xa0\xd6\x7e\x5d\x2c\xa6\xf7\x79\x83\xdf\xcc\xb5\xdb\xf4\x79\x31\x26\x1e\x1f\x15\x37\xf3\xcb\xca\x25\x3a\xfb\x6b\xf4\xfe\x5e\x76\x72\xe1\xdc\xc8\x60\xb3\xd6\xc8\xd2\x43\xaf\xe2\xd9\x75\x8b\x37\x5e\x95\x56\x92", + "6af44563fc468d51182f6c3be58d45932af1d985c6f283976c91a9ff421f383fe21dc7322f397ccead583e26b3e3fda067976a7f34665df25a2ced7b4b09cdec" + }, { + 70, + "\x32\xa9\xc1\x70\x33\x65\x8c\x54\xf2\x2c\x71\x35\xdd\xfc\x87\x9d\xe9\x4d\x79\x59\x3e\xf2\xdc\x7d\x30\x41\xbf\xa8\x72\x73\x83\x89\x86\x4e\xed\xa2\x78\x01\x79\x4c\xcc\x4f\xf1\xfc\xb5\xef\x3f\xc4\x88\x33\x80\x1d\x6f\xe9\x59\xe3\x62\x7f\x8e\xa1\x53\x6a\xd0\x0f\xa9\xc7\xd7\xd9\xf0\x43", + "6a47699dd3ada2f11bc4ea42072b06cc20857bf164497df1285400c250f5848b6f71957dbdc845f5daeab913036661f69387893fc2d61c25fa59b9d85b19f401" + }, { + 71, + "\x3d\x65\xf6\x9a\x59\x0a\x5b\xaa\xab\xcd\x27\x4f\xe3\xef\x9e\x88\x92\x0f\xfc\x7a\xdf\x05\xc1\x6d\x7b\x0f\x4d\x18\xd7\x2b\xac\x1e\x94\xc3\xb3\xd8\x3b\x8f\x4c\x55\x2e\xb8\x0e\x9f\xde\x39\x11\x40\x3f\x8b\x00\x05\x79\x81\x6f\x02\xe1\x71\x6f\xd6\x27\x94\x60\x31\xd0\xaf\x07\x93\xe7\xf3\xe1", + "ffb2d9450943c24b5933c24812459b75d3d9f380344c9bc06fa3e17ee448eca2f98ff79f7e2235ccd9f9a8176f68a2254bbc9b834d6ac8d2bfdbc1597c432c9f" + }, { + 72, + "\x76\xff\x8b\x20\xa1\x8c\xf1\x04\xf6\xcd\xb6\x5e\x2b\xa8\xf6\x6e\xcf\x84\x4a\xf7\xe8\x5e\x8e\xf2\xda\x19\xe8\x84\x8a\x16\x05\x2e\xc4\x05\xa6\x44\xda\xfb\x5c\xa0\x8e\xc4\x8f\x97\x32\x7a\xc5\x2c\x0e\x56\x21\x84\x02\xc7\x2a\x9a\x6d\xc1\xcf\x34\x4d\x58\xa7\x16\xa7\x8d\x7d\x75\x29\x68\x0b\xae", + "f8858144c6d709dd0689a526a548a43f17494950ba2ac20544799e8ea27201d78bce5b921e29a7b4029278e68341ef2a0ca4ba3894566b3c8f8950e3e545a689" + }, { + 73, + "\xca\x88\xdd\xdf\xc8\x76\xa1\x2f\x45\xf1\x95\x62\xbc\x9c\xa2\x50\xf4\x32\x67\xab\x25\x1a\x7f\x34\x5c\x3c\x02\x2e\x20\x14\x4e\x13\x56\x04\x07\x87\x62\xef\x5c\x8a\x8f\x03\x8c\xf1\xb1\xd6\xa9\x17\x09\xb5\x9d\xd0\x68\x39\x6a\x9e\x97\x1a\xb6\x28\xf7\x48\x86\xe7\x65\x38\x4a\x23\x60\x7c\x1a\x1e\x6e", + "4f3d9eeef349ca51a7e419af1686f42795abde58a85335ce68d496e81e4436a80a61dc143a4300008c23a3e71f4ba98743195a3694a8d02fee11bd314569abc0" + }, { + 74, + "\x0a\x78\xb1\x6b\x40\x26\xf7\xec\x06\x3d\xb4\xe7\xb7\x7c\x42\xa2\x98\xe5\x24\xe2\x68\x09\x3c\x50\x38\x85\x3e\x21\x7d\xcd\x65\xf6\x64\x28\x65\x01\x65\xfc\xa0\x6a\x1b\x4c\x9c\xf1\x53\x7f\xb5\xd4\x63\x63\x0f\xf3\xbd\x71\xcf\x32\xc3\x53\x8b\x1f\xdd\xa3\xfe\xd5\xc9\xf6\x01\x20\x33\x19\xb7\xe1\x86\x9a", + "6095c3df5b9db7ce524d76123f77421ce888b86a477ae8c6db1d0be8d326d22c852915ab03c0c81a5b7ac71e2c14e74bda17a78d2b10585fa214f6546eb710a0" + }, { + 75, + "\x20\xf1\x0e\xf9\xa0\xe6\x12\x86\x75\x34\x01\x71\xcd\x24\x8d\xf3\x0b\x58\x65\x57\x62\x0b\x61\x5c\xa3\x9a\x00\xdb\x53\x43\x15\xa9\x01\x2d\xbd\xbf\xd6\xa9\x94\x98\x6e\xb8\x29\xdb\xe6\xcd\xaf\x3a\x37\xd4\xf5\x9a\xc2\x72\x98\x74\x2c\x8f\x77\x7b\x6b\x12\x67\x7f\x21\xeb\x28\x91\x29\x57\x98\x68\x70\x5f\x27", + "b4ead3f860eabbd36c770d66c7356f8107acd1485c7c94178c2eaabd50266d7645d009972586ef83ed43ed92882137df5117b88f35231b894ec1741ae7501145" + }, { + 76, + "\x99\x5c\x8f\x74\x7e\xa4\x18\xf7\xd6\x3a\xba\x22\x60\xb3\x4a\xc3\xc7\xdc\xee\xbb\x78\x43\x8c\xa4\xb1\xf9\x82\xb7\xdb\x97\x98\xec\x1a\x7f\x32\x62\x22\x64\xcb\x02\x4c\x0d\x9e\x60\xe9\x55\xa6\xe1\xd6\x77\xc9\x23\x51\x88\x51\x99\x0a\x45\x9b\x76\x7d\x0f\x13\xcd\x80\x34\x60\xf6\x18\x70\xdb\x33\x91\xb4\x46\x93", + "a00a601edeaca83041dc452d438a8de549594e25d843c2cf60a0e009fb92d87abe28a72690ab657c8d35b43cd02d22ec0755de229d1f922fa6ca18a6d6c2aaae" + }, { + 77, + "\x0f\xeb\x23\xc7\xe4\xa1\x9b\xcb\xd7\x0b\xd3\x00\xd7\x6e\xc9\x04\x5d\x69\x6f\x8c\x96\x87\xf4\x9e\xc4\x15\x44\x00\xe2\x31\xd2\xf0\x86\x24\x95\x15\x0c\xf2\x50\xb6\xf1\x2f\x17\x2a\x7d\x13\x0f\x8f\xa5\xd1\x75\xbf\x2f\x25\xe2\x80\x17\x2c\xcd\xfb\x32\x79\x51\x70\x11\x65\x30\x27\x28\xa6\x19\xaa\x2f\x24\x26\x31\xc9", + "eeb6dee30c119fb1e1eb5c15ff2b32d8b9c7464a4e4cc6815cd251a6bae29b49961dd5c2fa9c44a9b142ca062c7072cbf3db04299b767789040196bf0c06aa76" + }, { + 78, + "\xac\x59\xa1\x10\x62\x3f\x1a\x64\x66\x6f\x16\x0e\xd3\x29\x26\x67\x6c\xb5\xbe\x25\xdd\x9d\x96\x2f\x44\x19\x51\xb0\xef\xcb\x5d\x6a\x67\xac\x1a\x4e\xae\x47\x3e\x49\xc6\x25\x78\x60\x72\x88\x53\xff\x41\x5c\x5e\x8e\xc7\x6a\x8a\x46\x2e\xcf\xd3\x43\xee\xac\x22\xda\xd8\x20\x72\x2c\x59\x73\x32\xfb\xfd\x94\xeb\xbd\x32\xc6", + "f65ea942ae0a47e73b02b1442e5b26083db79307f64dd34a039c476faf18d5c514bb77a2c412a6074a7afc326ea66c74e5705fe2abbabf274333325a15b61fd9" + }, { + 79, + "\x9e\x3e\x10\x77\xe1\x33\x3a\x1f\xb1\xaa\x63\x3c\xcf\x2f\x74\x65\x88\xad\x42\x64\x89\xea\x08\xdf\xf5\x51\x14\x38\xb5\xf4\xc0\xb1\x10\xd1\xa4\xd4\x7b\x54\x0a\x12\xb2\x1e\xa2\xaa\x07\x05\x78\xcc\xfa\x5c\x22\xfe\x0b\x74\x3e\xc0\xcc\x62\x1c\x6b\x3a\x03\xb7\x5f\x4d\x3e\xea\x5d\xce\x89\xe0\x32\x69\xaf\xcd\x96\x03\xd0\xdb", + "4b5c5df80c344c12388c723856cd06965b2190af652480476747dc2195ea3716f87c1762359583a5f31522f83f7833bec30f1f47d14540417dd463f5d258cd4a" + }, { + 80, + "\xe8\x81\xe3\x28\x4c\x79\xd8\xf5\x23\x7e\x69\x9e\x4f\xbc\xa8\x40\x90\xc6\x64\xbb\x53\x22\x9f\x58\xcb\x08\x42\xb0\x43\x67\x10\xc9\xb3\x29\xd9\x81\x91\xb8\xf0\x30\xe9\xc1\xdf\x89\xb0\x38\x58\xc1\x56\x9c\x6f\xf4\x9a\x7c\x07\xc4\xa2\x3a\x8a\x43\x4b\x0f\xde\x13\xbe\x4f\x94\xcb\x44\xee\x62\x9d\x5b\x44\xd3\x36\x09\x0d\x3d\xe6", + "147d8071c7871ef9256cff32aa63ea031404fa5ee4ec09c56afdd5da919b0cc84a9d35d142c417715203316011cc620cd6855bb117063a5e52867facc680d5f4" + }, { + 81, + "\xe5\x85\x21\x09\x89\x11\x50\x3d\xe8\x43\x11\x38\x7d\x37\x5c\x25\x92\x9e\x6e\x55\x07\x6e\xb6\x93\x4f\xd8\xf2\xb1\xbb\x7b\x96\x67\xfb\xd7\x6d\x5e\xe2\x04\x82\x87\x69\xa3\x41\xb1\xf7\x16\xda\x5b\xdf\xec\xe6\xc6\x2a\x9f\x4d\x4f\x98\x82\x67\xfc\xe1\xf5\x61\x55\x40\xdb\xe3\x75\x32\x4e\xef\x60\x7c\x91\x0d\x97\x6b\x45\xa5\xea\x5f", + "f97ba056fa41f43b8e1987072a09e828c71c5ff6ad4e37f9ab6b89e2a078933dd23052fa72c6615b613904259e9ff9b55ef7b923b89bc8752f6babddd256e117" + }, { + 82, + "\x37\x96\xcf\x51\xb8\x72\x66\x52\xa4\x20\x47\x33\xb8\xfb\xb0\x47\xcf\x00\xfb\x91\xa9\x83\x7e\x22\xec\x22\xb1\xa2\x68\xf8\x8e\x2c\x9f\x13\x3e\x5f\x85\x27\xf1\xb1\x84\x83\x0e\x07\xc3\x45\x8c\x83\xa8\xca\x9f\x9d\x9c\x69\x98\x76\x0e\x61\x06\x68\xba\x0f\x22\xe2\x2b\x65\x6a\x73\x7e\x97\x8b\x24\x6a\x17\x84\x0b\x7d\xc4\x09\x1d\xa8\x5f", + "c8a466199acbcbc93f2ce042968508c046901631e3118a2d0bf39a9b42b4197a379b3a86cdeca9df2de1a3eb71b79ae9bf2d6575eadf1878029c4093133f54d3" + }, { + 83, + "\x9a\xf6\x08\xd0\x31\xcc\xf3\x09\xd7\x27\x3c\x60\x7a\x8e\x5e\x36\x84\x0d\x44\x9b\x55\xdb\x5b\x13\xf0\x3a\xeb\x9a\xf4\x9f\xa7\xe7\xcf\x13\x83\xee\x2e\xd9\xc5\xa8\xb7\x51\x5f\x16\xfb\x1c\x7c\x84\xa6\x81\x59\x0b\xf9\x0f\x56\x59\x7b\x84\x4d\xb5\xeb\xee\x22\x3d\x78\x10\x9b\x72\x35\x07\x72\xf7\xc7\x2e\xa9\x96\x60\x3e\x1e\x84\xf2\xba\x5f", + "f0ded9495b4f64cac585be8a737cfa14247a4a81cdf7f01ebcb134ace71f5a83df2cd72e7773fea1e82beae17e13857372792c8231e2ab9fbeb633e399d5f0ae" + }, { + 84, + "\xd0\xdf\x1b\xdf\x1d\xf6\x20\x32\x41\x72\x2f\xb9\xc9\xc1\xcf\x74\x05\x01\x74\x97\xae\x15\x45\x38\xcc\xf9\x22\x4a\xd7\x52\xe6\xce\x1d\x4a\xe9\x48\x63\x9a\xca\x70\xcf\xe8\x6b\x2b\x06\x54\x3c\xb9\x91\x4e\xbd\x30\x85\xaa\x3e\x29\x63\xf6\xe9\xb9\x3d\x0b\x03\xa3\x1a\xe2\x6f\xcb\x9c\xa9\x74\xee\xe0\x16\xc0\x91\xa6\xfc\xac\x37\xb2\x1c\xc1\xd7", + "c2da3ea3c8a3fd88a5bc5dea2bc076f861abedefae5a5fbd941ddfd1c41cc3312eb2dc826c2c0f65414fe72ebee447d2f9b1a6a56302660d1f86632ee80a175f" + }, { + 85, + "\x8c\xbc\x94\x80\x55\x3a\xce\xf7\xbc\xdb\xa9\x71\x6e\xa8\xd6\x6b\x41\x31\x78\x09\x17\xde\x2b\x0b\x04\x80\x45\xfc\xb3\x2b\x5c\xac\x05\x48\x08\xe1\xfc\xe6\xe9\x4a\xd8\x51\xec\xb4\x7f\xe6\xcb\x80\x22\x25\xd3\x55\x1e\x08\xea\x12\x20\x93\xd0\x07\x8d\xad\xa5\x64\x21\x2e\xac\xf1\xd6\x39\x4e\x00\x07\xcc\x62\xa1\xd5\x95\xab\x14\xca\x08\xa2\x84\xbc", + "63b39b88ceb848188b37316e04560e75a5340ab8d417932d231c997e892b41daa69d9fe3e9a14dd19ccfbbfa01488c208e7b946cfaf16ca2b1bf7c8d8da4e6b2" + }, { + 86, + "\x38\xf1\x84\x44\x8f\x3c\xf8\x2a\x54\xca\xfc\x55\x6a\xff\x33\x6f\x23\xf9\x14\x9e\x61\x21\x34\xb3\xfc\x00\xc8\xa5\x64\x55\x65\x3d\x88\x64\x0b\x12\xf6\x90\x62\xb8\x43\x2c\x43\x35\xad\x8f\x7a\xb4\xff\x66\xcb\x7e\xb5\x4f\x33\x25\x61\xa3\x6f\x02\x4d\x92\xc3\xe2\x62\x76\xf4\xfd\x48\x61\x96\x28\xcf\xf8\x8e\x4b\x8e\x85\xcf\x14\xca\x47\x67\xed\x99\x0d", + "9a49265fc641c59f1a91872cdae490d3da73c0c60fd59648e1d17dba1a647a5b95629392bb4ff5163d1a3cb45427c1437a3b2e1d9f030c0a8bcc5ed22da9e2ed" + }, { + 87, + "\x70\x90\x06\x18\xb1\xe9\xe9\xdb\x62\x29\x6f\xb6\xc6\x59\x0c\x9f\x10\xb0\xa6\x32\x76\x5c\x48\x9c\x88\x7f\x1a\xb7\xc0\x77\x91\x76\x5a\x62\xe3\x84\x65\xe1\xbe\x28\x1b\x1d\x39\x6c\x6e\x08\x0b\x7e\xe3\xe6\xfa\x56\xa3\x0b\x97\x99\xd0\xe6\x29\xbe\x15\x3e\xe7\x6f\x81\xbc\x6a\x32\x95\xaa\x61\x48\x9b\xfa\x87\xd5\x3a\x8a\xd2\x42\x48\xa6\xed\xe0\xdf\xcf\xe9", + "1c8c3357ff1f8d6ac4defb3af462a73e09159e3a20c6506edd8cd3052df941c81f68c5fbb893912619e28640977fe8eaae8e9d5d4e7d5f132552cefab4540bac" + }, { + 88, + "\x4e\x6d\xda\xe0\xd8\x05\xaf\xcd\x10\xa0\x55\xbc\xe5\x84\xc8\x48\xd0\x50\xfb\x29\xfe\x8f\x1c\x64\xb1\x8e\x1a\xbf\xe4\x6b\x65\x78\x2e\x6f\xf5\x36\xe8\x9d\x8d\x40\x92\x8b\x41\xed\x73\x71\x36\x5c\x80\x80\xa9\x64\x7f\x75\x32\xce\x6c\x6d\x4a\xc2\x1c\xfb\x0c\x80\x20\x78\x38\x51\xec\x9a\x7d\xbc\x39\x48\xf8\xfc\xa7\xad\xf8\xb2\xa7\x8c\x04\xd8\x98\xd3\x1f\xf6", + "5c2f996c779b91b3c4639311f54fabbdde7e2212b53dbae4828c8399588fc00d3b2ae60918aaaf6bb48bc757e52b2bcea84f5d15bf4ec25d5519fb54f6f26e1b" + }, { + 89, + "\x69\x68\x25\xf6\xd6\xea\x81\x73\xec\x47\xd0\x95\x9a\x40\x1c\x4d\xdf\x69\xf8\xf0\x8d\xdd\x67\x8a\x4d\x2f\xf9\x76\xe3\xa4\x37\x2b\xb3\x9f\x41\x59\x84\x5c\xb6\x35\x85\xe1\xd4\x10\x8d\x32\xe1\x2f\xa7\xc5\xc9\xd7\xce\x35\x08\xa7\xf5\x3a\xca\x2b\x4b\xd9\x51\xad\xbc\xd8\x98\x4e\xbb\x75\x36\x56\x3f\x58\x84\xc9\x0b\xc5\x02\x3b\x33\x16\xf7\xe4\xdc\x69\x58\xf7\x43", + "3ce940ca96b00011375daa95c65f66907d69b3eb3b8d779e6fc971afcc05e990bc4c541f434590f6b18b68c080d0f24475a3e764e9cb85343301314ee2fb661e" + }, { + 90, + "\x79\xec\xdf\xd4\x7a\x29\xa7\x42\x20\xa5\x28\x19\xce\x45\x89\x74\x7f\x2b\x30\xb3\x64\xd0\x85\x2c\xce\x52\xf9\x1e\x4f\x0f\x48\xe6\x1c\x72\xfa\x76\xb6\x0d\x30\x02\xca\xe8\x9d\xfc\x55\x19\xd3\x43\x0b\x95\xc0\x98\xfa\x46\x78\x51\x6b\x5e\x35\x51\x09\xea\x9b\x37\x45\xaa\x41\xd6\xf8\x20\x6e\xe6\x4a\xe7\x20\xf8\xd4\x46\x53\xb0\x01\x05\x7f\x2e\xba\x7f\x63\xcd\x42\xf9", + "ba3d0fe04470f4cf8f08c46d82ae3afd1caea8c13bebbe026b5c1777aa59860af2e3da7751844e0be24072af48bc8a6fd77678aaee04e08f63395f5c8a465763" + }, { + 91, + "\x92\x63\xfe\x75\xe8\xf6\xc7\xd5\xd6\x42\xe2\xca\x6a\x6e\xea\x4f\x44\xe9\xa0\xf2\x49\x51\x3e\xd7\x9c\x94\x09\xff\xca\x55\x26\xca\x44\x91\xae\xbb\x13\x82\x05\x7c\xc7\xc3\x67\x22\xb0\xb6\xc3\xb1\x51\x23\xcd\xe3\x12\x21\x4f\x25\x35\x3a\xbf\xe3\x0b\xca\x17\x05\x68\xa8\xe1\xba\x54\x08\x91\x74\x03\xa0\x18\x34\x08\x0a\xb6\x07\xc5\x6a\x10\xd0\x26\x50\x82\x49\x8f\xe0\xb6", + "7736d7a7fc1eb05857ce7d88abfffa87f58c670bfdfc0a8031f60f379e4b6ad94ac8f13ffe28c697809b5cfac7f13be01e7496a85237c4025539051fb2e32fb6" + }, { + 92, + "\x78\xc1\x7b\xfe\x0e\x02\xeb\x52\x6d\x1a\x44\xa1\xac\x12\x7b\xe0\x82\x18\x14\x52\xb6\x25\x39\x4b\xd6\xdc\x09\x3a\x2c\xb4\x32\xe6\xee\x59\xc2\xf8\xb5\x50\x3a\xba\x30\xda\xe4\x1e\x1a\x1c\x67\x02\x69\x7c\x99\xb2\xc9\x4e\x94\xaf\x48\xb0\x0c\xaf\x53\xb2\xe0\xe4\xe1\xbb\xee\x81\xee\x28\x2c\x7b\x2b\x35\xf5\x8c\xf4\x21\xa0\x7e\x82\x8d\x57\xa6\x62\x26\x26\xaf\x25\x83\x53\x99", + "b56b6e343166328523e0d1693e5174da643ae83cf69c85a7b3c3bee247b77b84702069d9e6b4cab03bf17fe612009bf4239683ca78ca7e876aca7d07603ba714" + }, { + 93, + "\x29\x8b\xb3\x04\xa9\x20\xf9\x60\x44\x7d\x8f\xd3\x8b\x06\x1b\xf8\xfe\x4a\xc1\xf8\x71\xd8\xa0\xfe\xb4\x54\x9f\xeb\x72\xca\x69\x4a\x5a\x41\xb6\x86\x7d\x94\xcd\x5a\xf7\x7d\x46\x8a\xd2\xf3\x15\xd1\x27\xb6\xc4\x1a\x86\x28\x00\xf3\x98\x5e\x57\x3e\x03\x77\x40\x29\x8e\x2c\x5c\x61\x86\xa9\xfb\x83\x60\x9b\xe2\xd4\x9f\x8b\x4c\x31\xf9\x6a\x2e\x49\xb5\x6d\xbf\x09\x57\x1b\x38\x58\x7f", + "34e3878627904ffbbbd85266cc973c34f931e3cab5d4c31f841c553dd69f84838206067df4f9f3b9102001be19267151e673f5c2d4c2f8438a6999a0a325487d" + }, { + 94, + "\xa3\xcf\x71\x4b\xf1\x12\x64\x7e\x72\x7e\x8c\xfd\x46\x49\x9a\xcd\x35\xa6\x40\xdd\x39\x3d\xdd\x26\x3c\xd8\x5c\xf6\x22\x5f\x59\x89\x0a\x06\x86\xda\xd1\xc5\x4e\xb8\xd8\x09\xb8\x1c\x08\xa9\x8d\xba\x13\x1b\xbd\xd6\xfc\xe8\xff\x59\xd9\x5d\xb8\x24\xd8\x83\x1e\xa4\x80\x52\x9d\xa7\x39\x22\x7a\x6e\x0f\x62\xb6\x03\xb3\x8c\x35\xcd\xc2\x58\x1f\x61\x4a\x31\x87\x9b\x8b\xe5\x4a\xee\xfa\xa0", + "6f230ae4903ddbef0ba384c2e3506eab318bfd1a46ea76099f65a3fd529c91bc2865b9fd943e346de64626b8529f9db1377bf2c5e0129c66b50c6a5cfb364b3a" + }, { + 95, + "\x0a\x42\x7a\xe5\x5e\xf3\xa7\xe6\x04\x4a\x08\xcf\x61\x28\xcb\xaa\xab\xfd\x77\x6c\x4e\x93\x74\x70\x8f\x2e\xce\x24\x6f\xd7\x36\x03\xd2\xf5\x4a\xc3\xe0\x1d\x16\xcf\xac\x2b\xda\xf7\x13\x92\x0d\x66\xe8\xf0\xa3\xd5\x4e\xe6\x8c\xff\x64\x26\x7d\x55\x28\xcd\xf2\xf2\x95\xf4\x74\xd1\x0f\x81\x17\x3e\x01\x43\x48\x8a\xc5\x3f\xc5\x03\xc4\x44\xed\x23\xde\xc6\x3a\x08\x0c\xe9\x0c\x24\x43\xdb\xa8", + "f6bbe5d0cf13ddf41c1436748a5d1ccae2948547b452c2171c7c8e8b66c6ae4de3c0e8b2962bcb60d3de3608479f80e455c9024d9716c38f6f1206861ab1eaac" + }, { + 96, + "\x2c\xbb\xb8\x75\x11\xf4\x94\x8e\xfe\xc3\xa6\x1b\x51\x1e\xde\xdb\x1d\xda\x8b\x6e\xcf\xc0\x21\x0c\x11\xe4\x3a\x77\xee\x32\xdc\x2e\x37\x4a\xfa\xe4\x26\x8e\x3d\x30\x42\x78\x04\x86\x82\x32\xa9\x66\xb5\x60\x06\xd3\x21\x40\x37\x07\x6b\xf6\xa2\x65\xb7\x21\x35\xaf\x0f\xb2\xef\x79\x09\xfe\xa2\xde\xa4\x12\xf7\x71\x74\x46\xb2\x76\xff\x15\x37\x53\x66\x2b\x4d\x41\x48\xc0\x23\x47\xe3\x25\x91\x69", + "76897b87a8a1cf835c434f6d391c9e5227351af9d3e20a3389c796b98b424281a59068d9c8d567ec2bebc435b0126b059e2d86394a9854d6611e1c922f385496" + }, { + 97, + "\x2b\x23\x32\x4c\x99\x92\xf6\x0a\x7f\xc0\x10\x15\x9a\x03\xcb\x9a\x2b\x29\x0d\xf4\xfa\x6a\x82\x35\x9b\x9a\xf6\x02\xf0\xa4\x03\xa5\xef\x33\xed\x5d\xa5\xb2\xca\xf8\x7b\x77\xe6\xa4\xb9\x3b\x65\x03\x48\xce\x2a\x7d\xbc\x08\xf8\xda\x92\x03\xd7\x10\xb5\x87\xba\x59\x47\xc6\x5e\x89\x9f\x4a\x75\x9f\x8e\x2b\x04\x9a\xe7\x85\x0a\x8e\x3e\x29\x62\xf6\xef\x93\xea\x4c\x63\x1d\xe5\xd7\x8e\x72\x9e\xc5\xbc", + "3beea0b373ed09cf1c919c51d86d642c9125e0ee81698dc4cbadf02e9e6925efb562fd9b87301a6377ca192be79c4118deabc450b54639000c2e312945451fb5" + }, { + 98, + "\x40\x22\xf9\x30\xc7\x03\x3b\x00\xd9\x86\xc6\x5f\xf6\xbb\xbd\xf9\xeb\xd0\xe5\x8c\x52\x84\x4f\xf6\x58\xdf\x38\x93\xc3\x20\x2d\xc5\x33\xf8\x73\xd4\xa7\xf5\xa5\xf9\x44\x41\x9f\xb5\x52\x8c\x9b\x67\x88\x47\x9a\x1e\x89\x13\x06\xac\xae\x79\x95\xfc\x06\xdb\x70\xa5\x9b\xaa\x95\xbe\xf7\xda\x79\xf5\xe7\x93\xf2\xdb\x7f\x2a\x55\x82\x5e\x4f\xdb\x4a\x34\x88\x4a\xf8\x81\xde\xd1\x08\x9f\xd5\x33\x45\x02\xa2", + "0358775bbb733ccc49e78f544aeee512370d480d0e13c7e8d5c444c423e592146b45fdb91a1b694d35e36b60e4bc8397fca8bb9790e619339778b9cd1abe3fe9" + }, { + 99, + "\x1c\xb7\x7b\xa4\x3c\xe7\x7e\x23\x6b\x9f\xc9\x25\xf5\x89\xb1\xc0\x70\x78\x0a\x84\xf9\x9e\x8f\x50\xc1\xff\x84\x6a\xc9\x25\x99\xcf\xe9\x16\x12\xc8\x17\x83\x25\xbe\xe6\x42\xa3\x4f\x4d\xff\xdb\xa2\xaa\x2e\xbc\xf7\x06\x43\x39\x82\x9b\x26\xf2\x79\x93\xe1\x10\x6c\x13\x9c\x70\xd5\x78\xcc\x05\xf0\xe1\xa7\x77\xcc\xed\xdb\x10\xa2\xc6\x7f\xd9\x67\x5e\x4a\x00\x9d\xf8\x03\x7d\x6e\xeb\x38\xf5\xfb\xa2\x33\xdf", + "6502f46551a3fab3a96428fb97801d7a4aa2f17fef6603238df84e17c74309ed3d9489c8b16a9384ee634a3f86d0b3ba9a4dbc9c51ec8bd4bf8d61de6d3d87d7" + }, { + 100, + "\x52\x16\x7d\xe2\xd6\xc5\x02\xd9\x9f\xa1\x0c\x27\xb2\xab\x62\x03\xbd\xeb\xc2\xca\xfb\xbf\xde\xf1\x58\x72\xa4\x3d\xd6\x10\xc2\x36\x2f\x79\x6a\xd9\xbc\xb5\x52\x8d\x95\x87\x00\x58\xfa\x45\x44\x53\xf1\xe6\x06\x5b\x31\x5d\x41\x0a\x3f\x26\x50\xe5\xd7\x1e\x69\xd7\x8d\x97\x67\xdf\xb4\xac\xcc\x05\x7f\xd2\x06\x92\x66\xb0\xf1\x80\xcb\x31\x9e\x30\xde\xd7\x53\x5b\xbe\x52\xd2\x4b\xe1\x51\xde\x4b\xb5\x98\xfc\x5c", + "25cb3ed3983a91b4cf37a65193916c5e3e211b63e943e2f7b50a85d349a463b941aad33eff16561bdfdc92fda06a4e1d94b162de48f06d3c626940b31020925f" + }, { + 101, + "\xce\xde\x66\x97\xd4\x22\xdd\xaa\x78\xe2\xd5\x5a\xe0\x80\xb8\xb9\xe9\x35\x6c\x69\xbc\x55\x82\x01\xa2\xd4\xb0\xb3\x19\x0a\x81\x2c\x27\xb3\x4b\xbc\xee\x3a\x62\xb7\x81\x37\x8b\x1b\xf6\x36\xb3\x72\xbc\xba\xe1\xfa\x2f\x81\x6a\x04\x6a\x0a\x64\x9a\x5c\x55\x5c\x64\x1f\xea\x4c\xcd\x84\x1c\xc7\x61\xf3\x8f\x77\x79\x72\xf8\xc9\x1b\x03\x24\xe7\x1c\x33\x3c\xe7\x87\xf0\x47\x41\x43\x9b\xf0\x87\xef\x5e\x89\x50\x11\xc0", + "0be42a25d77ac6ad995c6be48e783380bad25a61732f87cefb0cce1a769cd69081f494a1a12d657664ef2b4d9c41f2ee83f6e9a84327d8756af9f985595e7d3b" + }, { + 102, + "\x56\xd1\x8d\x3e\x2e\x49\x64\x40\xd0\xa5\xc9\xe1\xbc\xb4\x64\xfa\xf5\xbc\x70\xa8\xb5\x62\x12\x4f\x5f\xc9\xe9\xde\xb5\xfe\xe6\x54\x4b\x94\x5e\x83\x3b\x8b\x5d\x13\x1b\x77\x3e\xcb\x2c\xdd\x78\x0c\xd4\xe1\xbb\x9e\x4f\x1e\x3c\xb0\xa1\xd6\x4d\x19\xcf\x4b\x30\xe4\x4e\x6c\x2d\x0c\xbc\xb4\xe2\x84\xce\x50\xdb\x7a\x8a\x80\x62\xdd\xb6\x3f\x98\x1d\x90\x26\xc5\x32\xbf\x8e\xed\xdf\x8a\xf5\xa4\x38\x48\xa3\x22\x62\x17\x8c", + "982dc61c91a93770582eee8025aa55da8e9edb966bf5cf70d4a6534c0d53a2789a8c4fb65b7fed478cda02ed1e0d198d85c5c735b2417c5fab5d34e969fc8e7e" + }, { + 103, + "\x25\xa7\x32\x0d\xfa\xec\x5a\xf6\x5d\xa4\xd0\xf8\x68\x8e\x29\xe8\xe9\x55\x32\xec\xc1\x66\x79\xea\x8a\xff\x0f\x40\x7d\x89\x8d\xb6\x92\x28\x55\xb0\xe8\x90\x1a\xa9\x68\x1a\xa3\xdc\xa6\x17\xcb\x44\x07\x64\xcd\xc7\x29\x3f\xbe\xaf\x7f\x58\x5b\x59\x3c\x2b\x05\x31\x73\x8e\x0a\xde\x7c\x86\x26\xb9\x99\x5f\x4a\x84\xd9\xfc\x9b\x59\x3d\x6b\xbe\xe0\x1a\xbc\x53\xc5\xbe\x14\xbf\x69\x56\xfd\x2f\xd8\x10\x00\xda\xfc\x7c\x76\x86", + "749c928c3d5510925bfe98659025b0ed7c01acd4d59a9bf1c54863a088091771dc9d407bdbf83b0f44b0902e10349ba79c84d0981d5e8c4f5c733a117fed0790" + }, { + 104, + "\x3d\x71\x77\xb2\x8f\xfd\x91\x6e\x7e\x06\x34\x89\x58\x33\xba\x0b\xd9\xe0\x65\x3d\xf2\xcc\x42\x02\xc8\x11\x53\x6a\x00\x5a\xec\x85\x3a\x50\x5e\x75\xdb\x55\xd3\xc7\x10\x75\x79\x04\x10\x99\xe3\x82\xa1\xfe\xac\x80\xdd\xe6\x5d\x72\x36\x8e\x90\x9a\xb8\x5f\x56\xd8\x8e\x68\xd7\xc3\xc8\x0c\x38\xf8\x5b\xf8\xc2\xb3\x69\x59\x40\x9c\xc3\x4b\xa8\xe3\xad\x94\xfe\x8e\xe1\x92\x76\x12\xd6\x72\xd9\x21\x41\xa3\x29\xc4\xdd\x8a\x88\xa9", + "14a331508cd7d94fcce56a66bf65f20870a281c8442f8dbd4c2371454a2b66f8d0994a0b67692e771efc6a5e0b887acae7d6f4ec7338e1aa89f2abc7034c4e4c" + }, { + 105, + "\xc0\x33\xe4\xa5\x12\x29\x7c\xae\xcd\xbe\xad\x89\x2b\x11\xa9\xf7\x00\x7a\xf9\xa7\x4b\xca\xb8\x9e\x0b\xd4\xff\xdd\x54\x2c\xa0\x3e\xa1\x2e\x17\xa0\x6c\x42\xbd\x43\xfc\x5f\x3f\x75\x7f\xce\x4f\x6f\x58\x31\x99\x7a\xba\xc3\xf9\x56\x76\xe1\xeb\xdb\x11\xca\x43\xe1\x1a\xa3\x1e\x5e\xba\xbe\x18\xce\x8d\x1b\xbf\xd8\xb0\x2f\x48\x2e\x1c\xe5\x81\xb5\x32\xe3\x07\xe6\x96\x0e\xb9\x74\x41\x50\x6c\x2e\xd2\x99\xe1\x28\x25\x23\xf4\x15\x27", + "95ac9b7d22aa458921874c4b4331e7d64761853217c3f83c601abcbccd7e2eaa6ca6ce9a22ebcfe5046d52f8a09097f043ab8bc59243fd770090bb432c3155e9" + }, { + 106, + "\x69\xff\xf0\xf1\xa3\xdb\xfb\x36\xe3\x2f\x02\x58\x19\xfa\x99\xea\x9a\x0e\xda\xef\x73\x14\x5b\xf7\xfc\xd0\x5d\x8b\xb0\xa6\x46\xcb\x3b\x5d\x52\x56\xd5\x24\x85\x6a\xcf\xd2\xe4\x4d\x6b\x72\xe4\xeb\xf1\xff\x23\xc0\xff\x6c\x56\xf8\x21\xe7\x82\xd5\xa1\x5f\x70\x52\xa1\x44\x5b\x06\x66\x8e\xeb\x4a\xf7\x00\x67\x9e\xe7\xae\x26\x49\x6f\xbd\x46\x40\xc0\x6a\xa1\x49\x96\x4d\xfd\x60\x11\xdf\x83\x5a\xc1\x3b\x73\xc8\xff\x21\x15\x1e\x84\x40", + "45d4daa652558d1c12beb0f5662c712f325b4c802fc6eb9ee039c949d002bb786f1a732712be941f9c5c79b3e5c43064d63a38578e5a54ee526acb735b9ad45f" + }, { + 107, + "\xb2\xc4\x39\xc9\x7a\xb7\xc6\x37\x36\xb3\x79\x63\x24\xd6\x8e\xeb\x7a\x47\x1e\xd1\x42\xbd\x96\x22\x68\x41\x67\xd6\x12\x34\xff\xf8\x2f\x93\xf9\x07\x53\x7a\x90\x9b\xc2\xe7\x5a\x4b\xcb\xc1\x33\xcf\x57\x19\x76\x62\xc1\xaf\x74\x6a\xe8\xb8\x1e\x5b\x83\xde\x05\xd9\xb5\x89\x85\x1d\xe2\x5d\x3c\x99\xc0\x04\xc1\xdf\xb1\x2d\x93\xbf\x50\xd4\x50\xaf\x49\xc4\x28\x71\x6f\x5b\x90\xef\x08\x8e\x3b\x6a\x6b\x2c\x46\xd3\xce\x67\xb3\x79\x59\x90\x18", + "c48ec83be5fa669e6ec8db90aca9676cfe2ec0d5e8e7a2431687bb953c0a300be3db4075cca3bac4dfa4d971baf0fa1aff46639db4b238856ff36d1dfcd520f1" + }, { + 108, + "\xc0\x16\xf5\x22\xf2\x6b\x74\x70\xe9\x22\xb9\xa2\x87\xe6\xd4\x5f\x6c\x28\x81\x3b\x68\xc1\x45\x7e\x36\xd9\xba\x26\x67\x08\x27\x2f\x9c\xbc\x54\x11\xf8\xdb\x9d\x8b\xd5\xa9\x44\x9f\xb6\xeb\x0c\xde\x7d\x4d\x03\xe5\xdf\x01\x9f\x28\x14\xa9\x0c\xee\xd3\x77\xc5\x9d\x7d\x92\x62\x38\x99\xbc\xb0\x26\x80\x33\x07\x35\x59\xd4\xd8\xde\x48\x86\x86\xcb\xe3\xd6\x77\x96\xe6\xdf\x6a\xd4\x27\x6d\x0b\x52\xcc\x62\xc4\x9e\xbb\x58\xd7\xc9\x52\x87\xaa\x6c", + "7402f1a99b47e102b3b73140c6771b07ee6c33b3715e9c4027c441bee40511b735d95e508baea78da26fded9b7038e9a53defa58448aba40dc1e62d7ec592107" + }, { + 109, + "\xa7\x66\xb2\xa7\xef\x91\x67\x21\xf4\x67\x7b\x67\xdb\xc6\x5e\xf9\xb4\xd1\xbd\xa1\xad\x4e\x53\xfc\x85\x4b\x02\x36\x44\x08\x22\x15\x2a\x11\x19\x39\xe5\xab\x2b\xa2\x07\x71\x94\x72\xb6\x3f\xd4\xf4\xa5\x4f\x4b\xde\x44\xa2\x05\xd3\x34\xa2\xd7\x2c\xfe\x05\xab\xf8\x04\xf4\x18\x41\xb8\x6d\x36\x92\x0b\xe6\xb0\xb5\x29\x33\x1a\xc1\x63\xa9\x85\x55\x6c\x84\x51\x1e\xc9\x86\x43\x9f\x83\xe1\xd7\x31\x1f\x57\xd8\x48\xcf\xa0\x2d\xf9\xea\x0c\xf6\xb9\x9a", + "ddd60f93a3babc78299cf763e7919d45ac6f479700e1adb05ab137acdf89c1521ecb9dfeacd091e58ca57a1db964a9c3cd1fa39192cc1e9f734caa1c5fa62975" + }, { + 110, + "\x10\xf2\xbe\x77\xa4\x05\x57\x71\xa6\x70\x07\xcd\x86\x30\xe3\x23\x0e\x38\x28\x84\x99\xcb\x16\x03\x80\x29\x01\x74\xd6\x6d\xa5\x74\x55\xb6\xba\xaa\x97\x85\xc8\x4c\x8a\x66\x3d\xe4\x1e\xd3\xbd\x54\x40\x55\xb9\x17\x0c\xec\x43\xcb\x3e\xb1\x20\xec\xea\xba\x1f\xe3\x6e\x3e\xaa\x3f\xa4\xf9\x9b\x42\x5c\xd2\x51\x9f\x09\xbc\x02\x82\xba\xda\x52\xd1\x4c\xe6\x25\xb1\xde\xd3\xb2\x4d\x86\xb1\xda\xd3\x42\xd2\xb7\xbe\x32\x2b\x77\x5b\x04\xfc\x6b\x86\xaf\xb4", + "a872fa33d463b3343cec57c20c66979c33e1ad067bfc703454696aab5dd0003bc194318f4a8ebbc74503feb7211a472dadee991efe3e38f21a1310f8a76eac80" + }, { + 111, + "\x32\x45\x33\xe6\x85\xf1\x85\x2e\x35\x8e\xea\x8e\xa8\xb8\x1c\x28\x8b\x3f\x3b\xeb\x1f\x2b\xc2\xb8\xd3\xfd\xba\xc3\x18\x38\x2e\x3d\x71\x20\xde\x30\xc9\xc2\x37\xaa\x0a\x34\x83\x1d\xeb\x1e\x5e\x06\x0a\x79\x69\xcd\x3a\x97\x42\xec\x1e\x64\xb3\x54\xf7\xeb\x29\x0c\xba\x1c\x68\x1c\x66\xcc\x7e\xa9\x94\xfd\xf5\x61\x4f\x60\x4d\x1a\x27\x18\xaa\xb5\x81\xc1\xc9\x49\x31\xb1\x38\x7e\x4b\x7d\xc7\x36\x35\xbf\x3a\x73\x01\x17\x40\x75\xfa\x70\xa9\x22\x7d\x85\xd3", + "3b26c5170729d0814153becb95f1b65cd42f9a6d0649d914e4f69d938b5e9dc041cd0f5c8da0b484d7c7bc7b1bdefb08fe8b1bfedc81109345bc9e9a399feedf" + }, { + 112, + "\x51\x89\x85\x97\x7e\xe2\x1d\x2b\xf6\x22\xa2\x05\x67\x12\x4f\xcb\xf1\x1c\x72\xdf\x80\x53\x65\x83\x5a\xb3\xc0\x41\xf4\xa9\xcd\x8a\x0a\xd6\x3c\x9d\xee\x10\x18\xaa\x21\xa9\xfa\x37\x20\xf4\x7d\xc4\x80\x06\xf1\xaa\x3d\xba\x54\x49\x50\xf8\x7e\x62\x7f\x36\x9b\xc2\x79\x3e\xde\x21\x22\x32\x74\x49\x2c\xce\xb7\x7b\xe7\xee\xa5\x0e\x5a\x50\x90\x59\x92\x9a\x16\xd3\x3a\x9f\x54\x79\x6c\xde\x57\x70\xc7\x4b\xd3\xec\xc2\x53\x18\x50\x3f\x1a\x41\x97\x64\x07\xaf\xf2", + "c00926a374cde55b8fbd77f50da1363da19744d3f464e07ce31794c5a61b6f9c85689fa1cfe136553527fd876be91673c2cac2dd157b2defea360851b6d92cf4" + }, { + 113, + "\x91\x59\x76\x72\x75\xba\x6f\x79\xcb\xb3\xd5\x8c\x01\x08\x33\x9d\x8c\x6a\x41\x13\x89\x91\xab\x7a\xa5\x8b\x14\x79\x3b\x54\x5b\x04\xbd\xa6\x1d\xd2\x55\x12\x7b\x12\xcc\x50\x1d\x5a\xaa\xd4\x76\xe0\x9f\xa1\x4a\xec\x21\x62\x6e\x8d\x57\xb7\xd0\x8c\x36\xcd\xb7\x9e\xea\x31\x4b\xdd\x77\xe6\x57\x79\xa0\xb5\x4e\xab\x08\xc4\x8c\xeb\x97\x6a\xdf\x63\x1f\x42\x46\xa3\x3f\x7e\xf8\x96\x88\x7e\xa8\xb5\xdf\xa2\x08\x7a\x22\x5c\x8c\x18\x0f\x89\x70\x69\x61\x01\xfc\x28\x3b", + "3cd3380a90868de17dee4bd4d7f90d7512696f0a92b2d089240d61a9d20cd3af094c78bf466c2d404dd2f662ec5f4a299be2adeadf627b98e50e1c072b769d62" + }, { + 114, + "\xfe\x2d\x8a\xe2\x00\xe6\x65\x7f\xdc\x74\x94\xaf\x5a\x12\xb2\xae\x94\x03\x48\xf1\xf9\x83\xf0\xba\x98\xfe\xbb\xe9\x9c\x80\xd1\x15\x12\x6d\x57\xdb\xf3\x72\x96\x76\x5e\xbb\x59\x90\x25\x66\x96\x58\x8b\x38\x51\xd5\x4c\x8f\xbe\x7a\xde\x98\xa6\xfa\xf7\xc2\x0b\x5e\x4f\x73\x0f\x54\xa7\xf9\x12\xca\x0a\xc3\x1b\xbb\x53\xd1\x79\x49\xef\x69\xaa\x0d\xe4\x0c\x7b\xab\x12\xa8\x71\xa9\xb9\x0f\x68\x81\x3c\xa8\x7a\xf4\x25\x64\x22\xa2\x68\xf4\xa1\xd8\xec\x3a\xa1\xa9\x47\xfd", + "8025a8608df0f6a01c34cdec012d4cb25852e1b100b68172fc4e86ac8b7126b64859cb9e767a7e59060989cedbd925afc475ca7369bd43f85ae590e224e036dd" + }, { + 115, + "\xdc\x28\x48\x4e\xbf\xd2\x93\xd6\x2a\xc7\x59\xd5\x75\x4b\xdf\x50\x24\x23\xe4\xd4\x19\xfa\x79\x02\x08\x05\x13\x4b\x2c\xe3\xdf\xf7\x38\xc7\x55\x6c\x91\xd8\x10\xad\xba\xd8\xdd\x21\x0f\x04\x12\x96\xb7\x3c\x21\x85\xd4\x64\x6c\x97\xfc\x0a\x5b\x69\xed\x49\xac\x8c\x7c\xed\x0b\xd1\xcf\xd7\xe3\xc3\xcc\xa4\x73\x74\xd1\x89\x24\x7d\xa6\x81\x1a\x40\xb0\xab\x09\x70\x67\xed\x4a\xd4\x0a\xde\x2e\x47\x91\xe3\x92\x04\xe3\x98\xb3\x20\x49\x71\x44\x58\x22\xa1\xbe\x0d\xd9\x3a\xf8", + "615115d2e8b62e345adaa4bdb95395a3b4fe27d71c4a111b86c1841463c5f03d6b20d164a39948ab08ae060720d05c10f6022e5c8caf2fa3bca2e04d9c539ded" + }, { + 116, + "\x5a\xf8\xc0\xf2\x6d\xb4\xe9\x9b\x47\xec\x2e\x4a\x01\xa7\x86\xe7\x78\x99\xe4\x6d\x46\x4a\xc3\x37\xf1\x75\x02\x7b\x61\xae\xf3\x14\x98\x48\xaf\x84\x9d\x76\xac\x39\xb9\xb0\x91\x0f\xe6\x59\x48\x17\x85\x9e\x55\x97\x4f\xa1\x67\x51\x8e\xd7\x2d\x08\x8d\xae\x6b\x41\x4d\x74\x4d\x47\x79\x74\xfb\x71\x9c\x62\x6d\xa7\x92\xf9\x81\x23\x3d\xe2\x4b\x75\x79\xd8\xac\xca\x51\x0a\x26\x6d\x73\xc0\xee\x8e\xe1\x42\x43\x43\xea\xf6\xff\xcc\x59\xc8\x6c\x1b\xec\xce\x58\x94\x07\x2c\x6c\x11", + "09da284d5b6556508be54c8ab6c97bbd472995c6bbd585917ecdb54ea9167208daaa070a7b2b7d8e93ce1315f0d1ef8d69667429c44dc5ee1499de57b229a398" + }, { + 117, + "\x49\xcd\x0b\xa0\xdf\x5b\xb3\xf4\x3f\x68\x46\x4e\x3e\x83\xe9\xcb\xd5\xd5\xee\x07\x7f\xfa\x55\x91\xe3\x0f\x93\x9c\xb3\x0c\x93\xf7\xd4\x54\xfb\x3f\xbf\x8b\xb0\x53\x27\xa8\x9c\x08\xdc\x4b\xaf\x1e\xef\x50\x23\x73\x17\xa4\x05\x77\x53\x57\xf1\xe0\xd1\xf3\x1d\x9f\x0f\x0d\x98\x12\x40\x19\xd4\x7b\xf1\x83\x63\xb1\xec\xfb\xfe\x15\x5c\x10\xcb\xc8\x33\x00\xe0\x1b\xc9\xce\x03\x47\xc5\x96\xb3\x5f\x41\x1e\x6d\x82\x29\xad\x28\x55\xe4\x20\x22\xb0\x37\x3a\xde\x98\x66\x3c\x6d\x6e\x9c", + "30cbf0679a97c871574d2fc05d7aa760c6bc8a864b7d246c39b9e812f9b7ff7b4ef5197dd5b69493306688b8564de1ad47d75505c913ba6a78788f8caf5788bd" + }, { + 118, + "\xa8\xa3\x7d\xfc\x08\x3a\xd2\xf4\x7f\xff\x46\x87\x38\xbf\x8b\x72\x8e\xb7\xf1\x90\x7e\x42\x7f\xa1\x5c\xb4\x42\x4b\xc6\x85\xe5\x5e\xd7\xb2\x82\x5c\x9c\x60\xb8\x39\xcc\xc2\xfe\x5f\xb3\x3e\x36\xf5\x70\xcb\x86\x61\x60\x9e\x63\x0b\xda\x05\xee\x64\x1d\x93\x84\x28\x86\x7d\x90\xe0\x07\x44\xa4\xaa\xd4\x94\xc9\x3c\x5f\x6d\x13\x27\x87\x80\x78\x59\x0c\xdc\xe1\xe6\x47\xc9\x82\x08\x18\xf4\x67\x64\x1f\xcd\x50\x8e\x2f\x2e\xbf\xd0\xff\x3d\x4f\x27\x23\x93\x47\x8f\x3b\x9e\x6f\x80\x6b\x43", + "8e1c91729be8eb40226f6c58a029380ef7edb9dc166a5c3cdbcefe90bd30d85cb7c4b248e66abf0a3a4c842281299bef6db88858d9e5ab5244f70b7969e1c072" + }, { + 119, + "\x36\xaf\x17\x59\x54\x94\xef\x79\x3c\x42\xf4\x84\x10\x24\x6d\xf0\x7d\x05\x93\x6a\x91\x8a\xfe\x74\xcd\x00\x5e\x53\x7c\x58\x6b\x28\x43\x70\x1f\x5d\xf8\x95\x22\x42\xb7\x45\x86\xf8\x33\x39\xb4\x8f\x4b\xa3\xa6\x6b\xde\xb4\x57\xec\xdf\x61\x78\x4e\xac\x67\x65\xcd\x9b\x8c\x57\x0d\xd6\x28\xdb\xba\x6a\xe5\x83\x6b\x9a\xc3\xdb\xcd\x79\x5f\x9e\xfd\xb8\x74\x2a\x35\xbc\xa2\x32\xab\xf3\x6e\xb3\xb6\x69\x8b\x29\x33\x96\x58\x02\x27\x7b\xa9\x53\xa6\xed\xca\xca\xf3\x30\xc1\xe4\xe8\xc7\xd4\x5f", + "158bfc348a30b4fabbe355a7d44bdc2122a4c850444c03f289003ce01bfc1ebf3ecc0febb6a8ff523d25db7681b05bdce048d11943ab476c1967cf6556c4a120" + }, { + 120, + "\x42\xd6\x6e\xdc\x5f\x22\xe0\xc1\x3c\x25\x50\x4c\x51\x01\xa5\xd1\x72\xd2\xdb\x72\x09\xe4\x61\xef\xa3\x23\xc0\xbf\xae\xd2\x7e\x5f\x80\x80\x42\xea\x9c\x38\x38\xea\x31\xf9\xb7\x6d\xe4\x65\x22\x5c\xcf\xbd\x0c\x09\xca\x0d\x9f\x07\xe9\xa4\x3e\x3e\x46\xc7\x69\x3e\x00\xa7\xe1\xd4\x83\x90\x0d\xdb\x0a\x62\x9d\x55\x63\x45\x6d\xbb\xf2\x99\xac\x91\xf9\x2c\x3d\x3c\x17\xb0\x5d\x18\x0e\x6c\x87\xc6\xc9\x31\x94\xc3\x9d\x90\x27\x3f\xcf\x4a\x48\x2c\x56\x08\x4f\x95\xe3\x4c\x04\x31\x1f\xa8\x04\x38", + "061afb119a3c60876e04c10f12ad0f4b977593dc5a2d21096a57e7d3f7d4d44fdef934b2c17d7530674e4f4a1c176dbdcc54811a22e1b8712e4192fc2d4bf8e8" + }, { + 121, + "\xf9\x1b\xb2\xe1\xa9\xc4\xcd\x96\xbf\x25\x04\x26\xb3\xa6\xaf\xd9\xb8\x7a\xc5\x1e\x93\x25\x4d\x2d\xae\x3b\x16\xec\x68\x6b\xa8\x0f\xb0\xbd\x7a\x84\xd2\x18\x66\x0e\x90\x07\x59\x30\x75\xbc\x4f\x4c\x66\x56\x7f\x0c\x7a\x5f\xd2\x01\x0c\x99\x9a\x8a\x0e\xfa\x81\xf8\x9f\xf5\xbf\xef\xe0\xfb\x91\x0f\x04\x42\xe6\xd4\xa7\xc5\x5b\xbb\x61\x8c\x69\xa7\x9a\x2d\xdd\x82\xa0\x93\x89\x27\xf6\xfe\x3a\x80\xf0\x4b\xea\xeb\x7c\x76\x36\xe3\x43\x5d\x12\xdc\xf1\xc6\xbb\x6e\xd0\xa4\xed\xb6\x9c\x96\x57\xfa\x93", + "6e692c8c694ee0a3565f37a299e0006b85ab4a821b20e76798220229f656efc6a20211a4e7e4ed77facde0d70e4d5d95bc8ed1d7a56d8df1446d562f044b344c" + }, { + 122, + "\xd1\xeb\x96\x1c\xa6\xa8\xf6\x7c\x49\xb6\x1e\x4d\x3c\xea\xa2\xa1\xde\x6f\x0e\xa9\x27\xb1\x32\xbf\x98\x7a\xbd\xaa\x72\x5b\x0e\x1e\x27\x4e\x46\x83\x0e\x99\xa2\xf7\x5a\xf6\x08\x96\x4d\xf0\xdf\xf9\xa9\x90\x24\xfc\x68\x39\xba\xc5\xac\xd1\x02\x02\xf9\x21\xac\x71\xa2\x7f\xcd\xa6\x81\xaa\x31\x09\xeb\xf5\xf2\x1e\xe3\xa8\x49\x09\x8e\xa3\xa5\x51\xe8\x44\xfa\xe4\xb4\x8b\x5c\x5b\xb9\x7c\xcc\x80\x2b\xc5\x52\x0d\x68\xa1\x4c\xb7\xe5\xfc\x05\x6b\x67\xd8\x89\xd8\x76\xef\xb8\x2d\x0e\x9a\x9a\x24\x99\xf1", + "39b2c76ec207120de4b320c7fe069e602c9c38f257596da7369395e87eb64b3acff988c1839ac269d5012c093f9edd4b7cabf13bdea7d42e969ab108269c6ab0" + }, { + 123, + "\xad\xf2\x26\x32\x00\xf3\x76\x88\x6b\xa7\xb6\xf5\xe4\x41\x1d\x5f\x07\xf7\xd9\xd1\x01\x59\x0c\x73\xac\xe1\x14\xba\xfb\xcb\x0f\xdc\x99\x26\x9e\x87\xcd\x2c\xea\xd2\xa1\xcf\xe5\x74\x43\x94\xd3\x33\xab\xa4\x08\xa0\x7e\x21\xf3\x02\x33\xb6\x5b\x90\x74\x72\xe9\xe3\xc7\xd6\xe7\xaa\x6d\x2c\x47\xa0\x8a\x1b\xe7\xbb\x87\x79\x13\xa6\xb5\x60\x4c\x72\x33\x84\x47\x89\x11\xc3\x39\xe3\xb5\xfe\x52\x7c\x7e\x28\x87\x05\xa8\x9c\x95\xd9\x70\xb4\x43\x34\x78\x97\xe7\x9f\x6c\x52\x2b\xaf\xe6\x2b\x11\xef\x8f\x31\x35", + "3c23d2d8cf4db6ac6a42e27208180f37668bef5ee0a3f879483c8e604e7f42583f202037b8d242c04a87345b8be6dc8b121d6484b9edad0d73c894c1288f5cae" + }, { + 124, + "\x18\xe7\x5b\x47\xd8\x98\xac\x62\x9c\x48\xe8\x0d\xbf\xb7\x5d\xae\x1e\x17\x00\xb7\x71\x16\x5e\xcc\xdb\x18\xd6\x28\xbf\xc4\x06\x3d\xd6\xc3\x83\x9a\x7e\xc4\xcd\x12\x55\xc4\x82\x1b\x07\x8c\xd1\x74\x64\x7b\x32\x0b\xb6\x85\x54\x1d\x51\x7c\x57\x9f\x6b\x8e\x3c\xdd\x2e\x10\x9a\x61\x0c\x7a\x92\x16\x53\xb2\x04\xad\x01\x8d\x03\x40\xd9\x93\x87\x35\xb6\x02\x62\x66\x20\x16\x76\x7e\x1d\x88\x24\xa6\x49\x54\x08\x62\x29\xc0\xe3\xb5\xbd\x9a\xd8\x8c\x54\xc1\xdc\x5a\xa4\xe7\x68\xff\x1a\x94\x70\xee\x6f\x6e\x99\x8f", + "01c756b7c20b5f95fd2b079ab6a50f28b946fb16266b07c6060945dc4fe9e0d279c5b1505b9ec7d8f8f3c9ebf0c5ee9365aec08cf278d65b64daeccc19d3cbf4" + }, { + 125, + "\xc2\x96\x33\x42\xcf\xaa\x88\xcc\xd1\x02\xa2\x58\xe6\xd6\x29\xf6\xb0\xd3\x67\xdd\x55\x11\x65\x02\xca\x44\x51\xea\x52\x36\x23\xbc\x41\x75\x81\x9a\x06\x48\xdf\x31\x68\xe8\xea\x8f\x10\xed\x27\x35\x48\x07\xd7\x6e\x02\xee\x1f\xdf\x1c\x9c\x65\x5e\xe2\xb9\xfd\x08\xd5\x57\x05\x8d\xab\xdf\x8d\xcf\x96\x4b\xfc\xac\xc9\x96\xae\x17\x39\x71\xe2\x6e\xa0\x38\xd4\x07\xc8\x24\x26\x0d\x06\xc2\x84\x8a\x04\xa4\x88\xc4\xc4\x56\xdb\xcd\xe2\x93\x9e\x56\x1a\xb9\x08\xc4\x09\x7b\x50\x86\x38\xd6\xcd\xa5\x56\x46\x5c\x9c\xc5", + "a4d2f59393a5fea612c3c745f4bb9f41aaf3a3ce1679aa8afc1a62baa4ed452819418c8ae1a1e658757976692390fc43d4decf7d855cd8b498b6dc60cae05a90" + }, { + 126, + "\x85\x36\x0c\x3d\x42\x57\xd9\x87\x8e\x2f\x5c\x16\xd3\xcd\x7d\x07\x47\xdf\x3d\x23\x1e\x1a\x8f\x63\xfd\xdc\x69\xb3\xb1\x10\x1a\xf7\x21\x53\xde\x4c\x81\x54\xb0\x90\xc9\x81\x5f\x24\x66\xe0\xe4\xf0\x2f\x3a\xf3\xa8\x9a\x7f\xd0\x4e\x30\x66\x64\xf9\x3e\x54\x90\xd4\xce\x7f\xc1\x69\xd5\x53\xc5\x20\xae\x15\xdd\x02\xc7\xc6\x13\xc3\x9b\x4a\xcd\x00\xe0\xc9\xa3\xc5\x01\x56\x6e\x52\xce\xce\xa1\x1f\x73\x03\xdd\x1d\xa6\x1a\xbf\x3f\x25\x32\xfd\x39\x60\x47\xb1\x88\x72\x55\xf4\xb2\x56\xc0\xaf\xcf\x58\xf3\xae\x48\xc9\x47", + "e8352ddcac59e377ea0f9c32bbb43dfd1b6c829fad1954240c41b7c45b0b09db11064b64e2442a96f6530aac2c4abf3beb1eae77f2bce4efe88fee1a70cf5423" + }, { + 127, + "\xc1\x3e\x6c\xa3\xab\xb8\x93\xaa\x5f\x82\xc4\xa8\xef\x75\x44\x60\x62\x8a\xf6\xb7\x5a\xf0\x21\x68\xf4\x5b\x72\xf8\xf0\x9e\x45\xed\x12\x7c\x20\x3b\xc7\xbb\x80\xff\x0c\x7b\xd9\x6f\x8c\xc6\xd8\x11\x08\x68\xeb\x2c\xfc\x01\x03\x7d\x80\x58\x99\x2a\x6c\xf2\xef\xfc\xbf\xe4\x98\xc8\x42\xe5\x3a\x2e\x68\xa7\x93\x86\x79\x68\xba\x18\xef\xc4\xa7\x8b\x21\xcd\xf6\xa1\x1e\x5d\xe8\x21\xdc\xab\xab\x14\x92\x1d\xdb\x33\x62\x5d\x48\xa1\x3b\xaf\xfa\xd6\xfe\x82\x72\xdb\xdf\x44\x33\xbd\x0f\x7b\x81\x3c\x98\x12\x69\xc3\x88\xf0\x01", + "6e56f77f6883d0bd4face8b8d557f144661989f66d51b1fe4b8fc7124d66d9d20218616fea1bcf86c08d63bf8f2f21845a3e519083b937e70aa7c358310b5a7c" + }, { + 128, + "\xfd\x22\x03\xe4\x67\x57\x4e\x83\x4a\xb0\x7c\x90\x97\xae\x16\x45\x32\xf2\x4b\xe1\xeb\x5d\x88\xf1\xaf\x77\x48\xce\xff\x0d\x2c\x67\xa2\x1f\x4e\x40\x97\xf9\xd3\xbb\x4e\x9f\xbf\x97\x18\x6e\x0d\xb6\xdb\x01\x00\x23\x0a\x52\xb4\x53\xd4\x21\xf8\xab\x9c\x9a\x60\x43\xaa\x32\x95\xea\x20\xd2\xf0\x6a\x2f\x37\x47\x0d\x8a\x99\x07\x5f\x1b\x8a\x83\x36\xf6\x22\x8c\xf0\x8b\x59\x42\xfc\x1f\xb4\x29\x9c\x7d\x24\x80\xe8\xe8\x2b\xce\x17\x55\x40\xbd\xfa\xd7\x75\x2b\xc9\x5b\x57\x7f\x22\x95\x15\x39\x4f\x3a\xe5\xce\xc8\x70\xa4\xb2\xf8", + "a21b1077d52b27ac545af63b32746c6e3c51cb0cb9f281eb9f3580a6d4996d5c9917d2a6e484627a9d5a06fa1b25327a9d710e027387fc3e07d7c4d14c6086cc" + }, { + 0, + NULL, + NULL + } +}; + +static void +run_tests(unsigned int digest_type, struct test_vector *test_vectors) +{ + struct sudo_digest *ctx; + unsigned char md[64]; /* SHA512_DIGEST_LENGTH */ + char mdhex[128 + 1]; /* SHA512_DIGEST_LENGTH * 2 + 1 */ + size_t i, j, digest_len; + + digest_len = sudo_digest_getlen(digest_type); + if (digest_len == 0) + sudo_fatalx("unable to get digest length for type %d", digest_type); + if (digest_len > ssizeof(md)) + sudo_fatalx("digest length too big for type %d", digest_type); + + ctx = sudo_digest_alloc(digest_type); + if (ctx == NULL) + sudo_fatal(NULL); + + for (i = 0; test_vectors[i].str != NULL; i++) { + ntests++; + sudo_digest_update(ctx, test_vectors[i].str, test_vectors[i].len); + sudo_digest_final(ctx, md); + + /* Convert md to a hex string. */ + for (j = 0; j < digest_len; j++) { + mdhex[j * 2] = hex[md[j] >> 4]; + mdhex[(j * 2) + 1] = hex[md[j] & 0x0f]; + } + mdhex[j * 2] = '\0'; + + if (strcmp(test_vectors[i].md, mdhex) != 0) { + sudo_warnx("test %u:%zu: expected %s, got %s", digest_type, i, + mdhex, test_vectors[i].md); + errors++; + } + sudo_digest_reset(ctx); + } + sudo_digest_free(ctx); + + return; +} + +/* + * Verify SHA2 functions using NIST byte-oriented short message test vectors. + */ +int +main(int argc, char *argv[]) +{ + int ch; + + initprogname(argc > 0 ? argv[0] : "digest_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; + } + } + + run_tests(SUDO_DIGEST_SHA224, sha224_vectors); + run_tests(SUDO_DIGEST_SHA256, sha256_vectors); + run_tests(SUDO_DIGEST_SHA512, sha512_vectors); + + 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/fnmatch/fnm_test.c b/lib/util/regress/fnmatch/fnm_test.c new file mode 100644 index 0000000..e4689c8 --- /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..e2e2723 --- /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; + ssize_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 == -1) { + 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..31548cc --- /dev/null +++ b/lib/util/regress/getdelim/getdelim_test.c @@ -0,0 +1,186 @@ +/* + * 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 ** restrict bufp, size_t * restrict bufsizep, + int delim, FILE * restrict 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..83518f3 --- /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..e400e60 --- /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..f3623a7 --- /dev/null +++ b/lib/util/regress/glob/globtest.c @@ -0,0 +1,224 @@ +/* $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') { + fputs("globtest: missing newline at EOF\n", stderr); + 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..097cbcd --- /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((size_t)ntests, sizeof(*test_data)); + 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); + } + return 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..5acaeb5 --- /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..b441431 --- /dev/null +++ b/lib/util/regress/mktemp/mktemp_test.c @@ -0,0 +1,205 @@ +/* + * 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, size_t 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, size_t 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, size_t 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, (int)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, i; + size_t pg; + int ch; + + 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 = (size_t)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 + ch = open("/dev/zero", O_RDWR); + if (ch == -1) + sudo_fatal("/dev/zero"); + p = mmap(NULL, pg * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE, ch, 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; + + for (i = MAX_TEMPLATE_LEN; i != 0; i--) { + /* 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..a6e70c1 --- /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..0a4f252 --- /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..3a94a72 --- /dev/null +++ b/lib/util/regress/parse_gids/parse_gids_test.c @@ -0,0 +1,124 @@ +/* + * 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; + size_t i; + int 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..330736c --- /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..ce629c6 --- /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..79b40fa --- /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..fb868f4 --- /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); + } + return 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..8264d4f --- /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..6e5998f --- /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", (id_t)-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..c7a31b0 --- /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..a90ed02 --- /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..dc65bc3 --- /dev/null +++ b/lib/util/regress/sudo_conf/conf_test.c @@ -0,0 +1,126 @@ +/* + * 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 + +sudo_noreturn 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(); + /* NOTREACHED */ + } + } + 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..9463767 --- /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..412d7a9 --- /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); + } + + return 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..411266c --- /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..73cf4af --- /dev/null +++ b/lib/util/roundup.c @@ -0,0 +1,81 @@ +/* + * 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. + */ +unsigned int +sudo_pow2_roundup_v1(unsigned int len) +{ + if (len < 64) + return 64; + +#ifdef HAVE___BUILTIN_CLZ + return 1U << (32 - __builtin_clz(len - 1)); +#else + len--; + len |= len >> 1; + len |= len >> 2; + len |= len >> 4; + len |= len >> 8; + len |= len >> 16; + len++; + return len; +#endif +} + +/* + * Round a size_t length to the next highest power of two. + * Always returns at least 64. + */ +size_t +sudo_pow2_roundup_v2(size_t len) +{ + if (len < 64) + return 64; + +#if defined(__LP64__) && defined(HAVE___BUILTIN_CLZL) + return 1UL << (64 - __builtin_clzl(len - 1)); +#elif !defined(__LP64__) && defined(HAVE___BUILTIN_CLZ) + return 1U << (32 - __builtin_clz(len - 1)); +#else + len--; + len |= len >> 1; + len |= len >> 2; + len |= len >> 4; + len |= len >> 8; + len |= len >> 16; +# ifdef __LP64__ + len |= len >> 32; +# endif + len++; + return len; +#endif +} diff --git a/lib/util/secure_path.c b/lib/util/secure_path.c new file mode 100644 index 0000000..aaceb57 --- /dev/null +++ b/lib/util/secure_path.c @@ -0,0 +1,203 @@ +/* + * 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 <errno.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); +} + +/* + * Verify that fd matches type and not writable by other users. + */ +int +sudo_secure_fd_v1(int fd, unsigned int type, uid_t uid, gid_t gid, + struct stat *sb) +{ + int ret = SUDO_PATH_MISSING; + debug_decl(sudo_secure_fd, SUDO_DEBUG_UTIL); + + if (fd != -1 && fstat(fd, sb) == 0) + ret = sudo_check_secure(sb, type, uid, gid); + debug_return_int(ret); +} + +/* + * 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, unsigned 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); +} + +/* + * Open the first file found in a colon-separated list of paths. + * Subsequent files in the path are only attempted if the + * previous file does not exist. Errors other than ENOENT are + * considered fatal and will stop processing the path. + * Sets name based on the last file it tried to open, even on error. + */ +int +sudo_open_conf_path_v1(const char *path, char *name, size_t namesize, + int (*fn)(const char *, int)) +{ + const char *cp, *ep, *path_end; + int fd = -1; + debug_decl(sudo_open_conf_path, SUDO_DEBUG_UTIL); + + path_end = path + strlen(path); + for (cp = sudo_strsplit(path, path_end, ":", &ep); + cp != NULL; cp = sudo_strsplit(NULL, path_end, ":", &ep)) { + + const size_t len = (size_t)(ep - cp); + if (len >= namesize) { + /* We always set name, even on error. */ + memcpy(name, cp, namesize - 1); + name[namesize - 1] = '\0'; + errno = ENAMETOOLONG; + break; + } + memcpy(name, cp, len); + name[len] = '\0'; + + fd = fn ? + fn(name, O_RDONLY|O_NONBLOCK) : open(name, O_RDONLY|O_NONBLOCK); + if (fd != -1) { + (void)fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK); + break; + } + if (errno != ENOENT) + break; + } + debug_return_int(fd); +} diff --git a/lib/util/setgroups.c b/lib/util/setgroups.c new file mode 100644 index 0000000..7298fcc --- /dev/null +++ b/lib/util/setgroups.c @@ -0,0 +1,56 @@ +/* + * 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) +{ + long maxgids; + int ret; + debug_decl(sudo_setgroups, SUDO_DEBUG_UTIL); + + if (ngids < 0) + debug_return_int(-1); + + ret = setgroups(ngids, (GETGROUPS_T *)gids); + if (ret == -1 && errno == EINVAL) { + /* Too many groups, try again with fewer. */ + maxgids = 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..7ec6504 --- /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..11acc5e --- /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])) { + size_t i; + for (i = 0; signame[i] != '\0'; i++) + signame[i] = (char)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..64765da --- /dev/null +++ b/lib/util/snprintf.c @@ -0,0 +1,1546 @@ +/* $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 * restrict fmt0, va_list ap, union arg **argtable); +static int __grow_type_table(unsigned char **typetable, int *tablesize); +static int xxxprintf(char ** restrict, size_t, int, const char * restrict, 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 ** restrict strp, size_t strsize, int alloc, const char * restrict 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 (*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 * restrict str, size_t n, const char * restrict 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 * restrict str, size_t n, char const * restrict 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 ** restrict str, const char * restrict 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 ** restrict str, char const * restrict 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..5b0bcbf --- /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 = (int)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..a124a4f --- /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 * restrict dst, const char * restrict 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 = (size_t)(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 + (size_t)(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..c2daada --- /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 * restrict dst, const char * restrict 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((size_t)(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..f44fc8d --- /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..c394515 --- /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..411252f --- /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..2343975 --- /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..61aebbc --- /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..7e66a53 --- /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 = (id_t)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..1902031 --- /dev/null +++ b/lib/util/strtomode.c @@ -0,0 +1,71 @@ +/* + * 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. + */ +mode_t +sudo_strtomode_v2(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_mode_t(0); + } + if (lval < 0 || lval > ACCESSPERMS) { + if (errstr != NULL) + *errstr = lval < 0 ? N_("value too small") : N_("value too large"); + errno = ERANGE; + debug_return_mode_t(0); + } + if (errstr != NULL) + *errstr = NULL; + debug_return_mode_t((mode_t)lval); +} + +int +sudo_strtomode_v1(const char *cp, const char **errstr) +{ + return (int)sudo_strtomode_v2(cp, errstr); +} diff --git a/lib/util/strtonum.c b/lib/util/strtonum.c new file mode 100644 index 0000000..78e42e9 --- /dev/null +++ b/lib/util/strtonum.c @@ -0,0 +1,192 @@ +/* + * 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; + int remainder; + char ch, sign; + + if (minval > maxval) { + errval = STN_INVALID; + goto done; + } + + /* Trim leading space and check sign, if any. */ + do { + ch = *cp++; + } while (isspace((unsigned char)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 = -(int)(minval % 10); + if (remainder < 0) { + lastval += 1; + remainder += 10; + } + for (;; ch = *cp++) { + if (!isdigit((unsigned char)ch)) + break; + ch -= '0'; + if (result < lastval || (result == lastval && ch > remainder)) { + /* Skip remaining digits. */ + do { + ch = *cp++; + } while (isdigit((unsigned char)ch)); + errval = STN_TOOSMALL; + break; + } else { + result *= 10; + result -= ch; + errval = STN_VALID; + } + } + if (result > maxval) + errval = STN_TOOBIG; + } else { + lastval = maxval / 10; + remainder = (int)(maxval % 10); + for (;; ch = *cp++) { + if (!isdigit((unsigned char)ch)) + break; + ch -= '0'; + if (result > lastval || (result == lastval && ch > remainder)) { + /* Skip remaining digits. */ + do { + ch = *cp++; + } while (isdigit((unsigned char)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..be22e45 --- /dev/null +++ b/lib/util/sudo_conf.c @@ -0,0 +1,809 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2009-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 <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> +#ifdef HAVE_DLOPEN +# include <dlfcn.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_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(1, sizeof(*info)); + 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 = (int)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); + +#ifdef RTLD_MEMBER + /* Handle names like sudoers.a(sudoers.so) for AIX. */ + const char *cp = strchr(prog, '('); + const char *ep = strchr(prog, ')'); + if (cp != NULL && ep != NULL) { + /* Match on the program name without the member. */ + size_t len = (size_t)(cp - prog); + if (strncmp(debug_spec->progname, prog, len) == 0 && + debug_spec->progname[len] == '\0') { + debug_return_ptr(&debug_spec->debug_files); + } + + /* Match on the member itself. */ + cp++; + len = (size_t)(ep - cp); + if (strncmp(debug_spec->progname, cp, len) == 0 && + debug_spec->progname[len] == '\0') { + debug_return_ptr(&debug_spec->debug_files); + } + } +#endif + } + 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; + size_t 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 *path, int conf_types) +{ + FILE *fp = NULL; + int fd = -1, ret = false; + char *prev_locale, *line = NULL; + unsigned int conf_lineno = 0; + char conf_file[PATH_MAX]; + 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"); + + if (path != NULL) { + /* Caller specified a single file, which must exist. */ + if (strlcpy(conf_file, path, sizeof(conf_file)) >= sizeof(conf_file)) { + errno = ENAMETOOLONG; + sudo_warn("%s", path); + goto done; + } + fd = open(conf_file, O_RDONLY); + if (fd == -1) { + sudo_warn(U_("unable to open %s"), conf_file); + goto done; + } + } else { +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + struct stat sb; + int error; + + /* _PATH_SUDO_CONF is a colon-separated list of path. */ + fd = sudo_open_conf_path(_PATH_SUDO_CONF, conf_file, + sizeof(conf_file), NULL); + error = sudo_secure_fd(fd, S_IFREG, ROOT_UID, (gid_t)-1, &sb); + switch (error) { + case SUDO_PATH_SECURE: + /* OK! */ + break; + 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); + goto done; + case SUDO_PATH_BAD_TYPE: + sudo_warnx(U_("%s is not a regular file"), conf_file); + goto done; + 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); + goto done; + case SUDO_PATH_WORLD_WRITABLE: + sudo_warnx(U_("%s is world writable"), conf_file); + goto done; + case SUDO_PATH_GROUP_WRITABLE: + sudo_warnx(U_("%s is group writable"), conf_file); + goto done; + default: + sudo_warnx("%s: internal error, unexpected error %d", + __func__, error); + goto done; + } +#else + /* No default sudo.conf when fuzzing. */ + goto done; +#endif /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ + } + + if ((fp = fdopen(fd, "r")) == NULL) { + sudo_warn(U_("unable to open %s"), conf_file); + 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; + size_t 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); + else if (fd != -1) + close(fd); + 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..47a7ba7 --- /dev/null +++ b/lib/util/sudo_debug.c @@ -0,0 +1,1184 @@ +/* + * 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. + */ + +/* + * 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[STRLEN_MAX_SIGNED(int) + 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, (size_t)new_size); + if (new_fds == NULL) + goto oom; + memset(new_fds + old_size, 0, (size_t)(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) { + size_t i; + unsigned int 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) { + size_t 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 (int)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; +} + +/* Functions versions of sudo_debug_enter* for backwards compatibility. */ +void +sudo_debug_enter_v1(const char *func, const char *file, int line, + unsigned int subsys) +{ + sudo_debug_enter(func, file, line, subsys); +} + +void +sudo_debug_exit_v1(const char *func, const char *file, int line, + unsigned int subsys) +{ + sudo_debug_exit(func, file, line, subsys); +} + +void +sudo_debug_exit_int_v1(const char *func, const char *file, int line, + unsigned int subsys, int ret) +{ + sudo_debug_exit_int(func, file, line, subsys, ret); +} + +void +sudo_debug_exit_uint_v1(const char *func, const char *file, int line, + unsigned int subsys, unsigned int ret) +{ + sudo_debug_exit_uint(func, file, line, subsys, ret); +} + +void +sudo_debug_exit_long_v1(const char *func, const char *file, int line, + unsigned int subsys, long ret) +{ + sudo_debug_exit_long(func, file, line, subsys, ret); +} + +void +sudo_debug_exit_id_t_v1(const char *func, const char *file, int line, + unsigned int subsys, id_t ret) +{ + sudo_debug_exit_id_t(func, file, line, subsys, ret); +} + +void +sudo_debug_exit_size_t_v1(const char *func, const char *file, int line, + unsigned int subsys, size_t ret) +{ + sudo_debug_exit_size_t(func, file, line, subsys, ret); +} + +void +sudo_debug_exit_ssize_t_v1(const char *func, const char *file, int line, + unsigned int subsys, ssize_t ret) +{ + sudo_debug_exit_ssize_t(func, file, line, subsys, ret); +} + +void +sudo_debug_exit_time_t_v1(const char *func, const char *file, int line, + unsigned int subsys, time_t ret) +{ + sudo_debug_exit_time_t(func, file, line, subsys, ret); +} + +void +sudo_debug_exit_mode_t_v1(const char *func, const char *file, int line, + unsigned int subsys, mode_t ret) +{ + sudo_debug_exit_mode_t(func, file, line, subsys, ret); +} + +void +sudo_debug_exit_bool_v1(const char *func, const char *file, int line, + unsigned int subsys, bool ret) +{ + sudo_debug_exit_bool(func, file, line, subsys, ret); +} + +void +sudo_debug_exit_str_v1(const char *func, const char *file, int line, + unsigned int subsys, const char *ret) +{ + sudo_debug_exit_str(func, file, line, subsys, ret); +} + +void +sudo_debug_exit_str_masked_v1(const char *func, const char *file, int line, + unsigned int subsys, const char *ret) +{ + sudo_debug_exit_str_masked(func, file, line, subsys, ret); +} + +void +sudo_debug_exit_ptr_v1(const char *func, const char *file, int line, + unsigned int subsys, const void *ret) +{ + sudo_debug_exit_ptr(func, file, line, subsys, ret); +} + +void +sudo_debug_write2_v1(int fd, const char *func, const char *file, int lineno, + const char *str, unsigned 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(unsigned 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 = (int)SUDO_DEBUG_PRI(level); + subsys = 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, + unsigned int level, const char * restrict fmt, va_list ap) +{ + int 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 = (int)SUDO_DEBUG_PRI(level); + subsys = SUDO_DEBUG_SUBSYS(level); + + /* Disable extra info if SUDO_DEBUG_LINENO is not enabled. */ + if (!ISSET(level, SUDO_DEBUG_LINENO)) { + func = NULL; + file = NULL; + lineno = 0; + } + + /* 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) { + int errcode, buflen = 0; + va_list ap2; + + if (fmt != NULL) { + /* Operate on a copy of ap to support multiple outputs. */ + va_copy(ap2, ap); + buflen = vsnprintf(static_buf, sizeof(static_buf), fmt, ap2); + va_end(ap2); + if (buflen < 0) { + sudo_warnx_nodebug("%s: invalid format string \"%s\"", + __func__, fmt); + buflen = 0; + } else if (buflen >= ssizeof(static_buf)) { + /* Not enough room in static buf, allocate dynamically. */ + va_copy(ap2, ap); + buflen = vasprintf(&buf, fmt, ap2); + if (buflen == -1) { + buf = static_buf; + buflen = (int)strlen(buf); + } + va_end(ap2); + } + } + errcode = ISSET(level, SUDO_DEBUG_ERRNO) ? saved_errno : 0; + sudo_debug_write2(output->fd, func, file, lineno, buf, + (unsigned int)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 * restrict 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, + unsigned int level, const char * restrict 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(unsigned int level, const char *path, char *const argv[], + char *const envp[]) +{ + int 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 buflen, plen; + debug_decl_func(sudo_debug_execve2); + + if (sudo_debug_active_instance == -1 || path == NULL) + goto out; + + /* Extract priority and subsystem from level. */ + pri = (int)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 >= sizeof(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, (unsigned int)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, + unsigned int subsys) +{ +} + +void +sudo_debug_exit_v1(const char *func, const char *file, int line, + unsigned int subsys) +{ +} + +void +sudo_debug_exit_int_v1(const char *func, const char *file, int line, + unsigned int subsys, int ret) +{ +} + +void +sudo_debug_exit_uint_v1(const char *func, const char *file, int line, + unsigned int subsys, unsigned int ret) +{ +} + +void +sudo_debug_exit_long_v1(const char *func, const char *file, int line, + unsigned int subsys, long ret) +{ +} + +void +sudo_debug_exit_id_t_v1(const char *func, const char *file, int line, + unsigned int subsys, id_t ret) +{ +} + +void +sudo_debug_exit_size_t_v1(const char *func, const char *file, int line, + unsigned int subsys, size_t ret) +{ +} + +void +sudo_debug_exit_ssize_t_v1(const char *func, const char *file, int line, + unsigned int subsys, ssize_t ret) +{ +} + +void +sudo_debug_exit_time_t_v1(const char *func, const char *file, int line, + unsigned int subsys, time_t ret) +{ +} + +void +sudo_debug_exit_mode_t_v1(const char *func, const char *file, int line, + unsigned int subsys, mode_t ret) +{ +} + +void +sudo_debug_exit_bool_v1(const char *func, const char *file, int line, + unsigned int subsys, bool ret) +{ +} + +void +sudo_debug_exit_str_v1(const char *func, const char *file, int line, + unsigned int subsys, const char *ret) +{ +} + +void +sudo_debug_exit_str_masked_v1(const char *func, const char *file, int line, + unsigned int subsys, const char *ret) +{ +} + +void +sudo_debug_exit_ptr_v1(const char *func, const char *file, int line, + unsigned int subsys, const void *ret) +{ +} + +void +sudo_debug_write2_v1(int fd, const char *func, const char *file, int lineno, + const char *str, unsigned int len, int errnum) +{ +} + +bool +sudo_debug_needed_v1(unsigned int level) +{ + return false; +} + +void +sudo_debug_vprintf2_v1(const char *func, const char *file, int lineno, + unsigned int level, const char * restrict fmt, va_list ap) +{ +} + +#ifdef NO_VARIADIC_MACROS +void +sudo_debug_printf_nvm_v1(int pri, const char * restrict fmt, ...) +{ +} +#endif /* NO_VARIADIC_MACROS */ + +void +sudo_debug_printf2_v1(const char *func, const char *file, int lineno, + unsigned int level, const char * restrict fmt, ...) +{ +} + +void +sudo_debug_execve2_v1(unsigned 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..0124b20 --- /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); // -V1048 + + 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..561c00e --- /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..8c06829 --- /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..a74e9d0 --- /dev/null +++ b/lib/util/term.c @@ -0,0 +1,491 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2011-2015, 2017-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 <sys/ioctl.h> +#include <sys/stat.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 + +/* 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) + +static struct termios orig_term; +static struct termios cur_term; +static bool 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 tcsetattr_nobg() 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 == -1 && 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) +{ + const int flags = flush ? (TCSASOFT|TCSAFLUSH) : (TCSASOFT|TCSADRAIN); + struct termios term = { 0 }; + bool ret = false; + debug_decl(sudo_term_restore, SUDO_DEBUG_UTIL); + + if (!changed) + debug_return_bool(true); + + sudo_lock_file(fd, SUDO_LOCK); + + /* Avoid changing term settings if changed out from under us. */ + if (tcgetattr(fd, &term) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: tcgetattr(%d)", __func__, fd); + goto unlock; + } + if ((term.c_iflag & INPUT_FLAGS) != (cur_term.c_iflag & INPUT_FLAGS)) { + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: not restoring terminal, " + "c_iflag changed; 0x%x, expected 0x%x", __func__, + (unsigned int)term.c_iflag, (unsigned int)cur_term.c_iflag); + /* Not an error. */ + ret = true; + goto unlock; + } + if ((term.c_oflag & OUTPUT_FLAGS) != (cur_term.c_oflag & OUTPUT_FLAGS)) { + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: not restoring terminal, " + "c_oflag changed; 0x%x, expected 0x%x", __func__, + (unsigned int)term.c_oflag, (unsigned int)cur_term.c_oflag); + /* Not an error. */ + ret = true; + goto unlock; + } +#if !TCSASOFT + /* Only systems without TCSASOFT make changes to c_cflag. */ + if ((term.c_cflag & CONTROL_FLAGS) != (cur_term.c_cflag & CONTROL_FLAGS)) { + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: not restoring terminal, " + "c_cflag changed; 0x%x, expected 0x%x", __func__, + (unsigned int)term.c_cflag, (unsigned int)cur_term.c_cflag); + /* Not an error. */ + ret = true; + goto unlock; + } +#endif + if ((term.c_lflag & LOCAL_FLAGS) != (cur_term.c_lflag & LOCAL_FLAGS)) { + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: not restoring terminal, " + "c_lflag changed; 0x%x, expected 0x%x", __func__, + (unsigned int)term.c_lflag, (unsigned int)cur_term.c_lflag); + /* Not an error. */ + ret = true; + goto unlock; + } + if (memcmp(term.c_cc, cur_term.c_cc, sizeof(term.c_cc)) != 0) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: not restoring terminal, c_cc[] changed", __func__); + /* Not an error. */ + ret = true; + goto unlock; + } + + if (tcsetattr_nobg(fd, flags, &orig_term) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: tcsetattr(%d)", __func__, fd); + goto unlock; + } + cur_term = orig_term; + changed = false; + ret = true; + +unlock: + sudo_lock_file(fd, SUDO_UNLOCK); + + debug_return_bool(ret); +} + +/* + * Disable terminal echo. + * Returns true on success or false on failure. + */ +bool +sudo_term_noecho_v1(int fd) +{ + struct termios term = { 0 }; + bool ret = false; + debug_decl(sudo_term_noecho, SUDO_DEBUG_UTIL); + + sudo_lock_file(fd, SUDO_LOCK); + if (!changed && tcgetattr(fd, &orig_term) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: tcgetattr(%d)", __func__, fd); + goto unlock; + } + + term = orig_term; + CLR(term.c_lflag, ECHO|ECHONL); +#ifdef VSTATUS + term.c_cc[VSTATUS] = _POSIX_VDISABLE; +#endif + if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: tcsetattr(%d)", __func__, fd); + goto unlock; + } + cur_term = term; + changed = true; + ret = true; + +unlock: + sudo_lock_file(fd, SUDO_UNLOCK); + debug_return_bool(ret); +} + +/* + * Returns true if term is in raw mode, else false. + */ +static bool +sudo_term_is_raw_int(struct termios *term) +{ + debug_decl(sudo_term_is_raw_int, SUDO_DEBUG_UTIL); + + 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); +} + +/* + * Returns true if fd refers to a tty in raw mode, else false. + */ +bool +sudo_term_is_raw_v1(int fd) +{ + struct termios term = { 0 }; + debug_decl(sudo_term_is_raw, SUDO_DEBUG_UTIL); + + if (!sudo_isatty(fd, NULL)) + debug_return_bool(false); + + sudo_lock_file(fd, SUDO_LOCK); + if (tcgetattr(fd, &term) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: tcgetattr(%d)", __func__, fd); + sudo_lock_file(fd, SUDO_UNLOCK); + debug_return_bool(false); + } + sudo_lock_file(fd, SUDO_UNLOCK); + + debug_return_bool(sudo_term_is_raw_int(&term)); +} + +/* + * Set terminal to raw mode as modified by flags. + * Returns true on success or false on failure. + */ +bool +sudo_term_raw_v1(int fd, unsigned int flags) +{ + struct termios term = { 0 }; + bool ret = false; + tcflag_t oflag; + debug_decl(sudo_term_raw, SUDO_DEBUG_UTIL); + + sudo_lock_file(fd, SUDO_LOCK); + if (!changed && tcgetattr(fd, &orig_term) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: tcgetattr(%d)", __func__, fd); + goto unlock; + } + + if (sudo_term_is_raw_int(&term)) { + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: fd %d already in raw mode", + __func__, fd); + ret = true; + goto unlock; + } + + /* + * Set terminal to raw mode but optionally enable terminal signals + * and/or preserve output flags. + */ + term = orig_term; + oflag = term.c_oflag; + cfmakeraw(&term); + if (ISSET(flags, SUDO_TERM_ISIG)) + SET(term.c_lflag, ISIG); + if (ISSET(flags, SUDO_TERM_OFLAG)) + term.c_oflag = oflag; + if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: tcsetattr(%d)", __func__, fd); + goto unlock; + } + cur_term = term; + changed = true; + ret = true; + +unlock: + sudo_lock_file(fd, SUDO_UNLOCK); + debug_return_bool(ret); +} + +/* + * Set terminal to cbreak mode. + * Returns true on success or false on failure. + */ +bool +sudo_term_cbreak_v1(int fd) +{ + struct termios term = { 0 }; + bool ret = false; + debug_decl(sudo_term_cbreak, SUDO_DEBUG_UTIL); + + sudo_lock_file(fd, SUDO_LOCK); + if (!changed && tcgetattr(fd, &orig_term) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: tcgetattr(%d)", __func__, fd); + goto unlock; + } + + /* Set terminal to half-cooked mode */ + term = orig_term; + 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) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: tcsetattr(%d)", __func__, fd); + goto unlock; + } + sudo_term_eof = term.c_cc[VEOF]; + sudo_term_erase = term.c_cc[VERASE]; + sudo_term_kill = term.c_cc[VKILL]; + cur_term = term; + changed = true; + ret = true; + +unlock: + sudo_lock_file(fd, SUDO_UNLOCK); + debug_return_bool(ret); +} + +/* + * 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; + unsigned int i; + bool ret = false; + debug_decl(sudo_term_copy, SUDO_DEBUG_UTIL); + + sudo_lock_file(src, SUDO_LOCK); + sudo_lock_file(dst, SUDO_LOCK); + if (tcgetattr(src, &tt_src) == -1 || tcgetattr(dst, &tt_dst) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: tcgetattr", __func__); + goto unlock; + } + + /* 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) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: tcsetattr(%d)", __func__, dst); + goto unlock; + } + ret = true; + + if (ioctl(src, TIOCGWINSZ, &wsize) == 0) + (void)ioctl(dst, IOCTL_REQ_CAST TIOCSWINSZ, &wsize); + +unlock: + sudo_lock_file(dst, SUDO_UNLOCK); + sudo_lock_file(src, SUDO_UNLOCK); + debug_return_bool(ret); +} + +/* + * Like isatty(3) but stats the fd and stores the result in sb. + * Only calls isatty(3) if fd is a character special device. + * Returns true if a tty, else returns false and sets errno. + */ +bool +sudo_isatty_v1(int fd, struct stat *sbp) +{ + bool ret = false; + struct stat sb; + debug_decl(sudo_isatty, SUDO_DEBUG_EXEC); + + if (sbp == NULL) + sbp = &sb; + + if (fstat(fd, sbp) == 0) { + if (!S_ISCHR(sbp->st_mode)) { + errno = ENOTTY; + } else { + ret = isatty(fd) == 1; + } + } else if (sbp != &sb) { + /* Always initialize sbp. */ + memset(sbp, 0, sizeof(*sbp)); + } + debug_return_bool(ret); +} diff --git a/lib/util/timegm.c b/lib/util/timegm.c new file mode 100644 index 0000000..acb5190 --- /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..3236925 --- /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; + size_t 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 * restrict devname, char * restrict 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..0b582d0 --- /dev/null +++ b/lib/util/ttysize.c @@ -0,0 +1,80 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2010-2012, 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. + */ + +/* + * 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 fd, int *rowp, int *colp) +{ + struct winsize wsize; + debug_decl(get_ttysize_ioctl, SUDO_DEBUG_UTIL); + + if (fd != -1 && sudo_isatty(fd, NULL)) { + if (ioctl(fd, TIOCGWINSZ, &wsize) == 0) { + if (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) +{ + sudo_get_ttysize_v2(STDERR_FILENO, rowp, colp); +} + +void +sudo_get_ttysize_v2(int fd, int *rowp, int *colp) +{ + debug_decl(sudo_get_ttysize, SUDO_DEBUG_UTIL); + + if (get_ttysize_ioctl(fd, rowp, colp) == -1) { + char *p; + + /* Fall back on $LINES and $COLUMNS. */ + if ((p = getenv("LINES")) == NULL || + (*rowp = (int)sudo_strtonum(p, 1, INT_MAX, NULL)) <= 0) { + *rowp = 24; + } + if ((p = getenv("COLUMNS")) == NULL || + (*colp = (int)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..b62902e --- /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..ca9dced --- /dev/null +++ b/lib/util/util.exp.in @@ -0,0 +1,178 @@ +@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_mode_t_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_uint_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_getlen_v2 +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_alloc_v2 +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_pending_v2 +sudo_ev_set_v1 +sudo_ev_set_v2 +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_get_ttysize_v2 +sudo_getgrouplist2_v1 +sudo_gethostname_v1 +sudo_gettime_awake_v1 +sudo_gettime_mono_v1 +sudo_gettime_real_v1 +sudo_hexchar_v1 +sudo_isatty_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_conf_path_v1 +sudo_open_parent_dir_v1 +sudo_parse_gids_v1 +sudo_parseln_v1 +sudo_parseln_v2 +sudo_pow2_roundup_v1 +sudo_pow2_roundup_v2 +sudo_rcstr_addref +sudo_rcstr_alloc +sudo_rcstr_delref +sudo_rcstr_dup +sudo_regex_compile_v1 +sudo_secure_dir_v1 +sudo_secure_fd_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_strtomode_v2 +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..74d6c65 --- /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..764c8db --- /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; + unsigned 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; +} |