diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:23:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:23:56 +0000 |
commit | 9620f76a210d9d8c1aaff25e99d6dc513f87e6e9 (patch) | |
tree | ceecc90fb95780872c35da764c5163f38e4727c4 /lib/util | |
parent | Initial commit. (diff) | |
download | sudo-9620f76a210d9d8c1aaff25e99d6dc513f87e6e9.tar.xz sudo-9620f76a210d9d8c1aaff25e99d6dc513f87e6e9.zip |
Adding upstream version 1.8.27.upstream/1.8.27upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/util')
112 files changed, 19060 insertions, 0 deletions
diff --git a/lib/util/Makefile.in b/lib/util/Makefile.in new file mode 100644 index 0000000..cadfcf2 --- /dev/null +++ b/lib/util/Makefile.in @@ -0,0 +1,1120 @@ +# +# Copyright (c) 2011-2018 Todd C. Miller <Todd.Miller@sudo.ws> +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# @configure_input@ +# + +#### Start of system configuration section. #### + +srcdir = @srcdir@ +devdir = @devdir@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +incdir = $(top_srcdir)/include +cross_compiling = @CROSS_COMPILING@ + +# Where to install things... +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +sbindir = @sbindir@ +sysconfdir = @sysconfdir@ +libexecdir = @libexecdir@ +datarootdir = @datarootdir@ +localstatedir = @localstatedir@ + +# File extension, mode and map file to use for shared libraries/objects +shlib_enable = @SHLIB_ENABLE@ +shlib_mode = @SHLIB_MODE@ +shlib_exp = ./util.exp +shlib_map = util.map +shlib_opt = util.opt + +# Compiler & tools to use +CC = @CC@ +LIBTOOL = @LIBTOOL@ +SED = @SED@ +AWK = @AWK@ + +# Our install program supports extra flags... +INSTALL = $(SHELL) $(top_srcdir)/install-sh -c +INSTALL_OWNER = -o $(install_uid) -g $(install_gid) +INSTALL_BACKUP = @INSTALL_BACKUP@ + +# C preprocessor defines +CPPDEFS = -D_PATH_SUDO_CONF=\"$(sysconfdir)/sudo.conf\" + +# C preprocessor flags +CPPFLAGS = -I$(incdir) -I$(top_builddir) -I$(srcdir) -I$(top_srcdir) $(CPPDEFS) @CPPFLAGS@ + +# Usually -O and/or -g +CFLAGS = @CFLAGS@ + +# 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 +SSP_CFLAGS = @SSP_CFLAGS@ +SSP_LDFLAGS = @SSP_LDFLAGS@ + +# Libtool style shared library version +SHLIB_VERSION = 0:0:0 + +# cppcheck options, usually set in the top-level Makefile +CPPCHECK_OPTS = -q --force --enable=warning,performance,portability --suppress=constStatement --error-exitcode=1 --inline-suppr -Dva_copy=va_copy -U__cplusplus -UQUAD_MAX -UQUAD_MIN -UUQUAD_MAX -U_POSIX_HOST_NAME_MAX -U_POSIX_PATH_MAX -U__NBBY -DNSIG=64 + +# splint options, usually set in the top-level Makefile +SPLINT_OPTS = -D__restrict= -checks + +# PVS-studio options +PVS_CFG = $(top_srcdir)/PVS-Studio.cfg +PVS_IGNORE = 'V707,V011,V002,V536' +PVS_LOG_OPTS = -a 'GA:1,2' -e -t errorfile -d $(PVS_IGNORE) + +# Regression tests +TEST_PROGS = atofoo_test conf_test hltq_test parseln_test progname_test \ + strsplit_test parse_gids_test getgrouplist_test @COMPAT_TEST_PROGS@ +TEST_LIBS = @LIBS@ +TEST_LDFLAGS = @LDFLAGS@ + +# 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 = @DIGEST@ event.lo fatal.lo key_val.lo gethostname.lo gettime.lo \ + getgrouplist.lo gidlist.lo lbuf.lo locking.lo parseln.lo progname.lo \ + secure_path.lo setgroups.lo strsplit.lo strtobool.lo strtoid.lo \ + strtomode.lo sudo_conf.lo sudo_debug.lo sudo_dso.lo term.lo \ + ttyname_dev.lo ttysize.lo @COMMON_OBJS@ @LTLIBOBJS@ + +IOBJS = $(LTOBJS:.lo=.i) + +POBJS = $(IOBJS:.i=.plog) + +ATOFOO_TEST_OBJS = atofoo_test.lo + +MKTEMP_TEST_OBJS = mktemp_test.lo + +PARSELN_TEST_OBJS = parseln_test.lo + +PROGNAME_TEST_OBJS = progname_test.lo progname.lo + +CONF_TEST_OBJS = conf_test.lo + +HLTQ_TEST_OBJS = hltq_test.lo + +FNM_TEST_OBJS = fnm_test.lo + +GLOBTEST_OBJS = globtest.lo + +STRSPLIT_TEST_OBJS = strsplit_test.lo + +PARSE_GIDS_TEST_OBJS = parse_gids_test.lo + +GETGROUPLIST_TEST_OBJS = getgrouplist_test.lo + +VSYSLOG_TEST_OBJS = vsyslog_test.lo vsyslog.lo + +all: libsudo_util.la + +pvs-log-files: $(POBJS) + +pvs-studio: $(POBJS) + plog-converter $(PVS_LOG_OPTS) $(POBJS) + +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) $(SSP_CFLAGS) $< + +.c.i: + $(CC) -E -o $@ $(CPPFLAGS) $< + +.i.plog: + ifile=$<; rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $${ifile%i}c --i-file $< --output-file $@ + +$(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) @LIBINTL@ @LIBMD@ @LIBPTHREAD@ @LIBDL@ @LIBRT@;; \ + *) \ + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(LDFLAGS) $(ASAN_LDFLAGS) $(SSP_LDFLAGS) $(LT_LDFLAGS) $(LTOBJS) -version-info $(SHLIB_VERSION) -rpath $(libexecdir)/sudo @LT_DEP_LIBS@ @LIBINTL@ @LIBMD@ @LIBPTHREAD@ @LIBDL@ @LIBRT@;; \ + esac + +siglist.c: mksiglist + ./mksiglist > $@ + +signame.c: mksigname + ./mksigname > $@ + +mksiglist: $(srcdir)/mksiglist.c $(srcdir)/mksiglist.h $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(CC) $(CPPFLAGS) $(CFLAGS) $(srcdir)/mksiglist.c -o $@ + +mksigname: $(srcdir)/mksigname.c $(srcdir)/mksigname.h $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(CC) $(CPPFLAGS) $(CFLAGS) $(srcdir)/mksigname.c -o $@ + +$(srcdir)/mksiglist.h: $(srcdir)/siglist.in + @if [ -n "$(DEVEL)" ]; then \ + $(AWK) 'BEGIN {print "/* public domain */\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)/mksigname.h: $(srcdir)/siglist.in + @if [ -n "$(DEVEL)" ]; then \ + $(AWK) 'BEGIN {print "/* public domain */\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 + +atofoo_test: $(ATOFOO_TEST_OBJS) libsudo_util.la + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(ATOFOO_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_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) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS) + +fnm_test: $(FNM_TEST_OBJS) libsudo_util.la + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(FNM_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_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) $(SSP_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) $(SSP_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) $(SSP_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) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS) + +progname_test: $(PROGNAME_TEST_OBJS) + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(PROGNAME_TEST_OBJS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_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) $(SSP_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) $(SSP_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) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS) + +vsyslog_test: $(VSYSLOG_TEST_OBJS) libsudo_util.la + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(VSYSLOG_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS) + +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) $(top_srcdir)/mkinstalldirs $(DESTDIR)$(libexecdir)/sudo + +install-binaries: + +install-includes: + +install-doc: + +install-plugin: + +uninstall: + $(LIBTOOL) $(LTFLAGS) --mode=uninstall rm -f $(DESTDIR)$(libexecdir)/sudo/libsudo_util.la + -test -z "$(INSTALL_BACKUP)" || \ + rf -f $(DESTDIR)$(libexecdir)/sudo/libsudo_util.*~ + +splint: + splint $(SPLINT_OPTS) -I$(incdir) -I$(top_builddir) -I$(top_srcdir) $(srcdir)/*.c + +cppcheck: + cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) -I$(top_srcdir) $(srcdir)/*.c + +pvs-log-files: $(POBJS) + +# Note: some regress checks are run from srcdir for consistent error messages +check: $(TEST_PROGS) + @if test X"$(cross_compiling)" != X"yes"; then \ + rval=0; \ + if test -f parse_gids_test; then \ + ./parse_gids_test || rval=`expr $$rval + $$?`; \ + fi; \ + if test -f strsplit_test; then \ + ./strsplit_test || rval=`expr $$rval + $$?`; \ + fi; \ + if test -f fnm_test; then \ + ./fnm_test $(srcdir)/regress/fnmatch/fnm_test.in || rval=`expr $$rval + $$?`; \ + fi; \ + if test -f globtest; then \ + mkdir -p `$(SED) 's@/[^/]*$$@@' $(srcdir)/regress/glob/files | sort -u`; \ + touch `cat $(srcdir)/regress/glob/files`; \ + chmod 0755 `grep '/r[^/]*$$' $(srcdir)/regress/glob/files`; \ + chmod 0444 `grep '/s[^/]*$$' $(srcdir)/regress/glob/files`; \ + chmod 0711 `grep '/t[^/]*$$' $(srcdir)/regress/glob/files`; \ + ./globtest $(srcdir)/regress/glob/globtest.in || rval=`expr $$rval + $$?`; \ + rm -rf fake; \ + fi; \ + if test -f mktemp_test; then \ + ./mktemp_test || rval=`expr $$rval + $$?`; \ + fi; \ + ./getgrouplist_test || rval=`expr $$rval + $$?`; \ + ./atofoo_test || rval=`expr $$rval + $$?`; \ + ./hltq_test || rval=`expr $$rval + $$?`; \ + ./progname_test || rval=`expr $$rval + $$?`; \ + rm -f ./progname_test2; ln -s ./progname_test ./progname_test2; \ + ./progname_test2 || rval=`expr $$rval + $$?`; \ + rm -f ./progname_test2; \ + if test -f vsyslog_test; then \ + ./vsyslog_test || rval=`expr $$rval + $$?`; \ + fi; \ + build_dir=`pwd`; \ + cd $(srcdir); \ + for dir in sudo_conf sudo_parseln; do \ + passed=0; failed=0; total=0; \ + mkdir -p $$build_dir/regress/$$dir; \ + for t in regress/$$dir/*.in; do \ + base=`basename $$t .in`; \ + out="$$build_dir/regress/$$dir/$${base}.out"; \ + out_ok="regress/$$dir/$${base}.out.ok"; \ + err="$$build_dir/regress/$$dir/$${base}.err"; \ + err_ok="regress/$$dir/$${base}.err.ok"; \ + if test "$$dir" = "sudo_conf"; then \ + $$build_dir/conf_test $$t >$$out 2>$$err; \ + else \ + $$build_dir/parseln_test <$$t >$$out 2>$$err; \ + fi; \ + if cmp $$out $$out_ok >/dev/null; then \ + passed=`expr $$passed + 1`; \ + echo "$$dir/$$base: OK"; \ + else \ + failed=`expr $$failed + 1`; \ + echo "$$dir/$$base: FAIL"; \ + diff $$out $$out_ok || true; \ + fi; \ + total=`expr $$total + 1`; \ + if test -s $$err_ok; then \ + if cmp $$err $$err_ok >/dev/null; then \ + passed=`expr $$passed + 1`; \ + echo "$$dir/$$base (stderr): OK"; \ + else \ + failed=`expr $$failed + 1`; \ + echo "$$dir/$$base (stderr): FAIL"; \ + diff $$err $$err_ok || true; \ + fi; \ + total=`expr $$total + 1`; \ + elif test -s $$err; then \ + failed=`expr $$failed + 1`; \ + echo "$$dir/$$base (stderr): FAIL"; \ + cat $$err 1>&2; \ + fi; \ + done; \ + if test $$failed -ne 0; then \ + rval=`expr $$rval + $$failed`; \ + fi; \ + echo "$$dir: $$passed/$$total tests passed; $$failed/$$total tests failed"; \ + done; \ + exit $$rval; \ + fi + +clean: + -$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f $(TEST_PROGS) *.lo *.o \ + *.la *.a *.i *.plog stamp-* core *.core core.* regress/*/*.out \ + regress/*/*.err + +mostlyclean: clean + +distclean: clean + -rm -rf Makefile mksiglist siglist.c mksigname signame.c .libs \ + $(shlib_exp) $(shlib_map) $(shlib_opt) + +clobber: distclean + +realclean: distclean + rm -f TAGS tags + +cleandir: realclean + +# 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_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_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_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)/sudo_compat.h \ + $(incdir)/sudo_rand.h $(srcdir)/arc4random.h \ + $(srcdir)/chacha_private.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/arc4random.c +arc4random.i: $(srcdir)/arc4random.c $(incdir)/sudo_compat.h \ + $(incdir)/sudo_rand.h $(srcdir)/arc4random.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_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) $(SSP_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 $@ +atofoo_test.lo: $(srcdir)/regress/atofoo/atofoo_test.c \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/atofoo/atofoo_test.c +atofoo_test.i: $(srcdir)/regress/atofoo/atofoo_test.c \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +atofoo_test.plog: atofoo_test.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/atofoo/atofoo_test.c --i-file $< --output-file $@ +closefrom.lo: $(srcdir)/closefrom.c $(incdir)/sudo_compat.h \ + $(top_builddir)/config.h $(top_builddir)/pathnames.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/closefrom.c +closefrom.i: $(srcdir)/closefrom.c $(incdir)/sudo_compat.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 $@ +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) $(SSP_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) $(SSP_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) $(SSP_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) $(SSP_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 $@ +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_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_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_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_queue.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_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_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_queue.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_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_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 $@ +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) $(SSP_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 $@ +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) $(SSP_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) $(SSP_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 $@ +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) $(SSP_CFLAGS) $(srcdir)/getaddrinfo.c +getaddrinfo.i: $(srcdir)/getaddrinfo.c $(incdir)/compat/getaddrinfo.h \ + $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +getaddrinfo.plog: getaddrinfo.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getaddrinfo.c --i-file $< --output-file $@ +getcwd.lo: $(srcdir)/getcwd.c $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/getcwd.c +getcwd.i: $(srcdir)/getcwd.c $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +getcwd.plog: getcwd.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getcwd.c --i-file $< --output-file $@ +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) $(SSP_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 $@ +getgrouplist.lo: $(srcdir)/getgrouplist.c $(incdir)/compat/nss_dbdefs.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) $(SSP_CFLAGS) $(srcdir)/getgrouplist.c +getgrouplist.i: $(srcdir)/getgrouplist.c $(incdir)/compat/nss_dbdefs.h \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.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_util.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_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_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) $(SSP_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 $@ +getline.lo: $(srcdir)/getline.c $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/getline.c +getline.i: $(srcdir)/getline.c $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +getline.plog: getline.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getline.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 $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_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 $(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) $(SSP_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 $@ +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_queue.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_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_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) $(SSP_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) $(SSP_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 $@ +hltq_test.lo: $(srcdir)/regress/tailq/hltq_test.c $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.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) $(SSP_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_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) $(SSP_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) $(SSP_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 $@ +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) $(SSP_CFLAGS) $(srcdir)/key_val.c +key_val.i: $(srcdir)/key_val.c $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +key_val.plog: key_val.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/key_val.c --i-file $< --output-file $@ +lbuf.lo: $(srcdir)/lbuf.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_debug.h $(incdir)/sudo_lbuf.h $(incdir)/sudo_queue.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/lbuf.c +lbuf.i: $(srcdir)/lbuf.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_debug.h $(incdir)/sudo_lbuf.h $(incdir)/sudo_queue.h \ + $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +lbuf.plog: lbuf.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/lbuf.c --i-file $< --output-file $@ +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) $(SSP_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 $@ +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) $(SSP_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 $@ +memset_s.lo: $(srcdir)/memset_s.c $(incdir)/sudo_compat.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/memset_s.c +memset_s.i: $(srcdir)/memset_s.c $(incdir)/sudo_compat.h \ + $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +memset_s.plog: memset_s.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/memset_s.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) $(SSP_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) $(SSP_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) $(SSP_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_util.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_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_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 $@ +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) $(SSP_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 $@ +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_util.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_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_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) $(SSP_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) $(SSP_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) $(SSP_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 $@ +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) $(SSP_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) $(SSP_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) $(SSP_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 $@ +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) $(SSP_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 $@ +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) $(SSP_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) $(SSP_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) $(SSP_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)/sudo_compat.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/sig2str.c +sig2str.i: $(srcdir)/sig2str.c $(incdir)/sudo_compat.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 $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) siglist.c +siglist.i: siglist.c $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(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 $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) signame.c +signame.i: signame.c $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(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)/sudo_compat.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/snprintf.c +snprintf.i: $(srcdir)/snprintf.c $(incdir)/sudo_compat.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 $@ +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) $(SSP_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) $(SSP_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) $(SSP_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) $(SSP_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 $@ +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) $(SSP_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) $(SSP_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_util.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_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_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) $(SSP_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 $@ +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) $(SSP_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 $@ +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) $(SSP_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 $@ +strtonum.lo: $(srcdir)/strtonum.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) $(SSP_CFLAGS) $(srcdir)/strtonum.c +strtonum.i: $(srcdir)/strtonum.c $(incdir)/sudo_compat.h \ + $(incdir)/sudo_gettext.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 $@ +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) $(SSP_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) $(SSP_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)/sudo_compat.h $(incdir)/sudo_dso.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/sudo_dso.c +sudo_dso.i: $(srcdir)/sudo_dso.c $(incdir)/sudo_compat.h $(incdir)/sudo_dso.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) $(SSP_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 $@ +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) $(SSP_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) $(SSP_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 $@ +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) $(SSP_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 $@ +vsyslog.lo: $(srcdir)/vsyslog.c $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/vsyslog.c +vsyslog.i: $(srcdir)/vsyslog.c $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +vsyslog.plog: vsyslog.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/vsyslog.c --i-file $< --output-file $@ +vsyslog_test.lo: $(srcdir)/regress/vsyslog/vsyslog_test.c \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/vsyslog/vsyslog_test.c +vsyslog_test.i: $(srcdir)/regress/vsyslog/vsyslog_test.c \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +vsyslog_test.plog: vsyslog_test.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/vsyslog/vsyslog_test.c --i-file $< --output-file $@ diff --git a/lib/util/aix.c b/lib/util/aix.c new file mode 100644 index 0000000..137de23 --- /dev/null +++ b/lib/util/aix.c @@ -0,0 +1,291 @@ +/* + * 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/types.h> +#include <sys/resource.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <usersec.h> +#include <uinfo.h> + +#define DEFAULT_TEXT_DOMAIN "sudo" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_debug.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; + char *soft; + 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(char *user, char *lim, int *valp) +{ + debug_decl(aix_getlimit, SUDO_DEBUG_UTIL) + + if (getuserattr(user, 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(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(U_("unable to open userdb")); + goto done; + } + ret = getuserattr(user, 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(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..8935d36 --- /dev/null +++ b/lib/util/arc4random.c @@ -0,0 +1,217 @@ +/* $OpenBSD: arc4random.c,v 1.54 2015/09/13 08:31:47 guenther Exp $ */ + +/* + * 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 + +#include <sys/types.h> +#include <sys/time.h> + +#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_rand.h" + +#define KEYSTREAM_ONLY +#include "chacha_private.h" + +#define minimum(a, b) ((a) < (b) ? (a) : (b)) + +#if defined(__GNUC__) || defined(_MSC_VER) +#define inline __inline +#else /* __GNUC__ || _MSC_VER */ +#define inline +#endif /* !__GNUC__ && !_MSC_VER */ + +#define KEYSZ 32 +#define IVSZ 8 +#define BLOCKSZ 64 +#define RSBUFSZ (16*BLOCKSZ) + +/* Marked MAP_INHERIT_ZERO, so zero'd out in fork children. */ +static struct _rs { + size_t rs_have; /* valid bytes at end of rs_buf */ + size_t rs_count; /* bytes till reseed */ +} *rs; + +/* Maybe be preserved in fork children, if _rs_allocate() decides. */ +static struct _rsx { + chacha_ctx rs_chacha; /* chacha context for random keystream */ + unsigned char rs_buf[RSBUFSZ]; /* keystream blocks */ +} *rsx; + +static inline int _rs_allocate(struct _rs **, struct _rsx **); +static inline void _rs_forkdetect(void); +#include "arc4random.h" + +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; + + if (rs == NULL) { + if (_rs_allocate(&rs, &rsx) == -1) + abort(); + } + + chacha_keysetup(&rsx->rs_chacha, buf, KEYSZ * 8, 0); + chacha_ivsetup(&rsx->rs_chacha, buf + KEYSZ); +} + +static void +_rs_stir(void) +{ + unsigned char rnd[KEYSZ + IVSZ]; + + if (getentropy(rnd, sizeof rnd) == -1) + _getentropy_fail(); + + if (!rs) + _rs_init(rnd, sizeof(rnd)); + else + _rs_rekey(rnd, sizeof(rnd)); + memset_s(rnd, sizeof(rnd), 0, sizeof(rnd)); /* discard source seed */ + + /* invalidate rs_buf */ + rs->rs_have = 0; + memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf)); + + rs->rs_count = 1600000; +} + +static inline void +_rs_stir_if_needed(size_t len) +{ + _rs_forkdetect(); + if (!rs || rs->rs_count <= len) + _rs_stir(); + if (rs->rs_count <= len) + rs->rs_count = 0; + else + rs->rs_count -= len; +} + +static inline void +_rs_rekey(unsigned char *dat, size_t datlen) +{ +#ifndef KEYSTREAM_ONLY + memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf)); +#endif + /* fill rs_buf with the keystream */ + chacha_encrypt_bytes(&rsx->rs_chacha, rsx->rs_buf, + rsx->rs_buf, sizeof(rsx->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++) + rsx->rs_buf[i] ^= dat[i]; + } + /* immediately reinit for backtracking resistance */ + _rs_init(rsx->rs_buf, KEYSZ + IVSZ); + memset(rsx->rs_buf, 0, KEYSZ + IVSZ); + rs->rs_have = sizeof(rsx->rs_buf) - KEYSZ - IVSZ; +} + +static inline void +_rs_random_buf(void *_buf, size_t n) +{ + unsigned char *buf = (unsigned char *)_buf; + unsigned char *keystream; + size_t m; + + _rs_stir_if_needed(n); + while (n > 0) { + if (rs->rs_have > 0) { + m = minimum(n, rs->rs_have); + keystream = rsx->rs_buf + sizeof(rsx->rs_buf) + - rs->rs_have; + memcpy(buf, keystream, m); + memset(keystream, 0, m); + buf += m; + n -= m; + rs->rs_have -= m; + } + if (rs->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->rs_have < sizeof(*val)) + _rs_rekey(NULL, 0); + keystream = rsx->rs_buf + sizeof(rsx->rs_buf) - rs->rs_have; + memcpy(val, keystream, sizeof(*val)); + memset(keystream, 0, sizeof(*val)); + rs->rs_have -= sizeof(*val); +} + +uint32_t +sudo_arc4random(void) +{ + uint32_t val; + + _ARC4_LOCK(); + _rs_random_u32(&val); + _ARC4_UNLOCK(); + return val; +} + +#ifdef notdef +void +sudo_arc4random_buf(void *buf, size_t n) +{ + _ARC4_LOCK(); + _rs_random_buf(buf, n); + _ARC4_UNLOCK(); +} +#endif + +#endif /* HAVE_ARC4RANDOM */ diff --git a/lib/util/arc4random.h b/lib/util/arc4random.h new file mode 100644 index 0000000..d649017 --- /dev/null +++ b/lib/util/arc4random.h @@ -0,0 +1,107 @@ +/* $OpenBSD: arc4random.h,v 1.4 2015/01/15 06:57:18 deraadt Exp $ */ + +/* + * 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. + */ + +/* + * Stub functions for portability. + */ + +#include <sys/mman.h> + +#include <signal.h> +#ifdef HAVE_PTHREAD_H +#include <pthread.h> + +static pthread_mutex_t arc4random_mtx = PTHREAD_MUTEX_INITIALIZER; +#define _ARC4_LOCK() pthread_mutex_lock(&arc4random_mtx) +#define _ARC4_UNLOCK() pthread_mutex_unlock(&arc4random_mtx) +#else +#define _ARC4_LOCK() +#define _ARC4_UNLOCK() +#endif /* HAVE_PTHREAD_H */ + +#ifdef HAVE_PTHREAD_ATFORK +# define _ARC4_ATFORK(f) pthread_atfork(NULL, NULL, (f)) +# else +# define _ARC4_ATFORK(f) +#endif + +#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS) +# define MAP_ANON MAP_ANONYMOUS +#endif + +static inline void +_getentropy_fail(void) +{ + raise(SIGKILL); +} + +static volatile sig_atomic_t _rs_forked; + +#ifdef HAVE_PTHREAD_ATFORK +static inline void +_rs_forkhandler(void) +{ + _rs_forked = 1; +} +#endif /* HAVE_PTHREAD_ATFORK */ + +static int wipeonfork; + +static inline void +_rs_forkdetect(void) +{ + if (!wipeonfork) { + static pid_t _rs_pid = 0; + pid_t pid = getpid(); + + if (_rs_pid == 0 || _rs_pid != pid || _rs_forked) { + _rs_pid = pid; + _rs_forked = 0; + if (rs) + memset(rs, 0, sizeof(*rs)); + } + } +} + +static inline int +_rs_allocate(struct _rs **rsp, struct _rsx **rsxp) +{ + if ((*rsp = (void *)mmap(NULL, sizeof(**rsp), PROT_READ|PROT_WRITE, + MAP_ANON|MAP_PRIVATE, -1, 0)) == MAP_FAILED) + return (-1); + + if ((*rsxp = (void *)mmap(NULL, sizeof(**rsxp), PROT_READ|PROT_WRITE, + MAP_ANON|MAP_PRIVATE, -1, 0)) == MAP_FAILED) { + munmap((void *)*rsp, sizeof(**rsp)); + *rsp = NULL; + return (-1); + } + +#ifdef MADV_WIPEONFORK + if (madvise (*rsp, sizeof(**rsp), MADV_WIPEONFORK) == 0 && + madvise (*rsxp, sizeof(**rsxp), MADV_WIPEONFORK) == 0) { + wipeonfork = 1; + } +#endif + + _ARC4_ATFORK(_rs_forkhandler); + return (0); +} diff --git a/lib/util/arc4random_uniform.c b/lib/util/arc4random_uniform.c new file mode 100644 index 0000000..51f4714 --- /dev/null +++ b/lib/util/arc4random_uniform.c @@ -0,0 +1,75 @@ +/* $OpenBSD: arc4random_uniform.c,v 1.2 2015/09/13 08:31:47 guenther Exp $ */ + +/* + * 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 <sys/types.h> +#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/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..6cfe320 --- /dev/null +++ b/lib/util/closefrom.c @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2004-2005, 2007, 2010, 2012-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> + +#ifndef HAVE_CLOSEFROM + +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <limits.h> +#ifdef HAVE_PSTAT_GETPROC +# include <sys/pstat.h> +#else +# include <dirent.h> +#endif + +#include "sudo_compat.h" +#include "pathnames.h" + +#ifndef _POSIX_OPEN_MAX +# define _POSIX_OPEN_MAX 20 +#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). We avoid checking + * resource limits since it is possible to open a file descriptor + * and then drop the rlimit such that it is below the open fd. + */ + maxfd = sysconf(_SC_OPEN_MAX); + if (maxfd < 0) + maxfd = _POSIX_OPEN_MAX; + + for (fd = lowfd; fd < maxfd; fd++) { +#ifdef __APPLE__ + /* Avoid potential libdispatch crash when we close its fds. */ + (void) fcntl((int) fd, F_SETFD, FD_CLOEXEC); +#else + (void) close((int) fd); +#endif + } +} + +/* + * 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 pstat; +#elif defined(HAVE_DIRFD) + const char *path; + DIR *dirp; +#endif + + /* Try the fast method first, if possible. */ +#if defined(HAVE_FCNTL_CLOSEM) + if (fcntl(lowfd, F_CLOSEM, 0) != -1) + return; +#endif +#if defined(HAVE_PSTAT_GETPROC) + if (pstat_getproc(&pstat, sizeof(pstat), 0, getpid()) != -1) { + int fd; + + for (fd = lowfd; fd <= pstat.pst_highestfd; fd++) + (void) close(fd); + return; + } +#elif defined(HAVE_DIRFD) + /* Use /proc/self/fd (or /dev/fd on FreeBSD) if it exists. */ +# if defined(__FreeBSD__) || defined(__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 = strtonum(dent->d_name, lowfd, INT_MAX, &errstr); + if (errstr == NULL && fd != dirfd(dirp)) { +# ifdef __APPLE__ + /* Avoid potential libdispatch crash when we close its fds. */ + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); +# else + (void) close(fd); +# endif + } + } + (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..f81d463 --- /dev/null +++ b/lib/util/digest.c @@ -0,0 +1,173 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#if defined(HAVE_STDINT_H) +# include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +# include <inttypes.h> +#endif +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_digest.h" + +#ifdef HAVE_SHA224UPDATE +# include <sha2.h> +#else +# include "compat/sha2.h" +#endif + +static struct digest_function { + const unsigned int digest_len; + void (*init)(SHA2_CTX *); +#ifdef SHA2_VOID_PTR + void (*update)(SHA2_CTX *, const void *, size_t); + void (*final)(void *, SHA2_CTX *); +#else + void (*update)(SHA2_CTX *, const unsigned char *, size_t); + void (*final)(unsigned char *, SHA2_CTX *); +#endif +} digest_functions[] = { + { + SHA224_DIGEST_LENGTH, + SHA224Init, + SHA224Update, + SHA224Final + }, { + SHA256_DIGEST_LENGTH, + SHA256Init, + SHA256Update, + SHA256Final + }, { + SHA384_DIGEST_LENGTH, + SHA384Init, + SHA384Update, + SHA384Final + }, { + SHA512_DIGEST_LENGTH, + SHA512Init, + SHA512Update, + SHA512Final + }, { + 0 + } +}; + +struct sudo_digest { + struct digest_function *func; + SHA2_CTX ctx; +}; + +struct sudo_digest * +sudo_digest_alloc_v1(int digest_type) +{ + debug_decl(sudo_digest_alloc, SUDO_DEBUG_UTIL) + struct digest_function *func = NULL; + struct sudo_digest *dig; + int i; + + for (i = 0; digest_functions[i].digest_len != 0; i++) { + if (digest_type == i) { + func = &digest_functions[i]; + break; + } + } + if (func == NULL) { + errno = EINVAL; + debug_return_ptr(NULL); + } + + if ((dig = malloc(sizeof(*dig))) == NULL) + debug_return_ptr(NULL); + func->init(&dig->ctx); + dig->func = func; + + debug_return_ptr(dig); +} + +void +sudo_digest_free_v1(struct sudo_digest *dig) +{ + debug_decl(sudo_digest_free, SUDO_DEBUG_UTIL) + + free(dig); + + debug_return; +} + +void +sudo_digest_reset_v1(struct sudo_digest *dig) +{ + debug_decl(sudo_digest_reset, SUDO_DEBUG_UTIL) + + dig->func->init(&dig->ctx); + + debug_return; +} + +int +sudo_digest_getlen_v1(int digest_type) +{ + debug_decl(sudo_digest_getlen, SUDO_DEBUG_UTIL) + int i; + + for (i = 0; digest_functions[i].digest_len != 0; i++) { + if (digest_type == i) + debug_return_int(digest_functions[i].digest_len); + } + + debug_return_int(-1); +} + +void +sudo_digest_update_v1(struct sudo_digest *dig, const void *data, size_t len) +{ + debug_decl(sudo_digest_update, SUDO_DEBUG_UTIL) + + dig->func->update(&dig->ctx, data, len); + + debug_return; +} + +void +sudo_digest_final_v1(struct sudo_digest *dig, unsigned char *md) +{ + debug_decl(sudo_digest_final, SUDO_DEBUG_UTIL) + + dig->func->final(md, &dig->ctx); + + debug_return; +} diff --git a/lib/util/digest_gcrypt.c b/lib/util/digest_gcrypt.c new file mode 100644 index 0000000..7d12fe2 --- /dev/null +++ b/lib/util/digest_gcrypt.c @@ -0,0 +1,153 @@ +/* + * 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> + +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <gcrypt.h> + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_digest.h" + +struct sudo_digest { + int gcry_digest_type; + unsigned int digest_len; + gcry_md_hd_t ctx; +}; + +/* Map sudo digest type to gcrypt digest type. */ +static int +sudo_digest_type_to_gcry(int digest_type) +{ + switch (digest_type) { + case SUDO_DIGEST_SHA224: + return GCRY_MD_SHA224; + break; + case SUDO_DIGEST_SHA256: + return GCRY_MD_SHA256; + break; + case SUDO_DIGEST_SHA384: + return GCRY_MD_SHA384; + break; + case SUDO_DIGEST_SHA512: + return GCRY_MD_SHA512; + break; + default: + return -1; + } +} + +struct sudo_digest * +sudo_digest_alloc_v1(int digest_type) +{ + debug_decl(sudo_digest_alloc, SUDO_DEBUG_UTIL) + struct sudo_digest *dig; + int gcry_digest_type; + + gcry_digest_type = sudo_digest_type_to_gcry(digest_type); + if (gcry_digest_type == -1) { + errno = EINVAL; + debug_return_ptr(NULL); + } + + if ((dig = malloc(sizeof(*dig))) == NULL) + debug_return_ptr(NULL); + dig->gcry_digest_type = gcry_digest_type; + dig->digest_len = gcry_md_get_algo_dlen(gcry_digest_type); + + if (gcry_md_open(&dig->ctx, gcry_digest_type, 0) != 0) { + free(dig); + debug_return_ptr(NULL); + } + + debug_return_ptr(dig); +} + +void +sudo_digest_free_v1(struct sudo_digest *dig) +{ + debug_decl(sudo_digest_free, SUDO_DEBUG_UTIL) + + if (dig != NULL) { + gcry_md_close(dig->ctx); + free(dig); + } + + debug_return; +} + +void +sudo_digest_reset_v1(struct sudo_digest *dig) +{ + debug_decl(sudo_digest_reset, SUDO_DEBUG_UTIL) + + gcry_md_reset(dig->ctx); + + debug_return; +} + +int +sudo_digest_getlen_v1(int digest_type) +{ + debug_decl(sudo_digest_getlen, SUDO_DEBUG_UTIL) + int gcry_digest_type; + + gcry_digest_type = sudo_digest_type_to_gcry(digest_type); + if (gcry_digest_type == -1) + debug_return_int(-1); + + debug_return_int(gcry_md_get_algo_dlen(gcry_digest_type)); +} + +void +sudo_digest_update_v1(struct sudo_digest *dig, const void *data, size_t len) +{ + debug_decl(sudo_digest_update, SUDO_DEBUG_UTIL) + + gcry_md_write(dig->ctx, data, len); + + debug_return; +} + +void +sudo_digest_final_v1(struct sudo_digest *dig, unsigned char *md) +{ + debug_decl(sudo_digest_final, SUDO_DEBUG_UTIL) + + gcry_md_final(dig->ctx); + memcpy(md, gcry_md_read(dig->ctx, 0), dig->digest_len); + + debug_return; +} diff --git a/lib/util/digest_openssl.c b/lib/util/digest_openssl.c new file mode 100644 index 0000000..af09684 --- /dev/null +++ b/lib/util/digest_openssl.c @@ -0,0 +1,163 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <openssl/sha.h> + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_digest.h" + +union ANY_CTX { + SHA256_CTX sha256; + SHA512_CTX sha512; +}; + +static struct digest_function { + const unsigned int digest_len; + int (*init)(union ANY_CTX *); + int (*update)(union ANY_CTX *, const void *, size_t); + int (*final)(unsigned char *, union ANY_CTX *); +} digest_functions[] = { + { + SHA224_DIGEST_LENGTH, + (int (*)(union ANY_CTX *))SHA224_Init, + (int (*)(union ANY_CTX *, const void *, size_t))SHA224_Update, + (int (*)(unsigned char *, union ANY_CTX *))SHA224_Final + }, { + SHA256_DIGEST_LENGTH, + (int (*)(union ANY_CTX *))SHA256_Init, + (int (*)(union ANY_CTX *, const void *, size_t))SHA256_Update, + (int (*)(unsigned char *, union ANY_CTX *))SHA256_Final + }, { + SHA384_DIGEST_LENGTH, + (int (*)(union ANY_CTX *))SHA384_Init, + (int (*)(union ANY_CTX *, const void *, size_t))SHA384_Update, + (int (*)(unsigned char *, union ANY_CTX *))SHA384_Final + }, { + SHA512_DIGEST_LENGTH, + (int (*)(union ANY_CTX *))SHA512_Init, + (int (*)(union ANY_CTX *, const void *, size_t))SHA512_Update, + (int (*)(unsigned char *, union ANY_CTX *))SHA512_Final + }, { + 0 + } +}; + +struct sudo_digest { + struct digest_function *func; + union ANY_CTX ctx; +}; + +struct sudo_digest * +sudo_digest_alloc_v1(int digest_type) +{ + debug_decl(sudo_digest_alloc, SUDO_DEBUG_UTIL) + struct digest_function *func = NULL; + struct sudo_digest *dig; + int i; + + for (i = 0; digest_functions[i].digest_len != 0; i++) { + if (digest_type == i) { + func = &digest_functions[i]; + break; + } + } + if (func == NULL) { + errno = EINVAL; + debug_return_ptr(NULL); + } + + if ((dig = malloc(sizeof(*dig))) == NULL) + debug_return_ptr(NULL); + func->init(&dig->ctx); + dig->func = func; + + debug_return_ptr(dig); +} + +void +sudo_digest_free_v1(struct sudo_digest *dig) +{ + debug_decl(sudo_digest_free, SUDO_DEBUG_UTIL) + + free(dig); + + debug_return; +} + +void +sudo_digest_reset_v1(struct sudo_digest *dig) +{ + debug_decl(sudo_digest_reset, SUDO_DEBUG_UTIL) + + dig->func->init(&dig->ctx); + + debug_return; +} +int +sudo_digest_getlen_v1(int digest_type) +{ + debug_decl(sudo_digest_getlen, SUDO_DEBUG_UTIL) + int i; + + for (i = 0; digest_functions[i].digest_len != 0; i++) { + if (digest_type == i) + debug_return_int(digest_functions[i].digest_len); + } + + debug_return_int(-1); +} + +void +sudo_digest_update_v1(struct sudo_digest *dig, const void *data, size_t len) +{ + debug_decl(sudo_digest_update, SUDO_DEBUG_UTIL) + + dig->func->update(&dig->ctx, data, len); + + debug_return; +} + +void +sudo_digest_final_v1(struct sudo_digest *dig, unsigned char *md) +{ + debug_decl(sudo_digest_final, SUDO_DEBUG_UTIL) + + dig->func->final(md, &dig->ctx); + + debug_return; +} diff --git a/lib/util/event.c b/lib/util/event.c new file mode 100644 index 0000000..05b3fd0 --- /dev/null +++ b/lib/util/event.c @@ -0,0 +1,798 @@ +/* + * 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 */ +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <errno.h> +#include <fcntl.h> +#include <time.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_debug.h" +#include "sudo_event.h" +#include "sudo_util.h" + +static void sudo_ev_init(struct sudo_event *ev, int fd, short events, + sudo_ev_callback_t callback, void *closure); + +/* Default event base when none is specified. */ +static struct sudo_event_base *default_base; + +/* We need the event base to be available from the signal handler. */ +static struct sudo_event_base *signal_base; + +/* + * Add an event to the base's active queue and mark it active. + * This is extern so sudo_ev_scan_impl() can call it. + */ +void +sudo_ev_activate(struct sudo_event_base *base, struct sudo_event *ev) +{ + TAILQ_INSERT_TAIL(&base->active, ev, active_entries); + SET(ev->flags, SUDO_EVQ_ACTIVE); +} + +/* + * Remove an event from the base's active queue and mark it inactive. + */ +static inline void +sudo_ev_deactivate(struct sudo_event_base *base, struct sudo_event *ev) +{ + CLR(ev->flags, SUDO_EVQ_ACTIVE); + TAILQ_REMOVE(&base->active, ev, active_entries); +} + +/* + * Clear out the base's active queue and mark all events as inactive. + */ +static void +sudo_ev_deactivate_all(struct sudo_event_base *base) +{ + struct sudo_event *ev; + debug_decl(sudo_ev_deactivate_all, SUDO_DEBUG_EVENT) + + while ((ev = TAILQ_FIRST(&base->active)) != NULL) + sudo_ev_deactivate(base, ev); + + debug_return; +} + +/* + * Activate all signal events for which the corresponding signal_pending[] + * flag is set. + */ +static void +sudo_ev_activate_sigevents(struct sudo_event_base *base) +{ + struct sudo_event *ev; + sigset_t set, oset; + int i; + debug_decl(sudo_ev_activate_sigevents, SUDO_DEBUG_EVENT) + + /* + * We treat this as a critical section since the signal handler + * could modify the siginfo[] entry. + */ + sigfillset(&set); + sigprocmask(SIG_BLOCK, &set, &oset); + base->signal_caught = 0; + for (i = 0; i < NSIG; i++) { + if (!base->signal_pending[i]) + continue; + base->signal_pending[i] = 0; + TAILQ_FOREACH(ev, &base->signals[i], entries) { + if (ISSET(ev->events, SUDO_EV_SIGINFO)) { + struct sudo_ev_siginfo_container *sc = ev->closure; + if (base->siginfo[i]->si_signo == 0) { + /* No siginfo available. */ + sc->siginfo = NULL; + } else { + sc->siginfo = (siginfo_t *)sc->si_buf; + memcpy(sc->siginfo, base->siginfo[i], sizeof(siginfo_t)); + } + } + /* Make event active. */ + ev->revents = ev->events & (SUDO_EV_SIGNAL|SUDO_EV_SIGINFO); + TAILQ_INSERT_TAIL(&base->active, ev, active_entries); + SET(ev->flags, SUDO_EVQ_ACTIVE); + } + } + sigprocmask(SIG_SETMASK, &oset, NULL); + + debug_return; +} + +/* + * Internal callback for SUDO_EV_SIGNAL and SUDO_EV_SIGINFO. + */ +static void +signal_pipe_cb(int fd, int what, void *v) +{ + struct sudo_event_base *base = v; + unsigned char ch; + ssize_t nread; + debug_decl(signal_pipe_cb, SUDO_DEBUG_EVENT) + + /* + * Drain signal_pipe, the signal handler updated base->signals_pending. + * Actual processing of signal events is done when poll/select is + * interrupted by a signal. + */ + while ((nread = read(fd, &ch, 1)) > 0) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: received signal %d", __func__, (int)ch); + } + if (nread == -1 && errno != EAGAIN) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "%s: error reading from signal pipe fd %d", __func__, fd); + } + + /* Activate signal events. */ + sudo_ev_activate_sigevents(base); + + debug_return; +} + +static int +sudo_ev_base_init(struct sudo_event_base *base) +{ + int i; + debug_decl(sudo_ev_base_init, SUDO_DEBUG_EVENT) + + TAILQ_INIT(&base->events); + TAILQ_INIT(&base->timeouts); + for (i = 0; i < NSIG; i++) + TAILQ_INIT(&base->signals[i]); + if (sudo_ev_base_alloc_impl(base) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR, + "%s: unable to allocate impl base", __func__); + goto bad; + } + if (pipe2(base->signal_pipe, O_NONBLOCK|O_CLOEXEC) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR, + "%s: unable to create signal pipe", __func__); + goto bad; + } + sudo_ev_init(&base->signal_event, base->signal_pipe[0], + SUDO_EV_READ|SUDO_EV_PERSIST, signal_pipe_cb, base); + + debug_return_int(0); +bad: + /* Note: signal_pipe[] not filled in. */ + sudo_ev_base_free_impl(base); + debug_return_int(-1); +} + +struct sudo_event_base * +sudo_ev_base_alloc_v1(void) +{ + struct sudo_event_base *base; + debug_decl(sudo_ev_base_alloc, SUDO_DEBUG_EVENT) + + base = calloc(1, sizeof(*base)); + if (base == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to allocate base", __func__); + debug_return_ptr(NULL); + } + if (sudo_ev_base_init(base) != 0) { + free(base); + debug_return_ptr(NULL); + } + debug_return_ptr(base); +} + +void +sudo_ev_base_free_v1(struct sudo_event_base *base) +{ + struct sudo_event *ev, *next; + int i; + debug_decl(sudo_ev_base_free, SUDO_DEBUG_EVENT) + + if (base == NULL) + debug_return; + + /* Reset the default base if necessary. */ + if (default_base == base) + default_base = NULL; + + /* Remove any existing events before freeing the base. */ + TAILQ_FOREACH_SAFE(ev, &base->events, entries, next) { + sudo_ev_del(base, ev); + } + for (i = 0; i < NSIG; i++) { + TAILQ_FOREACH_SAFE(ev, &base->signals[i], entries, next) { + sudo_ev_del(base, ev); + } + free(base->siginfo[i]); + free(base->orig_handlers[i]); + } + sudo_ev_base_free_impl(base); + close(base->signal_pipe[0]); + close(base->signal_pipe[1]); + free(base); + + debug_return; +} + +void +sudo_ev_base_setdef_v1(struct sudo_event_base *base) +{ + debug_decl(sudo_ev_base_setdef, SUDO_DEBUG_EVENT) + + default_base = base; + + debug_return; +} + +/* + * Clear and fill in a struct sudo_event. + */ +static void +sudo_ev_init(struct sudo_event *ev, int fd, short events, + sudo_ev_callback_t callback, void *closure) +{ + debug_decl(sudo_ev_init, SUDO_DEBUG_EVENT) + + /* XXX - sanity check events value */ + memset(ev, 0, sizeof(*ev)); + ev->fd = fd; + ev->events = events; + ev->pfd_idx = -1; + ev->callback = callback; + ev->closure = closure; + + debug_return; +} + +struct sudo_event * +sudo_ev_alloc_v1(int fd, short events, sudo_ev_callback_t callback, void *closure) +{ + struct sudo_event *ev; + debug_decl(sudo_ev_alloc, SUDO_DEBUG_EVENT) + + ev = malloc(sizeof(*ev)); + if (ev == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to allocate event", __func__); + debug_return_ptr(NULL); + } + /* For SUDO_EV_SIGINFO we use a container to store closure + siginfo_t */ + if (ISSET(events, SUDO_EV_SIGINFO)) { + struct sudo_ev_siginfo_container *container = + malloc(sizeof(*container) + sizeof(siginfo_t) - 1); + if (container == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to allocate siginfo container", __func__); + free(ev); + debug_return_ptr(NULL); + } + container->closure = closure; + closure = container; + } + sudo_ev_init(ev, fd, events, callback, closure); + + debug_return_ptr(ev); +} + +void +sudo_ev_free_v1(struct sudo_event *ev) +{ + debug_decl(sudo_ev_free, SUDO_DEBUG_EVENT) + + if (ev == NULL) + debug_return; + + /* Make sure ev is not in use before freeing it. */ + if (ISSET(ev->flags, SUDO_EVQ_INSERTED)) + (void)sudo_ev_del(NULL, ev); + if (ISSET(ev->events, SUDO_EV_SIGINFO)) + free(ev->closure); + free(ev); + + debug_return; +} + +static void +sudo_ev_handler(int signo, siginfo_t *info, void *context) +{ + unsigned char ch = (unsigned char)signo; + + if (signal_base != NULL) { + /* + * Update signals_pending[] and siginfo[]. + * All signals must be blocked any time siginfo[] is accessed. + * If no siginfo available, zero out the struct in base. + */ + if (info == NULL) + memset(signal_base->siginfo[signo], 0, sizeof(*info)); + else + memcpy(signal_base->siginfo[signo], info, sizeof(*info)); + signal_base->signal_pending[signo] = 1; + signal_base->signal_caught = 1; + + /* Wake up the other end of the pipe. */ + ignore_result(write(signal_base->signal_pipe[1], &ch, 1)); + } +} + +static int +sudo_ev_add_signal(struct sudo_event_base *base, struct sudo_event *ev, + bool tohead) +{ + const int signo = ev->fd; + debug_decl(sudo_ev_add_signal, SUDO_DEBUG_EVENT) + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: adding event %p to base %p, signal %d, events %d", + __func__, ev, base, signo, ev->events); + if (signo >= NSIG) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: signo %d larger than max %d", __func__, signo, NSIG - 1); + debug_return_int(-1); + } + if ((ev->events & ~(SUDO_EV_SIGNAL|SUDO_EV_SIGINFO|SUDO_EV_PERSIST)) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: invalid event set 0x%x", __func__, ev->events); + debug_return_int(-1); + } + + /* + * Allocate base->siginfo[signo] and base->orig_handlers[signo] as needed. + */ + if (base->siginfo[signo] == NULL) { + base->siginfo[signo] = malloc(sizeof(*base->siginfo[signo])); + if (base->siginfo[signo] == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to allocate siginfo for signo %d", + __func__, signo); + debug_return_int(-1); + } + } + if (base->orig_handlers[signo] == NULL) { + base->orig_handlers[signo] = + malloc(sizeof(*base->orig_handlers[signo])); + if (base->orig_handlers[signo] == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to allocate orig_handlers for signo %d", + __func__, signo); + debug_return_int(-1); + } + } + + /* Install signal handler as needed, saving the original value. */ + if (TAILQ_EMPTY(&base->signals[signo])) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_RESTART|SA_SIGINFO; + sa.sa_sigaction = sudo_ev_handler; + if (sigaction(signo, &sa, base->orig_handlers[signo]) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to install handler for signo %d", __func__, signo); + debug_return_int(-1); + } + base->num_handlers++; + } + + /* + * Insert signal event into the proper tail queue. + * Signal events are always persistent. + */ + ev->base = base; + if (tohead) { + TAILQ_INSERT_HEAD(&base->signals[signo], ev, entries); + } else { + TAILQ_INSERT_TAIL(&base->signals[signo], ev, entries); + } + SET(ev->events, SUDO_EV_PERSIST); + SET(ev->flags, SUDO_EVQ_INSERTED); + + /* Add the internal signal_pipe event on demand. */ + if (!ISSET(base->signal_event.flags, SUDO_EVQ_INSERTED)) + sudo_ev_add(base, &base->signal_event, NULL, true); + + /* Update global signal base so handler to update signals_pending[] */ + signal_base = base; + + debug_return_int(0); +} + +int +sudo_ev_add_v1(struct sudo_event_base *base, struct sudo_event *ev, + 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, + 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(timo, &evtmp->timeout, <)) + break; + } + if (evtmp != NULL) { + TAILQ_INSERT_BEFORE(evtmp, ev, timeouts_entries); + } else { + TAILQ_INSERT_TAIL(&base->timeouts, ev, timeouts_entries); + } + SET(ev->flags, SUDO_EVQ_TIMEOUTS); + } + debug_return_int(0); +} + +/* + * Remove an event from the base, if specified, or the base embedded + * in the event if not. Note that there are multiple tail queues. + */ +int +sudo_ev_del_v1(struct sudo_event_base *base, struct sudo_event *ev) +{ + debug_decl(sudo_ev_del, SUDO_DEBUG_EVENT) + + /* Make sure event is really in the queue. */ + if (!ISSET(ev->flags, SUDO_EVQ_INSERTED)) { + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: event %p not in queue", + __func__, ev); + debug_return_int(0); + } + + /* Check for event base mismatch, if one is specified. */ + if (base == NULL) { + if (ev->base == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: no base specified", + __func__); + debug_return_int(-1); + } + base = ev->base; + } else if (base != ev->base) { + sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: mismatch base %p, ev->base %p", + __func__, base, ev->base); + debug_return_int(-1); + } + + if (ev->events & (SUDO_EV_SIGNAL|SUDO_EV_SIGINFO)) { + const int signo = ev->fd; + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: removing event %p from base %p, signo %d, events %d", + __func__, ev, base, signo, ev->events); + + /* Unlink from signal event list. */ + TAILQ_REMOVE(&base->signals[signo], ev, entries); + if (TAILQ_EMPTY(&base->signals[signo])) { + if (sigaction(signo, base->orig_handlers[signo], NULL) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to restore handler for signo %d", + __func__, signo); + debug_return_int(-1); + } + base->num_handlers--; + } + if (base->num_handlers == 0) { + /* No registered signal events, remove internal event. */ + sudo_ev_del(base, &base->signal_event); + } + } else { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: removing event %p from base %p, fd %d, events %d", + __func__, ev, base, ev->fd, ev->events); + + /* Call backend. */ + if (ev->events & (SUDO_EV_READ|SUDO_EV_WRITE)) { + if (sudo_ev_del_impl(base, ev) != 0) + debug_return_int(-1); + } + + /* Unlink from event list. */ + TAILQ_REMOVE(&base->events, ev, entries); + + /* Unlink from timeouts list. */ + if (ISSET(ev->flags, SUDO_EVQ_TIMEOUTS)) + TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries); + } + + /* Unlink from active list. */ + if (ISSET(ev->flags, SUDO_EVQ_ACTIVE)) + TAILQ_REMOVE(&base->active, ev, active_entries); + + /* Mark event unused. */ + ev->flags = 0; + ev->pfd_idx = -1; + + debug_return_int(0); +} + +int +sudo_ev_dispatch_v1(struct sudo_event_base *base) +{ + return sudo_ev_loop_v1(base, 0); +} + +/* + * Run main event loop. + * Returns 0 on success, 1 if no events registered and -1 on error + */ +int +sudo_ev_loop_v1(struct sudo_event_base *base, int flags) +{ + struct timespec now; + struct sudo_event *ev; + int nready, rc = 0; + debug_decl(sudo_ev_loop, SUDO_DEBUG_EVENT) + + /* + * If sudo_ev_loopexit() was called when events were not running + * the next invocation of sudo_ev_loop() only runs once. + * All other base flags are ignored unless we are running events. + * Note that SUDO_EVLOOP_ONCE and SUDO_EVBASE_LOOPONCE are equivalent. + */ + base->flags |= (flags & SUDO_EVLOOP_ONCE); + base->flags &= (SUDO_EVBASE_LOOPEXIT|SUDO_EVBASE_LOOPONCE); + + for (;;) { +rescan: + /* Make sure we have some events. */ + if (TAILQ_EMPTY(&base->events)) { + rc = 1; + break; + } + + /* Call backend to scan for I/O events. */ + TAILQ_INIT(&base->active); + nready = sudo_ev_scan_impl(base, flags); + switch (nready) { + case -1: + if (errno == ENOMEM) + 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) + /* 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) + /* 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) + /* 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) + 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) + 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) +{ + struct timespec now; + debug_decl(sudo_ev_get_timeleft, SUDO_DEBUG_EVENT) + + if (!ISSET(ev->flags, SUDO_EVQ_TIMEOUTS)) { + sudo_timespecclear(ts); + debug_return_int(-1); + } + + sudo_gettime_mono(&now); + sudo_timespecsub(&ev->timeout, &now, ts); + if (ts->tv_sec < 0) + sudo_timespecclear(ts); + debug_return_int(0); +} diff --git a/lib/util/event_poll.c b/lib/util/event_poll.c new file mode 100644 index 0000000..22cda24 --- /dev/null +++ b/lib/util/event_poll.c @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <time.h> +#include <unistd.h> +#include <errno.h> +#include <poll.h> + +#include "sudo_compat.h" +#include "sudo_util.h" +#include "sudo_fatal.h" +#include "sudo_debug.h" +#include "sudo_event.h" + +int +sudo_ev_base_alloc_impl(struct sudo_event_base *base) +{ + int i; + debug_decl(sudo_ev_base_alloc_impl, SUDO_DEBUG_EVENT) + + base->pfd_high = -1; + base->pfd_max = 32; + base->pfds = reallocarray(NULL, base->pfd_max, sizeof(struct pollfd)); + if (base->pfds == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to allocate %d pollfds", __func__, base->pfd_max); + base->pfd_max = 0; + debug_return_int(-1); + } + for (i = 0; i < base->pfd_max; i++) { + base->pfds[i].fd = -1; + } + + debug_return_int(0); +} + +void +sudo_ev_base_free_impl(struct sudo_event_base *base) +{ + debug_decl(sudo_ev_base_free_impl, SUDO_DEBUG_EVENT) + free(base->pfds); + debug_return; +} + +int +sudo_ev_add_impl(struct sudo_event_base *base, struct sudo_event *ev) +{ + struct pollfd *pfd; + debug_decl(sudo_ev_add_impl, SUDO_DEBUG_EVENT) + + /* If out of space in pfds array, realloc. */ + if (base->pfd_free == base->pfd_max) { + struct pollfd *pfds; + int i; + + pfds = + reallocarray(base->pfds, base->pfd_max, 2 * sizeof(struct pollfd)); + if (pfds == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to allocate %d pollfds", __func__, base->pfd_max * 2); + debug_return_int(-1); + } + base->pfds = pfds; + base->pfd_max *= 2; + for (i = base->pfd_free; i < base->pfd_max; i++) { + base->pfds[i].fd = -1; + } + } + + /* Fill in pfd entry. */ + ev->pfd_idx = base->pfd_free; + pfd = &base->pfds[ev->pfd_idx]; + pfd->fd = ev->fd; + pfd->events = 0; + if (ISSET(ev->events, SUDO_EV_READ)) + pfd->events |= POLLIN; + if (ISSET(ev->events, SUDO_EV_WRITE)) + pfd->events |= POLLOUT; + + /* Update pfd_high and pfd_free. */ + if (ev->pfd_idx > base->pfd_high) + base->pfd_high = ev->pfd_idx; + for (;;) { + if (++base->pfd_free == base->pfd_max) + break; + if (base->pfds[base->pfd_free].fd == -1) + break; + } + + debug_return_int(0); +} + +int +sudo_ev_del_impl(struct sudo_event_base *base, struct sudo_event *ev) +{ + debug_decl(sudo_ev_del_impl, SUDO_DEBUG_EVENT) + + /* Mark pfd entry unused, add to free list and adjust high slot. */ + base->pfds[ev->pfd_idx].fd = -1; + if (ev->pfd_idx < base->pfd_free) + base->pfd_free = ev->pfd_idx; + while (base->pfd_high >= 0 && base->pfds[base->pfd_high].fd == -1) + base->pfd_high--; + + debug_return_int(0); +} + +#ifdef HAVE_PPOLL +static int +sudo_ev_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timo) +{ + return ppoll(fds, nfds, timo, NULL); +} +#else +static int +sudo_ev_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timo) +{ + const int timeout = + timo ? (timo->tv_sec * 1000) + (timo->tv_nsec / 1000000) : -1; + + return poll(fds, nfds, timeout); +} +#endif /* HAVE_PPOLL */ + +int +sudo_ev_scan_impl(struct sudo_event_base *base, int flags) +{ + struct timespec now, ts, *timeout; + struct sudo_event *ev; + int nready; + debug_decl(sudo_ev_scan_impl, SUDO_DEBUG_EVENT) + + if ((ev = TAILQ_FIRST(&base->timeouts)) != NULL) { + sudo_gettime_mono(&now); + sudo_timespecsub(&ev->timeout, &now, &ts); + if (ts.tv_sec < 0) + sudo_timespecclear(&ts); + timeout = &ts; + } else { + if (ISSET(flags, SUDO_EVLOOP_NONBLOCK)) { + sudo_timespecclear(&ts); + timeout = &ts; + } else { + timeout = NULL; + } + } + + nready = sudo_ev_poll(base->pfds, base->pfd_high + 1, timeout); + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d fds ready", __func__, nready); + switch (nready) { + case -1: + /* Error or interrupted by signal. */ + debug_return_int(-1); + 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->pfd_idx != -1 && base->pfds[ev->pfd_idx].revents) { + int what = 0; + if (base->pfds[ev->pfd_idx].revents & (POLLIN|POLLHUP|POLLNVAL|POLLERR)) + what |= (ev->events & SUDO_EV_READ); + if (base->pfds[ev->pfd_idx].revents & (POLLOUT|POLLHUP|POLLNVAL|POLLERR)) + what |= (ev->events & SUDO_EV_WRITE); + /* Make event active. */ + sudo_debug_printf(SUDO_DEBUG_DEBUG, + "%s: polled fd %d, events %d, activating %p", + __func__, ev->fd, what, ev); + ev->revents = what; + sudo_ev_activate(base, ev); + } + } + break; + } + debug_return_int(nready); +} diff --git a/lib/util/event_select.c b/lib/util/event_select.c new file mode 100644 index 0000000..a733633 --- /dev/null +++ b/lib/util/event_select.c @@ -0,0 +1,261 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <time.h> +#include <unistd.h> +#include <errno.h> + +#include "sudo_compat.h" +#include "sudo_util.h" +#include "sudo_fatal.h" +#include "sudo_debug.h" +#include "sudo_event.h" + +int +sudo_ev_base_alloc_impl(struct sudo_event_base *base) +{ + debug_decl(sudo_ev_base_alloc_impl, SUDO_DEBUG_EVENT) + + base->maxfd = NFDBITS - 1; + base->readfds_in = calloc(1, sizeof(fd_mask)); + base->writefds_in = calloc(1, sizeof(fd_mask)); + base->readfds_out = calloc(1, sizeof(fd_mask)); + base->writefds_out = calloc(1, sizeof(fd_mask)); + + if (base->readfds_in == NULL || base->writefds_in == NULL || + base->readfds_out == NULL || base->writefds_out == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to calloc(1, %zu)", __func__, sizeof(fd_mask)); + sudo_ev_base_free_impl(base); + debug_return_int(-1); + } + debug_return_int(0); +} + +void +sudo_ev_base_free_impl(struct sudo_event_base *base) +{ + debug_decl(sudo_ev_base_free_impl, SUDO_DEBUG_EVENT) + free(base->readfds_in); + free(base->writefds_in); + free(base->readfds_out); + free(base->writefds_out); + debug_return; +} + +int +sudo_ev_add_impl(struct sudo_event_base *base, struct sudo_event *ev) +{ + debug_decl(sudo_ev_add_impl, SUDO_DEBUG_EVENT) + + /* If out of space in fd sets, realloc. */ + if (ev->fd > base->maxfd) { + const int o = (base->maxfd + 1) / NFDBITS; + const int n = howmany(ev->fd + 1, NFDBITS); + const size_t used_bytes = o * sizeof(fd_mask); + const size_t new_bytes = (n - o) * sizeof(fd_mask); + fd_set *rfds_in, *wfds_in, *rfds_out, *wfds_out; + + rfds_in = reallocarray(base->readfds_in, n, sizeof(fd_mask)); + wfds_in = reallocarray(base->writefds_in, n, sizeof(fd_mask)); + rfds_out = reallocarray(base->readfds_out, n, sizeof(fd_mask)); + wfds_out = reallocarray(base->writefds_out, n, sizeof(fd_mask)); + if (rfds_in == NULL || wfds_in == NULL || + rfds_out == NULL || wfds_out == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to reallocarray(%d, %zu)", + __func__, n, sizeof(fd_mask)); + free(rfds_in); + free(wfds_in); + free(rfds_out); + free(wfds_out); + debug_return_int(-1); + } + + /* Clear newly allocated space. */ + memset((char *)rfds_in + used_bytes, 0, new_bytes); + memset((char *)wfds_in + used_bytes, 0, new_bytes); + memset((char *)rfds_out + used_bytes, 0, new_bytes); + memset((char *)wfds_out + used_bytes, 0, new_bytes); + + /* Update base. */ + base->readfds_in = rfds_in; + base->writefds_in = wfds_in; + base->readfds_out = rfds_out; + base->writefds_out = wfds_out; + base->maxfd = (n * NFDBITS) - 1; + } + + /* Set events and adjust high fd as needed. */ + if (ISSET(ev->events, SUDO_EV_READ)) { + sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: added fd %d to readfs", + __func__, ev->fd); + FD_SET(ev->fd, 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, 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, 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, base->writefds_in); + } + if (base->highfd == ev->fd) { + for (;;) { + if (FD_ISSET(base->highfd, base->readfds_in) || + FD_ISSET(base->highfd, base->writefds_in)) + break; + if (--base->highfd < 0) + break; + } + } + + debug_return_int(0); +} + +#ifdef HAVE_PSELECT +static int +sudo_ev_select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, const struct timespec *timeout) +{ + return pselect(nfds, readfds, writefds, exceptfds, timeout, NULL); +} +#else +static int +sudo_ev_select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, const struct timespec *timeout) +{ + struct timeval tvbuf, *tv = NULL; + + if (timeout != NULL) { + TIMESPEC_TO_TIMEVAL(&tvbuf, timeout); + tv = &tvbuf; + } + return select(nfds, readfds, writefds, exceptfds, tv); +} +#endif /* HAVE_PSELECT */ + +int +sudo_ev_scan_impl(struct sudo_event_base *base, int flags) +{ + struct timespec now, ts, *timeout; + struct sudo_event *ev; + size_t setsize; + int nready; + debug_decl(sudo_ev_loop, SUDO_DEBUG_EVENT) + + if ((ev = TAILQ_FIRST(&base->timeouts)) != NULL) { + sudo_gettime_mono(&now); + sudo_timespecsub(&ev->timeout, &now, &ts); + if (ts.tv_sec < 0) + sudo_timespecclear(&ts); + timeout = &ts; + } else { + if (ISSET(flags, SUDO_EVLOOP_NONBLOCK)) { + sudo_timespecclear(&ts); + timeout = &ts; + } else { + timeout = NULL; + } + } + + /* select() overwrites readfds/writefds so make a copy. */ + setsize = howmany(base->highfd + 1, NFDBITS) * sizeof(fd_mask); + memcpy(base->readfds_out, base->readfds_in, setsize); + memcpy(base->writefds_out, base->writefds_in, setsize); + + sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: select high fd %d", + __func__, base->highfd); + nready = sudo_ev_select(base->highfd + 1, base->readfds_out, + base->writefds_out, NULL, timeout); + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d fds ready", __func__, nready); + switch (nready) { + case -1: + /* Error or interrupted by signal. */ + debug_return_int(-1); + case 0: + /* Front end will activate timeout events. */ + break; + default: + /* Activate each I/O event that fired. */ + TAILQ_FOREACH(ev, &base->events, entries) { + if (ev->fd >= 0) { + int what = 0; + if (FD_ISSET(ev->fd, base->readfds_out)) + what |= (ev->events & SUDO_EV_READ); + if (FD_ISSET(ev->fd, base->writefds_out)) + what |= (ev->events & SUDO_EV_WRITE); + if (what != 0) { + /* Make event active. */ + sudo_debug_printf(SUDO_DEBUG_DEBUG, + "%s: selected fd %d, events %d, activating %p", + __func__, ev->fd, what, ev); + ev->revents = what; + sudo_ev_activate(base, ev); + } + } + } + break; + } + debug_return_int(nready); +} diff --git a/lib/util/fatal.c b/lib/util/fatal.c new file mode 100644 index 0000000..645b590 --- /dev/null +++ b/lib/util/fatal.c @@ -0,0 +1,338 @@ +/* + * 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 <sys/types.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 */ + +#define DEFAULT_TEXT_DOMAIN "sudo" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_queue.h" +#include "sudo_util.h" +#include "sudo_plugin.h" + +#ifndef HAVE_GETADDRINFO +# include "compat/getaddrinfo.h" +#endif + +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 bool (*sudo_warn_setlocale)(bool, int *); +static bool (*sudo_warn_setlocale_prev)(bool, int *); + +static void warning(const char *errstr, const char *fmt, va_list ap); + +static void +do_cleanup(void) +{ + struct sudo_fatal_callback *cb; + + /* Run callbacks, removing them from the list as we go. */ + while ((cb = SLIST_FIRST(&callbacks)) != NULL) { + SLIST_REMOVE_HEAD(&callbacks, entries); + cb->func(); + free(cb); + } +} + +void +sudo_fatal_nodebug_v1(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + warning(strerror(errno), fmt, ap); + va_end(ap); + do_cleanup(); + exit(EXIT_FAILURE); +} + +void +sudo_fatalx_nodebug_v1(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + warning(NULL, fmt, ap); + va_end(ap); + do_cleanup(); + exit(EXIT_FAILURE); +} + +void +sudo_vfatal_nodebug_v1(const char *fmt, va_list ap) +{ + warning(strerror(errno), fmt, ap); + do_cleanup(); + exit(EXIT_FAILURE); +} + +void +sudo_vfatalx_nodebug_v1(const char *fmt, va_list ap) +{ + warning(NULL, fmt, ap); + do_cleanup(); + exit(EXIT_FAILURE); +} + +void +sudo_warn_nodebug_v1(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + warning(strerror(errno), fmt, ap); + va_end(ap); +} + +void +sudo_warnx_nodebug_v1(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + warning(NULL, fmt, ap); + va_end(ap); +} + +void +sudo_vwarn_nodebug_v1(const char *fmt, va_list ap) +{ + warning(strerror(errno), fmt, ap); +} + +void +sudo_vwarnx_nodebug_v1(const char *fmt, va_list ap) +{ + warning(NULL, fmt, ap); +} + +void +sudo_gai_fatal_nodebug_v1(int errnum, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + warning(gai_strerror(errnum), fmt, ap); + va_end(ap); + do_cleanup(); + exit(EXIT_FAILURE); +} + +void +sudo_gai_vfatal_nodebug_v1(int errnum, const char *fmt, va_list ap) +{ + warning(gai_strerror(errnum), fmt, ap); + do_cleanup(); + exit(EXIT_FAILURE); +} + +void +sudo_gai_warn_nodebug_v1(int errnum, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + warning(gai_strerror(errnum), fmt, ap); + va_end(ap); +} + +void +sudo_gai_vwarn_nodebug_v1(int errnum, const char *fmt, va_list ap) +{ + warning(gai_strerror(errnum), fmt, ap); +} + +static void +warning(const char *errstr, const char *fmt, va_list ap) +{ + int cookie; + + /* 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 >= (int)sizeof(static_buf)) { + buf = malloc(++buflen); + if (buf != NULL) + (void)vsnprintf(buf, buflen, fmt, ap); + else + buf = static_buf; + } + msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG; + msgs[nmsgs++].msg = ": "; + msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG; + msgs[nmsgs++].msg = buf; + } + if (errstr != NULL) { + msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG; + msgs[nmsgs++].msg = ": "; + msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG; + msgs[nmsgs++].msg = errstr; + } + msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG; + msgs[nmsgs++].msg = "\n"; + sudo_warn_conversation(nmsgs, msgs, NULL, NULL); + if (buf != static_buf) + free(buf); + } else { + /* Write to the standard error. */ + fputs(getprogname(), stderr); + if (fmt != NULL) { + fputs(": ", stderr); + vfprintf(stderr, fmt, ap); + } + if (errstr != NULL) { + fputs(": ", stderr); + fputs(errstr, stderr); + } + putc('\n', stderr); + } + + /* Restore old locale as needed. */ + if (sudo_warn_setlocale != NULL) + sudo_warn_setlocale(true, &cookie); +} + +/* + * 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; + + /* Search for callback and remove if found, dupes are not allowed. */ + SLIST_FOREACH_PREVPTR(cb, prev, &callbacks, entries) { + if (cb->func == func) { + if (cb == SLIST_FIRST(&callbacks)) + SLIST_REMOVE_HEAD(&callbacks, entries); + else + SLIST_REMOVE_AFTER(*prev, entries); + free(cb); + return 0; + } + } + + 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(bool (*func)(bool, int *)) +{ + 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/fnmatch.c b/lib/util/fnmatch.c new file mode 100644 index 0000000..3526fda --- /dev/null +++ b/lib/util/fnmatch.c @@ -0,0 +1,502 @@ +/* $OpenBSD: fnmatch.c,v 1.15 2011/02/10 21:31:59 stsp Exp $ */ + +/* 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. + */ + +/* + * 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 <sys/types.h> + +#include <stdio.h> +#include <ctype.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_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; surpress '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/getaddrinfo.c b/lib/util/getaddrinfo.c new file mode 100644 index 0000000..046e4db --- /dev/null +++ b/lib/util/getaddrinfo.c @@ -0,0 +1,412 @@ +/* + * 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 consistant 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 support getaddrinfo natively. + * + * 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 <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <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 native 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 = strtonum(servname, 0, USHRT_MAX, &errstr); + if (errstr == NULL) { + *port = value; + } else if (errno == ERANGE) { + return EAI_SERVICE; + } else { + if (flags & AI_NUMERICSERV) + return EAI_NONAME; + if (*type != 0) + protocol = (*type == SOCK_DGRAM) ? "udp" : "tcp"; + else + protocol = NULL; + + /* + * We really technically should be generating an addrinfo struct for + * each possible protocol unless type is set, but this works well + * enough for what I need this for. + */ + servent = getservbyname(servname, protocol); + if (servent == NULL) + return EAI_NONAME; + if (strcmp(servent->s_proto, "udp") == 0) + *type = SOCK_DGRAM; + else if (strcmp(servent->s_proto, "tcp") == 0) + *type = SOCK_STREAM; + else + return EAI_SERVICE; + *port = htons(servent->s_port); + } + return 0; +} + + +/* + * Look up a host and fill in a linked list of addrinfo structs with the + * results, one per IP address of the returned host. Takes the name or IP + * address of the host as a string, the lookup flags, the type of socket (to + * fill into the addrinfo structs), the port (likewise), and a pointer to + * where the head of the linked list should be put. Returns 0 on success or + * the appropriate EAI_* error. + */ +static int +gai_lookup(const char *nodename, int flags, int socktype, unsigned short port, + struct addrinfo **res) +{ + struct addrinfo *ai, *first, *prev; + struct in_addr addr; + struct hostent *host; + const char *canonical; + int i; + + if (inet_pton(AF_INET, nodename, &addr)) { + canonical = (flags & AI_CANONNAME) ? nodename : NULL; + ai = gai_addrinfo_new(socktype, canonical, addr, port); + if (ai == NULL) + return EAI_MEMORY; + *res = ai; + return 0; + } else { + if (flags & AI_NUMERICHOST) + return EAI_NONAME; + host = gethostbyname(nodename); + if (host == NULL) + switch (h_errno) { + case HOST_NOT_FOUND: + return EAI_NONAME; + case TRY_AGAIN: + case NO_DATA: + return EAI_AGAIN; + case NO_RECOVERY: + return EAI_FAIL; + case NETDB_INTERNAL: + default: + return EAI_SYSTEM; + } + if (host->h_addr_list[0] == NULL) + return EAI_FAIL; + canonical = (flags & AI_CANONNAME) + ? ((host->h_name != NULL) ? host->h_name : nodename) + : NULL; + first = NULL; + prev = NULL; + for (i = 0; host->h_addr_list[i] != NULL; i++) { + if (host->h_length != sizeof(addr)) { + freeaddrinfo(first); + return EAI_FAIL; + } + memcpy(&addr, host->h_addr_list[i], sizeof(addr)); + ai = gai_addrinfo_new(socktype, canonical, addr, port); + if (ai == NULL) { + freeaddrinfo(first); + return EAI_MEMORY; + } + if (first == NULL) { + first = ai; + prev = ai; + } else { + prev->ai_next = ai; + prev = ai; + } + } + *res = first; + return 0; + } +} + + +/* + * The actual getaddrinfo implementation. + */ +int +sudo_getaddrinfo(const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) +{ + struct addrinfo *ai; + struct in_addr addr; + int flags, socktype, status; + unsigned short port; + + /* Take the hints into account and check them for validity. */ + if (hints != NULL) { + flags = hints->ai_flags; + socktype = hints->ai_socktype; + if ((flags & AI_INTERNAL_ALL) != flags) + return EAI_BADFLAGS; + if (hints->ai_family != AF_UNSPEC && hints->ai_family != AF_INET) + return EAI_FAMILY; + if (socktype != 0 && socktype != SOCK_STREAM && socktype != SOCK_DGRAM) + return EAI_SOCKTYPE; + + /* EAI_SOCKTYPE isn't quite right, but there isn't anything better. */ + if (hints->ai_protocol != 0) { + int protocol = hints->ai_protocol; + if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) + return EAI_SOCKTYPE; + } + } else { + flags = 0; + socktype = 0; + } + + /* + * See what we're doing. If nodename is null, either AI_PASSIVE is set or + * we're getting information for connecting to a service on the loopback + * address. Otherwise, we're getting information for connecting to a + * remote system. + */ + if (servname == NULL) + port = 0; + else { + status = gai_service(servname, flags, &socktype, &port); + if (status != 0) + return status; + } + if (nodename != NULL) + return gai_lookup(nodename, flags, socktype, port, res); + else { + if (servname == NULL) + return EAI_NONAME; + if ((flags & AI_PASSIVE) == AI_PASSIVE) + addr.s_addr = INADDR_ANY; + else + addr.s_addr = htonl(0x7f000001UL); + ai = gai_addrinfo_new(socktype, NULL, addr, port); + if (ai == NULL) + return EAI_MEMORY; + *res = ai; + return 0; + } +} +#endif /* HAVE_GETADDRINFO */ diff --git a/lib/util/getcwd.c b/lib/util/getcwd.c new file mode 100644 index 0000000..b7f2012 --- /dev/null +++ b/lib/util/getcwd.c @@ -0,0 +1,248 @@ +/* + * Copyright (c) 1989, 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <config.h> + +#ifndef HAVE_GETCWD + +#include <sys/types.h> +#include <sys/stat.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <unistd.h> +#include <dirent.h> + +#include "sudo_compat.h" + +#define ISDOT(dp) \ + (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \ + (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) + +#if defined(HAVE_STRUCT_DIRENT_D_NAMLEN) && HAVE_STRUCT_DIRENT_D_NAMLEN +# define NAMLEN(dirent) (dirent)->d_namlen +#else +# define NAMLEN(dirent) strlen((dirent)->d_name) +#endif + +char * +sudo_getcwd(char *pt, size_t size) +{ + struct dirent *dp; + DIR *dir = NULL; + dev_t dev; + ino_t ino; + int first; + char *bpt, *bup; + struct stat s; + dev_t root_dev; + ino_t root_ino; + size_t ptsize, upsize; + int save_errno; + char *ept, *eup, *up; + + /* + * If no buffer specified by the user, allocate one as necessary. + * If a buffer is specified, the size has to be non-zero. The path + * is built from the end of the buffer backwards. + */ + if (pt) { + ptsize = 0; + if (!size) { + errno = EINVAL; + return NULL; + } + ept = pt + size; + } else { + if ((pt = malloc(ptsize = 1024 - 4)) == NULL) + return NULL; + ept = pt + ptsize; + } + bpt = ept - 1; + *bpt = '\0'; + + /* + * Allocate bytes (1024 - malloc space) for the string of "../"'s. + * Should always be enough (it's 340 levels). If it's not, allocate + * as necessary. Special * case the first stat, it's ".", not "..". + */ + if ((up = malloc(upsize = 1024 - 4)) == NULL) + goto err; + eup = up + PATH_MAX; + bup = up; + up[0] = '.'; + up[1] = '\0'; + + /* Save root values, so know when to stop. */ + if (stat("/", &s)) + goto err; + root_dev = s.st_dev; + root_ino = s.st_ino; + + errno = 0; /* XXX readdir has no error return. */ + + for (first = 1;; first = 0) { + /* Stat the current level. */ + if (lstat(up, &s)) + goto err; + + /* Save current node values. */ + ino = s.st_ino; + dev = s.st_dev; + + /* Check for reaching root. */ + if (root_dev == dev && root_ino == ino) { + *--bpt = '/'; + /* + * It's unclear that it's a requirement to copy the + * path to the beginning of the buffer, but it's always + * been that way and stuff would probably break. + */ + bcopy(bpt, pt, ept - bpt); + free(up); + return pt; + } + + /* + * Build pointer to the parent directory, allocating memory + * as necessary. Max length is 3 for "../", the largest + * possible component name, plus a trailing NULL. + */ + if (bup + 3 + MAXNAMLEN + 1 >= eup) { + char *nup; + + if ((nup = reallocarray(up, upsize, 2)) == NULL) + goto err; + upsize *= 2; + up = nup; + bup = up; + eup = up + upsize; + } + *bup++ = '.'; + *bup++ = '.'; + *bup = '\0'; + + /* Open and stat parent directory. */ + if (!(dir = opendir(up)) || fstat(dirfd(dir), &s)) + goto err; + + /* Add trailing slash for next directory. */ + *bup++ = '/'; + + /* + * If it's a mount point, have to stat each element because + * the inode number in the directory is for the entry in the + * parent directory, not the inode number of the mounted file. + */ + save_errno = 0; + if (s.st_dev == dev) { + for (;;) { + if (!(dp = readdir(dir))) + goto notfound; + if (dp->d_fileno == ino) + break; + } + } else + for (;;) { + if (!(dp = readdir(dir))) + goto notfound; + if (ISDOT(dp)) + continue; + bcopy(dp->d_name, bup, NAMLEN(dp) + 1); + + /* Save the first error for later. */ + if (lstat(up, &s)) { + if (!save_errno) + save_errno = errno; + errno = 0; + continue; + } + if (s.st_dev == dev && s.st_ino == ino) + break; + } + + /* + * Check for length of the current name, preceding slash, + * leading slash. + */ + if (bpt - pt <= NAMLEN(dp) + (first ? 1 : 2)) { + size_t len, off; + char *npt; + + if (!ptsize) { + errno = ERANGE; + goto err; + } + off = bpt - pt; + len = ept - bpt; + if ((npt = reallocarray(pt, ptsize, 2)) == NULL) + goto err; + ptsize *= 2; + pt = npt; + bpt = pt + off; + ept = pt + ptsize; + bcopy(bpt, ept - len, len); + bpt = ept - len; + } + if (!first) + *--bpt = '/'; + bpt -= NAMLEN(dp); + bcopy(dp->d_name, bpt, NAMLEN(dp)); + (void)closedir(dir); + + /* Truncate any file name. */ + *bup = '\0'; + } + +notfound: + /* + * If readdir set errno, use it, not any saved error; otherwise, + * didn't find the current directory in its parent directory, set + * errno to ENOENT. + */ + if (!errno) + errno = save_errno ? save_errno : ENOENT; + /* FALLTHROUGH */ +err: + if (ptsize) + free(pt); + if (up) + free(up); + if (dir) + (void)closedir(dir); + return NULL; +} +#endif /* HAVE_GETCWD */ diff --git a/lib/util/getentropy.c b/lib/util/getentropy.c new file mode 100644 index 0000000..bfd7dcd --- /dev/null +++ b/lib/util/getentropy.c @@ -0,0 +1,605 @@ +/* + * 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 + */ + +#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_SYSCTL +# 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 + +#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 + +#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 + +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); + + 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 sanity 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 sane */ + if (fstat(fd, &st) == -1 || !S_ISCHR(st.st_mode)) { + close(fd); + goto nodevrandom; + } + for (i = 0; i < len; ) { + size_t wanted = len - i; + ssize_t ret = read(fd, (char *)buf + i, wanted); + + if (ret == -1) { + if (errno == EAGAIN || errno == EINTR) + continue; + close(fd); + goto nodevrandom; + } + i += ret; + } + close(fd); + if (gotdata(buf, len) == 0) { + errno = save_errno; + return (0); /* satisfied */ + } +nodevrandom: + errno = EIO; + return (-1); +} + +#if defined(HAVE_SYSCTL) && defined(KERN_ARND) +static int +getentropy_sysctl(void *buf, size_t len) +{ + int save_errno = errno; + int mib[2]; + size_t i; + + mib[0] = CTL_KERN; + mib[1] = KERN_ARND; + + for (i = 0; i < len; ) { + size_t chunk = len - i; + + if (sysctl(mib, 2, (char *)buf + i, &chunk, NULL, 0) == -1) + goto sysctlfailed; + i += chunk; + } + if (gotdata(buf, len) == 0) { + errno = save_errno; + return (0); /* satisfied */ + } +sysctlfailed: + errno = EIO; + return (-1); +} +#elif defined(SYS__sysctl) && defined(RANDOM_UUID) +static int +getentropy_sysctl(void *buf, size_t len) +{ + static int mib[3]; + size_t i; + int save_errno = errno; + + mib[0] = CTL_KERN; + mib[1] = KERN_RANDOM; + mib[2] = RANDOM_UUID; + + for (i = 0; i < len; ) { + size_t chunk = min(len - i, 16); + + /* SYS__sysctl because some systems already removed sysctl() */ + struct __sysctl_args args = { + .name = mib, + .nlen = 3, + .oldval = (char *)buf + i, + .oldlenp = &chunk, + }; + if (syscall(SYS__sysctl, &args) != 0) + goto sysctlfailed; + i += chunk; + } + if (gotdata(buf, len) == 0) { + errno = save_errno; + return (0); /* satisfied */ + } +sysctlfailed: + errno = EIO; + return (-1); +} +#else +static int +getentropy_sysctl(void *buf, size_t len) +{ + errno = ENOTSUP; + return (-1); +} +#endif + +#if defined(SYS_getrandom) && defined(GRND_NONBLOCK) +static int +getentropy_getrandom(void *buf, size_t len) +{ + int pre_errno = errno; + int ret; + + /* + * Try descriptor-less getrandom(), in non-blocking mode. + * + * The design of Linux getrandom is broken. It has an + * uninitialized phase coupled with blocking behaviour, which + * is unacceptable from within a library at boot time without + * possible recovery. See http://bugs.python.org/issue26839#msg267745 + */ + do { + ret = syscall(SYS_getrandom, buf, len, GRND_NONBLOCK); + } while (ret == -1 && errno == EINTR); + + if (ret < 0 || (size_t)ret != len) + return (-1); + errno = pre_errno; + return (0); +} +#else +static int +getentropy_getrandom(void *buf, size_t len) +{ + errno = ENOTSUP; + return (-1); +} +#endif + +#ifdef HAVE_CLOCK_GETTIME +static const int cl[] = { + CLOCK_REALTIME, +#ifdef CLOCK_MONOTONIC + CLOCK_MONOTONIC, +#endif +#ifdef CLOCK_MONOTONIC_RAW + CLOCK_MONOTONIC_RAW, +#endif +#ifdef CLOCK_TAI + CLOCK_TAI, +#endif +#ifdef CLOCK_VIRTUAL + CLOCK_VIRTUAL, +#endif +#ifdef CLOCK_UPTIME + CLOCK_UPTIME, +#endif +#ifdef CLOCK_PROCESS_CPUTIME_ID + CLOCK_PROCESS_CPUTIME_ID, +#endif +#ifdef CLOCK_THREAD_CPUTIME_ID + CLOCK_THREAD_CPUTIME_ID, +#endif +}; +#endif /* HAVE_CLOCK_GETTIME */ + +#ifdef HAVE_DL_ITERATE_PHDR +static int +getentropy_phdr(struct dl_phdr_info *info, size_t size, void *data) +{ + struct sudo_digest *ctx = data; + + sudo_digest_update(ctx, &info->dlpi_addr, sizeof (info->dlpi_addr)); + return (0); +} +#endif + +static int +getentropy_fallback(void *buf, size_t len) +{ + unsigned char *results = NULL; + int save_errno = errno, e, pgs = sysconf(_SC_PAGESIZE), faster = 0, repeat; + int ret = -1; + static int cnt; + struct timespec ts; + struct timeval tv; + struct rusage ru; + sigset_t sigset; + struct stat st; + struct sudo_digest *ctx; + static pid_t lastpid; + pid_t pid; + size_t i, ii, m, digest_len; + char *p; + + if ((ctx = sudo_digest_alloc(SUDO_DIGEST_SHA512)) == NULL) + goto done; + digest_len = sudo_digest_getlen(SUDO_DIGEST_SHA512); + if (digest_len == (size_t)-1 || (results = malloc(digest_len)) == NULL) + goto done; + + pid = getpid(); + if (lastpid == pid) { + faster = 1; + repeat = 2; + } else { + faster = 0; + lastpid = pid; + repeat = REPEAT; + } + for (i = 0; i < len; ) { + int j; + for (j = 0; j < repeat; j++) { + HX((e = gettimeofday(&tv, NULL)) == -1, tv); + if (e != -1) { + cnt += (int)tv.tv_sec; + cnt += (int)tv.tv_usec; + } +#ifdef HAVE_DL_ITERATE_PHDR + dl_iterate_phdr(getentropy_phdr, ctx); +#endif + +#ifdef HAVE_CLOCK_GETTIME + for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]); ii++) + HX(clock_gettime(cl[ii], &ts) == -1, ts); +#endif /* HAVE_CLOCK_GETTIME */ + + HX((pid = getpid()) == -1, pid); + HX((pid = getsid(pid)) == -1, pid); + HX((pid = getppid()) == -1, pid); + HX((pid = getpgid(0)) == -1, pid); + HX((e = getpriority(0, 0)) == -1, e); + + if (!faster) { + ts.tv_sec = 0; + ts.tv_nsec = 1; + (void) nanosleep(&ts, NULL); + } + + HX(sigpending(&sigset) == -1, sigset); + HX(sigprocmask(SIG_BLOCK, NULL, &sigset) == -1, + sigset); + + 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(NULL, + mm[m].npg * pgs, + PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANON, -1, + (off_t)0), mm[m].p); + if (mm[m].p != MAP_FAILED) { + size_t mo; + + /* Touch some memory... */ + p = mm[m].p; + mo = cnt % + (mm[m].npg * pgs - 1); + p[mo] = 1; + cnt += (int)((long)(mm[m].p) + / pgs); + } + + /* Check cnts and times... */ + for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]); + ii++) { + HX((e = clock_gettime(cl[ii], + &ts)) == -1, ts); + if (e != -1) + cnt += (int)ts.tv_nsec; + } + + HX((e = getrusage(RUSAGE_SELF, + &ru)) == -1, ru); + if (e != -1) { + cnt += (int)ru.ru_utime.tv_sec; + cnt += (int)ru.ru_utime.tv_usec; + } + } + + for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) { + if (mm[m].p != MAP_FAILED) + munmap(mm[m].p, mm[m].npg * pgs); + mm[m].p = MAP_FAILED; + } + + HX(stat(".", &st) == -1, st); + HX(stat("/", &st) == -1, st); + +#ifdef HAVE_SYS_STATVFS_H + HX(statvfs(".", &stvfs) == -1, stvfs); + HX(statvfs("/", &stvfs) == -1, stvfs); +#endif + HX((e = fstat(0, &st)) == -1, st); + if (e == -1) { + if (S_ISREG(st.st_mode) || + S_ISFIFO(st.st_mode) || + S_ISSOCK(st.st_mode)) { +#ifdef HAVE_SYS_STATVFS_H + HX(fstatvfs(0, &stvfs) == -1, + stvfs); +#endif + HX((off = lseek(0, (off_t)0, + SEEK_CUR)) < 0, off); + } + if (S_ISCHR(st.st_mode)) { + HX(tcgetattr(0, &tios) == -1, + tios); +#if 0 + } else if (S_ISSOCK(st.st_mode)) { + struct sockaddr_storage ss; + socklen_t ssl; + memset(&ss, 0, sizeof ss); + ssl = sizeof(ss); + HX(getpeername(0, + (void *)&ss, &ssl) == -1, + ss); +#endif + } + } + + HX((e = getrusage(RUSAGE_CHILDREN, + &ru)) == -1, ru); + if (e != -1) { + cnt += (int)ru.ru_utime.tv_sec; + cnt += (int)ru.ru_utime.tv_usec; + } + } else { + /* Subsequent hashes absorb previous result */ + HR(results, digest_len); + } + + HX((e = gettimeofday(&tv, NULL)) == -1, tv); + if (e != -1) { + cnt += (int)tv.tv_sec; + cnt += (int)tv.tv_usec; + } + + HD(cnt); + } + +#ifdef HAVE_GETAUXVAL +#ifdef AT_RANDOM + /* Not as random as you think but we take what we are given */ + p = (char *) getauxval(AT_RANDOM); + if (p) + HR(p, 16); +#endif +#ifdef AT_SYSINFO_EHDR + p = (char *) getauxval(AT_SYSINFO_EHDR); + if (p) + HR(p, pgs); +#endif +#ifdef AT_BASE + p = (char *) getauxval(AT_BASE); + if (p) + HD(p); +#endif +#endif /* HAVE_GETAUXVAL */ + + sudo_digest_final(ctx, results); + sudo_digest_reset(ctx); + memcpy((char *)buf + i, results, min(digest_len, len - i)); + i += min(digest_len, len - i); + } + if (gotdata(buf, len) == 0) { + errno = save_errno; + ret = 0; /* satisfied */ + } else { + errno = EIO; + } +done: + sudo_digest_free(ctx); + if (results != NULL) { + memset_s(results, sizeof(results), 0, sizeof(results)); + free(results); + } + return (ret); +} + +#endif /* HAVE_GETENTROPY */ diff --git a/lib/util/getgrouplist.c b/lib/util/getgrouplist.c new file mode 100644 index 0000000..2537363 --- /dev/null +++ b/lib/util/getgrouplist.c @@ -0,0 +1,517 @@ +/* + * Copyright (c) 2010, 2011, 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 <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <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_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) +{ + GETGROUPS_T *groups = *groupsp; + int ngroups; +#ifndef HAVE_GETGROUPLIST_2 + int grpsize, tries; +#endif + + /* For static group vector, just use getgrouplist(3). */ + if (groups != NULL) + return getgrouplist(name, basegid, groups, ngroupsp); + +#ifdef HAVE_GETGROUPLIST_2 + if ((ngroups = getgrouplist_2(name, basegid, groupsp)) == -1) + return -1; + *ngroupsp = ngroups; + return 0; +#else + grpsize = (int)sysconf(_SC_NGROUPS_MAX); + if (grpsize < 0) + grpsize = NGROUPS_MAX; + grpsize++; /* include space for the primary gid */ + /* + * It is possible to belong to more groups in the group database + * than NGROUPS_MAX. + */ + for (tries = 0; tries < 10; tries++) { + free(groups); + groups = reallocarray(NULL, grpsize, sizeof(*groups)); + if (groups == NULL) + return -1; + ngroups = grpsize; + if (getgrouplist(name, basegid, groups, &ngroups) != -1) { + *groupsp = groups; + *ngroupsp = ngroups; + return 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); + return -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; + +#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') { + for (cp = grset; *cp != '\0'; cp++) { + if (*cp == ',') + grpsize++; + } + } + groups = reallocarray(NULL, grpsize, sizeof(*groups)); + if (groups == NULL) + return -1; + } else { + /* Static group vector. */ + if (grpsize < 1) + return -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, NULL, NULL, &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; + + return 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; + + /* Must at least have space to copy instr -> buf. */ + if (inlen >= buflen) + return 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) + return 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 = ""; + grp->gr_gid = 0; + grp->gr_mem = NULL; + yp = 1; + } + + if ((fieldsep = strchr(cp = fieldsep, ':')) == NULL) + return yp ? NSS_STR_PARSE_SUCCESS : NSS_STR_PARSE_PARSE; + *fieldsep++ = '\0'; + grp->gr_passwd = cp; + + if ((fieldsep = strchr(cp = fieldsep, ':')) == NULL) + return yp ? NSS_STR_PARSE_SUCCESS : NSS_STR_PARSE_PARSE; + *fieldsep++ = '\0'; + id = sudo_strtoid(cp, NULL, NULL, &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) + return NSS_STR_PARSE_ERANGE; + return 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); + for (;;) { + if (gr_mem == gr_end) + return 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; + } + return 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; + + /* 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) + return NSS_UNAVAIL; + + /* Parse groups file string -> struct group. */ + grp = buf->result; + error = (*gbm->str2ent)(instr, inlen, grp, buf->buffer, buf->buflen); + if (error || grp->gr_mem == NULL) + goto done; + + for (gr_mem = grp->gr_mem; *gr_mem != NULL; gr_mem++) { + if (strcmp(*gr_mem, user) == 0) { + /* Append to gid_array unless gr_gid is a dupe. */ + for (i = 0; i < gbm->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); + return 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); + + 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) + return -1; + gbm.maxgids <<= 2; + gbm.process_cstr = process_cstr_dynamic; + } else { + /* Static group vector. */ + if (gbm.maxgids <= 0) + return -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); + return -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; + return 0; + } + *ngroupsp = gbm.maxgids; + return -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; + + 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) + return -1; + grpsize <<= 2; + } else { + /* Static group vector. */ + if (grpsize < 1) + return -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; + + return 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..ed5061c --- /dev/null +++ b/lib/util/gethostname.c @@ -0,0 +1,59 @@ +/* + * 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 <sys/types.h> +#include <stdio.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/getline.c b/lib/util/getline.c new file mode 100644 index 0000000..153037b --- /dev/null +++ b/lib/util/getline.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2009-2010, 2012-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_GETLINE + +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <limits.h> + +#include "sudo_compat.h" + +#ifdef HAVE_FGETLN +ssize_t +sudo_getline(char **bufp, size_t *bufsizep, FILE *fp) +{ + char *buf, *cp; + size_t bufsize; + size_t len; + + buf = fgetln(fp, &len); + if (buf) { + bufsize = *bufp ? *bufsizep : 0; + if (bufsize == 0 || bufsize - 1 < len) { + bufsize = len + 1; + cp = realloc(*bufp, bufsize); + if (cp == NULL) + return -1; + *bufp = cp; + *bufsizep = bufsize; + } + memcpy(*bufp, buf, len); + (*bufp)[len] = '\0'; + } + return buf ? len : -1; +} +#else +ssize_t +sudo_getline(char **bufp, size_t *bufsizep, FILE *fp) +{ + char *buf, *cp; + size_t bufsize; + ssize_t len = 0; + + buf = *bufp; + bufsize = *bufsizep; + if (buf == NULL || bufsize == 0) { + bufsize = LINE_MAX; + cp = realloc(buf, bufsize); + if (cp == NULL) + return -1; + buf = cp; + } + + for (;;) { + if (fgets(buf + len, bufsize - len, fp) == NULL) { + len = -1; + break; + } + len = strlen(buf); + if (!len || buf[len - 1] == '\n' || feof(fp)) + break; + cp = reallocarray(buf, bufsize, 2); + if (cp == NULL) + return -1; + bufsize *= 2; + buf = cp; + } + *bufp = buf; + *bufsizep = bufsize; + return len; +} +#endif /* HAVE_FGETLN */ +#endif /* HAVE_GETLINE */ diff --git a/lib/util/getopt_long.c b/lib/util/getopt_long.c new file mode 100644 index 0000000..d88c3a5 --- /dev/null +++ b/lib/util/getopt_long.c @@ -0,0 +1,629 @@ +/* $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 $ */ + +/* + * 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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ + +#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 "" + +#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 + 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..f4d4664 --- /dev/null +++ b/lib/util/gettime.c @@ -0,0 +1,227 @@ +/* + * 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/types.h> +#include <sys/time.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <errno.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/gidlist.c b/lib/util/gidlist.c new file mode 100644 index 0000000..1adf9bc --- /dev/null +++ b/lib/util/gidlist.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#include <grp.h> + +#define DEFAULT_TEXT_DOMAIN "sudo" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +/* + * Parse a comma-separated list of gids into an allocated array of GETGROUPS_T. + * If a pointer to the base gid is specified, it is stored as the first element + * in the array. + * Returns the number of gids in the allocated array. + */ +int +sudo_parse_gids_v1(const char *gidstr, const gid_t *basegid, GETGROUPS_T **gidsp) +{ + int ngids = 0; + GETGROUPS_T *gids; + const char *cp = gidstr; + const char *errstr; + char *ep; + debug_decl(sudo_parse_gids, SUDO_DEBUG_UTIL) + + /* Count groups. */ + if (*cp != '\0') { + ngids++; + do { + if (*cp++ == ',') + ngids++; + } while (*cp != '\0'); + } + /* Base gid is optional. */ + if (basegid != NULL) + ngids++; + /* Allocate and fill in array. */ + if (ngids != 0) { + gids = reallocarray(NULL, ngids, sizeof(GETGROUPS_T)); + if (gids == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_int(-1); + } + ngids = 0; + if (basegid != NULL) + gids[ngids++] = *basegid; + cp = gidstr; + do { + gids[ngids] = (GETGROUPS_T) sudo_strtoid(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..f3a6d57 --- /dev/null +++ b/lib/util/glob.c @@ -0,0 +1,958 @@ +/* + * 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/types.h> +#include <sys/stat.h> + +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <unistd.h> +#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 *, unsigned int); +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 < 0 || pglob->gl_pathc < 0 || + pglob->gl_offs >= INT_MAX || pglob->gl_pathc >= INT_MAX || + pglob->gl_pathc >= INT_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, 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; + ssize_t i; + size_t newn, len; + char *copy = NULL; + const Char *p; + + newn = 2 + pglob->gl_pathc + pglob->gl_offs; + if (pglob->gl_offs >= INT_MAX || + pglob->gl_pathc >= INT_MAX || + newn >= INT_MAX || + SIZE_MAX / sizeof(*pathv) <= newn) { + nospace: + for (i = pglob->gl_offs; i < (ssize_t)(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; ) + *--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) +{ + int 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, unsigned int len) +{ + + while (len--) { + if ((*buf++ = *str++) == EOS) + return 0; + } + return 1; +} + +#ifdef DEBUG +static void +qprintf(const char *str, Char *s) +{ + Char *p; + + (void)printf("%s:\n", str); + for (p = s; *p; p++) + (void)printf("%c", CHAR(*p)); + (void)printf("\n"); + for (p = s; *p; p++) + (void)printf("%c", *p & M_PROTECT ? '"' : ' '); + (void)printf("\n"); + for (p = s; *p; p++) + (void)printf("%c", ismeta(*p) ? '_' : ' '); + (void)printf("\n"); +} +#endif /* DEBUG */ +#endif /* HAVE_GLOB */ diff --git a/lib/util/inet_ntop.c b/lib/util/inet_ntop.c new file mode 100644 index 0000000..f9072c5 --- /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 $ */ + +/* 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 +#ifndef INET6_ADDRSTRLEN +# define INET6_ADDRSTRLEN 46 +#endif + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +/* const char * + * inet_ntop4(src, dst, size) + * format an IPv4 address, more or less like inet_ntoa() + * return: + * `dst' (as a const) + * notes: + * (1) uses no statics + * (2) takes a unsigned char* not an in_addr as input + * author: + * Paul Vixie, 1996. + */ +static const char * +inet_ntop4(const unsigned char *src, char *dst, socklen_t size) +{ + const char fmt[] = "%u.%u.%u.%u"; + int len; + + len = snprintf(dst, size, fmt, src[0], src[1], src[2], src[3]); + if (len <= 0 || len >= size) { + errno = ENOSPC; + return (NULL); + } + return (dst); +} + +#ifdef HAVE_STRUCT_IN6_ADDR +/* const char * + * inet_ntop6(src, dst, size) + * convert IPv6 binary address into presentation (printable) format + * author: + * Paul Vixie, 1996. + */ +static const char * +inet_ntop6(const unsigned char *src, char *dst, socklen_t size) +{ + /* + * Note that int32_t and int16_t need only be "at least" large enough + * to contain a value of the specified size. On some systems, like + * Crays, there is no such thing as an integer variable with 16 bits. + * Keep this in mind if you think this function should have been coded + * to use pointer overlays. All the world's not a VAX. + */ + char *cp, *ep; + struct { int base, len; } best, cur; + unsigned int words[NS_IN6ADDRSZ / NS_INT16SZ]; + int i; + int advance; + + /* + * Preprocess: + * Copy the input (bytewise) array into a wordwise array. + * Find the longest run of 0x00's in src[] for :: shorthanding. + */ + memset(words, 0, sizeof(words)); + for (i = 0; i < NS_IN6ADDRSZ; i++) + words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); + best.base = -1; + best.len = 0; + cur.base = -1; + cur.len = 0; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { + if (words[i] == 0) { + if (cur.base == -1) + cur.base = i, cur.len = 1; + else + cur.len++; + } else { + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + cur.base = -1; + } + } + } + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + } + if (best.base != -1 && best.len < 2) + best.base = -1; + + /* + * Format the result. + */ + cp = dst; + ep = dst + size; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ) && cp < ep; i++) { + /* Are we inside the best run of 0x00's? */ + if (best.base != -1 && i >= best.base && + i < (best.base + best.len)) { + if (i == best.base) { + if (cp + 1 >= ep) { + errno = ENOSPC; + return (NULL); + } + *cp++ = ':'; + } + continue; + } + /* Are we following an initial run of 0x00s or any real hex? */ + if (i != 0) { + if (cp + 1 >= ep) { + errno = ENOSPC; + return (NULL); + } + *cp++ = ':'; + } + /* Is this address an encapsulated IPv4? */ + if (i == 6 && best.base == 0 && + (best.len == 6 || + (best.len == 7 && words[7] != 0x0001) || + (best.len == 5 && words[5] == 0xffff))) { + if (!inet_ntop4(src + 12, cp, (socklen_t)(ep - cp))) + return (NULL); + cp += strlen(cp); + break; + } + advance = snprintf(cp, (size_t)(ep - cp), "%x", words[i]); + if (advance <= 0 || advance >= ep - cp) { + errno = ENOSPC; + return (NULL); + } + cp += advance; + } + /* Was it a trailing run of 0x00's? */ + if (best.base != -1 && + (best.base + best.len) == (NS_IN6ADDRSZ / NS_INT16SZ)) { + if (cp + 1 >= ep) { + errno = ENOSPC; + return (NULL); + } + *cp++ = ':'; + } + if (cp + 1 >= ep) { + errno = ENOSPC; + return (NULL); + } + *cp++ = '\0'; + + return (dst); +} +#endif /* HAVE_STRUCT_IN6_ADDR */ + +/* const char * + * inet_ntop(af, src, dst, size) + * convert a network format address to presentation format. + * return: + * pointer to presentation format address (`dst'), or NULL (see errno). + * author: + * Paul Vixie, 1996. + */ +const char * +sudo_inet_ntop(int af, const void *src, char *dst, socklen_t size) +{ + switch (af) { + case AF_INET: + return (inet_ntop4(src, dst, size)); +#ifdef HAVE_STRUCT_IN6_ADDR + case AF_INET6: + return (inet_ntop6(src, dst, size)); +#endif + default: + errno = EAFNOSUPPORT; + return (NULL); + } + /* NOTREACHED */ +} + +#endif /* !HAVE_INET_NTOP */ diff --git a/lib/util/inet_pton.c b/lib/util/inet_pton.c new file mode 100644 index 0000000..ba3ec61 --- /dev/null +++ b/lib/util/inet_pton.c @@ -0,0 +1,254 @@ +/* $OpenBSD: inet_pton.c,v 1.8 2010/05/06 15:47:14 claudio Exp $ */ + +/* 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> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <errno.h> + +#include "sudo_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..54f88a5 --- /dev/null +++ b/lib/util/isblank.c @@ -0,0 +1,37 @@ +/* + * 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 <sys/types.h> + +#include "sudo_compat.h" + +#undef isblank +int +isblank(int ch) +{ + return ch == ' ' || ch == '\t'; +} +#endif /* HAVE_ISBLANK */ diff --git a/lib/util/key_val.c b/lib/util/key_val.c new file mode 100644 index 0000000..9bbd043 --- /dev/null +++ b/lib/util/key_val.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2012, 2014-2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ + +#include "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..5e48258 --- /dev/null +++ b/lib/util/lbuf.c @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2007-2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <unistd.h> +#include <ctype.h> + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_lbuf.h" + +void +sudo_lbuf_init_v1(struct sudo_lbuf *lbuf, sudo_lbuf_output_t output, + int indent, const char *continuation, int cols) +{ + debug_decl(sudo_lbuf_init, SUDO_DEBUG_UTIL) + + lbuf->output = output; + lbuf->continuation = continuation; + lbuf->indent = indent; + lbuf->cols = cols; + lbuf->error = 0; + lbuf->len = 0; + lbuf->size = 0; + lbuf->buf = NULL; + + debug_return; +} + +void +sudo_lbuf_destroy_v1(struct sudo_lbuf *lbuf) +{ + debug_decl(sudo_lbuf_destroy, SUDO_DEBUG_UTIL) + + free(lbuf->buf); + lbuf->buf = NULL; + + debug_return; +} + +static bool +sudo_lbuf_expand(struct sudo_lbuf *lbuf, int extra) +{ + debug_decl(sudo_lbuf_expand, SUDO_DEBUG_UTIL) + + if (lbuf->len + extra + 1 >= lbuf->size) { + char *new_buf; + int new_size = lbuf->size; + + do { + new_size += 256; + } while (lbuf->len + extra + 1 >= new_size); + if ((new_buf = realloc(lbuf->buf, new_size)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate memory"); + lbuf->error = 1; + debug_return_bool(false); + } + lbuf->buf = new_buf; + lbuf->size = new_size; + } + debug_return_bool(true); +} + +/* + * Parse the format and append strings, only %s and %% escapes are supported. + * Any characters in set are quoted with a backslash. + */ +bool +sudo_lbuf_append_quoted_v1(struct sudo_lbuf *lbuf, const char *set, const char *fmt, ...) +{ + int len, saved_len = lbuf->len; + bool ret = false; + char *cp, *s; + va_list ap; + debug_decl(sudo_lbuf_append_quoted, SUDO_DEBUG_UTIL) + + if (sudo_lbuf_error(lbuf)) + debug_return_bool(false); + + va_start(ap, fmt); + while (*fmt != '\0') { + if (fmt[0] == '%' && fmt[1] == 's') { + if ((s = va_arg(ap, char *)) == NULL) + s = "(NULL)"; + while ((cp = strpbrk(s, set)) != NULL) { + len = (int)(cp - s); + if (!sudo_lbuf_expand(lbuf, len + 2)) + goto done; + memcpy(lbuf->buf + lbuf->len, s, len); + lbuf->len += len; + lbuf->buf[lbuf->len++] = '\\'; + lbuf->buf[lbuf->len++] = *cp; + s = cp + 1; + } + if (*s != '\0') { + len = strlen(s); + if (!sudo_lbuf_expand(lbuf, len)) + goto done; + memcpy(lbuf->buf + lbuf->len, s, len); + lbuf->len += len; + } + fmt += 2; + continue; + } + if (!sudo_lbuf_expand(lbuf, 2)) + goto done; + if (strchr(set, *fmt) != NULL) + lbuf->buf[lbuf->len++] = '\\'; + lbuf->buf[lbuf->len++] = *fmt++; + } + ret = true; + +done: + if (!ret) + lbuf->len = saved_len; + if (lbuf->size != 0) + lbuf->buf[lbuf->len] = '\0'; + va_end(ap); + + debug_return_bool(ret); +} + +/* + * Parse the format and append strings, only %s and %% escapes are supported. + */ +bool +sudo_lbuf_append_v1(struct sudo_lbuf *lbuf, const char *fmt, ...) +{ + int len, saved_len = lbuf->len; + bool ret = false; + va_list ap; + char *s; + debug_decl(sudo_lbuf_append, SUDO_DEBUG_UTIL) + + if (sudo_lbuf_error(lbuf)) + debug_return_bool(false); + + va_start(ap, fmt); + while (*fmt != '\0') { + if (fmt[0] == '%' && fmt[1] == 's') { + if ((s = va_arg(ap, char *)) == NULL) + s = "(NULL)"; + len = strlen(s); + if (!sudo_lbuf_expand(lbuf, len)) + goto done; + memcpy(lbuf->buf + lbuf->len, s, len); + lbuf->len += len; + fmt += 2; + continue; + } + if (!sudo_lbuf_expand(lbuf, 1)) + goto done; + lbuf->buf[lbuf->len++] = *fmt++; + } + ret = true; + +done: + if (!ret) + lbuf->len = saved_len; + if (lbuf->size != 0) + lbuf->buf[lbuf->len] = '\0'; + va_end(ap); + + debug_return_bool(ret); +} + +/* XXX - check output function return value */ +static void +sudo_lbuf_println(struct sudo_lbuf *lbuf, char *line, int len) +{ + char *cp, save; + int i, have, contlen = 0; + int indent = lbuf->indent; + bool is_comment = false; + debug_decl(sudo_lbuf_println, SUDO_DEBUG_UTIL) + + /* Comment lines don't use continuation and only indent is for "# " */ + if (line[0] == '#' && isblank((unsigned char)line[1])) { + is_comment = true; + indent = 2; + } + if (lbuf->continuation != NULL && !is_comment) + contlen = strlen(lbuf->continuation); + + /* + * Print the buffer, splitting the line as needed on a word + * boundary. + */ + cp = line; + have = lbuf->cols; + while (cp != NULL && *cp != '\0') { + char *ep = NULL; + int need = len - (int)(cp - line); + + if (need > have) { + have -= contlen; /* subtract for continuation char */ + if ((ep = memrchr(cp, ' ', have)) == NULL) + ep = memchr(cp + have, ' ', need - have); + if (ep != NULL) + need = (int)(ep - cp); + } + if (cp != line) { + if (is_comment) { + lbuf->output("# "); + } else { + /* indent continued lines */ + /* XXX - build up string instead? */ + for (i = 0; i < indent; i++) + lbuf->output(" "); + } + } + /* NUL-terminate cp for the output function and restore afterwards */ + save = cp[need]; + cp[need] = '\0'; + lbuf->output(cp); + cp[need] = save; + cp = ep; + + /* + * If there is more to print, reset have, incremement cp past + * the whitespace, and print a line continuaton char if needed. + */ + if (cp != NULL) { + have = lbuf->cols - indent; + ep = line + len; + while (cp < ep && isblank((unsigned char)*cp)) { + cp++; + } + if (contlen) + lbuf->output(lbuf->continuation); + } + lbuf->output("\n"); + } + + debug_return; +} + +/* + * Print the buffer with word wrap based on the tty width. + * The lbuf is reset on return. + * XXX - check output function return value + */ +void +sudo_lbuf_print_v1(struct sudo_lbuf *lbuf) +{ + char *cp, *ep; + int len; + debug_decl(sudo_lbuf_print, SUDO_DEBUG_UTIL) + + if (lbuf->buf == NULL || lbuf->len == 0) + goto done; + + /* For very small widths just give up... */ + len = lbuf->continuation ? strlen(lbuf->continuation) : 0; + if (lbuf->cols <= lbuf->indent + len + 20) { + if (lbuf->len > 0) { + lbuf->buf[lbuf->len] = '\0'; + lbuf->output(lbuf->buf); + if (lbuf->buf[lbuf->len - 1] != '\n') + lbuf->output("\n"); + } + goto done; + } + + /* Print each line in the buffer */ + for (cp = lbuf->buf; cp != NULL && *cp != '\0'; ) { + if (*cp == '\n') { + lbuf->output("\n"); + cp++; + } else { + len = lbuf->len - (cp - lbuf->buf); + if ((ep = memchr(cp, '\n', len)) != NULL) + len = (int)(ep - cp); + if (len) + sudo_lbuf_println(lbuf, cp, len); + cp = ep ? ep + 1 : NULL; + } + } + +done: + lbuf->len = 0; /* reset the buffer for re-use. */ + lbuf->error = 0; + + debug_return; +} + +bool +sudo_lbuf_error_v1(struct sudo_lbuf *lbuf) +{ + if (lbuf != NULL && lbuf->error != 0) + return true; + return false; +} + +void +sudo_lbuf_clearerr_v1(struct sudo_lbuf *lbuf) +{ + if (lbuf != NULL) + lbuf->error = 0; +} diff --git a/lib/util/locking.c b/lib/util/locking.c new file mode 100644 index 0000000..64499e6 --- /dev/null +++ b/lib/util/locking.c @@ -0,0 +1,121 @@ +/* + * 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 <sys/types.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_util.h" +#include "sudo_debug.h" + +/* + * Lock/unlock all or part of a file. + */ +#ifdef HAVE_LOCKF +bool +sudo_lock_file_v1(int fd, int type) +{ + return sudo_lock_region_v1(fd, type, 0); +} + +bool +sudo_lock_region_v1(int fd, int type, off_t len) +{ + int op; + debug_decl(sudo_lock_region, SUDO_DEBUG_UTIL) + + switch (type) { + case SUDO_LOCK: + op = F_LOCK; + break; + case SUDO_TLOCK: + op = F_TLOCK; + break; + case SUDO_UNLOCK: + op = F_ULOCK; + break; + default: + errno = EINVAL; + debug_return_bool(false); + } + debug_return_bool(lockf(fd, op, len) == 0); +} +#else +bool +sudo_lock_file_v1(int fd, int type) +{ + return sudo_lock_region_v1(fd, type, 0); +} + +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: + lock.l_type = F_WRLCK; + func = F_SETLKW; + break; + case SUDO_TLOCK: + lock.l_type = F_WRLCK; + func = F_SETLK; + break; + case SUDO_UNLOCK: + lock.l_type = F_UNLCK; + func = F_SETLK; + break; + default: + 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/memrchr.c b/lib/util/memrchr.c new file mode 100644 index 0000000..225b542 --- /dev/null +++ b/lib/util/memrchr.c @@ -0,0 +1,49 @@ +/* + * 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/memset_s.c b/lib/util/memset_s.c new file mode 100644 index 0000000..2fea12d --- /dev/null +++ b/lib/util/memset_s.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2013-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> + +#include <sys/types.h> +#include <errno.h> +#include <limits.h> +#if defined(HAVE_STDINT_H) +# include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +# include <inttypes.h> +#endif + +#include "sudo_compat.h" + +#ifndef RSIZE_MAX +# if defined(SIZE_MAX) +# define RSIZE_MAX (SIZE_MAX >> 1) +# elif defined(__LP64__) +# define RSIZE_MAX 0x7fffffffffffffffUL +# else +# define RSIZE_MAX 0x7fffffffU +# endif +#endif + +/* + * Simple implementation of C11 memset_s() function. + * We use a volatile pointer when updating the byte string. + * Most compilers will avoid optimizing away access to a + * volatile pointer, even if the pointer appears to be unused + * after the call. + * + * Note that C11 does not specify the return value on error, only + * that it be non-zero. We use EINVAL for all errors. + */ +errno_t +sudo_memset_s(void *v, rsize_t smax, int c, rsize_t n) +{ + errno_t ret = 0; + volatile unsigned char *s = v; + + /* Fatal runtime-constraint violations. */ + if (s == NULL || smax > RSIZE_MAX) { + ret = errno = EINVAL; + goto done; + } + /* Non-fatal runtime-constraint violation, n must not exceed smax. */ + if (n > smax) { + n = smax; + ret = errno = EINVAL; + } + /* Updating through a volatile pointer should not be optimized away. */ + while (n--) + *s++ = (unsigned char)c; +done: + return ret; +} diff --git a/lib/util/mksiglist.c b/lib/util/mksiglist.c new file mode 100644 index 0000000..6b5d1ca --- /dev/null +++ b/lib/util/mksiglist.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2010-2012, 2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + + +#include <config.h> + +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> + +#include "sudo_compat.h" + +__dso_public int main(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + static char *sudo_sys_siglist[NSIG]; + int i; + +#include "mksiglist.h" + + printf("#include <config.h>\n"); + printf("#include <sys/types.h>\n"); + printf("#include <signal.h>\n"); + printf("#include \"sudo_compat.h\"\n\n"); + printf("const char *const sudo_sys_siglist[NSIG] = {\n"); + for (i = 0; i < NSIG; i++) { + if (sudo_sys_siglist[i] != NULL) { + printf(" \"%s\",\n", sudo_sys_siglist[i]); + } else { + printf(" \"Signal %d\",\n", i); + } + } + printf("};\n"); + + exit(0); +} diff --git a/lib/util/mksiglist.h b/lib/util/mksiglist.h new file mode 100644 index 0000000..c17cf88 --- /dev/null +++ b/lib/util/mksiglist.h @@ -0,0 +1,174 @@ +/* public domain */ + +#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 SIGUNUSED + if (sudo_sys_siglist[SIGUNUSED] == NULL) + sudo_sys_siglist[SIGUNUSED] = "Unused"; +#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 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/mksigname.c b/lib/util/mksigname.c new file mode 100644 index 0000000..535ef46 --- /dev/null +++ b/lib/util/mksigname.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2010-2012, 2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + + +#include <config.h> + +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> + +#include "sudo_compat.h" + +__dso_public int main(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + static char *sudo_sys_signame[NSIG]; + int i; + +#include "mksigname.h" + + printf("#include <config.h>\n"); + printf("#include <sys/types.h>\n"); + printf("#include <signal.h>\n"); + printf("#include \"sudo_compat.h\"\n\n"); + printf("const char *const sudo_sys_signame[NSIG] = {\n"); + for (i = 0; i < NSIG; i++) { + if (sudo_sys_signame[i] != NULL) { + printf(" \"%s\",\n", sudo_sys_signame[i]); + } else { + printf(" \"Signal %d\",\n", i); + } + } + printf("};\n"); + + exit(0); +} diff --git a/lib/util/mksigname.h b/lib/util/mksigname.h new file mode 100644 index 0000000..f3bc5d7 --- /dev/null +++ b/lib/util/mksigname.h @@ -0,0 +1,175 @@ +/* public domain */ + +sudo_sys_signame[0] = "Signal 0"; +#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 SIGUNUSED + if (sudo_sys_signame[SIGUNUSED] == NULL) + sudo_sys_signame[SIGUNUSED] = "UNUSED"; +#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 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/mktemp.c b/lib/util/mktemp.c new file mode 100644 index 0000000..f153924 --- /dev/null +++ b/lib/util/mktemp.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2001, 2003, 2004, 2008-2011, 2013, 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> + +#if !defined(HAVE_MKSTEMPS) || !defined(HAVE_MKDTEMP) + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#endif /* HAVE_STDLIB_H */ +#if defined(HAVE_STDINT_H) +# include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +# include <inttypes.h> +#endif +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <ctype.h> +#include <unistd.h> +#include <time.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 + +static int +mktemp_internal(char *path, int slen, int mode) +{ + char *start, *cp, *ep; + const char tempchars[] = TEMPCHARS; + unsigned int r, 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; + + tries = 1; + for (start = ep; start > path && start[-1] == 'X'; start--) { + if (tries < INT_MAX / NUM_CHARS) + tries *= NUM_CHARS; + } + tries *= 2; + if (ep - start < MIN_X) { + errno = EINVAL; + return -1; + } + + do { + for (cp = start; cp != ep; cp++) { + r = arc4random_uniform(NUM_CHARS); + *cp = tempchars[r]; + } + + switch (mode) { + case MKTEMP_FILE: + fd = open(path, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR); + if (fd != -1 || errno != EEXIST) + return fd; + break; + case MKTEMP_DIR: + if (mkdir(path, S_IRWXU) == 0) + return 0; + if (errno != EEXIST) + return -1; + break; + } + } while (--tries); + + errno = EEXIST; + return -1; +} + +int +sudo_mkstemps(char *path, int slen) +{ + return mktemp_internal(path, slen, MKTEMP_FILE); +} + +char * +sudo_mkdtemp(char *path) +{ + if (mktemp_internal(path, 0, MKTEMP_DIR) == -1) + return NULL; + return path; +} +#endif /* !HAVE_MKSTEMPS || !HAVE_MKDTEMP */ diff --git a/lib/util/nanosleep.c b/lib/util/nanosleep.c new file mode 100644 index 0000000..7c09b5b --- /dev/null +++ b/lib/util/nanosleep.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2009-2011, 2013, 2017-2018 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#ifndef HAVE_NANOSLEEP + +#include <sys/types.h> +#include <sys/time.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif /* HAVE_SYS_SELECT_H */ +#include <time.h> +#include <errno.h> + +#include "sudo_compat.h" +#include "sudo_util.h" + +int +sudo_nanosleep(const struct timespec *ts, struct timespec *rts) +{ + struct timeval timeout, endtime, now; + int rval; + + if (ts->tv_sec == 0 && ts->tv_nsec < 1000) { + timeout.tv_sec = 0; + timeout.tv_usec = 1; + } else { + TIMESPEC_TO_TIMEVAL(&timeout, ts); + } + if (rts != NULL) { + if (gettimeofday(&endtime, NULL) == -1) + return -1; + sudo_timevaladd(&endtime, &timeout, &endtime); + } + rval = select(0, NULL, NULL, NULL, &timeout); + if (rts != NULL && rval == -1 && errno == EINTR) { + if (gettimeofday(&now, NULL) == -1) + return -1; + sudo_timevalsub(&endtime, &now, &endtime); + TIMEVAL_TO_TIMESPEC(&endtime, rts); + } + return rval; +} +#endif /* HAVE_NANOSLEEP */ diff --git a/lib/util/parseln.c b/lib/util/parseln.c new file mode 100644 index 0000000..ec345a3 --- /dev/null +++ b/lib/util/parseln.c @@ -0,0 +1,136 @@ +/* + * 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 <sys/types.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRING_H */ +#include <ctype.h> +#include <unistd.h> +#include <fcntl.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 getline buffer? + * could also make comment char and line continuation configurable + */ +ssize_t +sudo_parseln_v2(char **bufp, size_t *bufsizep, unsigned int *lineno, FILE *fp, int flags) +{ + size_t linesize = 0, total = 0; + ssize_t len; + char *cp, *line = NULL; + bool continued, comment; + debug_decl(sudo_parseln, SUDO_DEBUG_UTIL) + + do { + comment = false; + continued = false; + len = getline(&line, &linesize, fp); + if (len == -1) + break; + if (lineno != NULL) + (*lineno)++; + + /* Remove trailing newline(s) if present. */ + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) + line[--len] = '\0'; + + /* Remove comments or check for line continuation (but not both) */ + if ((cp = strchr(line, '#')) != NULL) { + if (cp == line || !ISSET(flags, PARSELN_COMM_BOL)) { + *cp = '\0'; + len = (ssize_t)(cp - line); + comment = true; + } + } + if (!comment && !ISSET(flags, PARSELN_CONT_IGN)) { + if (len > 0 && line[len - 1] == '\\' && (len == 1 || line[len - 2] != '\\')) { + line[--len] = '\0'; + continued = true; + } + } + + /* Trim leading and trailing whitespace */ + if (!continued) { + while (len > 0 && isblank((unsigned char)line[len - 1])) + line[--len] = '\0'; + } + for (cp = line; isblank((unsigned char)*cp); cp++) + len--; + + if (*bufp == NULL || total + len >= *bufsizep) { + void *tmp; + size_t size = total + len + 1; + + if (size < 64) { + size = 64; + } else if (size <= 0x80000000) { + /* Round up to next highest power of two. */ + size--; + size |= size >> 1; + size |= size >> 2; + size |= size >> 4; + size |= size >> 8; + size |= size >> 16; + size++; + } + if ((tmp = realloc(*bufp, size)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate memory"); + len = -1; + total = 0; + break; + } + *bufp = tmp; + *bufsizep = size; + } + memcpy(*bufp + total, cp, len + 1); + total += len; + } while (continued); + free(line); + if (len == -1 && total == 0) + debug_return_ssize_t(-1); + debug_return_ssize_t(total); +} + +ssize_t +sudo_parseln_v1(char **bufp, size_t *bufsizep, unsigned int *lineno, FILE *fp) +{ + return sudo_parseln_v2(bufp, bufsizep, lineno, fp, 0); +} diff --git a/lib/util/pipe2.c b/lib/util/pipe2.c new file mode 100644 index 0000000..89a904c --- /dev/null +++ b/lib/util/pipe2.c @@ -0,0 +1,64 @@ +/* + * 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 <sys/types.h> + +#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_NONBLOCK)) { + int flags = fcntl(fildes[0], F_GETFL, 0); + if (flags == -1) + goto bad; + if (fcntl(fildes[0], F_SETFL, flags | O_NONBLOCK) == -1) + goto bad; + flags = fcntl(fildes[1], F_GETFL, 0); + if (flags == -1) + goto bad; + if (fcntl(fildes[1], F_SETFL, flags | O_NONBLOCK) == -1) + goto bad; + } + 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; + } + return 0; +bad: + close(fildes[0]); + close(fildes[1]); + return -1; +} + +#endif /* HAVE_PIPE2 */ diff --git a/lib/util/progname.c b/lib/util/progname.c new file mode 100644 index 0000000..ffe946c --- /dev/null +++ b/lib/util/progname.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ + +#include "sudo_compat.h" +#include "sudo_util.h" + +#ifdef HAVE_GETPROGNAME + +void +initprogname(const char *name) +{ +# ifdef HAVE_SETPROGNAME + const char *progname; + + /* Fall back on "name" if getprogname() returns an empty string. */ + if ((progname = getprogname()) != NULL && *progname != '\0') + name = progname; + + /* Check for libtool prefix and strip it if present. */ + if (name[0] == 'l' && name[1] == 't' && name[2] == '-' && name[3] != '\0') + name += 3; + + /* Update internal progname if needed. */ + if (name != progname) + setprogname(name); +# endif + return; +} + +#else /* !HAVE_GETPROGNAME */ + +static const char *progname = ""; + +void +initprogname(const char *name) +{ +# ifdef HAVE___PROGNAME + extern const char *__progname; + + if (__progname != NULL && *__progname != '\0') + progname = __progname; + else +# endif + if ((progname = strrchr(name, '/')) != NULL) { + progname++; + } else { + progname = name; + } + + /* Check for libtool prefix and strip it if present. */ + if (progname[0] == 'l' && progname[1] == 't' && progname[2] == '-' && + progname[3] != '\0') + progname += 3; +} + +const char * +sudo_getprogname(void) +{ + return progname; +} +#endif /* !HAVE_GETPROGNAME */ diff --git a/lib/util/pw_dup.c b/lib/util/pw_dup.c new file mode 100644 index 0000000..4257adf --- /dev/null +++ b/lib/util/pw_dup.c @@ -0,0 +1,105 @@ +/* + * 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 <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <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/reallocarray.c b/lib/util/reallocarray.c new file mode 100644 index 0000000..ee2ef4a --- /dev/null +++ b/lib/util/reallocarray.c @@ -0,0 +1,56 @@ +/* $OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $ */ +/* + * 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 <sys/types.h> +#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/regress/atofoo/atofoo_test.c b/lib/util/regress/atofoo/atofoo_test.c new file mode 100644 index 0000000..1ad78eb --- /dev/null +++ b/lib/util/regress/atofoo/atofoo_test.c @@ -0,0 +1,183 @@ +/* + * 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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif + +#include "sudo_compat.h" +#include "sudo_util.h" +#include "sudo_fatal.h" + +__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 } +}; + +static int +test_strtobool(int *ntests) +{ + struct strtobool_data *d; + int errors = 0; + int value; + + 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++; + } + } + + return errors; +} + +/* sudo_strtoid() tests */ +static struct strtoid_data { + const char *idstr; + id_t id; + const char *sep; + const char *ep; +} strtoid_data[] = { + { "0,1", 0, ",", "," }, + { "10", 10, NULL, NULL }, + { "-2", -2, NULL, NULL }, +#if SIZEOF_ID_T != SIZEOF_LONG_LONG + { "-2", (id_t)4294967294U, NULL, NULL }, +#endif + { "4294967294", (id_t)4294967294U, NULL, NULL }, + { NULL, 0, NULL, NULL } +}; + +static int +test_strtoid(int *ntests) +{ + struct strtoid_data *d; + const char *errstr; + char *ep; + int errors = 0; + id_t value; + + for (d = strtoid_data; d->idstr != NULL; d++) { + (*ntests)++; + errstr = "some error"; + value = sudo_strtoid(d->idstr, d->sep, &ep, &errstr); + if (errstr != NULL) { + if (d->id != (id_t)-1) { + 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++; + } + } + + return errors; +} + +/* 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 } +}; + +static int +test_strtomode(int *ntests) +{ + struct strtomode_data *d; + const char *errstr; + int errors = 0; + mode_t mode; + + 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++; + } + } + + return errors; +} + +/* + * Simple tests for sudo_strtobool(), sudo_strtoid(), sudo_strtomode(). + */ +int +main(int argc, char *argv[]) +{ + int errors = 0; + int ntests = 0; + + initprogname(argc > 0 ? argv[0] : "atofoo"); + + errors += test_strtobool(&ntests); + errors += test_strtoid(&ntests); + errors += test_strtomode(&ntests); + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + + exit(errors); +} diff --git a/lib/util/regress/fnmatch/fnm_test.c b/lib/util/regress/fnmatch/fnm_test.c new file mode 100644 index 0000000..a70a847 --- /dev/null +++ b/lib/util/regress/fnmatch/fnm_test.c @@ -0,0 +1,85 @@ +/* $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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ + +#include "sudo_compat.h" +#include "sudo_util.h" + +#ifdef HAVE_FNMATCH +# include <fnmatch.h> +#else +# include "compat/fnmatch.h" +#endif + +__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 errors = 0, tests = 0, flags, got, want; + + initprogname(argc > 0 ? argv[0] : "fnm_test"); + + if (argc > 1) { + if ((fp = fopen(argv[1], "r")) == NULL) { + perror(argv[1]); + exit(1); + } + } + + /* + * 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++; + } + tests++; + } + } + if (tests != 0) { + printf("fnmatch: %d test%s run, %d errors, %d%% success rate\n", + tests, tests == 1 ? "" : "s", errors, + (tests - errors) * 100 / tests); + } + exit(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/getgrouplist/getgrouplist_test.c b/lib/util/regress/getgrouplist/getgrouplist_test.c new file mode 100644 index 0000000..4d44cf2 --- /dev/null +++ b/lib/util/regress/getgrouplist/getgrouplist_test.c @@ -0,0 +1,104 @@ +/* + * 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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif +#include <pwd.h> +#include <grp.h> + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +__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 i, j, ntests = 0; + int ngroups; + gid_t basegid; + initprogname(argc > 0 ? argv[0] : "getgrouplist_test"); + + 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 (errors != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } +#endif /* HAVE_GETGROUPLIST_2 */ + exit(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..99859f0 --- /dev/null +++ b/lib/util/regress/glob/globtest.c @@ -0,0 +1,216 @@ +/* $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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_GLOB +# include <glob.h> +#else +# include "compat/glob.h" +#endif +#include <errno.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 *); +__dso_public int main(int argc, char *argv[]); + +int +main(int argc, char **argv) +{ + FILE *fp = stdin; + char buf[2048], *cp, *ep; + int errors = 0, tests = 0, lineno; + struct gl_entry entry; + size_t len; + + initprogname(argc > 0 ? argv[0] : "globtest"); + + if (argc > 1) { + if ((fp = fopen(argv[1], "r")) == NULL) { + perror(argv[1]); + exit(1); + } + } + + /* + * Read in test file, which is formatted thusly: + * + * [pattern] <flags> + * result1 + * result2 + * result3 + * ... + * + */ + lineno = 0; + memset(&entry, 0, sizeof(entry)); + while (fgets(buf, sizeof(buf), fp) != NULL) { + lineno++; + len = strlen(buf); + if (len > 0) { + if (buf[len - 1] != '\n') { + fprintf(stderr, + "globtest: missing newline at EOF\n"); + exit(1); + } + buf[--len] = '\0'; + } + if (len == 0) + continue; /* blank line */ + + if (buf[0] == '[') { + /* check previous pattern */ + if (entry.pattern[0]) { + errors += test_glob(&entry); + tests++; + } + + /* start new entry */ + if ((cp = strrchr(buf + 1, ']')) == NULL) { + fprintf(stderr, + "globtest: invalid entry on line %d\n", + lineno); + exit(1); + } + len = cp - buf - 1; + if (len >= sizeof(entry.pattern)) { + fprintf(stderr, + "globtest: pattern too big on line %d\n", + lineno); + exit(1); + } + memcpy(entry.pattern, buf + 1, len); + entry.pattern[len] = '\0'; + + cp += 2; + if (*cp++ != '<') { + fprintf(stderr, + "globtest: invalid entry on line %d\n", + lineno); + exit(1); + } + ep = strchr(cp, '>'); + if (ep == NULL) { + fprintf(stderr, + "globtest: invalid entry on line %d\n", + lineno); + exit(1); + } + *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); + exit(1); + } + } + entry.nresults = 0; + continue; + } + if (!entry.pattern[0]) { + fprintf(stderr, "globtest: missing entry on line %d\n", + lineno); + exit(1); + } + + if (entry.nresults + 1 > MAX_RESULTS) { + fprintf(stderr, + "globtest: too many results for %s, max %d\n", + entry.pattern, MAX_RESULTS); + exit(1); + } + entry.results[entry.nresults++] = strdup(buf); + } + if (entry.pattern[0]) { + errors += test_glob(&entry); /* test last pattern */ + tests++; + } + if (tests != 0) { + printf("glob: %d test%s run, %d errors, %d%% success rate\n", + tests, tests == 1 ? "" : "s", errors, + (tests - errors) * 100 / tests); + } + exit(errors); +} + +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)); + exit(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/mktemp/mktemp_test.c b/lib/util/regress/mktemp/mktemp_test.c new file mode 100644 index 0000000..c8b9ceb --- /dev/null +++ b/lib/util/regress/mktemp/mktemp_test.c @@ -0,0 +1,196 @@ +/* + * 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/types.h> +#include <sys/mman.h> +#include <sys/stat.h> + +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <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 + +#define MAX_TEMPLATE_LEN 10 +#define MAX_TRIES 100 +#define MIN_Xs 6 + +#define SUFFIX ".suff" +#define SLEN (sizeof SUFFIX - 1) + +__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 + */ +int +check(int fd, char const *kind, char const *path, char const *prefix, + size_t plen, char const *suffix, size_t slen, int tlen) +{ + struct stat sb, fsb; + char const *p; + + if (tlen < MIN_Xs) { + if (fd != -1) + sudo_fatalx("%s(%s) succeed with too few Xs", kind, path); + if (errno != EINVAL) + sudo_fatal("%s(%s) failed with wrong errno: %d", kind, path, errno); + return 1; + } + if (fd == -1) + sudo_fatal("%s(%s)", kind, path); + if (stat(path, &sb)) + sudo_fatal("%s: stat(%s)", kind, path); + if (fd >= 0) { + if (fstat(fd, &fsb)) + sudo_fatal("%s: fstat(%d==%s)", kind, fd, path); + if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino) + sudo_fatalx("%s: stat mismatch", kind); + } + if (memcmp(path, prefix, plen) != 0) + sudo_fatalx("%s: prefix changed! %s vs %s", kind, prefix, path); + if (memcmp(path + plen + tlen, suffix, slen + 1) != 0) + sudo_fatalx("%s: suffix changed! %s vs %s", kind, suffix, path); + for (p = path + plen; p < path + plen + tlen; p++) + if (*p == '\0') + sudo_fatalx("%s: unexpected truncation", kind); + else if (*p == 'X') + return 0; + return 1; +} + +void +try_mkdtemp(char *p, char const *prefix, int len) +{ + size_t plen = strlen(prefix); + int fd, tries, ok; + + for (tries = 0; tries < MAX_TRIES; tries++) { + memcpy(p, prefix, plen); + memset(p + plen, 'X', len); + p[plen + len] = '\0'; + fd = mkdtemp(p) ? -2 : -1; + ok = check(fd, "mkdtemp", p, prefix, plen, "", 0, len); + rmdir(p); + if (ok) + return; + } + sudo_fatalx("mkdtemp: exceeded MAX_TRIES"); +} + +void +try_mkstemps(char *p, char const *prefix, int len, char const *suffix) +{ + size_t plen = strlen(prefix); + size_t slen = strlen(suffix); + int tries, fd, ok; + + for (tries = 0; tries < MAX_TRIES; tries++) { + memcpy(p, prefix, plen); + memset(p + plen, 'X', len); + memcpy(p + plen + len, suffix, slen + 1); + fd = mkstemps(p, slen); + ok = check(fd, "mkstemp", p, prefix, plen, suffix, slen, len); + close(fd); + unlink(p); + if (ok) + return; + } + sudo_fatalx("mkstemps: exceeded MAX_TRIES"); +} + +int +main(int argc, char *argv[]) +{ + char cwd[PATH_MAX + 1]; + char *p; + size_t clen; + long pg; + int i; + + initprogname(argc > 0 ? argv[0] : "mktemp_test"); + + pg = sysconf(_SC_PAGESIZE); + if (getcwd(cwd, sizeof cwd - 1) == NULL) + sudo_fatal("getcwd"); + clen = strlen(cwd); + cwd[clen++] = '/'; + cwd[clen] = '\0'; +#ifdef MAP_ANON + p = mmap(NULL, pg * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); +#else + i = open("/dev/zero", O_RDWR); + if (i == -1) + sudo_fatal("/dev/zero"); + p = mmap(NULL, pg * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE, i, 0); +#endif + if (p == MAP_FAILED) + sudo_fatal("mmap"); + if (mprotect(p, pg, PROT_NONE) || mprotect(p + pg * 2, pg, PROT_NONE)) + sudo_fatal("mprotect"); + p += pg; + + i = MAX_TEMPLATE_LEN + 1; + while (i-- > 0) { + /* try first at the start of a page, no prefix */ + try_mkdtemp(p, "", i); + /* now at the end of the page, no prefix */ + try_mkdtemp(p + pg - i - 1, "", i); + /* start of the page, prefixed with the cwd */ + try_mkdtemp(p, cwd, i); + /* how about at the end of the page, prefixed with cwd? */ + try_mkdtemp(p + pg - clen - i - 1, cwd, i); + + /* again, with mkstemps() and an empty suffix */ + /* try first at the start of a page, no prefix */ + try_mkstemps(p, "", i, ""); + /* now at the end of the page, no prefix */ + try_mkstemps(p + pg - i - 1, "", i, ""); + /* start of the page, prefixed with the cwd */ + try_mkstemps(p, cwd, i, ""); + /* how about at the end of the page, prefixed with cwd? */ + try_mkstemps(p + pg - clen - i - 1, cwd, i, ""); + + /* mkstemps() and a non-empty suffix */ + /* try first at the start of a page, no prefix */ + try_mkstemps(p, "", i, SUFFIX); + /* now at the end of the page, no prefix */ + try_mkstemps(p + pg - i - SLEN - 1, "", i, SUFFIX); + /* start of the page, prefixed with the cwd */ + try_mkstemps(p, cwd, i, SUFFIX); + /* how about at the end of the page, prefixed with cwd? */ + try_mkstemps(p + pg - clen - i - SLEN - 1, cwd, i, SUFFIX); + } + + return 0; +} diff --git a/lib/util/regress/parse_gids/parse_gids_test.c b/lib/util/regress/parse_gids/parse_gids_test.c new file mode 100644 index 0000000..674bd96 --- /dev/null +++ b/lib/util/regress/parse_gids/parse_gids_test.c @@ -0,0 +1,114 @@ +/* + * 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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +__dso_public int main(int argc, char *argv[]); + +/* + * Test that sudo_parse_gids() works as expected. + */ + +struct parse_gids_test { + const char *gids; + gid_t *baseptr; + gid_t basegid; + int ngids; + const GETGROUPS_T *gidlist; +}; + +static const GETGROUPS_T test1_out[] = { 0, 1, 2, 3, 4 }; +static const GETGROUPS_T test2_out[] = { 1, 2, 3, 4 }; +static const GETGROUPS_T test3_out[] = { 0, 1, (gid_t)-2, 3, 4 }; + +/* XXX - test syntax errors too */ +static struct parse_gids_test test_data[] = { + { "1,2,3,4", &test_data[0].basegid, 0, 5, test1_out }, + { "1,2,3,4", NULL, 0, 4, test2_out }, + { "1,-2,3,4", &test_data[2].basegid, 0, 5, test3_out }, + { NULL, false, 0, 0, NULL } +}; + +static void +dump_gids(const char *prefix, int ngids, const GETGROUPS_T *gidlist) +{ + int i; + + fprintf(stderr, "%s: %s: ", getprogname(), prefix); + for (i = 0; i < ngids; i++) { + fprintf(stderr, "%s%d", i ? ", " : "", (int)gidlist[i]); + } + fputc('\n', stderr); +} + +int +main(int argc, char *argv[]) +{ + GETGROUPS_T *gidlist = NULL; + int i, j, errors = 0, ntests = 0; + int ngids; + initprogname(argc > 0 ? argv[0] : "parse_gids_test"); + + for (i = 0; test_data[i].gids != NULL; i++) { + free(gidlist); + ngids = sudo_parse_gids(test_data[i].gids, test_data[i].baseptr, &gidlist); + if (ngids == -1) + exit(1); /* out of memory? */ + 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; + } + } + } + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + exit(errors); +} diff --git a/lib/util/regress/progname/progname_test.c b/lib/util/regress/progname/progname_test.c new file mode 100644 index 0000000..37c5c22 --- /dev/null +++ b/lib/util/regress/progname/progname_test.c @@ -0,0 +1,65 @@ +/* + * 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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif + +#include "sudo_compat.h" +#include "sudo_util.h" + +__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[]) +{ + char *progbase = "progname_test"; + + if (argc > 0) { + if ((progbase = strrchr(argv[0], '/')) != NULL) + progbase++; + else + progbase = argv[0]; + } + initprogname(progbase); + + /* Make sure getprogname() matches basename of argv[0]. */ + if (strcmp(getprogname(), progbase) != 0) { + printf("%s: FAIL: incorrect program name \"%s\"\n", + progbase, getprogname()); + exit(1); + } + + exit(0); +} diff --git a/lib/util/regress/strsplit/strsplit_test.c b/lib/util/regress/strsplit/strsplit_test.c new file mode 100644 index 0000000..c9aecda --- /dev/null +++ b/lib/util/regress/strsplit/strsplit_test.c @@ -0,0 +1,111 @@ +/* + * 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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +__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 i, j, errors = 0, ntests = 0; + size_t len; + initprogname(argc > 0 ? argv[0] : "strsplit_test"); + + for (i = 0; test_data[i].input != NULL; i++) { + input_end = test_data[i].input + test_data[i].input_len; + cp = sudo_strsplit(test_data[i].input, input_end, " \t", &ep); + for (j = 0; test_data[i].output[j] != NULL; j++) { + ntests++; + len = strlen(test_data[i].output[j]); + if ((size_t)(ep - cp) != len) { + sudo_warnx_nodebug("failed test #%d: bad length, expected " + "%zu, got %zu", ntests, len, (size_t)(ep - cp)); + errors++; + continue; + } + ntests++; + if (strncmp(cp, test_data[i].output[j], len) != 0) { + sudo_warnx_nodebug("failed test #%d: expected %s, got %.*s", + ntests, test_data[i].output[j], (int)(ep - cp), cp); + errors++; + continue; + } + cp = sudo_strsplit(NULL, input_end, " \t", &ep); + } + ntests++; + if (cp != NULL) { + sudo_warnx_nodebug("failed test #%d: extra tokens \"%.*s\"", + ntests, (int)(input_end - cp), cp); + errors++; + } + } + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + exit(errors); +} diff --git a/lib/util/regress/sudo_conf/conf_test.c b/lib/util/regress/sudo_conf/conf_test.c new file mode 100644 index 0000000..534b1df --- /dev/null +++ b/lib/util/regress/sudo_conf/conf_test.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2013-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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif + +#include "sudo_compat.h" +#include "sudo_conf.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +static void sudo_conf_dump(void); + +__dso_public int main(int argc, char *argv[]); + +/* + * 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[]) +{ + initprogname(argc > 0 ? argv[0] : "conf_test"); + if (argc != 2) { + fprintf(stderr, "usage: %s conf_file\n", getprogname()); + exit(EXIT_FAILURE); + } + sudo_conf_clear_paths(); + if (sudo_conf_read(argv[1], SUDO_CONF_ALL) == -1) + exit(EXIT_FAILURE); + sudo_conf_dump(); + + exit(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()); + 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_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..41282d7 --- /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 dummy versions of the execv(), +# execve() and fexecve() library functions that just return an error. +# This is used to implement the "noexec" functionality on systems that +# support C<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..1880748 --- /dev/null +++ b/lib/util/regress/sudo_conf/test1.out.ok @@ -0,0 +1,7 @@ +Set disable_coredump false +Set group_source static +Set max_groups -1 +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..af42145 --- /dev/null +++ b/lib/util/regress/sudo_conf/test2.out.ok @@ -0,0 +1,3 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 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..819d638 --- /dev/null +++ b/lib/util/regress/sudo_conf/test3.out.ok @@ -0,0 +1,5 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 +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..af42145 --- /dev/null +++ b/lib/util/regress/sudo_conf/test4.out.ok @@ -0,0 +1,3 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 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..af42145 --- /dev/null +++ b/lib/util/regress/sudo_conf/test5.out.ok @@ -0,0 +1,3 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 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..1f62f84 --- /dev/null +++ b/lib/util/regress/sudo_conf/test6.out.ok @@ -0,0 +1,3 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups 16 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..5644109 --- /dev/null +++ b/lib/util/regress/sudo_conf/test7.out.ok @@ -0,0 +1,7 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 +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..ac46dd8 --- /dev/null +++ b/lib/util/regress/sudo_parseln/parseln_test.c @@ -0,0 +1,58 @@ +/* + * 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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif + +#include "sudo_compat.h" +#include "sudo_util.h" + +__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; + + initprogname(argc > 0 ? argv[0] : "parseln_test"); + + while (sudo_parseln(&line, &linesize, &lineno, stdin, 0) != -1) + printf("%6u\t%s\n", lineno, line); + free(line); + exit(0); +} diff --git a/lib/util/regress/sudo_parseln/test1.in b/lib/util/regress/sudo_parseln/test1.in new file mode 100644 index 0000000..c605bb5 --- /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 dummy versions of the execv(), +# execve() and fexecve() library functions that just return an error. +# This is used to implement the "noexec" functionality on systems that +# support C<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..5333a15 --- /dev/null +++ b/lib/util/regress/tailq/hltq_test.c @@ -0,0 +1,199 @@ +/* + * 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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_queue.h" +#include "sudo_util.h" + +__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 errors = 0; + int ntests = 0; + + initprogname(argc > 0 ? argv[0] : "hltq_test"); + + /* + * 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++; + + printf("%s: %d tests run, %d errors, %d%% success rate\n", getprogname(), + ntests, errors, (ntests - errors) * 100 / ntests); + + exit(errors); +} diff --git a/lib/util/regress/vsyslog/vsyslog_test.c b/lib/util/regress/vsyslog/vsyslog_test.c new file mode 100644 index 0000000..70a8d0d --- /dev/null +++ b/lib/util/regress/vsyslog/vsyslog_test.c @@ -0,0 +1,131 @@ +/* + * 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. + */ + +#include <config.h> + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif +#include <errno.h> + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +__dso_public int main(int argc, char *argv[]); + +/* + * Test that sudo_vsyslog() works as expected. + */ +static char *expected_result; +static int errors; +static int ntests; + +/* + * Dummy version of syslog to verify the message + */ +void +syslog(int priority, const char *fmt, ...) +{ + va_list ap; + const char *msg; + + if (strcmp(fmt, "%s") != 0) + sudo_fatalx_nodebug("Expected syslog format \"%%s\", got \"%s\"", fmt); + + va_start(ap, fmt); + msg = va_arg(ap, char *); + if (strcmp(msg, expected_result) != 0) { + sudo_warnx_nodebug("Expected \"%s\", got \"%s\"", expected_result, msg); + errors++; + } else { + ntests++; + } + va_end(ap); +} + +static void +test_vsyslog(int priority, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + sudo_vsyslog(priority, fmt, ap); + va_end(ap); +} + +int +main(int argc, char *argv[]) +{ + char buf1[1024 * 16], buf2[1024 * 16]; + initprogname(argc > 0 ? argv[0] : "vsyslog_test"); + + /* Test small buffer. */ + expected_result = "sudo: millert : TTY=ttypa ; PWD=/etc/mail ; USER=root ; TSID=000AB0 ; COMMAND=/usr/sbin/newaliases"; + test_vsyslog(0, + "%s: %s : TTY=%s ; PWD=%s ; USER=%s ; TSID=%s ; COMMAND=%s", + "sudo", "millert", "ttypa", "/etc/mail", "root", "000AB0", + "/usr/sbin/newaliases"); + + /* Test small buffer w/ errno. */ + snprintf(buf1, sizeof(buf1), + "unable to open %s: %s", "/var/log/sudo-io/seq", strerror(ENOENT)); + expected_result = buf1; + errno = ENOENT; + test_vsyslog(0, "unable to open %s: %m", "/var/log/sudo-io/seq"); + + /* Test large buffer > 8192 bytes. */ + memset(buf1, 'a', 8192); + buf1[8192] = '\0'; + expected_result = buf1; + test_vsyslog(0, "%s", buf1); + + /* Test large buffer w/ errno > 8192 bytes. */ + memset(buf1, 'b', 8184); + buf1[8184] = '\0'; + snprintf(buf2, sizeof(buf2), "%s: %s", buf1, strerror(EINVAL)); + expected_result = buf2; + errno = EINVAL; + test_vsyslog(0, "%s: %m", buf1); + + /* Test large format string > 8192 bytes, expect truncation to 2048. */ + memset(buf1, 'b', 8184); + buf1[8184] = '\0'; + snprintf(buf2, sizeof(buf2), "%.*s", 2047, buf1); + expected_result = buf2; + test_vsyslog(0, buf1); + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } else { + printf("%s: error, no tests run!\n", getprogname()); + errors = 1; + } + exit(errors); +} diff --git a/lib/util/secure_path.c b/lib/util/secure_path.c new file mode 100644 index 0000000..b1d4415 --- /dev/null +++ b/lib/util/secure_path.c @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2012, 2014-2016 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <unistd.h> +#include <errno.h> + +#include "sudo_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_secure_path(const char *path, unsigned int type, uid_t uid, gid_t gid, struct stat *sbp) +{ + struct stat sb; + int ret = SUDO_PATH_MISSING; + debug_decl(sudo_secure_path, SUDO_DEBUG_UTIL) + + if (path != NULL && stat(path, &sb) == 0) { + 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; + } else { + ret = SUDO_PATH_SECURE; + } + if (sbp) + (void) memcpy(sbp, &sb, sizeof(struct stat)); + } + + debug_return_int(ret); +} + +/* + * Verify that path is a regular file and not writable by other users. + */ +int +sudo_secure_file_v1(const char *path, uid_t uid, gid_t gid, struct stat *sbp) +{ + return sudo_secure_path(path, _S_IFREG, uid, gid, sbp); +} + +/* + * 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 *sbp) +{ + return sudo_secure_path(path, _S_IFDIR, uid, gid, sbp); +} diff --git a/lib/util/setgroups.c b/lib/util/setgroups.c new file mode 100644 index 0000000..9b81456 --- /dev/null +++ b/lib/util/setgroups.c @@ -0,0 +1,52 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <grp.h> +#include <limits.h> + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +int +sudo_setgroups_v1(int ngids, const GETGROUPS_T *gids) +{ + int maxgids, ret; + debug_decl(sudo_setgroups, SUDO_DEBUG_UTIL) + + ret = setgroups(ngids, (GETGROUPS_T *)gids); + if (ret == -1 && errno == EINVAL) { + /* Too many groups, try again with fewer. */ + maxgids = (int)sysconf(_SC_NGROUPS_MAX); + if (maxgids == -1) + maxgids = NGROUPS_MAX; + if (ngids > maxgids) + ret = setgroups(maxgids, (GETGROUPS_T *)gids); + } + debug_return_int(ret); +} diff --git a/lib/util/sha2.c b/lib/util/sha2.c new file mode 100644 index 0000000..9a172e3 --- /dev/null +++ b/lib/util/sha2.c @@ -0,0 +1,522 @@ +/* + * 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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#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 */ + memset_s(T, sizeof(T), 0, sizeof(T)); + memset_s(W, sizeof(W), 0, 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] & 504) != 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. */ + memset_s(T, sizeof(T), 0, sizeof(T)); + memset_s(W, sizeof(W), 0, 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] & 1008) != 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..a6d4c1a --- /dev/null +++ b/lib/util/sig2str.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 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. + */ + +/* + * 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 <sys/types.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <signal.h> +#include <unistd.h> + +#include "sudo_compat.h" + +#if defined(HAVE_DECL_SYS_SIGNAME) && HAVE_DECL_SYS_SIGNAME == 1 +# define sudo_sys_signame sys_signame +#elif defined(HAVE_DECL__SYS_SIGNAME) && HAVE_DECL__SYS_SIGNAME == 1 +# define sudo_sys_signame _sys_signame +#elif defined(HAVE_DECL_SYS_SIGABBREV) && HAVE_DECL_SYS_SIGABBREV == 1 +# define sudo_sys_signame sys_sigabbrev +#else +# ifdef HAVE_SYS_SIGABBREV + /* sys_sigabbrev is not declared by glibc */ +# define sudo_sys_signame sys_sigabbrev +# endif +extern const char *const sudo_sys_signame[NSIG]; +#endif + +/* + * Translate signal number to name. + */ +int +sudo_sig2str(int signo, char *signame) +{ +#if defined(SIGRTMIN) && defined(SIGRTMAX) + /* Realtime signal support as per Solaris. */ + if (signo >= SIGRTMIN && signo <= SIGRTMAX) { + snprintf(signame, SIG2STR_MAX, "RTMIN+%d", (signo - SIGRTMIN)); + return 0; + } +#endif + if (signo > 0 && signo < NSIG && sudo_sys_signame[signo] != NULL) { + strlcpy(signame, sudo_sys_signame[signo], SIG2STR_MAX); + 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..f149eb5 --- /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 +# before BUS (Older Linux doesn't really have a BUS, but defines it to UNUSED) + UNUSED Unused + BUS Bus error + SEGV Memory fault + SYS Bad system call + 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..4b779d1 --- /dev/null +++ b/lib/util/snprintf.c @@ -0,0 +1,1592 @@ +/* $OpenBSD: vfprintf.c,v 1.67 2014/12/21 00:23:30 daniel Exp $ */ +/*- + * 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 <sys/types.h> +#include <sys/mman.h> + +#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> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <unistd.h> +#ifdef PRINTF_WIDE_CHAR +# include <wchar.h> +#endif +#include <fcntl.h> + +#include "sudo_compat.h" + +/* Avoid printf format attacks by ignoring the %n escape. */ +#define NO_PRINTF_PERCENT_N + +union arg { + int intarg; + unsigned int uintarg; + long longarg; + unsigned long ulongarg; + long long longlongarg; + unsigned long long ulonglongarg; + ptrdiff_t ptrdiffarg; + size_t sizearg; + ssize_t ssizearg; + intmax_t intmaxarg; + uintmax_t uintmaxarg; + void *pvoidarg; + char *pchararg; + signed char *pschararg; + short *pshortarg; + int *pintarg; + long *plongarg; + long long *plonglongarg; + ptrdiff_t *pptrdiffarg; + ssize_t *pssizearg; + intmax_t *pintmaxarg; +#ifdef FLOATING_POINT + double doublearg; + long double longdoublearg; +#endif +#ifdef PRINTF_WIDE_CHAR + wint_t wintarg; + wchar_t *pwchararg; +#endif +}; + +static int __find_arguments(const char *fmt0, va_list ap, union arg **argtable, + size_t *argtablesiz); +static int __grow_type_table(unsigned char **typetable, int *tablesize); +static int xxxprintf(char **, size_t, int, const char *, va_list); + +#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS) +# define MAP_ANON MAP_ANONYMOUS +#endif + +/* + * Allocate "size" bytes via mmap. + */ +static void * +mmap_alloc(size_t size) +{ + void *p; +#ifndef MAP_ANON + int fd; + + if ((fd = open("/dev/zero", O_RDWR)) == -1) + return NULL; + p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + close(fd); +#else + p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); +#endif + if (p == MAP_FAILED) + return NULL; + return p; +} + +/* + * Unmap "size" bytes of the ptr. + */ +static void +mmap_free(void *ptr, size_t size) +{ + if (ptr != NULL) + munmap(ptr, size); +} + +#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) { + errno = EILSEQ; + 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) { + errno = EILSEQ; + 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); + errno = EILSEQ; + return NULL; + } + convbuf[nbytes] = '\0'; + return convbuf; +} +#endif + +#ifdef FLOATING_POINT +#include <float.h> +#include <locale.h> +#include <math.h> +#include "floatio.h" +#include "gdtoa.h" + +#define DEFPREC 6 + +static int exponent(char *, int, int); +#endif /* FLOATING_POINT */ + +/* + * The size of the buffer we use as scratch space for integer + * conversions, among other things. Technically, we would need the + * most space for base 10 conversions with thousands' grouping + * characters between each pair of digits. 100 bytes is a + * conservative overestimate even for a 128-bit uintmax_t. + */ +#define BUF 100 + +#define STATIC_ARG_TBL_SIZE 8 /* Size of static argument table. */ + + +/* + * Macros for converting digits to letters and vice versa + */ +#define to_digit(c) ((c) - '0') +#define is_digit(c) ((unsigned int)to_digit(c) <= 9) +#define to_char(n) ((n) + '0') + +/* + * Flags used during conversion. + */ +#define ALT 0x0001 /* alternate form */ +#define LADJUST 0x0004 /* left adjustment */ +#define LONGDBL 0x0008 /* long double */ +#define LONGINT 0x0010 /* long integer */ +#define LLONGINT 0x0020 /* long long integer */ +#define SHORTINT 0x0040 /* short integer */ +#define ZEROPAD 0x0080 /* zero (as opposed to blank) pad */ +#define FPT 0x0100 /* Floating point number */ +#define PTRINT 0x0200 /* (unsigned) ptrdiff_t */ +#define SIZEINT 0x0400 /* (signed) size_t */ +#define CHARINT 0x0800 /* 8 bit integer */ +#undef MAXINT /* Also defined by HP-UX param.h... */ +#define MAXINT 0x1000 /* largest integer size (intmax_t) */ + +/* + * Actual printf innards. + */ +static int +xxxprintf(char **strp, size_t strsize, int alloc, const char *fmt0, va_list ap) +{ + char *fmt; /* format string */ + int ch; /* character from fmt */ + int n, n2; /* 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]; + size_t argtablesiz; + 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"; + + /* 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) + + /* BEWARE, PAD uses `n' and PRINTANDPAD uses `n2'. */ +#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 { \ + 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) \ + n2 = 0; \ + cp = fmt; \ + while (is_digit(*cp)) { \ + APPEND_DIGIT(n2, *cp); \ + cp++; \ + } \ + if (*cp == '$') { \ + int hold = nextarg; \ + if (argtable == NULL) { \ + argtable = statargtable; \ + __find_arguments(fmt0, orgap, &argtable, &argtablesiz); \ + } \ + nextarg = n2; \ + val = GETARG(int); \ + nextarg = hold; \ + fmt = ++cp; \ + } else { \ + val = GETARG(int); \ + } + +/* +* 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++) + /* void */; + if ((n = fmt - cp) != 0) { + if (n > INT_MAX - ret) + goto overflow; + PRINT(cp, n); + ret += n; + } + 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; + __find_arguments(fmt0, orgap, + &argtable, &argtablesiz); + } + 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; + __find_arguments(fmt0, orgap, + &argtable, &argtablesiz); + } + 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) { + errno = EILSEQ; + 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': +#ifdef PRINTF_WIDE_CHAR + if (flags & LONGINT) { + wchar_t *wcp; + + if (convbuf != NULL) { + free(convbuf); + convbuf = NULL; + } + if ((wcp = GETARG(wchar_t *)) == NULL) { + cp = "(null)"; + } else { + convbuf = __wcsconv(wcp, prec); + if (convbuf == NULL) + goto done; + cp = convbuf; + } + } else +#endif /* PRINTF_WIDE_CHAR */ + if ((cp = GETARG(char *)) == NULL) + cp = "(null)"; + if (prec >= 0) { + /* + * can't use strlen; can only look for the + * NUL in the first `prec' characters, and + * strlen() will go further. + */ + char *p = memchr(cp, 0, prec); + + size = p ? (p - cp) : prec; + } else { + size_t len; + + if ((len = strlen(cp)) > 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 = "bug in vfprintf: 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 + if (convbuf) + free(convbuf); +#endif +#ifdef FLOATING_POINT + if (dtoaresult) + __freedtoa(dtoaresult); +#endif + if (argtable != NULL && argtable != statargtable) { + mmap_free(argtable, argtablesiz); + 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, + size_t *argtablesiz) +{ + char *fmt; /* format string */ + int ch; /* character from fmt */ + int n, n2; /* 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() \ + 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); \ + } + 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++) + /* void */; + 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) { + *argtablesiz = sizeof(union arg) * (tablemax + 1); + *argtable = mmap_alloc(*argtablesiz); + 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) { + mmap_free(typetable, *argtablesiz); + typetable = NULL; + } + return ret; +} + +/* + * Increase the size of the type table. + */ +static int +__grow_type_table(unsigned char **typetable, int *tablesize) +{ + unsigned char *oldtable = *typetable; + int newsize = *tablesize * 2; + + if (newsize < sysconf(_SC_PAGESIZE)) + newsize = sysconf(_SC_PAGESIZE); + + if (*tablesize == STATIC_ARG_TBL_SIZE) { + *typetable = mmap_alloc(newsize); + if (*typetable == NULL) + return -1; + memcpy(*typetable, oldtable, *tablesize); + } else { + unsigned char *new = mmap_alloc(newsize); + if (new == NULL) + return -1; + memmove(new, *typetable, *tablesize); + mmap_free(*typetable, *tablesize); + *typetable = new; + } + memset(*typetable + *tablesize, T_UNUSED, (newsize - *tablesize)); + + *tablesize = newsize; + return 0; +} + + +#ifdef FLOATING_POINT +static int +exponent(char *p0, int exp, int fmtch) +{ + char *p, *t; + char expbuf[MAXEXPDIG]; + + p = p0; + *p++ = fmtch; + if (exp < 0) { + exp = -exp; + *p++ = '-'; + } else + *p++ = '+'; + t = expbuf + MAXEXPDIG; + if (exp > 9) { + do { + *--t = to_char(exp % 10); + } while ((exp /= 10) > 9); + *--t = to_char(exp); + for (; t < expbuf + MAXEXPDIG; *p++ = *t++) + /* nothing */; + } else { + /* + * Exponents for decimal floating point conversions + * (%[eEgG]) must be at least two characters long, + * whereas exponents for hexadecimal conversions can + * be only one character long. + */ + if (fmtch == 'e' || fmtch == 'E') + *p++ = '0'; + *p++ = to_char(exp); + } + return p - p0; +} +#endif /* FLOATING_POINT */ + +#if !defined(HAVE_VSNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF) +int +sudo_vsnprintf(char *str, size_t n, const char *fmt, va_list ap) +{ + if (n > INT_MAX) { + errno = EOVERFLOW; + *str = '\0'; + return -1; + } + return xxxprintf(&str, n, 0, fmt, ap); +} +#endif /* !HAVE_VSNPRINTF || PREFER_PORTABLE_SNPRINTF */ + +#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF) +int +sudo_snprintf(char *str, size_t n, char const *fmt, ...) +{ + int ret; + va_list ap; + + if (n > INT_MAX) { + errno = EOVERFLOW; + *str = '\0'; + return -1; + } + va_start(ap, fmt); + ret = xxxprintf(&str, n, 0, fmt, ap); + va_end(ap); + return ret; +} +#endif /* !HAVE_SNPRINTF || PREFER_PORTABLE_SNPRINTF */ + +#if !defined(HAVE_VASPRINTF) || defined(PREFER_PORTABLE_SNPRINTF) +int +sudo_vasprintf(char **str, const char *fmt, va_list ap) +{ + int ret; + + ret = xxxprintf(str, 0, 1, fmt, ap); + if (ret == -1) + *str = NULL; + return ret; +} +#endif /* !HAVE_VASPRINTF || PREFER_PORTABLE_SNPRINTF */ + +#if !defined(HAVE_ASPRINTF) || defined(PREFER_PORTABLE_SNPRINTF) +int +sudo_asprintf(char **str, char const *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = xxxprintf(str, 0, 1, fmt, ap); + va_end(ap); + if (ret == -1) + *str = NULL; + return ret; +} +#endif /* !HAVE_ASPRINTF || PREFER_PORTABLE_SNPRINTF */ + +#endif /* !HAVE_VSNPRINTF || !HAVE_SNPRINTF || !HAVE_VASPRINTF || !HAVE_ASPRINTF || PREFER_PORTABLE_SNPRINTF */ diff --git a/lib/util/strlcat.c b/lib/util/strlcat.c new file mode 100644 index 0000000..fee8dc1 --- /dev/null +++ b/lib/util/strlcat.c @@ -0,0 +1,68 @@ +/* $OpenBSD: strlcat.c,v 1.15 2015/03/02 21:41:08 millert Exp $ */ + +/* + * 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 <sys/types.h> +#include <string.h> + +#include "sudo_compat.h" + +/* + * Appends src to string dst of size dsize (unlike strncat, dsize is the + * full size of dst, not space left). At most dsize-1 characters + * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). + * Returns strlen(src) + MIN(dsize, strlen(initial dst)). + * If retval >= dsize, truncation occurred. + */ +size_t +sudo_strlcat(char *dst, const char *src, size_t dsize) +{ + const char *odst = dst; + const char *osrc = src; + size_t n = dsize; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end. */ + while (n-- != 0 && *dst != '\0') + dst++; + dlen = dst - odst; + n = dsize - dlen; + + if (n-- == 0) + return(dlen + strlen(src)); + while (*src != '\0') { + if (n != 0) { + *dst++ = *src; + n--; + } + src++; + } + *dst = '\0'; + + return(dlen + (src - osrc)); /* count does not include NUL */ +} +#endif /* HAVE_STRLCAT */ diff --git a/lib/util/strlcpy.c b/lib/util/strlcpy.c new file mode 100644 index 0000000..ea8ab4d --- /dev/null +++ b/lib/util/strlcpy.c @@ -0,0 +1,62 @@ +/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */ + +/* + * 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 <sys/types.h> + +#include "sudo_compat.h" + +/* + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + */ +size_t +sudo_strlcpy(char *dst, const char *src, size_t dsize) +{ + const char *osrc = src; + size_t nleft = dsize; + + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) + continue; + } + + return(src - osrc - 1); /* count does not include NUL */ +} +#endif /* HAVE_STRLCPY */ diff --git a/lib/util/strndup.c b/lib/util/strndup.c new file mode 100644 index 0000000..5d9ea5a --- /dev/null +++ b/lib/util/strndup.c @@ -0,0 +1,56 @@ +/* $OpenBSD: strndup.c,v 1.1 2010/05/18 22:24:55 tedu Exp $ */ + +/* + * 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 <sys/types.h> + +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ + +#include "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..7344e9f --- /dev/null +++ b/lib/util/strnlen.c @@ -0,0 +1,43 @@ +/* $OpenBSD: strnlen.c,v 1.5 2014/06/10 04:17:37 deraadt Exp $ */ + +/* + * 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..a6f9eea --- /dev/null +++ b/lib/util/strsignal.c @@ -0,0 +1,55 @@ +/* + * 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 <sys/types.h> + +#include <stdio.h> +#include <signal.h> + +#define DEFAULT_TEXT_DOMAIN "sudo" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ + +#include "sudo_compat.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 _("Unknown signal"); +} +#endif /* HAVE_STRSIGNAL */ diff --git a/lib/util/strsplit.c b/lib/util/strsplit.c new file mode 100644 index 0000000..728f225 --- /dev/null +++ b/lib/util/strsplit.c @@ -0,0 +1,78 @@ +/* + * 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 <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <grp.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..74a6fd3 --- /dev/null +++ b/lib/util/strtobool.c @@ -0,0 +1,82 @@ +/* + * 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 <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ + +#include "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..2339a88 --- /dev/null +++ b/lib/util/strtoid.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2013-2016 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#if defined(HAVE_STDINT_H) +# include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +# include <inttypes.h> +#endif +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif +#include <ctype.h> +#include <errno.h> +#include <limits.h> + +#define DEFAULT_TEXT_DOMAIN "sudo" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +/* + * 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. + */ +#if SIZEOF_ID_T == SIZEOF_LONG_LONG +id_t +sudo_strtoid_v1(const char *p, const char *sep, char **endp, const char **errstr) +{ + char *ep; + id_t ret = 0; + long long llval; + bool valid = false; + debug_decl(sudo_strtoid, SUDO_DEBUG_UTIL) + + /* skip leading space so we can pick up the sign, if any */ + while (isspace((unsigned char)*p)) + p++; + if (sep == NULL) + sep = ""; + errno = 0; + llval = strtoll(p, &ep, 10); + if (ep != p) { + /* check for valid separator (including '\0') */ + do { + if (*ep == *sep) + valid = true; + } while (*sep++ != '\0'); + } + if (!valid) { + if (errstr != NULL) + *errstr = N_("invalid value"); + errno = EINVAL; + goto done; + } + if (errno == ERANGE) { + if (errstr != NULL) { + if (llval == LLONG_MAX) + *errstr = N_("value too large"); + else + *errstr = N_("value too small"); + } + goto done; + } + ret = (id_t)llval; + if (errstr != NULL) + *errstr = NULL; + if (endp != NULL) + *endp = ep; +done: + debug_return_id_t(ret); +} +#else +id_t +sudo_strtoid_v1(const char *p, const char *sep, char **endp, const char **errstr) +{ + char *ep; + id_t ret = 0; + bool valid = false; + debug_decl(sudo_strtoid, SUDO_DEBUG_UTIL) + + /* skip leading space so we can pick up the sign, if any */ + while (isspace((unsigned char)*p)) + p++; + if (sep == NULL) + sep = ""; + errno = 0; + if (*p == '-') { + long lval = strtol(p, &ep, 10); + if (ep != p) { + /* check for valid separator (including '\0') */ + do { + if (*ep == *sep) + valid = true; + } while (*sep++ != '\0'); + } + if (!valid) { + if (errstr != NULL) + *errstr = N_("invalid value"); + errno = EINVAL; + goto done; + } + if ((errno == ERANGE && lval == LONG_MAX) || lval > INT_MAX) { + errno = ERANGE; + if (errstr != NULL) + *errstr = N_("value too large"); + goto done; + } + if ((errno == ERANGE && lval == LONG_MIN) || lval < INT_MIN) { + errno = ERANGE; + if (errstr != NULL) + *errstr = N_("value too small"); + goto done; + } + ret = (id_t)lval; + } else { + unsigned long ulval = strtoul(p, &ep, 10); + if (ep != p) { + /* check for valid separator (including '\0') */ + do { + if (*ep == *sep) + valid = true; + } while (*sep++ != '\0'); + } + if (!valid) { + if (errstr != NULL) + *errstr = N_("invalid value"); + errno = EINVAL; + goto done; + } + if ((errno == ERANGE && ulval == ULONG_MAX) || ulval > UINT_MAX) { + errno = ERANGE; + if (errstr != NULL) + *errstr = N_("value too large"); + goto done; + } + ret = (id_t)ulval; + } + if (errstr != NULL) + *errstr = NULL; + if (endp != NULL) + *endp = ep; +done: + debug_return_id_t(ret); +} +#endif /* SIZEOF_ID_T == 8 */ diff --git a/lib/util/strtomode.c b/lib/util/strtomode.c new file mode 100644 index 0000000..5a1ec94 --- /dev/null +++ b/lib/util/strtomode.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#define DEFAULT_TEXT_DOMAIN "sudo" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +/* + * Parse an octal file mode in the range [0, 0777]. + * On success, returns the parsed mode and clears errstr. + * On error, returns 0 and sets errstr. + */ +int +sudo_strtomode_v1(const char *cp, const char **errstr) +{ + char *ep; + long lval; + debug_decl(sudo_strtomode, SUDO_DEBUG_UTIL) + + errno = 0; + lval = strtol(cp, &ep, 8); + if (ep == cp || *ep != '\0') { + if (errstr != NULL) + *errstr = N_("invalid value"); + errno = EINVAL; + debug_return_int(0); + } + if (lval < 0 || lval > ACCESSPERMS) { + if (errstr != NULL) + *errstr = lval < 0 ? N_("value too small") : N_("value too large"); + errno = ERANGE; + debug_return_int(0); + } + if (errstr != NULL) + *errstr = NULL; + debug_return_int((int)lval); +} diff --git a/lib/util/strtonum.c b/lib/util/strtonum.c new file mode 100644 index 0000000..aedbc02 --- /dev/null +++ b/lib/util/strtonum.c @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2013-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> + +#include <sys/types.h> + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ + +#define DEFAULT_TEXT_DOMAIN "sudo" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ + +#include "sudo_compat.h" + +#ifdef HAVE_STRTONUM + +/* + * The OpenBSD strtonum error string too short to be translated sensibly. + * This wrapper just changes errstr as follows: + * invalid -> invalid value + * too large -> value too large + * too small -> value too small + */ +long long +sudo_strtonum(const char *str, long long minval, long long maxval, + const char **errstrp) +{ + long long retval; + const char *errstr; + +# undef strtonum + retval = strtonum(str, minval, maxval, &errstr); + if (errstr != NULL) { + if (errno == EINVAL) { + errstr = N_("invalid value"); + } else if (errno == ERANGE) { + errstr = strcmp(errstr, "too large") == 0 ? + N_("value too large") : N_("value too small"); + } + } + if (errstrp != NULL) + *errstrp = errstr; + return retval; +} + +#else + +enum strtonum_err { + STN_VALID, + STN_INVALID, + STN_TOOSMALL, + STN_TOOBIG +}; + +/* + * 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 unsigned char *ustr = (const unsigned char *)str; + enum strtonum_err errval = STN_VALID; + long long lastval, result = 0; + unsigned char dig, sign; + int remainder; + + if (minval > maxval) { + errval = STN_INVALID; + goto done; + } + + /* Trim leading space and check sign, if any. */ + while (isspace(*ustr)) { + ustr++; + } + switch (*ustr) { + case '-': + sign = '-'; + ustr++; + break; + case '+': + ustr++; + /* FALLTHROUGH */ + default: + sign = '+'; + break; + } + + /* + * To prevent overflow we determine the highest (or lowest in + * the case of negative numbers) value result can have *before* + * if its multiplied (divided) by 10 as well as the remainder. + * If result matches this value and the next digit is larger than + * the remainder, we know the result is out of range. + * The remainder is always positive since it is compared against + * an unsigned digit. + */ + if (sign == '-') { + lastval = minval / 10; + remainder = -(minval % 10); + if (remainder < 0) { + lastval += 1; + remainder += 10; + } + while ((dig = *ustr++) != '\0') { + if (!isdigit(dig)) { + errval = STN_INVALID; + break; + } + dig -= '0'; + if (result < lastval || (result == lastval && dig > remainder)) { + errval = STN_TOOSMALL; + break; + } else { + result *= 10; + result -= dig; + } + } + if (result > maxval) + errval = STN_TOOBIG; + } else { + lastval = maxval / 10; + remainder = maxval % 10; + while ((dig = *ustr++) != '\0') { + if (!isdigit(dig)) { + errval = STN_INVALID; + break; + } + dig -= '0'; + if (result > lastval || (result == lastval && dig > remainder)) { + errval = STN_TOOBIG; + break; + } else { + result *= 10; + result += dig; + } + } + if (result < minval) + errval = STN_TOOSMALL; + } + +done: + switch (errval) { + 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; + } + return result; +} +#endif /* HAVE_STRTONUM */ diff --git a/lib/util/sudo_conf.c b/lib/util/sudo_conf.c new file mode 100644 index 0000000..1af6929 --- /dev/null +++ b/lib/util/sudo_conf.c @@ -0,0 +1,658 @@ +/* + * Copyright (c) 2009-2017 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> + +#define DEFAULT_TEXT_DOMAIN "sudo" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "pathnames.h" +#include "sudo_plugin.h" +#include "sudo_conf.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +#ifdef __TANDEM +# define ROOT_UID 65535 +#else +# define ROOT_UID 0 +#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; + char *pval; +}; + +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_NOEXEC 2 +#define SUDO_CONF_PATH_PLUGIN_DIR 3 +#define SUDO_CONF_PATH_DEVSEARCH 4 + +static struct sudo_conf_data { + bool disable_coredump; + bool probe_interfaces; + int group_source; + int max_groups; + struct sudo_conf_debug_list debugging; + struct plugin_info_list plugins; + struct sudo_conf_path_table path_table[6]; +} sudo_conf_data = { + true, + true, + GROUP_SOURCE_ADAPTIVE, + -1, + TAILQ_HEAD_INITIALIZER(sudo_conf_data.debugging), + TAILQ_HEAD_INITIALIZER(sudo_conf_data.plugins), + { + { "askpass", sizeof("askpass") - 1, false, _PATH_SUDO_ASKPASS }, + { "sesh", sizeof("sesh") - 1, false, _PATH_SUDO_SESH }, + { "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 } + } +}; + +/* + * "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(cur->pval); + cur->pval = pval; + cur->dynamic = true; + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %s:%u: Path %s %s", + __func__, conf_file, lineno, cur->pname, + pval ? pval : "(none)"); + debug_return_int(true); + } + } + sudo_debug_printf(SUDO_DEBUG_WARN, "%s: %s:%u: unknown path %s", + __func__, conf_file, lineno, entry); + debug_return_int(false); +bad: + sudo_warnx(U_("invalid Path value \"%s\" in %s, line %u"), + entry, conf_file, lineno); + debug_return_int(false); +} + +/* + * "Debug program /path/to/log flags,..." + */ +static int +parse_debug(const char *entry, const char *conf_file, unsigned int lineno) +{ + struct sudo_conf_debug *debug_spec; + struct sudo_debug_file *debug_file = NULL; + const char *ep, *path, *progname, *flags; + const char *entry_end = entry + strlen(entry); + size_t pathlen, prognamelen; + debug_decl(parse_debug, SUDO_DEBUG_UTIL) + + /* Parse progname. */ + progname = sudo_strsplit(entry, entry_end, " \t", &ep); + if (progname == NULL) + debug_return_int(false); /* not enough fields */ + prognamelen = (size_t)(ep - progname); + + /* Parse path. */ + path = sudo_strsplit(NULL, entry_end, " \t", &ep); + if (path == NULL) + debug_return_int(false); /* not enough fields */ + pathlen = (size_t)(ep - path); + + /* Remainder is flags (freeform). */ + flags = sudo_strsplit(NULL, entry_end, " \t", &ep); + if (flags == NULL) + debug_return_int(false); /* not enough fields */ + + /* If progname already exists, use it, else alloc a new one. */ + TAILQ_FOREACH(debug_spec, &sudo_conf_data.debugging, entries) { + if (strncmp(debug_spec->progname, progname, prognamelen) == 0 && + debug_spec->progname[prognamelen] == '\0') + break; + } + if (debug_spec == NULL) { + debug_spec = malloc(sizeof(*debug_spec)); + if (debug_spec == NULL) + goto oom; + debug_spec->progname = strndup(progname, prognamelen); + if (debug_spec->progname == NULL) { + free(debug_spec); + debug_spec = NULL; + goto oom; + } + TAILQ_INIT(&debug_spec->debug_files); + TAILQ_INSERT_TAIL(&sudo_conf_data.debugging, debug_spec, entries); + } + debug_file = calloc(1, sizeof(*debug_file)); + if (debug_file == NULL) + goto oom; + debug_file->debug_file = strndup(path, pathlen); + if (debug_file->debug_file == NULL) + goto oom; + debug_file->debug_flags = strdup(flags); + if (debug_file->debug_flags == NULL) + goto oom; + TAILQ_INSERT_TAIL(&debug_spec->debug_files, debug_file, entries); + + debug_return_int(true); +oom: + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + if (debug_file != NULL) { + free(debug_file->debug_file); + free(debug_file->debug_flags); + free(debug_file); + } + debug_return_int(-1); +} + +/* + * "Plugin symbol /path/to/log args..." + */ +static int +parse_plugin(const char *entry, const char *conf_file, unsigned int lineno) +{ + struct plugin_info *info = NULL; + const char *ep, *path, *symbol; + const char *entry_end = entry + strlen(entry); + char **options = NULL; + size_t pathlen, symlen; + unsigned int nopts = 0; + debug_decl(parse_plugin, SUDO_DEBUG_UTIL) + + /* Parse symbol. */ + symbol = sudo_strsplit(entry, entry_end, " \t", &ep); + if (symbol == NULL) + debug_return_int(false); /* not enough fields */ + symlen = (size_t)(ep - symbol); + + /* Parse path. */ + path = sudo_strsplit(NULL, entry_end, " \t", &ep); + if (path == NULL) + debug_return_int(false); /* not enough fields */ + pathlen = (size_t)(ep - path); + + /* Split options into an array if present. */ + while (isblank((unsigned char)*ep)) + ep++; + if (*ep != '\0') { + /* Count number of options and allocate array. */ + const char *cp, *opt = ep; + + /* Count and allocate options array. */ + for (nopts = 0, cp = sudo_strsplit(opt, entry_end, " \t", &ep); + cp != NULL; cp = sudo_strsplit(NULL, entry_end, " \t", &ep)) { + nopts++; + } + options = reallocarray(NULL, nopts + 1, sizeof(*options)); + if (options == NULL) + goto oom; + + /* Fill in options array. */ + for (nopts = 0, cp = sudo_strsplit(opt, entry_end, " \t", &ep); + cp != NULL; cp = sudo_strsplit(NULL, entry_end, " \t", &ep)) { + options[nopts] = strndup(cp, (size_t)(ep - cp)); + if (options[nopts] == NULL) + goto oom; + nopts++; + } + options[nopts] = NULL; + } + + info = calloc(sizeof(*info), 1); + if (info == NULL) + goto oom; + info->symbol_name = strndup(symbol, symlen); + if (info->symbol_name == NULL) + goto oom; + info->path = strndup(path, pathlen); + if (info->path == NULL) + goto oom; + info->options = options; + info->lineno = lineno; + TAILQ_INSERT_TAIL(&sudo_conf_data.plugins, info, entries); + + debug_return_int(true); +oom: + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + if (options != NULL) { + while (nopts--) + free(options[nopts]); + free(options); + } + if (info != NULL) { + free(info->symbol_name); + free(info->path); + free(info); + } + debug_return_int(-1); +} + +static int +set_var_disable_coredump(const char *strval, const char *conf_file, + unsigned int lineno) +{ + int val = sudo_strtobool(strval); + debug_decl(set_var_disable_coredump, SUDO_DEBUG_UTIL) + + if (val == -1) { + sudo_warnx(U_("invalid value for %s \"%s\" in %s, line %u"), + "disable_coredump", strval, conf_file, lineno); + debug_return_bool(false); + } + sudo_conf_data.disable_coredump = val; + debug_return_bool(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.group_source = GROUP_SOURCE_ADAPTIVE; + } else if (strcasecmp(strval, "static") == 0) { + sudo_conf_data.group_source = GROUP_SOURCE_STATIC; + } else if (strcasecmp(strval, "dynamic") == 0) { + sudo_conf_data.group_source = GROUP_SOURCE_DYNAMIC; + } else { + sudo_warnx(U_("unsupported group source \"%s\" in %s, line %u"), strval, + conf_file, lineno); + debug_return_bool(false); + } + debug_return_bool(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 = strtonum(strval, 1, INT_MAX, NULL); + if (max_groups <= 0) { + sudo_warnx(U_("invalid max groups \"%s\" in %s, line %u"), strval, + conf_file, lineno); + debug_return_bool(false); + } + sudo_conf_data.max_groups = max_groups; + debug_return_bool(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_bool(false); + } + sudo_conf_data.probe_interfaces = val; + debug_return_bool(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_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.group_source; +} + +int +sudo_conf_max_groups_v1(void) +{ + return sudo_conf_data.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; + size_t prognamelen, progbaselen; + const char *progbase = progname; + debug_decl(sudo_conf_debug_files, SUDO_DEBUG_UTIL) + + /* Determine basename if program is fully qualified (like for plugins). */ + prognamelen = progbaselen = strlen(progname); + if (*progname == '/') { + progbase = strrchr(progname, '/'); + progbaselen = strlen(++progbase); + } + /* Convert sudoedit -> sudo. */ + if (progbaselen > 4 && strcmp(progbase + 4, "edit") == 0) { + progbaselen -= 4; + } + TAILQ_FOREACH(debug_spec, &sudo_conf_data.debugging, entries) { + const char *prog = progbase; + size_t len = progbaselen; + + if (debug_spec->progname[0] == '/') { + /* Match fully-qualified name, if possible. */ + prog = progname; + len = prognamelen; + } + if (strncmp(debug_spec->progname, prog, len) == 0 && + debug_spec->progname[len] == '\0') { + debug_return_ptr(&debug_spec->debug_files); + } + } + debug_return_ptr(NULL); +} + +bool +sudo_conf_disable_coredump_v1(void) +{ + return sudo_conf_data.disable_coredump; +} + +bool +sudo_conf_probe_interfaces_v1(void) +{ + return sudo_conf_data.probe_interfaces; +} + +/* + * Reads in /etc/sudo.conf and populates sudo_conf_data. + */ +int +sudo_conf_read_v1(const char *conf_file, int conf_types) +{ + struct stat sb; + FILE *fp = NULL; + int ret = false; + char *prev_locale, *line = NULL; + unsigned int conf_lineno = 0; + size_t linesize = 0; + debug_decl(sudo_conf_read, SUDO_DEBUG_UTIL) + + if ((prev_locale = setlocale(LC_ALL, NULL)) == NULL) { + sudo_warn("setlocale(LC_ALL, NULL)"); + debug_return_int(-1); + } + if ((prev_locale = strdup(prev_locale)) == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_int(-1); + } + + /* Parse sudo.conf in the "C" locale. */ + if (prev_locale[0] != 'C' || prev_locale[1] != '\0') + setlocale(LC_ALL, "C"); + + if (conf_file == NULL) { + conf_file = _PATH_SUDO_CONF; + switch (sudo_secure_file(conf_file, ROOT_UID, -1, &sb)) { + case SUDO_PATH_SECURE: + 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 stat %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: + /* NOTREACHED */ + goto done; + } + } + + if ((fp = fopen(conf_file, "r")) == NULL) { + if (errno != ENOENT && geteuid() == ROOT_UID) + sudo_warn(U_("unable to open %s"), conf_file); + goto done; + } + + while (sudo_parseln(&line, &linesize, &conf_lineno, fp, 0) != -1) { + struct sudo_conf_table *cur; + unsigned int i; + char *cp; + + if (*(cp = line) == '\0') + continue; /* empty line or comment */ + + for (i = 0, cur = sudo_conf_table; cur->name != NULL; i++, cur++) { + if (strncasecmp(cp, cur->name, cur->namelen) == 0 && + isblank((unsigned char)cp[cur->namelen])) { + if (ISSET(conf_types, (1 << i))) { + cp += cur->namelen; + while (isblank((unsigned char)*cp)) + cp++; + ret = cur->parser(cp, conf_file, conf_lineno); + if (ret == -1) + goto done; + } + break; + } + } + if (cur->name == NULL) { + sudo_debug_printf(SUDO_DEBUG_WARN, + "%s: %s:%u: unsupported entry: %s", __func__, conf_file, + conf_lineno, line); + } + } + ret = true; + +done: + if (fp != NULL) + fclose(fp); + free(line); + + /* Restore locale if needed. */ + if (prev_locale[0] != 'C' || prev_locale[1] != '\0') + setlocale(LC_ALL, prev_locale); + free(prev_locale); + debug_return_int(ret); +} + +/* + * Used by the sudo_conf regress test to clear compile-time path settings. + */ +void +sudo_conf_clear_paths_v1(void) +{ + struct sudo_conf_path_table *cur; + debug_decl(sudo_conf_clear_paths, SUDO_DEBUG_UTIL) + + for (cur = sudo_conf_data.path_table; cur->pname != NULL; cur++) { + if (cur->dynamic) + free(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..593af92 --- /dev/null +++ b/lib/util/sudo_debug.c @@ -0,0 +1,877 @@ +/* + * Copyright (c) 2011-2017 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <time.h> + +#define DEFAULT_TEXT_DOMAIN "sudo" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_plugin.h" +#include "sudo_debug.h" +#include "sudo_conf.h" +#include "sudo_util.h" + +/* + * 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; + struct sudo_debug_output_list outputs; +}; + +/* Support up to 10 instances. */ +#define SUDO_DEBUG_INSTANCE_MAX 10 +static struct sudo_debug_instance *sudo_debug_instances[SUDO_DEBUG_INSTANCE_MAX]; +static int sudo_debug_last_instance = -1; + +static char sudo_debug_pidstr[(((sizeof(int) * 8) + 2) / 3) + 3]; +static size_t sudo_debug_pidlen; + +#define round_nfds(_n) (((_n) + (4 * NBBY) - 1) & ~((4 * NBBY) - 1)) +static int sudo_debug_fds_size; +static unsigned char *sudo_debug_fds; +static int sudo_debug_max_fd = -1; + +/* Default instance index to use for common utility functions. */ +static int sudo_debug_active_instance = -1; + +/* + * Free the specified output structure. + */ +static void +sudo_debug_free_output(struct sudo_debug_output *output) +{ + free(output->filename); + free(output->settings); + if (output->fd != -1) + close(output->fd); + free(output); +} + +/* + * Create a new output file for the specified debug instance. + * Returns NULL if the file cannot be opened or memory cannot be allocated. + */ +static struct sudo_debug_output * +sudo_debug_new_output(struct sudo_debug_instance *instance, + struct sudo_debug_file *debug_file) +{ + 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 bad; + output->fd = -1; + output->settings = reallocarray(NULL, instance->max_subsystem + 1, + sizeof(int)); + if (output->settings == NULL) + goto bad; + output->filename = strdup(debug_file->debug_file); + if (output->filename == NULL) + goto bad; + output->fd = -1; + + /* 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) + goto bad; + ignore_result(fchown(output->fd, (uid_t)-1, 0)); + } + (void)fcntl(output->fd, F_SETFD, FD_CLOEXEC); + if (sudo_debug_fds_size < output->fd) { + /* Bump fds size to the next multiple of 4 * NBBY. */ + const int old_size = sudo_debug_fds_size / NBBY; + const int new_size = round_nfds(output->fd + 1) / NBBY; + unsigned char *new_fds; + + new_fds = realloc(sudo_debug_fds, new_size); + if (new_fds == NULL) + goto bad; + memset(new_fds + old_size, 0, new_size - old_size); + sudo_debug_fds = new_fds; + sudo_debug_fds_size = new_size * NBBY; + } + sudo_setbit(sudo_debug_fds, output->fd); + if (output->fd > sudo_debug_max_fd) + sudo_debug_max_fd = output->fd; + + /* Parse Debug conf string. */ + buf = strdup(debug_file->debug_flags); + if (buf == NULL) + goto bad; + 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; +bad: + sudo_warn_nodebug(NULL); + 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_v1(const char *program, const char *const subsystems[], + unsigned int ids[], struct sudo_conf_debug_file_list *debug_files) +{ + struct sudo_debug_instance *instance = NULL; + struct sudo_debug_output *output; + struct sudo_debug_file *debug_file; + int idx, free_idx = -1; + debug_decl_func(sudo_debug_register); + + if (debug_files == NULL) + return SUDO_DEBUG_INSTANCE_INITIALIZER; + + /* Use default subsystem names if none are provided. */ + if (subsystems == NULL) { + subsystems = sudo_debug_default_subsystems; + } else if (ids == NULL) { + /* If subsystems are specified we must have ids[] too. */ + return SUDO_DEBUG_INSTANCE_ERROR; + } + + /* Search for existing instance. */ + for (idx = 0; idx <= sudo_debug_last_instance; idx++) { + if (sudo_debug_instances[idx] == NULL) { + free_idx = idx; + continue; + } + if (sudo_debug_instances[idx]->subsystems == subsystems && + strcmp(sudo_debug_instances[idx]->program, program) == 0) { + instance = sudo_debug_instances[idx]; + break; + } + } + + if (instance == NULL) { + unsigned int i, j, max_id = NUM_DEF_SUBSYSTEMS - 1; + + /* Fill in subsystem name -> id mapping as needed. */ + if (ids != NULL) { + for (i = 0; subsystems[i] != NULL; i++) { + /* Check default subsystems. */ + for (j = 0; j < NUM_DEF_SUBSYSTEMS; j++) { + if (strcmp(subsystems[i], sudo_debug_default_subsystems[j]) == 0) + break; + } + if (j == NUM_DEF_SUBSYSTEMS) + j = ++max_id; + ids[i] = ((j + 1) << 6); + } + } + + if (free_idx != -1) + idx = free_idx; + if (idx == SUDO_DEBUG_INSTANCE_MAX) { + /* XXX - realloc? */ + sudo_warnx_nodebug("too many debug instances (max %d)", SUDO_DEBUG_INSTANCE_MAX); + return SUDO_DEBUG_INSTANCE_ERROR; + } + if (idx != sudo_debug_last_instance + 1 && idx != free_idx) { + sudo_warnx_nodebug("%s: instance number mismatch: expected %d or %d, got %d", __func__, sudo_debug_last_instance + 1, free_idx, idx); + return SUDO_DEBUG_INSTANCE_ERROR; + } + if ((instance = malloc(sizeof(*instance))) == NULL) + return SUDO_DEBUG_INSTANCE_ERROR; + if ((instance->program = strdup(program)) == NULL) { + free(instance); + return SUDO_DEBUG_INSTANCE_ERROR; + } + instance->subsystems = subsystems; + instance->subsystem_ids = ids; + instance->max_subsystem = max_id; + SLIST_INIT(&instance->outputs); + sudo_debug_instances[idx] = instance; + if (idx != free_idx) + sudo_debug_last_instance++; + } else { + /* Check for matching instance but different ids[]. */ + if (ids != NULL && instance->subsystem_ids != ids) { + unsigned int i; + + for (i = 0; subsystems[i] != NULL; i++) + ids[i] = instance->subsystem_ids[i]; + } + } + + TAILQ_FOREACH(debug_file, debug_files, entries) { + output = sudo_debug_new_output(instance, debug_file); + 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; +} + +/* + * De-register the specified instance from the debug subsystem + * and free up any associated data structures. + */ +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 */ + + /* 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; +} + +int +sudo_debug_get_instance_v1(const char *program) +{ + int idx; + + for (idx = 0; idx <= sudo_debug_last_instance; idx++) { + if (sudo_debug_instances[idx] == NULL) + continue; + if (strcmp(sudo_debug_instances[idx]->program, program) == 0) + return idx; + } + return SUDO_DEBUG_INSTANCE_INITIALIZER; +} + +pid_t +sudo_debug_fork_v1(void) +{ + pid_t pid; + + if ((pid = fork()) == 0) { + (void)snprintf(sudo_debug_pidstr, sizeof(sudo_debug_pidstr), "[%d] ", + (int)getpid()); + sudo_debug_pidlen = strlen(sudo_debug_pidstr); + } + + return pid; +} + +void +sudo_debug_enter_v1(const char *func, const char *file, int line, + int subsys) +{ + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "-> %s @ %s:%d", func, file, line); +} + +void +sudo_debug_exit_v1(const char *func, const char *file, int line, + int subsys) +{ + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d", func, file, line); +} + +void +sudo_debug_exit_int_v1(const char *func, const char *file, int line, + int subsys, int ret) +{ + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %d", func, file, line, ret); +} + +void +sudo_debug_exit_long_v1(const char *func, const char *file, int line, + int subsys, long ret) +{ + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %ld", func, file, line, ret); +} + +void +sudo_debug_exit_id_t_v1(const char *func, const char *file, int line, + int subsys, id_t ret) +{ +#if SIZEOF_ID_T == 8 + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %lld", func, file, line, (long long)ret); +#else + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %d", func, file, line, (int)ret); +#endif +} + +void +sudo_debug_exit_size_t_v1(const char *func, const char *file, int line, + int subsys, size_t ret) +{ + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %zu", func, file, line, ret); +} + +void +sudo_debug_exit_ssize_t_v1(const char *func, const char *file, int line, + int subsys, ssize_t ret) +{ + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %zd", func, file, line, ret); +} + +void +sudo_debug_exit_time_t_v1(const char *func, const char *file, int line, + int subsys, time_t ret) +{ +#if SIZEOF_TIME_T == 8 + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %lld", func, file, line, (long long)ret); +#else + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %d", func, file, line, (int)ret); +#endif +} + +void +sudo_debug_exit_bool_v1(const char *func, const char *file, int line, + int subsys, bool ret) +{ + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %s", func, file, line, ret ? "true" : "false"); +} + +void +sudo_debug_exit_str_v1(const char *func, const char *file, int line, + int subsys, const char *ret) +{ + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %s", func, file, line, ret ? ret : "(null)"); +} + +void +sudo_debug_exit_str_masked_v1(const char *func, const char *file, int line, + int subsys, const char *ret) +{ + static const char stars[] = "********************************************************************************"; + int len = ret ? strlen(ret) : sizeof("(null)") - 1; + + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %.*s", func, file, line, len, ret ? stars : "(null)"); +} + +void +sudo_debug_exit_ptr_v1(const char *func, const char *file, int line, + int subsys, const void *ret) +{ + sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE, + "<- %s @ %s:%d := %p", func, file, line, ret); +} + +void +sudo_debug_write2_v1(int fd, const char *func, const char *file, int lineno, + const char *str, int len, int errnum) +{ + char *timestr, numbuf[(((sizeof(int) * 8) + 2) / 3) + 2]; + time_t now; + struct iovec iov[12]; + int iovcnt = 3; + + /* 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 = ": "; + 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 = " @ "; + iov[iovcnt].iov_len = 3; + iovcnt++; + + iov[iovcnt].iov_base = (char *)func; + iov[iovcnt].iov_len = strlen(func); + iovcnt++; + + iov[iovcnt].iov_base = "() "; + 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 = "\n"; + iov[iovcnt].iov_len = 1; + iovcnt++; + + /* Do timestamp last due to ctime's static buffer. */ + time(&now); + timestr = ctime(&now) + 4; + timestr[15] = ' '; /* replace year with a space */ + timestr[16] = '\0'; + iov[0].iov_base = timestr; + iov[0].iov_len = 16; + + /* Write message in a single syscall */ + ignore_result(writev(fd, iov, iovcnt)); +} + +void +sudo_debug_vprintf2_v1(const char *func, const char *file, int lineno, int level, + const char *fmt, va_list ap) +{ + int buflen, pri, saved_errno = errno; + unsigned int subsys; + char static_buf[1024], *buf = static_buf; + struct sudo_debug_instance *instance; + struct sudo_debug_output *output; + debug_decl_func(sudo_debug_vprintf2); + + if (sudo_debug_active_instance == -1) + goto out; + + /* Extract priority and subsystem from level. */ + pri = SUDO_DEBUG_PRI(level); + subsys = SUDO_DEBUG_SUBSYS(level); + + /* Find matching instance. */ + if (sudo_debug_active_instance > sudo_debug_last_instance) { + sudo_warnx_nodebug("%s: invalid instance ID %d, max %d", + __func__, sudo_debug_active_instance, sudo_debug_last_instance); + goto out; + } + instance = sudo_debug_instances[sudo_debug_active_instance]; + if (instance == NULL) { + sudo_warnx_nodebug("%s: unregistered instance index %d", __func__, + sudo_debug_active_instance); + goto out; + } + + SLIST_FOREACH(output, &instance->outputs, entries) { + /* Make sure we want debug info at this level. */ + if (subsys <= instance->max_subsystem && output->settings[subsys] >= pri) { + va_list ap2; + + /* Operate on a copy of ap to support multiple outputs. */ + va_copy(ap2, ap); + buflen = fmt ? vsnprintf(static_buf, sizeof(static_buf), fmt, ap2) : 0; + va_end(ap2); + if (buflen >= (int)sizeof(static_buf)) { + va_list ap3; + + /* Not enough room in static buf, allocate dynamically. */ + va_copy(ap3, ap); + buflen = vasprintf(&buf, fmt, ap3); + va_end(ap3); + } + if (buflen != -1) { + int errcode = ISSET(level, SUDO_DEBUG_ERRNO) ? saved_errno : 0; + if (ISSET(level, SUDO_DEBUG_LINENO)) + sudo_debug_write2(output->fd, func, file, lineno, buf, buflen, errcode); + else + sudo_debug_write2(output->fd, NULL, NULL, 0, buf, buflen, errcode); + if (buf != static_buf) { + free(buf); + buf = static_buf; + } + } + } + } +out: + errno = saved_errno; +} + +#ifdef NO_VARIADIC_MACROS +void +sudo_debug_printf_nvm_v1(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + sudo_debug_vprintf2(NULL, NULL, 0, pri, fmt, ap); + va_end(ap); +} +#endif /* NO_VARIADIC_MACROS */ + +void +sudo_debug_printf2_v1(const char *func, const char *file, int lineno, int level, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + sudo_debug_vprintf2(func, file, lineno, level, fmt, ap); + va_end(ap); +} + +#define EXEC_PREFIX "exec " + +void +sudo_debug_execve2_v1(int level, const char *path, char *const argv[], char *const envp[]) +{ + int buflen, pri, saved_errno = errno; + unsigned int subsys; + struct sudo_debug_instance *instance; + struct sudo_debug_output *output; + char * const *av; + char *cp, static_buf[4096], *buf = static_buf; + size_t plen; + debug_decl_func(sudo_debug_execve2); + + if (sudo_debug_active_instance == -1) + goto out; + + /* Extract priority and subsystem from level. */ + pri = SUDO_DEBUG_PRI(level); + subsys = SUDO_DEBUG_SUBSYS(level); + + /* Find matching instance. */ + if (sudo_debug_active_instance > sudo_debug_last_instance) { + sudo_warnx_nodebug("%s: invalid instance ID %d, max %d", + __func__, sudo_debug_active_instance, sudo_debug_last_instance); + goto out; + } + instance = sudo_debug_instances[sudo_debug_active_instance]; + if (instance == NULL) { + sudo_warnx_nodebug("%s: unregistered instance index %d", __func__, + sudo_debug_active_instance); + goto out; + } + if (subsys > instance->max_subsystem) + goto out; + + SLIST_FOREACH(output, &instance->outputs, entries) { + bool log_envp = false; + + /* Make sure we want debug info at this level. */ + if (output->settings[subsys] < pri) + continue; + + /* Log envp for debug level "debug". */ + if (output->settings[subsys] >= SUDO_DEBUG_DEBUG - 1 && envp[0] != NULL) + log_envp = true; + + /* Alloc and build up buffer. */ + plen = strlen(path); + buflen = sizeof(EXEC_PREFIX) -1 + plen; + if (argv[0] != NULL) { + buflen += sizeof(" []") - 1; + for (av = argv; *av; av++) + buflen += strlen(*av) + 1; + buflen--; + } + if (log_envp) { + buflen += sizeof(" []") - 1; + for (av = envp; *av; av++) + buflen += strlen(*av) + 1; + buflen--; + } + if (buflen >= (int)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[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) { + *cp++ = ' '; + *cp++ = '['; + for (av = envp; *av; av++) { + size_t avlen = strlen(*av); + memcpy(cp, *av, avlen); + cp += avlen; + *cp++ = ' '; + } + cp[-1] = ']'; + } + + *cp = '\0'; + + sudo_debug_write(output->fd, buf, buflen, 0); + if (buf != static_buf) { + free(buf); + buf = static_buf; + } + } +out: + errno = saved_errno; +} + +/* + * Returns the active instance or SUDO_DEBUG_INSTANCE_INITIALIZER + * if no instance is active. + */ +int +sudo_debug_get_active_instance_v1(void) +{ + return sudo_debug_active_instance; +} + +/* + * Sets a new active instance, returning the old one. + * Note that the old instance may be SUDO_DEBUG_INSTANCE_INITIALIZER + * if this is the only instance. + */ +int +sudo_debug_set_active_instance_v1(int idx) +{ + const int old_idx = sudo_debug_active_instance; + + if (idx >= -1 && idx <= sudo_debug_last_instance) + sudo_debug_active_instance = idx; + return old_idx; +} + +/* + * Replace the ofd with nfd in all outputs if present. + * Also updates sudo_debug_fds. + */ +void +sudo_debug_update_fd_v1(int ofd, int nfd) +{ + int idx; + + if (ofd <= sudo_debug_max_fd && sudo_isset(sudo_debug_fds, ofd)) { + /* Update sudo_debug_fds. */ + sudo_clrbit(sudo_debug_fds, ofd); + sudo_setbit(sudo_debug_fds, nfd); + + /* Update the outputs. */ + for (idx = 0; idx <= sudo_debug_last_instance; idx++) { + struct sudo_debug_instance *instance; + struct sudo_debug_output *output; + + instance = sudo_debug_instances[idx]; + if (instance == NULL) + continue; + SLIST_FOREACH(output, &instance->outputs, entries) { + if (output->fd == ofd) + output->fd = nfd; + } + } + } +} + +/* + * Returns the highest debug output fd or -1 if no debug files open. + * Fills in fds with the value of sudo_debug_fds. + */ +int +sudo_debug_get_fds_v1(unsigned char **fds) +{ + *fds = sudo_debug_fds; + return sudo_debug_max_fd; +} diff --git a/lib/util/sudo_dso.c b/lib/util/sudo_dso.c new file mode 100644 index 0000000..39d4381 --- /dev/null +++ b/lib/util/sudo_dso.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2010, 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. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#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" + +/* + * 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 + +void * +sudo_dso_load_v1(const char *path, int mode) +{ + struct sudo_preload_table *pt; + int flags = 0; + + /* 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)) + flags |= RTLD_LAZY; + if (ISSET(mode, SUDO_DSO_NOW)) + flags |= RTLD_NOW; + if (ISSET(mode, SUDO_DSO_GLOBAL)) + flags |= RTLD_GLOBAL; + if (ISSET(mode, SUDO_DSO_LOCAL)) + flags |= RTLD_LOCAL; + + return dlopen(path, flags); +} + +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/term.c b/lib/util/term.c new file mode 100644 index 0000000..5151ffd --- /dev/null +++ b/lib/util/term.c @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2011-2015, 2017 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <errno.h> +#include <signal.h> +#include <termios.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +/* TCSASOFT is a BSD extension that ignores control flags and speed. */ +#ifndef TCSASOFT +# define TCSASOFT 0 +#endif + +/* Non-standard termios input flags */ +#ifndef IUCLC +# define IUCLC 0 +#endif +#ifndef IMAXBEL +# define IMAXBEL 0 +#endif +#ifndef IUTF8 +# define IUTF8 0 +#endif + +/* Non-standard termios output flags */ +#ifndef OLCUC +# define OLCUC 0 +#endif +#ifndef ONLCR +# define ONLCR 0 +#endif +#ifndef OCRNL +# define OCRNL 0 +#endif +#ifndef ONOCR +# define ONOCR 0 +#endif +#ifndef ONLRET +# define ONLRET 0 +#endif + +/* Non-standard termios local flags */ +#ifndef XCASE +# define XCASE 0 +#endif +#ifndef IEXTEN +# define IEXTEN 0 +#endif +#ifndef ECHOCTL +# define ECHOCTL 0 +#endif +#ifndef ECHOKE +# define ECHOKE 0 +#endif +#ifndef PENDIN +# define PENDIN 0 +#endif + +#ifndef _POSIX_VDISABLE +# ifdef VDISABLE +# define _POSIX_VDISABLE VDISABLE +# else +# define _POSIX_VDISABLE 0 +# endif +#endif + +static struct termios term, oterm; +static int changed; + +/* tgetpass() needs to know the erase and kill chars for cbreak mode. */ +__dso_public int sudo_term_eof; +__dso_public int sudo_term_erase; +__dso_public int sudo_term_kill; + +static volatile sig_atomic_t got_sigttou; + +/* + * SIGTTOU signal handler for term_restore that just sets a flag. + */ +static void +sigttou(int signo) +{ + got_sigttou = 1; +} + +/* + * Like tcsetattr() but restarts on EINTR _except_ for SIGTTOU. + * Returns 0 on success or -1 on failure, setting errno. + * Sets got_sigttou on failure if interrupted by SIGTTOU. + */ +static int +tcsetattr_nobg(int fd, int flags, struct termios *tp) +{ + struct sigaction sa, osa; + int rc; + + /* + * If we receive SIGTTOU from tcsetattr() it means we are + * not in the foreground process group. + * This should be less racy than using tcgetpgrp(). + */ + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = sigttou; + got_sigttou = 0; + sigaction(SIGTTOU, &sa, &osa); + do { + rc = tcsetattr(fd, flags, tp); + } while (rc != 0 && errno == EINTR && !got_sigttou); + sigaction(SIGTTOU, &osa, NULL); + + return rc; +} + +/* + * Restore saved terminal settings if we are in the foreground process group. + * Returns true on success or false on failure. + */ +bool +sudo_term_restore_v1(int fd, bool flush) +{ + debug_decl(sudo_term_restore, SUDO_DEBUG_UTIL) + + if (changed) { + const int flags = flush ? (TCSASOFT|TCSAFLUSH) : (TCSASOFT|TCSADRAIN); + if (tcsetattr_nobg(fd, flags, &oterm) != 0) + debug_return_bool(false); + changed = 0; + } + debug_return_bool(true); +} + +/* + * Disable terminal echo. + * Returns true on success or false on failure. + */ +bool +sudo_term_noecho_v1(int fd) +{ + debug_decl(sudo_term_noecho, SUDO_DEBUG_UTIL) + + if (!changed && tcgetattr(fd, &oterm) != 0) + debug_return_bool(false); + (void) memcpy(&term, &oterm, sizeof(term)); + CLR(term.c_lflag, ECHO|ECHONL); +#ifdef VSTATUS + term.c_cc[VSTATUS] = _POSIX_VDISABLE; +#endif + if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) { + changed = 1; + debug_return_bool(true); + } + debug_return_bool(false); +} + +/* + * Set terminal to raw mode. + * Returns true on success or false on failure. + */ +bool +sudo_term_raw_v1(int fd, int isig) +{ + struct termios term; + debug_decl(sudo_term_raw, SUDO_DEBUG_UTIL) + + if (!changed && tcgetattr(fd, &oterm) != 0) + debug_return_bool(false); + (void) memcpy(&term, &oterm, sizeof(term)); + /* Set terminal to raw mode */ + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 0; + CLR(term.c_iflag, ICRNL | IGNCR | INLCR | IUCLC | IXON); + CLR(term.c_oflag, OPOST); + CLR(term.c_lflag, ECHO | ICANON | ISIG | IEXTEN); + if (isig) + SET(term.c_lflag, ISIG); + if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) { + changed = 1; + debug_return_bool(true); + } + debug_return_bool(false); +} + +/* + * Set terminal to cbreak mode. + * Returns true on success or false on failure. + */ +bool +sudo_term_cbreak_v1(int fd) +{ + debug_decl(sudo_term_cbreak, SUDO_DEBUG_UTIL) + + if (!changed && tcgetattr(fd, &oterm) != 0) + debug_return_bool(false); + (void) memcpy(&term, &oterm, sizeof(term)); + /* Set terminal to half-cooked mode */ + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 0; + /* cppcheck-suppress redundantAssignment */ + CLR(term.c_lflag, ECHO | ECHONL | ICANON | IEXTEN); + /* cppcheck-suppress redundantAssignment */ + SET(term.c_lflag, ISIG); +#ifdef VSTATUS + term.c_cc[VSTATUS] = _POSIX_VDISABLE; +#endif + if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) { + sudo_term_eof = term.c_cc[VEOF]; + sudo_term_erase = term.c_cc[VERASE]; + sudo_term_kill = term.c_cc[VKILL]; + changed = 1; + debug_return_bool(true); + } + debug_return_bool(false); +} + +/* Termios flags to copy between terminals. */ +#define INPUT_FLAGS (IGNPAR|PARMRK|INPCK|ISTRIP|INLCR|IGNCR|ICRNL|IUCLC|IXON|IXANY|IXOFF|IMAXBEL|IUTF8) +#define OUTPUT_FLAGS (OPOST|OLCUC|ONLCR|OCRNL|ONOCR|ONLRET) +#define CONTROL_FLAGS (CS7|CS8|PARENB|PARODD) +#define LOCAL_FLAGS (ISIG|ICANON|XCASE|ECHO|ECHOE|ECHOK|ECHONL|NOFLSH|TOSTOP|IEXTEN|ECHOCTL|ECHOKE|PENDIN) + +/* + * Copy terminal settings from one descriptor to another. + * We cannot simply copy the struct termios as src and dst may be + * different terminal types (pseudo-tty vs. console or glass tty). + * Returns true on success or false on failure. + */ +bool +sudo_term_copy_v1(int src, int dst) +{ + struct termios tt_src, tt_dst; + struct winsize wsize; + speed_t speed; + int i; + debug_decl(sudo_term_copy, SUDO_DEBUG_UTIL) + + if (tcgetattr(src, &tt_src) != 0 || tcgetattr(dst, &tt_dst) != 0) + debug_return_bool(false); + + /* Clear select input, output, control and local flags. */ + CLR(tt_dst.c_iflag, INPUT_FLAGS); + CLR(tt_dst.c_oflag, OUTPUT_FLAGS); + CLR(tt_dst.c_cflag, CONTROL_FLAGS); + CLR(tt_dst.c_lflag, LOCAL_FLAGS); + + /* Copy select input, output, control and local flags. */ + SET(tt_dst.c_iflag, (tt_src.c_iflag & INPUT_FLAGS)); + SET(tt_dst.c_oflag, (tt_src.c_oflag & OUTPUT_FLAGS)); + SET(tt_dst.c_cflag, (tt_src.c_cflag & CONTROL_FLAGS)); + SET(tt_dst.c_lflag, (tt_src.c_lflag & LOCAL_FLAGS)); + + /* Copy special chars from src verbatim. */ + for (i = 0; i < NCCS; i++) + tt_dst.c_cc[i] = tt_src.c_cc[i]; + + /* Copy speed from src (zero output speed closes the connection). */ + if ((speed = cfgetospeed(&tt_src)) == B0) + speed = B38400; + cfsetospeed(&tt_dst, speed); + speed = cfgetispeed(&tt_src); + cfsetispeed(&tt_dst, speed); + + if (tcsetattr_nobg(dst, TCSASOFT|TCSAFLUSH, &tt_dst) == -1) + debug_return_bool(false); + + if (ioctl(src, TIOCGWINSZ, &wsize) == 0) + (void)ioctl(dst, TIOCSWINSZ, &wsize); + + debug_return_bool(true); +} diff --git a/lib/util/ttyname_dev.c b/lib/util/ttyname_dev.c new file mode 100644 index 0000000..3d8e20a --- /dev/null +++ b/lib/util/ttyname_dev.c @@ -0,0 +1,316 @@ +/* + * 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> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <dirent.h> + +#include <pathnames.h> +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_conf.h" +#include "sudo_util.h" + +#if defined(HAVE_DEVNAME) +/* + * Like ttyname() but uses a dev_t instead of an open fd. + * Returns name on success and NULL on failure, setting errno. + * The BSD version uses devname(). + */ +char * +sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen) +{ + char *dev; + debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL) + + /* Some versions of devname() return NULL on failure, others do not. */ + dev = devname(tdev, S_IFCHR); + if (dev != NULL && *dev != '?' && *dev != '#') { + if (strlcpy(name, _PATH_DEV, namelen) < namelen && + strlcat(name, dev, namelen) < namelen) + debug_return_str(name); + errno = ERANGE; + } else { + /* Not all versions of devname() set errno. */ + errno = ENOENT; + } + debug_return_str(NULL); +} +#elif defined(HAVE__TTYNAME_DEV) +extern char *_ttyname_dev(dev_t rdev, char *buffer, size_t buflen); + +/* + * Like ttyname() but uses a dev_t instead of an open fd. + * Returns name on success and NULL on failure, setting errno. + * This version is just a wrapper around _ttyname_dev(). + */ +char * +sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen) +{ + int serrno = errno; + debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL) + + /* + * _ttyname_dev() sets errno to ERANGE if namelen is too small + * but does not modify it if tdev is not found. + */ + errno = ENOENT; + if (_ttyname_dev(tdev, name, namelen) == NULL) + debug_return_str(NULL); + errno = serrno; + + debug_return_str(name); +} +#else +/* + * Device nodes to ignore. + */ +static const char *ignore_devs[] = { + _PATH_DEV "stdin", + _PATH_DEV "stdout", + _PATH_DEV "stderr", + NULL +}; + +/* + * Do a scan of a directory looking for the specified device. + * Does not descend into subdirectories. + * Returns name on success and NULL on failure, setting errno. + */ +static char * +sudo_ttyname_scan(const char *dir, dev_t rdev, char *name, size_t namelen) +{ + size_t sdlen; + char pathbuf[PATH_MAX]; + char *ret = NULL; + struct dirent *dp; + struct stat sb; + unsigned int i; + DIR *d = NULL; + debug_decl(sudo_ttyname_scan, SUDO_DEBUG_UTIL) + + if (dir[0] == '\0') { + errno = ENOENT; + goto done; + } + if ((d = opendir(dir)) == NULL) + goto done; + + if (fstat(dirfd(d), &sb) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to fstat %s", dir); + goto done; + } + if ((sb.st_mode & S_IWOTH) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "ignoring world-writable directory %s", dir); + errno = ENOENT; + goto done; + } + + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "scanning for dev %u in %s", (unsigned int)rdev, dir); + + sdlen = strlen(dir); + while (sdlen > 0 && dir[sdlen - 1] == '/') + sdlen--; + if (sdlen + 1 >= sizeof(pathbuf)) { + errno = ERANGE; + goto done; + } + memcpy(pathbuf, dir, sdlen); + pathbuf[sdlen++] = '/'; + + while ((dp = readdir(d)) != NULL) { + struct stat sb; + + /* Skip anything starting with "." */ + if (dp->d_name[0] == '.') + continue; + + pathbuf[sdlen] = '\0'; + if (strlcat(pathbuf, dp->d_name, sizeof(pathbuf)) >= sizeof(pathbuf)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s%s is too big to fit in pathbuf", pathbuf, dp->d_name); + continue; + } + + /* Ignore device nodes listed in ignore_devs[]. */ + for (i = 0; ignore_devs[i] != NULL; i++) { + if (strcmp(pathbuf, ignore_devs[i]) == 0) + break; + } + if (ignore_devs[i] != NULL) { + sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, + "ignoring %s", pathbuf); + continue; + } + +# if defined(HAVE_STRUCT_DIRENT_D_TYPE) + /* + * Avoid excessive stat() calls by checking dp->d_type. + */ + switch (dp->d_type) { + case DT_CHR: + case DT_LNK: + case DT_UNKNOWN: + break; + default: + /* Not a character device or link, skip it. */ + sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, + "skipping non-device %s", pathbuf); + continue; + } +# endif + if (stat(pathbuf, &sb) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to stat %s", pathbuf); + continue; + } + if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "resolved dev %u as %s", (unsigned int)rdev, pathbuf); + if (strlcpy(name, pathbuf, namelen) < namelen) { + ret = name; + } else { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to store %s, have %zu, need %zu", + pathbuf, namelen, strlen(pathbuf) + 1); + errno = ERANGE; + } + goto done; + } + } + +done: + if (d != NULL) + closedir(d); + debug_return_str(ret); +} + +static char * +sudo_dev_check(dev_t rdev, const char *devname, char *buf, size_t buflen) +{ + struct stat sb; + debug_decl(sudo_dev_check, SUDO_DEBUG_UTIL) + + if (stat(devname, &sb) == 0) { + if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "comparing dev %u to %s: match!", + (unsigned int)rdev, devname); + if (strlcpy(buf, devname, buflen) < buflen) + debug_return_str(buf); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to store %s, have %zu, need %zu", + devname, buflen, strlen(devname) + 1); + errno = ERANGE; + } + } + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "comparing dev %u to %s: no", (unsigned int)rdev, devname); + debug_return_str(NULL); +} + +/* + * Like ttyname() but uses a dev_t instead of an open fd. + * Returns name on success and NULL on failure, setting errno. + * Generic version. + */ +char * +sudo_ttyname_dev_v1(dev_t rdev, char *buf, size_t buflen) +{ + const char *devsearch, *devsearch_end; + char path[PATH_MAX], *ret; + const char *cp, *ep; + size_t len; + debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL) + + /* + * First, check /dev/console. + */ + ret = sudo_dev_check(rdev, _PATH_DEV "console", buf, buflen); + if (ret != NULL) + goto done; + + /* + * Then check the device search path. + */ + devsearch = sudo_conf_devsearch_path(); + devsearch_end = devsearch + strlen(devsearch); + for (cp = sudo_strsplit(devsearch, devsearch_end, ":", &ep); + cp != NULL; cp = sudo_strsplit(NULL, devsearch_end, ":", &ep)) { + + len = (size_t)(ep - cp); + if (len >= sizeof(path)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "devsearch entry %.*s too long", (int)len, cp); + continue; + } + memcpy(path, cp, len); + path[len] = '\0'; + + if (strcmp(path, _PATH_DEV "pts") == 0) { + /* Special case /dev/pts */ + len = (size_t)snprintf(path, sizeof(path), "%spts/%u", + _PATH_DEV, (unsigned int)minor(rdev)); + if (len >= sizeof(path)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "devsearch entry %spts/%u too long", + _PATH_DEV, (unsigned int)minor(rdev)); + continue; + } + ret = sudo_dev_check(rdev, path, buf, buflen); + if (ret != NULL) + goto done; + } else { + /* Scan path, looking for rdev. */ + ret = sudo_ttyname_scan(path, rdev, buf, buflen); + if (ret != NULL || errno == ENOMEM) + goto done; + } + } + +done: + debug_return_str(ret); +} +#endif diff --git a/lib/util/ttysize.c b/lib/util/ttysize.c new file mode 100644 index 0000000..66fa146 --- /dev/null +++ b/lib/util/ttysize.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010-2012, 2014-2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <termios.h> /* for struct winsize on HP-UX */ +#include <limits.h> + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +static int +get_ttysize_ioctl(int *rowp, int *colp) +{ + struct winsize wsize; + debug_decl(get_ttysize_ioctl, SUDO_DEBUG_UTIL) + + if (ioctl(STDERR_FILENO, TIOCGWINSZ, &wsize) == 0 && + wsize.ws_row != 0 && wsize.ws_col != 0) { + *rowp = wsize.ws_row; + *colp = wsize.ws_col; + debug_return_int(0); + } + debug_return_int(-1); +} + +void +sudo_get_ttysize_v1(int *rowp, int *colp) +{ + debug_decl(sudo_get_ttysize, SUDO_DEBUG_UTIL) + + if (get_ttysize_ioctl(rowp, colp) == -1) { + char *p; + + /* Fall back on $LINES and $COLUMNS. */ + if ((p = getenv("LINES")) == NULL || + (*rowp = strtonum(p, 1, INT_MAX, NULL)) <= 0) { + *rowp = 24; + } + if ((p = getenv("COLUMNS")) == NULL || + (*colp = strtonum(p, 1, INT_MAX, NULL)) <= 0) { + *colp = 80; + } + } + + debug_return; +} diff --git a/lib/util/util.exp.in b/lib/util/util.exp.in new file mode 100644 index 0000000..510c34c --- /dev/null +++ b/lib/util/util.exp.in @@ -0,0 +1,119 @@ +@COMPAT_EXP@initprogname +sudo_arc4random_uniform +sudo_conf_askpass_path_v1 +sudo_conf_clear_paths_v1 +sudo_conf_debug_files_v1 +sudo_conf_debugging_v1 +sudo_conf_devsearch_path_v1 +sudo_conf_disable_coredump_v1 +sudo_conf_group_source_v1 +sudo_conf_max_groups_v1 +sudo_conf_noexec_path_v1 +sudo_conf_plugin_dir_path_v1 +sudo_conf_plugins_v1 +sudo_conf_probe_interfaces_v1 +sudo_conf_read_v1 +sudo_conf_sesh_path_v1 +sudo_debug_deregister_v1 +sudo_debug_enter_v1 +sudo_debug_execve2_v1 +sudo_debug_exit_bool_v1 +sudo_debug_exit_id_t_v1 +sudo_debug_exit_int_v1 +sudo_debug_exit_long_v1 +sudo_debug_exit_ptr_v1 +sudo_debug_exit_size_t_v1 +sudo_debug_exit_ssize_t_v1 +sudo_debug_exit_str_masked_v1 +sudo_debug_exit_str_v1 +sudo_debug_exit_time_t_v1 +sudo_debug_exit_v1 +sudo_debug_fork_v1 +sudo_debug_get_active_instance_v1 +sudo_debug_get_fds_v1 +sudo_debug_get_instance_v1 +sudo_debug_printf2_v1 +sudo_debug_register_v1 +sudo_debug_set_active_instance_v1 +sudo_debug_update_fd_v1 +sudo_debug_vprintf2_v1 +sudo_debug_write2_v1 +sudo_digest_alloc_v1 +sudo_digest_final_v1 +sudo_digest_free_v1 +sudo_digest_getlen_v1 +sudo_digest_reset_v1 +sudo_digest_update_v1 +sudo_dso_findsym_v1 +sudo_dso_load_v1 +sudo_dso_preload_table_v1 +sudo_dso_strerror_v1 +sudo_dso_unload_v1 +sudo_ev_add_v1 +sudo_ev_add_v2 +sudo_ev_alloc_v1 +sudo_ev_base_alloc_v1 +sudo_ev_base_free_v1 +sudo_ev_base_setdef_v1 +sudo_ev_del_v1 +sudo_ev_dispatch_v1 +sudo_ev_free_v1 +sudo_ev_get_timeleft_v1 +sudo_ev_get_timeleft_v2 +sudo_ev_got_break_v1 +sudo_ev_got_exit_v1 +sudo_ev_loop_v1 +sudo_ev_loopbreak_v1 +sudo_ev_loopcontinue_v1 +sudo_ev_loopexit_v1 +sudo_fatal_callback_deregister_v1 +sudo_fatal_callback_register_v1 +sudo_fatal_nodebug_v1 +sudo_fatalx_nodebug_v1 +sudo_gai_fatal_nodebug_v1 +sudo_gai_vfatal_nodebug_v1 +sudo_gai_vwarn_nodebug_v1 +sudo_gai_warn_nodebug_v1 +sudo_get_ttysize_v1 +sudo_getgrouplist2_v1 +sudo_gethostname_v1 +sudo_gettime_awake_v1 +sudo_gettime_mono_v1 +sudo_gettime_real_v1 +sudo_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_new_key_val_v1 +sudo_parse_gids_v1 +sudo_parseln_v1 +sudo_parseln_v2 +sudo_secure_dir_v1 +sudo_secure_file_v1 +sudo_setgroups_v1 +sudo_strsplit_v1 +sudo_strtobool_v1 +sudo_strtoid_v1 +sudo_strtomode_v1 +sudo_term_cbreak_v1 +sudo_term_copy_v1 +sudo_term_eof +sudo_term_erase +sudo_term_kill +sudo_term_noecho_v1 +sudo_term_raw_v1 +sudo_term_restore_v1 +sudo_ttyname_dev_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..579391f --- /dev/null +++ b/lib/util/utimens.c @@ -0,0 +1,200 @@ +/* + * 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/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <stdio.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/vsyslog.c b/lib/util/vsyslog.c new file mode 100644 index 0000000..359a8f3 --- /dev/null +++ b/lib/util/vsyslog.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016-2017 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif /* HAVE_STRINGS_H */ +#include <syslog.h> + +#include "sudo_compat.h" + +#ifndef HAVE_VSYSLOG +void +sudo_vsyslog(int pri, const char *fmt, va_list ap) +{ + int saved_errno = errno; + char *cp, *ep, msgbuf[8192], new_fmt[2048]; + va_list ap2; + size_t len; + + /* Rewrite fmt, replacing %m with an errno string. */ + for (cp = new_fmt, ep = new_fmt + sizeof(new_fmt); *fmt != '\0'; fmt++) { + if (fmt[0] == '%' && fmt[1] == 'm') { + fmt++; + len = strlcpy(cp, strerror(saved_errno), (ep - cp)); + if (len >= (size_t)(ep - cp)) + len = (size_t)(ep - cp) - 1; + cp += len; + } else { + if (fmt[0] == '%' && fmt[1] == '%') { + fmt++; + if (cp < ep - 1) + *cp++ = '%'; + } + if (cp < ep - 1) + *cp++ = *fmt; + } + } + *cp = '\0'; + + /* Format message and log it, using a static buffer if possible. */ + va_copy(ap2, ap); + len = (size_t)vsnprintf(msgbuf, sizeof(msgbuf), new_fmt, ap2); + va_end(ap2); + if (len < sizeof(msgbuf)) { + syslog(pri, "%s", msgbuf); + } else { + /* Too big for static buffer? */ + char *buf; + if (vasprintf(&buf, new_fmt, ap) != -1) { + syslog(pri, "%s", buf); + free(buf); + } + } +} +#endif /* HAVE_VSYSLOG */ |