summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 12:52:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 12:52:13 +0000
commitf8e5c55a036f0e2e2a958e30456270f3f9eba933 (patch)
tree4a06ff510774a7a3373e492df4e2984d7b0664b1 /lib
parentInitial commit. (diff)
downloadsudo-f8e5c55a036f0e2e2a958e30456270f3f9eba933.tar.xz
sudo-f8e5c55a036f0e2e2a958e30456270f3f9eba933.zip
Adding upstream version 1.9.5p2.upstream/1.9.5p2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib')
-rw-r--r--lib/eventlog/Makefile.in241
-rw-r--r--lib/eventlog/eventlog.c1463
-rw-r--r--lib/eventlog/logwrap.c90
-rw-r--r--lib/eventlog/regress/logwrap/check_wrap.c108
-rw-r--r--lib/eventlog/regress/logwrap/check_wrap.in4
-rw-r--r--lib/eventlog/regress/logwrap/check_wrap.out.ok179
-rw-r--r--lib/iolog/Makefile.in368
-rw-r--r--lib/iolog/host_port.c102
-rw-r--r--lib/iolog/hostcheck.c389
-rw-r--r--lib/iolog/iolog_fileio.c1061
-rw-r--r--lib/iolog/iolog_json.c789
-rw-r--r--lib/iolog/iolog_json.h50
-rw-r--r--lib/iolog/iolog_path.c130
-rw-r--r--lib/iolog/iolog_util.c431
-rw-r--r--lib/iolog/regress/host_port/host_port_test.c145
-rw-r--r--lib/iolog/regress/iolog_json/check_iolog_json.c265
-rw-r--r--lib/iolog/regress/iolog_json/test1.in34
-rw-r--r--lib/iolog/regress/iolog_json/test2.in28
-rw-r--r--lib/iolog/regress/iolog_json/test2.out.ok34
-rw-r--r--lib/iolog/regress/iolog_json/test3.in22
-rw-r--r--lib/iolog/regress/iolog_mkpath/check_iolog_mkpath.c91
-rw-r--r--lib/iolog/regress/iolog_path/check_iolog_path.c273
-rw-r--r--lib/iolog/regress/iolog_path/data96
-rw-r--r--lib/iolog/regress/iolog_util/check_iolog_util.c148
-rw-r--r--lib/logsrv/Makefile.in187
-rw-r--r--lib/logsrv/log_server.pb-c.c1753
-rw-r--r--lib/logsrv/log_server.proto135
-rw-r--r--lib/logsrv/protobuf-c.c3662
-rw-r--r--lib/util/Makefile.in1399
-rw-r--r--lib/util/aix.c290
-rw-r--r--lib/util/arc4random.c205
-rw-r--r--lib/util/arc4random_buf.c68
-rw-r--r--lib/util/arc4random_uniform.c76
-rw-r--r--lib/util/cfmakeraw.c58
-rw-r--r--lib/util/chacha_private.h222
-rw-r--r--lib/util/closefrom.c143
-rw-r--r--lib/util/digest.c165
-rw-r--r--lib/util/digest_gcrypt.c141
-rw-r--r--lib/util/digest_openssl.c154
-rw-r--r--lib/util/dup3.c74
-rw-r--r--lib/util/event.c862
-rw-r--r--lib/util/event_poll.c227
-rw-r--r--lib/util/event_select.c250
-rw-r--r--lib/util/explicit_bzero.c77
-rw-r--r--lib/util/fatal.c339
-rw-r--r--lib/util/fchmodat.c69
-rw-r--r--lib/util/fnmatch.c499
-rw-r--r--lib/util/freezero.c38
-rw-r--r--lib/util/fstatat.c70
-rw-r--r--lib/util/getaddrinfo.c406
-rw-r--r--lib/util/getcwd.c244
-rw-r--r--lib/util/getdelim.c81
-rw-r--r--lib/util/getentropy.c622
-rw-r--r--lib/util/getgrouplist.c513
-rw-r--r--lib/util/gethostname.c59
-rw-r--r--lib/util/getopt_long.c624
-rw-r--r--lib/util/gettime.c224
-rw-r--r--lib/util/getusershell.c132
-rw-r--r--lib/util/gidlist.c87
-rw-r--r--lib/util/glob.c953
-rw-r--r--lib/util/inet_ntop.c232
-rw-r--r--lib/util/inet_pton.c252
-rw-r--r--lib/util/isblank.c37
-rw-r--r--lib/util/json.c388
-rw-r--r--lib/util/key_val.c56
-rw-r--r--lib/util/lbuf.c323
-rw-r--r--lib/util/locking.c143
-rw-r--r--lib/util/logfac.c90
-rw-r--r--lib/util/logpri.c85
-rw-r--r--lib/util/memrchr.c51
-rw-r--r--lib/util/mkdir_parents.c111
-rw-r--r--lib/util/mksiglist.c57
-rw-r--r--lib/util/mksiglist.h174
-rw-r--r--lib/util/mksigname.c57
-rw-r--r--lib/util/mksigname.h175
-rw-r--r--lib/util/mktemp.c123
-rw-r--r--lib/util/nanosleep.c65
-rw-r--r--lib/util/openat.c63
-rw-r--r--lib/util/parseln.c131
-rw-r--r--lib/util/pipe2.c64
-rw-r--r--lib/util/pread.c48
-rw-r--r--lib/util/progname.c135
-rw-r--r--lib/util/pw_dup.c99
-rw-r--r--lib/util/pwrite.c48
-rw-r--r--lib/util/reallocarray.c57
-rw-r--r--lib/util/regress/fnmatch/fnm_test.c78
-rw-r--r--lib/util/regress/fnmatch/fnm_test.in6
-rw-r--r--lib/util/regress/getdelim/getdelim_test.c144
-rw-r--r--lib/util/regress/getgrouplist/getgrouplist_test.c100
-rw-r--r--lib/util/regress/glob/files47
-rw-r--r--lib/util/regress/glob/globtest.c210
-rw-r--r--lib/util/regress/glob/globtest.in64
-rw-r--r--lib/util/regress/mktemp/mktemp_test.c188
-rw-r--r--lib/util/regress/parse_gids/parse_gids_test.c105
-rw-r--r--lib/util/regress/progname/progname_test.c56
-rw-r--r--lib/util/regress/strsig/strsig_test.c306
-rw-r--r--lib/util/regress/strsplit/strsplit_test.c102
-rw-r--r--lib/util/regress/strtofoo/strtobool_test.c85
-rw-r--r--lib/util/regress/strtofoo/strtoid_test.c105
-rw-r--r--lib/util/regress/strtofoo/strtomode_test.c78
-rw-r--r--lib/util/regress/strtofoo/strtonum_test.c122
-rw-r--r--lib/util/regress/sudo_conf/conf_test.c95
-rw-r--r--lib/util/regress/sudo_conf/test1.in82
-rw-r--r--lib/util/regress/sudo_conf/test1.out.ok8
-rw-r--r--lib/util/regress/sudo_conf/test2.in0
-rw-r--r--lib/util/regress/sudo_conf/test2.out.ok4
-rw-r--r--lib/util/regress/sudo_conf/test3.in2
-rw-r--r--lib/util/regress/sudo_conf/test3.out.ok6
-rw-r--r--lib/util/regress/sudo_conf/test4.err.ok1
-rw-r--r--lib/util/regress/sudo_conf/test4.in1
-rw-r--r--lib/util/regress/sudo_conf/test4.out.ok4
-rw-r--r--lib/util/regress/sudo_conf/test5.err.ok1
-rw-r--r--lib/util/regress/sudo_conf/test5.in1
-rw-r--r--lib/util/regress/sudo_conf/test5.out.ok4
-rw-r--r--lib/util/regress/sudo_conf/test6.in1
-rw-r--r--lib/util/regress/sudo_conf/test6.out.ok4
-rw-r--r--lib/util/regress/sudo_conf/test7.in4
-rw-r--r--lib/util/regress/sudo_conf/test7.out.ok8
-rw-r--r--lib/util/regress/sudo_conf/test8.err.ok1
-rw-r--r--lib/util/regress/sudo_conf/test8.in1
-rw-r--r--lib/util/regress/sudo_conf/test8.out.ok4
-rw-r--r--lib/util/regress/sudo_parseln/parseln_test.c49
-rw-r--r--lib/util/regress/sudo_parseln/test1.in72
-rw-r--r--lib/util/regress/sudo_parseln/test1.out.ok72
-rw-r--r--lib/util/regress/sudo_parseln/test2.in8
-rw-r--r--lib/util/regress/sudo_parseln/test2.out.ok3
-rw-r--r--lib/util/regress/sudo_parseln/test3.in1
-rw-r--r--lib/util/regress/sudo_parseln/test3.out.ok1
-rw-r--r--lib/util/regress/sudo_parseln/test4.in4
-rw-r--r--lib/util/regress/sudo_parseln/test4.out.ok2
-rw-r--r--lib/util/regress/sudo_parseln/test5.in1
-rw-r--r--lib/util/regress/sudo_parseln/test5.out.ok0
-rw-r--r--lib/util/regress/sudo_parseln/test6.in3
-rw-r--r--lib/util/regress/sudo_parseln/test6.out.ok2
-rw-r--r--lib/util/regress/tailq/hltq_test.c190
-rw-r--r--lib/util/regress/vsyslog/vsyslog_test.c130
-rw-r--r--lib/util/roundup.c43
-rw-r--r--lib/util/secure_path.c79
-rw-r--r--lib/util/setgroups.c52
-rw-r--r--lib/util/sha2.c515
-rw-r--r--lib/util/sig2str.c100
-rw-r--r--lib/util/siglist.in56
-rw-r--r--lib/util/snprintf.c1588
-rw-r--r--lib/util/str2sig.c174
-rw-r--r--lib/util/strlcat.c69
-rw-r--r--lib/util/strlcpy.c64
-rw-r--r--lib/util/strndup.c51
-rw-r--r--lib/util/strnlen.c45
-rw-r--r--lib/util/strsignal.c52
-rw-r--r--lib/util/strsplit.c73
-rw-r--r--lib/util/strtobool.c77
-rw-r--r--lib/util/strtoid.c111
-rw-r--r--lib/util/strtomode.c65
-rw-r--r--lib/util/strtonum.c193
-rw-r--r--lib/util/sudo_conf.c758
-rw-r--r--lib/util/sudo_debug.c962
-rw-r--r--lib/util/sudo_dso.c320
-rw-r--r--lib/util/term.c290
-rw-r--r--lib/util/ttyname_dev.c311
-rw-r--r--lib/util/ttysize.c71
-rw-r--r--lib/util/unlinkat.c60
-rw-r--r--lib/util/util.exp.in145
-rw-r--r--lib/util/utimens.c200
-rw-r--r--lib/util/uuid.c111
-rw-r--r--lib/util/vsyslog.c79
-rw-r--r--lib/zlib/Makefile.in220
-rw-r--r--lib/zlib/adler32.c186
-rw-r--r--lib/zlib/compress.c86
-rw-r--r--lib/zlib/crc32.c442
-rw-r--r--lib/zlib/crc32.h441
-rw-r--r--lib/zlib/deflate.c2165
-rw-r--r--lib/zlib/deflate.h349
-rw-r--r--lib/zlib/gzclose.c25
-rw-r--r--lib/zlib/gzguts.h218
-rw-r--r--lib/zlib/gzlib.c637
-rw-r--r--lib/zlib/gzread.c654
-rw-r--r--lib/zlib/gzwrite.c665
-rw-r--r--lib/zlib/infback.c641
-rw-r--r--lib/zlib/inffast.c323
-rw-r--r--lib/zlib/inffast.h11
-rw-r--r--lib/zlib/inffixed.h94
-rw-r--r--lib/zlib/inflate.c1583
-rw-r--r--lib/zlib/inflate.h125
-rw-r--r--lib/zlib/inftrees.c304
-rw-r--r--lib/zlib/inftrees.h62
-rw-r--r--lib/zlib/trees.c1203
-rw-r--r--lib/zlib/trees.h128
-rw-r--r--lib/zlib/uncompr.c93
-rw-r--r--lib/zlib/zconf.h.in563
-rw-r--r--lib/zlib/zlib.exp85
-rw-r--r--lib/zlib/zlib.h1912
-rw-r--r--lib/zlib/zutil.c325
-rw-r--r--lib/zlib/zutil.h271
193 files changed, 47804 insertions, 0 deletions
diff --git a/lib/eventlog/Makefile.in b/lib/eventlog/Makefile.in
new file mode 100644
index 0000000..134a5d2
--- /dev/null
+++ b/lib/eventlog/Makefile.in
@@ -0,0 +1,241 @@
+#
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+# @configure_input@
+#
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+abs_srcdir = @abs_srcdir@
+top_srcdir = @top_srcdir@
+abs_top_srcdir = @abs_top_srcdir@
+top_builddir = @top_builddir@
+abs_top_builddir = @abs_top_builddir@
+devdir = @devdir@
+scriptdir = $(top_srcdir)/scripts
+incdir = $(top_srcdir)/include
+
+# Compiler & tools to use
+CC = @CC@
+LIBTOOL = @LIBTOOL@
+
+# C preprocessor flags
+CPPFLAGS = -I$(incdir) -I$(top_builddir) -I$(srcdir) -I$(top_srcdir) @CPPFLAGS@
+
+# Usually -O and/or -g
+CFLAGS = @CFLAGS@
+
+# Flags to pass to the link stage
+LDFLAGS = @LDFLAGS@
+
+# Flags to pass to libtool
+LTFLAGS = @LT_STATIC@
+
+# Libraries
+LT_LIBS = $(top_builddir)/lib/util/libsudo_util.la
+LIBS = $(LT_LIBS)
+
+# Address sanitizer flags
+ASAN_CFLAGS = @ASAN_CFLAGS@
+ASAN_LDFLAGS = @ASAN_LDFLAGS@
+
+# PIE flags
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+
+# Stack smashing protection flags
+SSP_CFLAGS = @SSP_CFLAGS@
+SSP_LDFLAGS = @SSP_LDFLAGS@
+
+# cppcheck options, usually set in the top-level Makefile
+CPPCHECK_OPTS = -q --enable=warning,performance,portability --suppress=constStatement --suppress=compareBoolExpressionWithInt --error-exitcode=1 --inline-suppr -Dva_copy=va_copy -U__cplusplus -UQUAD_MAX -UQUAD_MIN -UUQUAD_MAX -U_POSIX_HOST_NAME_MAX -U_POSIX_PATH_MAX -U__NBBY -DNSIG=64
+
+# splint options, usually set in the top-level Makefile
+SPLINT_OPTS = -D__restrict= -checks
+
+# PVS-studio options
+PVS_CFG = $(top_srcdir)/PVS-Studio.cfg
+PVS_IGNORE = 'V707,V011,V002,V536'
+PVS_LOG_OPTS = -a 'GA:1,2' -e -t errorfile -d $(PVS_IGNORE)
+
+# Set to non-empty for development mode
+DEVEL = @DEVEL@
+
+#### End of system configuration section. ####
+
+SHELL = @SHELL@
+
+TEST_PROGS = check_wrap
+
+LIBEVENTLOG_OBJS = eventlog.lo logwrap.lo
+
+IOBJS = $(LIBEVENTLOG_OBJS:.lo=.i)
+
+POBJS = $(IOBJS:.i=.plog)
+
+GENERATED = log_server.pb-c.h log_server.pb-c.c
+
+CHECK_WRAP_OBJS = check_wrap.lo logwrap.lo
+
+all: libsudo_eventlog.la
+
+pvs-log-files: $(POBJS)
+
+pvs-studio: $(POBJS)
+ plog-converter $(PVS_LOG_OPTS) $(POBJS)
+
+depend:
+ $(scriptdir)/mkdep.pl --srcdir=$(abs_top_srcdir) \
+ --builddir=$(abs_top_builddir) lib/eventlog/Makefile.in
+ cd $(top_builddir) && ./config.status --file lib/eventlog/Makefile
+
+Makefile: $(srcdir)/Makefile.in
+ cd $(top_builddir) && ./config.status --file lib/eventlog/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 $@
+
+$(devdir)/log_server.pb-c.c: $(srcdir)/log_server.proto
+ @if [ -n "$(DEVEL)" ]; then \
+ cmd='protoc-c --c_out=$(devdir) --proto_path=$(srcdir) $(srcdir)/log_server.proto'; \
+ echo "$$cmd"; eval $$cmd; \
+ cmd='$(scriptdir)/unanon $(devdir)/log_server.pb-c.h $(devdir)/log_server.pb-c.c'; \
+ echo "$$cmd"; eval $$cmd; \
+ if [ "$(devdir)" == "$(srcdir)" ]; then \
+ cmd='mv -f $(devdir)/log_server.pb-c.h $(incdir)/log_server.pb-c.h'; \
+ else \
+ cmd='mv -f $(devdir)/log_server.pb-c.h $(top_builddir)/log_server.pb-c.h'; \
+ fi; \
+ echo "$$cmd"; eval $$cmd; \
+ fi
+
+libsudo_eventlog.la: $(LIBEVENTLOG_OBJS)
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(LIBEVENTLOG_OBJS) $(LT_LIBS)
+
+check_wrap: $(CHECK_WRAP_OBJS) $(LIBUTIL)
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_WRAP_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS)
+
+pre-install:
+
+install:
+
+install-binaries:
+
+install-includes:
+
+install-doc:
+
+install-plugin:
+
+uninstall:
+
+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)
+
+check: $(TEST_PROGS)
+ @if test X"$(cross_compiling)" != X"yes"; then \
+ LC_ALL=C; export LC_ALL; \
+ unset LANG || LANG=; \
+ MALLOC_OPTIONS=S; export MALLOC_OPTIONS; \
+ MALLOC_CONF="abort:true,junk:true"; export MALLOC_CONF; \
+ umask 022; \
+ rval=0; \
+ mkdir -p regress/logwrap; \
+ ./check_wrap $(srcdir)/regress/logwrap/check_wrap.in > regress/logwrap/check_wrap.out; \
+ diff regress/logwrap/check_wrap.out $(srcdir)/regress/logwrap/check_wrap.out.ok || rval=`expr $$rval + $$?`; \
+ exit $$rval; \
+ fi
+
+clean:
+ -$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f *.lo *.o *.la
+ -rm -f *.i *.plog stamp-* core *.core core.*
+
+mostlyclean: clean
+
+distclean: clean
+ -rm -rf Makefile .libs
+ @if [ -n "$(DEVEL)" -a "$(devdir)" != "$(srcdir)" ]; then \
+ cmd='rm -rf $(GENERATED)'; \
+ echo "$$cmd"; eval $$cmd; \
+ fi
+
+clobber: distclean
+
+realclean: distclean
+ rm -f TAGS tags
+
+cleandir: realclean
+
+# Autogenerated dependencies, do not modify
+check_wrap.lo: $(srcdir)/regress/logwrap/check_wrap.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/logwrap/check_wrap.c
+check_wrap.i: $(srcdir)/regress/logwrap/check_wrap.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+check_wrap.plog: check_wrap.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/logwrap/check_wrap.c --i-file $< --output-file $@
+eventlog.lo: $(srcdir)/eventlog.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_json.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/eventlog.c
+eventlog.i: $(srcdir)/eventlog.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_json.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+eventlog.plog: eventlog.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/eventlog.c --i-file $< --output-file $@
+logwrap.lo: $(srcdir)/logwrap.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_eventlog.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)/logwrap.c
+logwrap.i: $(srcdir)/logwrap.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_eventlog.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+logwrap.plog: logwrap.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/logwrap.c --i-file $< --output-file $@
diff --git a/lib/eventlog/eventlog.c b/lib/eventlog/eventlog.c
new file mode 100644
index 0000000..ae16e2e
--- /dev/null
+++ b/lib/eventlog/eventlog.c
@@ -0,0 +1,1463 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1994-1996, 1998-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <locale.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "pathnames.h"
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_eventlog.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_json.h"
+#include "sudo_queue.h"
+#include "sudo_util.h"
+
+#define LL_HOST_STR "HOST="
+#define LL_TTY_STR "TTY="
+#define LL_CHROOT_STR "CHROOT="
+#define LL_CWD_STR "PWD="
+#define LL_USER_STR "USER="
+#define LL_GROUP_STR "GROUP="
+#define LL_ENV_STR "ENV="
+#define LL_CMND_STR "COMMAND="
+#define LL_TSID_STR "TSID="
+
+#define IS_SESSID(s) ( \
+ isalnum((unsigned char)(s)[0]) && isalnum((unsigned char)(s)[1]) && \
+ (s)[2] == '/' && \
+ isalnum((unsigned char)(s)[3]) && isalnum((unsigned char)(s)[4]) && \
+ (s)[5] == '/' && \
+ isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \
+ (s)[8] == '\0')
+
+static FILE *eventlog_stub_open_log(int type, const char *logfile);
+static void eventlog_stub_close_log(int type, FILE *fp);
+
+/* Eventlog config settings (default values). */
+static struct eventlog_config evl_conf = {
+ EVLOG_NONE, /* type */
+ EVLOG_SUDO, /* format */
+ LOG_NOTICE, /* syslog_acceptpri */
+ LOG_ALERT, /* syslog_rejectpri */
+ LOG_ALERT, /* syslog_alertpri */
+ MAXSYSLOGLEN, /* syslog_maxlen */
+ 0, /* file_maxlen */
+ ROOT_UID, /* mailuid */
+ false, /* omit_hostname */
+ _PATH_SUDO_LOGFILE, /* logpath */
+ "%h %e %T", /* time_fmt */
+#ifdef _PATH_SUDO_SENDMAIL
+ _PATH_SUDO_SENDMAIL, /* mailerpath */
+#else
+ NULL, /* mailerpath (disabled) */
+#endif
+ "-t", /* mailerflags */
+ NULL, /* mailfrom */
+ MAILTO, /* mailto */
+ N_(MAILSUBJECT), /* mailsub */
+ eventlog_stub_open_log, /* open_log */
+ eventlog_stub_close_log /* close_log */
+};
+
+/*
+ * Allocate and fill in a new logline.
+ */
+static char *
+new_logline(int flags, const char *message, const char *errstr,
+ const struct eventlog *evlog)
+{
+ char *line = NULL, *evstr = NULL;
+ const char *iolog_file = evlog->iolog_file;
+ const char *tty, *tsid = NULL;
+ char sessid[7];
+ size_t len = 0;
+ int i;
+ debug_decl(new_logline, SUDO_DEBUG_UTIL);
+
+ if (ISSET(flags, EVLOG_RAW)) {
+ if (errstr != NULL) {
+ if (asprintf(&line, "%s: %s", message, errstr) == -1)
+ goto oom;
+ } else {
+ if ((line = strdup(message)) == NULL)
+ goto oom;
+ }
+ debug_return_str(line);
+ }
+
+ /* A TSID may be a sudoers-style session ID or a free-form string. */
+ if (iolog_file != NULL) {
+ if (IS_SESSID(iolog_file)) {
+ sessid[0] = iolog_file[0];
+ sessid[1] = iolog_file[1];
+ sessid[2] = iolog_file[3];
+ sessid[3] = iolog_file[4];
+ sessid[4] = iolog_file[6];
+ sessid[5] = iolog_file[7];
+ sessid[6] = '\0';
+ tsid = sessid;
+ } else {
+ tsid = iolog_file;
+ }
+ }
+
+ /* Sudo-format logs use the short form of the ttyname. */
+ if ((tty = evlog->ttyname) != NULL) {
+ if (strncmp(tty, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ tty += sizeof(_PATH_DEV) - 1;
+ }
+
+ /*
+ * Compute line length
+ */
+ if (message != NULL)
+ len += strlen(message) + 3;
+ if (errstr != NULL)
+ len += strlen(errstr) + 3;
+ if (evlog->submithost != NULL && !evl_conf.omit_hostname)
+ len += sizeof(LL_HOST_STR) + 2 + strlen(evlog->submithost);
+ if (tty != NULL)
+ len += sizeof(LL_TTY_STR) + 2 + strlen(tty);
+ if (evlog->runchroot != NULL)
+ len += sizeof(LL_CHROOT_STR) + 2 + strlen(evlog->runchroot);
+ if (evlog->runcwd != NULL)
+ len += sizeof(LL_CWD_STR) + 2 + strlen(evlog->runcwd);
+ if (evlog->runuser != NULL)
+ len += sizeof(LL_USER_STR) + 2 + strlen(evlog->runuser);
+ if (evlog->rungroup != NULL)
+ len += sizeof(LL_GROUP_STR) + 2 + strlen(evlog->rungroup);
+ if (tsid != NULL)
+ len += sizeof(LL_TSID_STR) + 2 + strlen(tsid);
+ if (evlog->env_add != NULL) {
+ size_t evlen = 0;
+ char * const *ep;
+
+ for (ep = evlog->env_add; *ep != NULL; ep++)
+ evlen += strlen(*ep) + 1;
+ if (evlen != 0) {
+ if ((evstr = malloc(evlen)) == NULL)
+ goto oom;
+ ep = evlog->env_add;
+ if (strlcpy(evstr, *ep, evlen) >= evlen)
+ goto toobig;
+ while (*++ep != NULL) {
+ if (strlcat(evstr, " ", evlen) >= evlen ||
+ strlcat(evstr, *ep, evlen) >= evlen)
+ goto toobig;
+ }
+ len += sizeof(LL_ENV_STR) + 2 + evlen;
+ }
+ }
+ if (evlog->command != NULL) {
+ len += sizeof(LL_CMND_STR) - 1 + strlen(evlog->command);
+ if (evlog->argv != NULL) {
+ for (i = 1; evlog->argv[i] != NULL; i++)
+ len += strlen(evlog->argv[i]) + 1;
+ }
+ }
+
+ /*
+ * Allocate and build up the line.
+ */
+ if ((line = malloc(++len)) == NULL)
+ goto oom;
+ line[0] = '\0';
+
+ if (message != NULL) {
+ if (strlcat(line, message, len) >= len ||
+ strlcat(line, errstr ? " : " : " ; ", len) >= len)
+ goto toobig;
+ }
+ if (errstr != NULL) {
+ if (strlcat(line, errstr, len) >= len ||
+ strlcat(line, " ; ", len) >= len)
+ goto toobig;
+ }
+ if (evlog->submithost != NULL && !evl_conf.omit_hostname) {
+ if (strlcat(line, LL_HOST_STR, len) >= len ||
+ strlcat(line, evlog->submithost, len) >= len ||
+ strlcat(line, " ; ", len) >= len)
+ goto toobig;
+ }
+ if (tty != NULL) {
+ if (strlcat(line, LL_TTY_STR, len) >= len ||
+ strlcat(line, tty, len) >= len ||
+ strlcat(line, " ; ", len) >= len)
+ goto toobig;
+ }
+ if (evlog->runchroot != NULL) {
+ if (strlcat(line, LL_CHROOT_STR, len) >= len ||
+ strlcat(line, evlog->runchroot, len) >= len ||
+ strlcat(line, " ; ", len) >= len)
+ goto toobig;
+ }
+ if (evlog->runcwd != NULL) {
+ if (strlcat(line, LL_CWD_STR, len) >= len ||
+ strlcat(line, evlog->runcwd, len) >= len ||
+ strlcat(line, " ; ", len) >= len)
+ goto toobig;
+ }
+ if (evlog->runuser != NULL) {
+ if (strlcat(line, LL_USER_STR, len) >= len ||
+ strlcat(line, evlog->runuser, len) >= len ||
+ strlcat(line, " ; ", len) >= len)
+ goto toobig;
+ }
+ if (evlog->rungroup != NULL) {
+ if (strlcat(line, LL_GROUP_STR, len) >= len ||
+ strlcat(line, evlog->rungroup, len) >= len ||
+ strlcat(line, " ; ", len) >= len)
+ goto toobig;
+ }
+ if (tsid != NULL) {
+ if (strlcat(line, LL_TSID_STR, len) >= len ||
+ strlcat(line, tsid, len) >= len ||
+ strlcat(line, " ; ", len) >= len)
+ goto toobig;
+ }
+ if (evstr != NULL) {
+ if (strlcat(line, LL_ENV_STR, len) >= len ||
+ strlcat(line, evstr, len) >= len ||
+ strlcat(line, " ; ", len) >= len)
+ goto toobig;
+ free(evstr);
+ evstr = NULL;
+ }
+ if (evlog->command != NULL) {
+ if (strlcat(line, LL_CMND_STR, len) >= len)
+ goto toobig;
+ if (strlcat(line, evlog->command, len) >= len)
+ goto toobig;
+ if (evlog->argv != NULL) {
+ for (i = 1; evlog->argv[i] != NULL; i++) {
+ if (strlcat(line, " ", len) >= len ||
+ strlcat(line, evlog->argv[i], len) >= len)
+ goto toobig;
+ }
+ }
+ }
+
+ debug_return_str(line);
+oom:
+ free(evstr);
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_str(NULL);
+toobig:
+ free(evstr);
+ free(line);
+ sudo_warnx(U_("internal error, %s overflow"), __func__);
+ debug_return_str(NULL);
+}
+
+static void
+closefrom_nodebug(int lowfd)
+{
+ unsigned char *debug_fds;
+ int fd, startfd;
+ debug_decl(closefrom_nodebug, SUDO_DEBUG_UTIL);
+
+ startfd = sudo_debug_get_fds(&debug_fds) + 1;
+ if (lowfd > startfd)
+ startfd = lowfd;
+
+ /* Close fds higher than the debug fds. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "closing fds >= %d", startfd);
+ closefrom(startfd);
+
+ /* Close fds [lowfd, startfd) that are not in debug_fds. */
+ for (fd = lowfd; fd < startfd; fd++) {
+ if (sudo_isset(debug_fds, fd))
+ continue;
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "closing fd %d", fd);
+#ifdef __APPLE__
+ /* Avoid potential libdispatch crash when we close its fds. */
+ (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
+#else
+ (void) close(fd);
+#endif
+ }
+ debug_return;
+}
+
+#define MAX_MAILFLAGS 63
+
+static void __attribute__((__noreturn__))
+exec_mailer(int pipein)
+{
+ char *last, *mflags, *p, *argv[MAX_MAILFLAGS + 1];
+ const char *mpath = evl_conf.mailerpath;
+ int i;
+ char * const root_envp[] = {
+ "HOME=/",
+ "PATH=/usr/bin:/bin:/usr/sbin:/sbin",
+ "LOGNAME=root",
+ "USER=root",
+# ifdef _AIX
+ "LOGIN=root",
+# endif
+ NULL
+ };
+ debug_decl(exec_mailer, SUDO_DEBUG_UTIL);
+
+ /* Set stdin to read side of the pipe. */
+ if (dup3(pipein, STDIN_FILENO, 0) == -1) {
+ syslog(LOG_ERR, _("unable to dup stdin: %m")); // -V618
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "unable to dup stdin: %s", strerror(errno));
+ sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
+ _exit(127);
+ }
+
+ /* Build up an argv based on the mailer path and flags */
+ if ((mflags = strdup(evl_conf.mailerflags)) == NULL) {
+ syslog(LOG_ERR, _("unable to allocate memory")); // -V618
+ sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
+ _exit(127);
+ }
+ if ((argv[0] = strrchr(mpath, '/')))
+ argv[0]++;
+ else
+ argv[0] = (char *)mpath;
+
+ i = 1;
+ if ((p = strtok_r(mflags, " \t", &last))) {
+ do {
+ argv[i] = p;
+ } while (++i < MAX_MAILFLAGS && (p = strtok_r(NULL, " \t", &last)));
+ }
+ argv[i] = NULL;
+
+ /*
+ * Depending on the config, either run the mailer as root
+ * (so user cannot kill it) or as the user (for the paranoid).
+ */
+ if (setuid(ROOT_UID) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to change uid to %u",
+ ROOT_UID);
+ }
+ if (evl_conf.mailuid != ROOT_UID) {
+ if (setuid(evl_conf.mailuid) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to change uid to %u",
+ (unsigned int)evl_conf.mailuid);
+ }
+ }
+ sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
+ if (evl_conf.mailuid == ROOT_UID)
+ execve(mpath, argv, root_envp);
+ else
+ execv(mpath, argv);
+ syslog(LOG_ERR, _("unable to execute %s: %m"), mpath); // -V618
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to execute %s: %s",
+ mpath, strerror(errno));
+ _exit(127);
+}
+
+/* Send a message to MAILTO user */
+static bool
+send_mail(const struct eventlog *evlog, const char *fmt, ...)
+{
+ const char *cp, *timefmt = evl_conf.time_fmt;
+ char timebuf[1024];
+ struct tm *tm;
+ time_t now;
+ FILE *mail;
+ int fd, pfd[2], status;
+ pid_t pid, rv;
+ struct stat sb;
+ va_list ap;
+#if defined(HAVE_NL_LANGINFO) && defined(CODESET)
+ char *locale;
+#endif
+ debug_decl(send_mail, SUDO_DEBUG_UTIL);
+
+ /* If mailer is disabled just return. */
+ if (evl_conf.mailerpath == NULL || evl_conf.mailto == NULL)
+ debug_return_bool(true);
+
+ /* Make sure the mailer exists and is a regular file. */
+ if (stat(evl_conf.mailerpath, &sb) != 0 || !S_ISREG(sb.st_mode))
+ debug_return_bool(false);
+
+ time(&now);
+ if ((tm = gmtime(&now)) == NULL)
+ debug_return_bool(false);
+
+ /* Fork and return, child will daemonize. */
+ switch (pid = sudo_debug_fork()) {
+ case -1:
+ /* Error. */
+ sudo_warn("%s", U_("unable to fork"));
+ debug_return_bool(false);
+ break;
+ case 0:
+ /* Child. */
+ switch (fork()) {
+ case -1:
+ /* Error. */
+ syslog(LOG_ERR, _("unable to fork: %m")); // -V618
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to fork: %s",
+ strerror(errno));
+ sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
+ _exit(EXIT_FAILURE);
+ case 0:
+ /* Grandchild continues below. */
+ sudo_debug_enter(__func__, __FILE__, __LINE__, sudo_debug_subsys);
+ break;
+ default:
+ /* Parent will wait for us. */
+ _exit(EXIT_SUCCESS);
+ }
+ break;
+ default:
+ /* Parent. */
+ for (;;) {
+ rv = waitpid(pid, &status, 0);
+ if (rv == -1 && errno != EINTR)
+ break;
+ if (rv != -1 && !WIFSTOPPED(status))
+ break;
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "child (%d) exit value %d", (int)rv, status);
+ debug_return_bool(true);
+ }
+
+ /* Daemonize - disassociate from session/tty. */
+ if (setsid() == -1)
+ sudo_warn("setsid");
+ if (chdir("/") == -1)
+ sudo_warn("chdir(/)");
+ fd = open(_PATH_DEVNULL, O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (fd != -1) {
+ (void) dup2(fd, STDIN_FILENO);
+ (void) dup2(fd, STDOUT_FILENO);
+ (void) dup2(fd, STDERR_FILENO);
+ }
+
+ /* Close non-debug fds so we don't leak anything. */
+ closefrom_nodebug(STDERR_FILENO + 1);
+
+ if (pipe2(pfd, O_CLOEXEC) == -1) {
+ syslog(LOG_ERR, _("unable to open pipe: %m")); // -V618
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to open pipe: %s",
+ strerror(errno));
+ sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
+ _exit(EXIT_FAILURE);
+ }
+
+ switch (pid = sudo_debug_fork()) {
+ case -1:
+ /* Error. */
+ syslog(LOG_ERR, _("unable to fork: %m")); // -V618
+ sudo_debug_printf(
+ SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to fork");
+ sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
+ _exit(EXIT_FAILURE);
+ break;
+ case 0:
+ /* Child. */
+ exec_mailer(pfd[0]);
+ /* NOTREACHED */
+ }
+
+ (void) close(pfd[0]);
+ if ((mail = fdopen(pfd[1], "w")) == NULL) {
+ syslog(LOG_ERR, "fdopen: %m");
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to fdopen pipe");
+ sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
+ _exit(EXIT_FAILURE);
+ }
+
+ /* Pipes are all setup, send message. */
+ (void) fprintf(mail, "To: %s\nFrom: %s\nAuto-Submitted: %s\nSubject: ",
+ evl_conf.mailto,
+ evl_conf.mailfrom ? evl_conf.mailfrom :
+ (evlog ? evlog->submituser : "root"),
+ "auto-generated");
+ for (cp = _(evl_conf.mailsub); *cp; cp++) {
+ /* Expand escapes in the subject */
+ if (*cp == '%' && *(cp+1) != '%') {
+ switch (*(++cp)) {
+ case 'h':
+ if (evlog != NULL)
+ (void) fputs(evlog->submithost, mail);
+ break;
+ case 'u':
+ if (evlog != NULL)
+ (void) fputs(evlog->submituser, mail);
+ break;
+ default:
+ cp--;
+ break;
+ }
+ } else
+ (void) fputc(*cp, mail);
+ }
+
+#if defined(HAVE_NL_LANGINFO) && defined(CODESET)
+ locale = setlocale(LC_ALL, NULL);
+ if (locale[0] != 'C' || locale[1] != '\0')
+ (void) fprintf(mail, "\nContent-Type: text/plain; charset=\"%s\"\nContent-Transfer-Encoding: 8bit", nl_langinfo(CODESET));
+#endif /* HAVE_NL_LANGINFO && CODESET */
+
+ strftime(timebuf, sizeof(timebuf), timefmt, tm);
+ if (evlog != NULL) {
+ (void) fprintf(mail, "\n\n%s : %s : %s : ", evlog->submithost, timebuf,
+ evlog->submituser);
+ } else {
+ (void) fprintf(mail, "\n\n%s : ", timebuf);
+ }
+ va_start(ap, fmt);
+ (void) vfprintf(mail, fmt, ap);
+ va_end(ap);
+ fputs("\n\n", mail);
+
+ fclose(mail);
+ for (;;) {
+ rv = waitpid(pid, &status, 0);
+ if (rv == -1 && errno != EINTR)
+ break;
+ if (rv != -1 && !WIFSTOPPED(status))
+ break;
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "child (%d) exit value %d", (int)rv, status);
+ sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
+ _exit(EXIT_SUCCESS);
+}
+
+static bool
+json_add_timestamp(struct json_container *json, const char *name,
+ const struct timespec *ts)
+{
+ const char *timefmt = evl_conf.time_fmt;
+ struct json_value json_value;
+ time_t secs = ts->tv_sec;
+ char timebuf[1024];
+ struct tm *tm;
+ debug_decl(json_add_timestamp, SUDO_DEBUG_PLUGIN);
+
+ if ((tm = gmtime(&secs)) == NULL)
+ debug_return_bool(false);
+
+ if (!sudo_json_open_object(json, name))
+ goto oom;
+
+ json_value.type = JSON_NUMBER;
+ json_value.u.number = ts->tv_sec;
+ if (!sudo_json_add_value(json, "seconds", &json_value))
+ goto oom;
+
+ json_value.type = JSON_NUMBER;
+ json_value.u.number = ts->tv_nsec;
+ if (!sudo_json_add_value(json, "nanoseconds", &json_value))
+ goto oom;
+
+ strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", tm);
+ json_value.type = JSON_STRING;
+ json_value.u.string = timebuf;
+ if (!sudo_json_add_value(json, "iso8601", &json_value))
+ goto oom;
+
+ strftime(timebuf, sizeof(timebuf), timefmt, tm);
+ json_value.type = JSON_STRING;
+ json_value.u.string = timebuf;
+ if (!sudo_json_add_value(json, "localtime", &json_value))
+ goto oom;
+
+ if (!sudo_json_close_object(json))
+ goto oom;
+
+ debug_return_bool(true);
+oom:
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "%s: %s", __func__, "unable to allocate memory");
+ debug_return_bool(false);
+}
+
+/*
+ * Store the contents of struct eventlog as JSON.
+ * The submit_time and iolog_path members are not stored, they should
+ * be stored and formatted by the caller.
+ */
+bool
+eventlog_store_json(struct json_container *json, const struct eventlog *evlog)
+{
+ struct json_value json_value;
+ size_t i;
+ char *cp;
+ debug_decl(eventlog_store_json, SUDO_DEBUG_UTIL);
+
+ /* Required settings. */
+ if (evlog->command == NULL || evlog->submituser == NULL ||
+ evlog->runuser == NULL)
+ debug_return_bool(false);
+
+ /*
+ * The most important values are written first in case
+ * the log record gets truncated.
+ * Note: submit_time and iolog_path are not stored here.
+ */
+
+ json_value.type = JSON_STRING;
+ json_value.u.string = evlog->submituser;
+ if (!sudo_json_add_value(json, "submituser", &json_value))
+ goto oom;
+
+ json_value.type = JSON_STRING;
+ json_value.u.string = evlog->command;
+ if (!sudo_json_add_value(json, "command", &json_value))
+ goto oom;
+
+ json_value.type = JSON_STRING;
+ json_value.u.string = evlog->runuser;
+ if (!sudo_json_add_value(json, "runuser", &json_value))
+ goto oom;
+
+ if (evlog->rungroup != NULL) {
+ json_value.type = JSON_STRING;
+ json_value.u.string = evlog->rungroup;
+ if (!sudo_json_add_value(json, "rungroup", &json_value))
+ goto oom;
+ }
+
+ if (evlog->runchroot != NULL) {
+ json_value.type = JSON_STRING;
+ json_value.u.string = evlog->runchroot;
+ if (!sudo_json_add_value(json, "runchroot", &json_value))
+ goto oom;
+ }
+
+ if (evlog->runcwd != NULL) {
+ json_value.type = JSON_STRING;
+ json_value.u.string = evlog->runcwd;
+ if (!sudo_json_add_value(json, "runcwd", &json_value))
+ goto oom;
+ }
+
+ if (evlog->ttyname != NULL) {
+ json_value.type = JSON_STRING;
+ json_value.u.string = evlog->ttyname;
+ if (!sudo_json_add_value(json, "ttyname", &json_value))
+ goto oom;
+ }
+
+ if (evlog->submithost != NULL) {
+ json_value.type = JSON_STRING;
+ json_value.u.string = evlog->submithost;
+ if (!sudo_json_add_value(json, "submithost", &json_value))
+ goto oom;
+ }
+
+ if (evlog->cwd != NULL) {
+ json_value.type = JSON_STRING;
+ json_value.u.string = evlog->cwd;
+ if (!sudo_json_add_value(json, "submitcwd", &json_value))
+ goto oom;
+ }
+
+ if (evlog->rungroup!= NULL && evlog->rungid != (gid_t)-1) {
+ json_value.type = JSON_ID;
+ json_value.u.id = evlog->rungid;
+ if (!sudo_json_add_value(json, "rungid", &json_value))
+ goto oom;
+ }
+
+ if (evlog->runuid != (uid_t)-1) {
+ json_value.type = JSON_ID;
+ json_value.u.id = evlog->runuid;
+ if (!sudo_json_add_value(json, "runuid", &json_value))
+ goto oom;
+ }
+
+ json_value.type = JSON_NUMBER;
+ json_value.u.number = evlog->columns;
+ if (!sudo_json_add_value(json, "columns", &json_value))
+ goto oom;
+
+ json_value.type = JSON_NUMBER;
+ json_value.u.number = evlog->lines;
+ if (!sudo_json_add_value(json, "lines", &json_value))
+ goto oom;
+
+ if (evlog->argv != NULL) {
+ if (!sudo_json_open_array(json, "runargv"))
+ goto oom;
+ for (i = 0; (cp = evlog->argv[i]) != NULL; i++) {
+ json_value.type = JSON_STRING;
+ json_value.u.string = cp;
+ if (!sudo_json_add_value(json, NULL, &json_value))
+ goto oom;
+ }
+ if (!sudo_json_close_array(json))
+ goto oom;
+ }
+
+ if (evlog->envp != NULL) {
+ if (!sudo_json_open_array(json, "runenv"))
+ goto oom;
+ for (i = 0; (cp = evlog->envp[i]) != NULL; i++) {
+ json_value.type = JSON_STRING;
+ json_value.u.string = cp;
+ if (!sudo_json_add_value(json, NULL, &json_value))
+ goto oom;
+ }
+ if (!sudo_json_close_array(json))
+ goto oom;
+ }
+
+ debug_return_bool(true);
+
+oom:
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_bool(false);
+}
+
+static bool
+default_json_cb(struct json_container *json, void *v)
+{
+ return eventlog_store_json(json, v);
+}
+
+static char *
+format_json(int event_type, const char *reason, const char *errstr,
+ const struct eventlog *evlog, const struct timespec *event_time,
+ eventlog_json_callback_t info_cb, void *info, bool compact)
+{
+ const char *type_str;
+ const char *time_str;
+ struct json_container json = { 0 };
+ struct json_value json_value;
+ struct timespec now;
+ debug_decl(format_json, SUDO_DEBUG_UTIL);
+
+ if (info_cb == NULL) {
+ info_cb = default_json_cb;
+ info = (void *)evlog;
+ }
+
+ if (sudo_gettime_real(&now) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to read the clock");
+ debug_return_str(NULL);
+ }
+
+ switch (event_type) {
+ case EVLOG_ACCEPT:
+ type_str = "accept";
+ time_str = "submit_time";
+ break;
+ case EVLOG_REJECT:
+ type_str = "reject";
+ time_str = "submit_time";
+ break;
+ case EVLOG_ALERT:
+ type_str = "alert";
+ time_str = "alert_time";
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unexpected event type %d", event_type);
+ debug_return_str(NULL);
+ }
+
+ if (!sudo_json_init(&json, 4, compact, false))
+ goto bad;
+ if (!sudo_json_open_object(&json, type_str))
+ goto bad;
+
+ /* Reject and Alert events include a reason and optional error string. */
+ if (reason != NULL) {
+ char *ereason = NULL;
+
+ if (errstr != NULL) {
+ if (asprintf(&ereason, _("%s: %s"), reason, errstr) == -1) {
+ sudo_warnx(U_("%s: %s"), __func__,
+ U_("unable to allocate memory"));
+ goto bad;
+ }
+ }
+ json_value.type = JSON_STRING;
+ json_value.u.string = ereason ? ereason : reason;
+ if (!sudo_json_add_value(&json, "reason", &json_value)) {
+ free(ereason);
+ goto bad;
+ }
+ free(ereason);
+ }
+
+ /* XXX - create and log uuid? */
+
+ /* Log event time on server (set earlier) */
+ if (!json_add_timestamp(&json, "server_time", &now)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "unable format timestamp");
+ goto bad;
+ }
+
+ /* Log event time from client */
+ if (!json_add_timestamp(&json, time_str, event_time)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "unable format timestamp");
+ goto bad;
+ }
+
+ /* Event log info may be missing for alert messages. */
+ if (evlog != NULL) {
+ if (evlog->iolog_path != NULL) {
+ json_value.type = JSON_STRING;
+ json_value.u.string = evlog->iolog_path;
+ if (!sudo_json_add_value(&json, "iolog_path", &json_value))
+ goto bad;
+ }
+
+ /* Write log info. */
+ if (!info_cb(&json, info))
+ goto bad;
+ }
+
+ if (!sudo_json_close_object(&json))
+ goto bad;
+
+ /* Caller is responsible for freeing the buffer. */
+ debug_return_str(sudo_json_get_buf(&json));
+
+bad:
+ sudo_json_free(&json);
+ debug_return_str(NULL);
+}
+
+/*
+ * Log a message to syslog, pre-pending the username and splitting the
+ * message into parts if it is longer than syslog_maxlen.
+ */
+static bool
+do_syslog_sudo(int pri, char *logline, const struct eventlog *evlog)
+{
+ size_t len, maxlen;
+ char *p, *tmp, save;
+ const char *fmt;
+ debug_decl(do_syslog_sudo, SUDO_DEBUG_UTIL);
+
+ evl_conf.open_log(EVLOG_SYSLOG, NULL);
+
+ if (evlog == NULL) {
+ /* Not a command, just log it as-is. */
+ syslog(pri, "%s", logline);
+ goto done;
+ }
+
+ /*
+ * Log the full line, breaking into multiple syslog(3) calls if necessary
+ */
+ fmt = _("%8s : %s");
+ maxlen = evl_conf.syslog_maxlen -
+ (strlen(fmt) - 5 + strlen(evlog->submituser));
+ for (p = logline; *p != '\0'; ) {
+ len = strlen(p);
+ if (len > maxlen) {
+ /*
+ * Break up the line into what will fit on one syslog(3) line
+ * Try to avoid breaking words into several lines if possible.
+ */
+ tmp = memrchr(p, ' ', maxlen);
+ if (tmp == NULL)
+ tmp = p + maxlen;
+
+ /* NULL terminate line, but save the char to restore later */
+ save = *tmp;
+ *tmp = '\0';
+
+ syslog(pri, fmt, evlog->submituser, p);
+
+ *tmp = save; /* restore saved character */
+
+ /* Advance p and eliminate leading whitespace */
+ for (p = tmp; *p == ' '; p++)
+ continue;
+ } else {
+ syslog(pri, fmt, evlog->submituser, p);
+ p += len;
+ }
+ fmt = _("%8s : (command continued) %s");
+ maxlen = evl_conf.syslog_maxlen -
+ (strlen(fmt) - 5 + strlen(evlog->submituser));
+ }
+done:
+ evl_conf.close_log(EVLOG_SYSLOG, NULL);
+
+ debug_return_bool(true);
+}
+
+static bool
+do_syslog_json(int pri, int event_type, const char *reason,
+ const char *errstr, const struct eventlog *evlog,
+ const struct timespec *event_time,
+ eventlog_json_callback_t info_cb, void *info)
+{
+ char *json_str;
+ debug_decl(do_syslog_json, SUDO_DEBUG_UTIL);
+
+ /* Format as a compact JSON message (no newlines) */
+ json_str = format_json(event_type, reason, errstr, evlog, event_time,
+ info_cb, info, true);
+ if (json_str == NULL)
+ debug_return_bool(false);
+
+ /* Syslog it with a @cee: prefix */
+ /* TODO: use evl_conf.syslog_maxlen to break up long messages. */
+ evl_conf.open_log(EVLOG_SYSLOG, NULL);
+ syslog(pri, "@cee:{%s}", json_str);
+ evl_conf.close_log(EVLOG_SYSLOG, NULL);
+ free(json_str);
+ debug_return_bool(true);
+}
+
+/*
+ * Log a message to syslog in either sudo or JSON format.
+ */
+static bool
+do_syslog(int event_type, int flags, const char *reason, const char *errstr,
+ const struct eventlog *evlog, const struct timespec *event_time,
+ eventlog_json_callback_t info_cb, void *info)
+{
+ char *logline = NULL;
+ bool ret = false;
+ int pri;
+ debug_decl(do_syslog, SUDO_DEBUG_UTIL);
+
+ /* Sudo format logs and mailed logs use the same log line format. */
+ if (evl_conf.format == EVLOG_SUDO || ISSET(flags, EVLOG_MAIL)) {
+ logline = new_logline(flags, reason, errstr, evlog);
+ if (logline == NULL)
+ debug_return_bool(false);
+
+ if (ISSET(flags, EVLOG_MAIL)) {
+ if (!send_mail(evlog, "%s", logline)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to mail log line");
+ }
+ if (ISSET(flags, EVLOG_MAIL_ONLY)) {
+ free(logline);
+ debug_return_bool(true);
+ }
+ }
+ }
+
+ switch (event_type) {
+ case EVLOG_ACCEPT:
+ pri = evl_conf.syslog_acceptpri;
+ break;
+ case EVLOG_REJECT:
+ pri = evl_conf.syslog_rejectpri;
+ break;
+ case EVLOG_ALERT:
+ pri = evl_conf.syslog_alertpri;
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unexpected event type %d", event_type);
+ pri = -1;
+ break;
+ }
+ if (pri == -1) {
+ /* syslog disabled for this message type */
+ free(logline);
+ debug_return_bool(true);
+ }
+
+ switch (evl_conf.format) {
+ case EVLOG_SUDO:
+ ret = do_syslog_sudo(pri, logline, evlog);
+ break;
+ case EVLOG_JSON:
+ ret = do_syslog_json(pri, event_type, reason, errstr, evlog,
+ event_time, info_cb, info);
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unexpected eventlog format %d", evl_conf.format);
+ break;
+ }
+ free(logline);
+
+ debug_return_bool(ret);
+}
+
+static bool
+do_logfile_sudo(const char *logline, const struct eventlog *evlog,
+ const struct timespec *event_time)
+{
+ char *full_line, timebuf[8192], *timestr = NULL;
+ const char *timefmt = evl_conf.time_fmt;
+ const char *logfile = evl_conf.logpath;
+ time_t tv_sec = event_time->tv_sec;
+ struct tm *timeptr;
+ bool ret = false;
+ FILE *fp;
+ int len;
+ debug_decl(do_logfile_sudo, SUDO_DEBUG_UTIL);
+
+ if ((fp = evl_conf.open_log(EVLOG_FILE, logfile)) == NULL)
+ debug_return_bool(false);
+
+ if (!sudo_lock_file(fileno(fp), SUDO_LOCK)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to lock log file %s", logfile);
+ goto done;
+ }
+
+ if ((timeptr = localtime(&tv_sec)) != NULL) {
+ /* strftime() does not guarantee to NUL-terminate so we must check. */
+ timebuf[sizeof(timebuf) - 1] = '\0';
+ if (strftime(timebuf, sizeof(timebuf), timefmt, timeptr) != 0 &&
+ timebuf[sizeof(timebuf) - 1] == '\0') {
+ timestr = timebuf;
+ }
+ }
+ len = asprintf(&full_line, "%s : %s : %s",
+ timestr ? timestr : "invalid date", evlog->submituser, logline);
+ if (len == -1) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto done;
+ }
+ eventlog_writeln(fp, full_line, len, evl_conf.file_maxlen);
+ (void)fflush(fp);
+ if (ferror(fp)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to write log file %s", logfile);
+ goto done;
+ }
+ ret = true;
+
+done:
+ (void)sudo_lock_file(fileno(fp), SUDO_UNLOCK);
+ evl_conf.close_log(EVLOG_FILE, fp);
+ debug_return_bool(ret);
+}
+
+static bool
+do_logfile_json(int event_type, const char *reason, const char *errstr,
+ const struct eventlog *evlog, const struct timespec *event_time,
+ eventlog_json_callback_t info_cb, void *info)
+{
+ const char *logfile = evl_conf.logpath;
+ struct stat sb;
+ char *json_str;
+ int ret = false;
+ FILE *fp;
+ debug_decl(do_logfile_json, SUDO_DEBUG_UTIL);
+
+ if ((fp = evl_conf.open_log(EVLOG_FILE, logfile)) == NULL)
+ debug_return_bool(false);
+
+ json_str = format_json(event_type, reason, errstr, evlog, event_time,
+ info_cb, info, false);
+ if (json_str == NULL)
+ goto done;
+
+ if (!sudo_lock_file(fileno(fp), SUDO_LOCK)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to lock log file %s", logfile);
+ goto done;
+ }
+
+ /* Note: assumes file ends in "\n}\n" */
+ if (fstat(fileno(fp), &sb) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "unable to stat %s", logfile);
+ goto done;
+ }
+ if (sb.st_size == 0) {
+ /* New file */
+ putc('{', fp);
+ } else if (fseeko(fp, -3, SEEK_END) == 0) {
+ /* Continue file, overwrite the final "\n}\n" */
+ putc(',', fp);
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "unable to seek %s", logfile);
+ goto done;
+ }
+ fputs(json_str, fp);
+ fputs("\n}\n", fp); /* close JSON */
+ fflush(fp);
+ /* XXX - check for file error and recover */
+
+ ret = true;
+
+done:
+ free(json_str);
+ (void)sudo_lock_file(fileno(fp), SUDO_UNLOCK);
+ evl_conf.close_log(EVLOG_FILE, fp);
+ debug_return_bool(ret);
+}
+
+static bool
+do_logfile(int event_type, int flags, const char *reason, const char *errstr,
+ const struct eventlog *evlog, const struct timespec *event_time,
+ eventlog_json_callback_t info_cb, void *info)
+{
+ bool ret = false;
+ char *logline = NULL;
+ debug_decl(do_logfile, SUDO_DEBUG_UTIL);
+
+ /* Sudo format logs and mailed logs use the same log line format. */
+ if (evl_conf.format == EVLOG_SUDO || ISSET(flags, EVLOG_MAIL)) {
+ logline = new_logline(flags, reason, errstr, evlog);
+ if (logline == NULL)
+ debug_return_bool(false);
+
+ if (ISSET(flags, EVLOG_MAIL)) {
+ if (!send_mail(evlog, "%s", logline)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to mail log line");
+ }
+ if (ISSET(flags, EVLOG_MAIL_ONLY)) {
+ free(logline);
+ debug_return_bool(true);
+ }
+ }
+ }
+
+ switch (evl_conf.format) {
+ case EVLOG_SUDO:
+ ret = do_logfile_sudo(logline ? logline : reason, evlog, event_time);
+ break;
+ case EVLOG_JSON:
+ ret = do_logfile_json(event_type, reason, errstr, evlog,
+ event_time, info_cb, info);
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unexpected eventlog format %d", evl_conf.format);
+ break;
+ }
+ free(logline);
+
+ debug_return_bool(ret);
+}
+
+bool
+eventlog_accept(const struct eventlog *evlog, int flags,
+ eventlog_json_callback_t info_cb, void *info)
+{
+ const int log_type = evl_conf.type;
+ bool ret = true;
+ debug_decl(log_accept, SUDO_DEBUG_UTIL);
+
+ if (log_type == EVLOG_NONE)
+ debug_return_bool(true);
+
+ if (ISSET(log_type, EVLOG_SYSLOG)) {
+ if (!do_syslog(EVLOG_ACCEPT, flags, NULL, NULL, evlog,
+ &evlog->submit_time, info_cb, info))
+ ret = false;
+ CLR(flags, EVLOG_MAIL);
+ }
+ if (ISSET(log_type, EVLOG_FILE)) {
+ if (!do_logfile(EVLOG_ACCEPT, flags, NULL, NULL, evlog,
+ &evlog->submit_time, info_cb, info))
+ ret = false;
+ }
+
+ debug_return_bool(ret);
+}
+
+bool
+eventlog_reject(const struct eventlog *evlog, int flags, const char *reason,
+ eventlog_json_callback_t info_cb, void *info)
+{
+ const int log_type = evl_conf.type;
+ bool ret = true;
+ debug_decl(log_reject, SUDO_DEBUG_UTIL);
+
+ if (ISSET(log_type, EVLOG_SYSLOG)) {
+ if (!do_syslog(EVLOG_REJECT, flags, reason, NULL, evlog,
+ &evlog->submit_time, info_cb, info))
+ ret = false;
+ CLR(flags, EVLOG_MAIL);
+ }
+ if (ISSET(log_type, EVLOG_FILE)) {
+ if (!do_logfile(EVLOG_REJECT, flags, reason, NULL, evlog,
+ &evlog->submit_time, info_cb, info))
+ ret = false;
+ }
+
+ debug_return_bool(ret);
+}
+
+bool
+eventlog_alert(const struct eventlog *evlog, int flags,
+ struct timespec *alert_time, const char *reason, const char *errstr)
+{
+ const int log_type = evl_conf.type;
+ bool ret = true;
+ debug_decl(log_alert, SUDO_DEBUG_UTIL);
+
+ if (ISSET(log_type, EVLOG_SYSLOG)) {
+ if (!do_syslog(EVLOG_ALERT, flags, reason, errstr, evlog, alert_time,
+ NULL, NULL))
+ ret = false;
+ CLR(flags, EVLOG_MAIL);
+ }
+ if (ISSET(log_type, EVLOG_FILE)) {
+ if (!do_logfile(EVLOG_ALERT, flags, reason, errstr, evlog, alert_time,
+ NULL, NULL))
+ ret = false;
+ }
+
+ debug_return_bool(ret);
+}
+
+/*
+ * Free the strings in a struct eventlog.
+ */
+void
+eventlog_free(struct eventlog *evlog)
+{
+ int i;
+ debug_decl(eventlog_free, SUDO_DEBUG_UTIL);
+
+ if (evlog != NULL) {
+ free(evlog->iolog_path);
+ free(evlog->command);
+ free(evlog->cwd);
+ free(evlog->runchroot);
+ free(evlog->runcwd);
+ free(evlog->rungroup);
+ free(evlog->runuser);
+ free(evlog->submithost);
+ free(evlog->submituser);
+ free(evlog->submitgroup);
+ free(evlog->ttyname);
+ if (evlog->argv != NULL) {
+ for (i = 0; evlog->argv[i] != NULL; i++)
+ free(evlog->argv[i]);
+ free(evlog->argv);
+ }
+ if (evlog->envp != NULL) {
+ for (i = 0; evlog->envp[i] != NULL; i++)
+ free(evlog->envp[i]);
+ free(evlog->envp);
+ }
+ free(evlog);
+ }
+
+ debug_return;
+}
+
+static FILE *
+eventlog_stub_open_log(int type, const char *logfile)
+{
+ debug_decl(eventlog_stub_open_log, SUDO_DEBUG_UTIL);
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
+ "open_log not set, using stub");
+ debug_return_ptr(NULL);
+}
+
+static void
+eventlog_stub_close_log(int type, FILE *fp)
+{
+ debug_decl(eventlog_stub_close_log, SUDO_DEBUG_UTIL);
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
+ "close_log not set, using stub");
+ debug_return;
+}
+
+/*
+ * Set eventlog config settings.
+ */
+
+void
+eventlog_set_type(int type)
+{
+ evl_conf.type = type;
+}
+
+void
+eventlog_set_format(enum eventlog_format format)
+{
+ evl_conf.format = format;
+}
+
+void
+eventlog_set_syslog_acceptpri(int pri)
+{
+ evl_conf.syslog_acceptpri = pri;
+}
+
+void
+eventlog_set_syslog_rejectpri(int pri)
+{
+ evl_conf.syslog_rejectpri = pri;
+}
+
+void
+eventlog_set_syslog_alertpri(int pri)
+{
+ evl_conf.syslog_alertpri = pri;
+}
+
+void
+eventlog_set_syslog_maxlen(int len)
+{
+ evl_conf.syslog_maxlen = len;
+}
+
+void
+eventlog_set_file_maxlen(int len)
+{
+ evl_conf.file_maxlen = len;
+}
+
+void
+eventlog_set_mailuid(uid_t uid)
+{
+ evl_conf.mailuid = uid;
+}
+
+void
+eventlog_set_omit_hostname(bool omit_hostname)
+{
+ evl_conf.omit_hostname = omit_hostname;
+}
+
+void
+eventlog_set_logpath(const char *path)
+{
+ evl_conf.logpath = path;
+}
+
+void
+eventlog_set_time_fmt(const char *fmt)
+{
+ evl_conf.time_fmt = fmt;
+}
+
+void
+eventlog_set_mailerpath(const char *path)
+{
+ evl_conf.mailerpath = path;
+}
+
+void
+eventlog_set_mailerflags(const char *mflags)
+{
+ evl_conf.mailerflags = mflags;
+}
+
+void
+eventlog_set_mailfrom(const char *from_addr)
+{
+ evl_conf.mailfrom = from_addr;
+}
+
+void
+eventlog_set_mailto(const char *to_addr)
+{
+ evl_conf.mailto = to_addr;
+}
+
+void
+eventlog_set_mailsub(const char *subject)
+{
+ evl_conf.mailsub = subject;
+}
+
+void
+eventlog_set_open_log(FILE *(*fn)(int type, const char *))
+{
+ evl_conf.open_log = fn;
+}
+
+void
+eventlog_set_close_log(void (*fn)(int type, FILE *))
+{
+ evl_conf.close_log = fn;
+}
+
+bool
+eventlog_setconf(struct eventlog_config *conf)
+{
+ debug_decl(eventlog_setconf, SUDO_DEBUG_UTIL);
+
+ if (conf != NULL) {
+ memcpy(&evl_conf, conf, sizeof(evl_conf));
+ } else {
+ memset(&evl_conf, 0, sizeof(evl_conf));
+ }
+
+ /* Apply default values where possible. */
+ if (evl_conf.syslog_maxlen == 0)
+ evl_conf.syslog_maxlen = MAXSYSLOGLEN;
+ if (evl_conf.logpath == NULL)
+ evl_conf.logpath = _PATH_SUDO_LOGFILE;
+ if (evl_conf.time_fmt == NULL)
+ evl_conf.time_fmt = "%h %e %T";
+#ifdef _PATH_SUDO_SENDMAIL
+ if (evl_conf.mailerpath == NULL)
+ evl_conf.mailerpath = _PATH_SUDO_SENDMAIL;
+#endif
+ if (evl_conf.mailerflags == NULL)
+ evl_conf.mailerflags = "-t";
+ if (evl_conf.mailto == NULL)
+ evl_conf.mailto = MAILTO;
+ if (evl_conf.mailsub == NULL)
+ evl_conf.mailsub = N_(MAILSUBJECT);
+ if (evl_conf.open_log == NULL)
+ evl_conf.open_log = eventlog_stub_open_log;
+ if (evl_conf.close_log == NULL)
+ evl_conf.close_log = eventlog_stub_close_log;
+
+ debug_return_bool(true);
+}
diff --git a/lib/eventlog/logwrap.c b/lib/eventlog/logwrap.c
new file mode 100644
index 0000000..4c35320
--- /dev/null
+++ b/lib/eventlog/logwrap.c
@@ -0,0 +1,90 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011, 2014-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+#include "sudo_eventlog.h"
+
+size_t
+eventlog_writeln(FILE *fp, char *line, size_t linelen, size_t maxlen)
+{
+ char *indent = "";
+ char *beg = line;
+ char *end;
+ int len;
+ size_t outlen = 0;
+ debug_decl(eventlog_writeln, SUDO_DEBUG_UTIL);
+
+ if (maxlen < sizeof(EVENTLOG_INDENT)) {
+ /* Maximum length too small, disable wrapping. */
+ outlen = fwrite(line, 1, linelen, fp);
+ if (outlen != linelen)
+ debug_return_size_t(-1);
+ if (fputc('\n', fp) == EOF)
+ debug_return_size_t(-1);
+ debug_return_int(outlen + 1);
+ }
+
+ /*
+ * Print out line with word wrap around maxlen characters.
+ */
+ while (linelen > maxlen) {
+ end = beg + maxlen;
+ while (end != beg && *end != ' ')
+ end--;
+ if (beg == end) {
+ /* Unable to find word break within maxlen, look beyond. */
+ end = strchr(beg + maxlen, ' ');
+ if (end == NULL)
+ break; /* no word break */
+ }
+ len = fprintf(fp, "%s%.*s\n", indent, (int)(end - beg), beg);
+ if (len < 0)
+ debug_return_size_t(-1);
+ outlen += len;
+ while (*end == ' ')
+ end++;
+ linelen -= (end - beg);
+ beg = end;
+ if (indent[0] == '\0') {
+ indent = EVENTLOG_INDENT;
+ maxlen -= sizeof(EVENTLOG_INDENT) - 1;
+ }
+ }
+ /* Print remainder, if any. */
+ if (linelen) {
+ len = fprintf(fp, "%s%s\n", indent, beg);
+ if (len < 0)
+ debug_return_size_t(-1);
+ outlen += len;
+ }
+
+ debug_return_size_t(outlen);
+}
diff --git a/lib/eventlog/regress/logwrap/check_wrap.c b/lib/eventlog/regress/logwrap/check_wrap.c
new file mode 100644
index 0000000..9e0b1fa
--- /dev/null
+++ b/lib/eventlog/regress/logwrap/check_wrap.c
@@ -0,0 +1,108 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_eventlog.h"
+#include "sudo_fatal.h"
+#include "sudo_plugin.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s inputfile\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ size_t len;
+ FILE *fp;
+ char *line, lines[2][2048];
+ int lineno = 0;
+ int which = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_wrap");
+
+ if (argc != 2)
+ usage();
+
+ fp = fopen(argv[1], "r");
+ if (fp == NULL)
+ sudo_fatalx("unable to open %s", argv[1]);
+
+ /*
+ * Each test record consists of a log entry on one line and a list of
+ * line lengths to test it with on the next. E.g.
+ *
+ * Jun 30 14:49:51 : millert : TTY=ttypn ; PWD=/usr/src/local/millert/hg/sudo/trunk/plugins/sudoers ; USER=root ; TSID=0004LD ; COMMAND=/usr/local/sbin/visudo
+ * 60-80,40
+ */
+ while ((line = fgets(lines[which], sizeof(lines[which]), fp)) != NULL) {
+ char *cp, *last;
+
+ len = strcspn(line, "\n");
+ line[len] = '\0';
+
+ /* If we read the 2nd line, parse list of line lengths and check. */
+ if (which) {
+ lineno++;
+ for (cp = strtok_r(lines[1], ",", &last); cp != NULL; cp = strtok_r(NULL, ",", &last)) {
+ const char *errstr;
+ char *dash;
+ size_t maxlen;
+
+ /* May be either a number or a range. */
+ dash = strchr(cp, '-');
+ if (dash != NULL) {
+ *dash = '\0';
+ len = sudo_strtonum(cp, 0, INT_MAX, &errstr);
+ if (errstr == NULL)
+ maxlen = sudo_strtonum(dash + 1, 0, INT_MAX, &errstr);
+ } else {
+ len = maxlen = sudo_strtonum(cp, 0, INT_MAX, &errstr);
+ }
+ if (errstr != NULL)
+ sudo_fatalx("%s: invalid length on line %d\n", argv[1], lineno);
+ while (len <= maxlen) {
+ if (len == 0)
+ printf("# word wrap disabled\n");
+ else
+ printf("# word wrap at %d characters\n", (int)len);
+ eventlog_writeln(stdout, lines[0], strlen(lines[0]), len);
+ len++;
+ }
+ }
+ }
+ which = !which;
+ }
+
+ exit(0);
+}
diff --git a/lib/eventlog/regress/logwrap/check_wrap.in b/lib/eventlog/regress/logwrap/check_wrap.in
new file mode 100644
index 0000000..e8e7081
--- /dev/null
+++ b/lib/eventlog/regress/logwrap/check_wrap.in
@@ -0,0 +1,4 @@
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ; PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+0,60-80,120,140
+Jun 26 18:00:06 : millert : TTY=ttypm ; PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+0,60-80,120,140
diff --git a/lib/eventlog/regress/logwrap/check_wrap.out.ok b/lib/eventlog/regress/logwrap/check_wrap.out.ok
new file mode 100644
index 0000000..55e9da8
--- /dev/null
+++ b/lib/eventlog/regress/logwrap/check_wrap.out.ok
@@ -0,0 +1,179 @@
+# word wrap disabled
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ; PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 60 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1
+ ; PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 61 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1
+ ; PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 62 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 63 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 64 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 65 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 66 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 67 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool
+ list users
+# word wrap at 68 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool
+ list users
+# word wrap at 69 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool
+ list users
+# word wrap at 70 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool
+ list users
+# word wrap at 71 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool
+ list users
+# word wrap at 72 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 73 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 74 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 75 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 76 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 77 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 78 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 79 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 80 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 120 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ; PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 140 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ; PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+# word wrap disabled
+Jun 26 18:00:06 : millert : TTY=ttypm ; PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 60 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 61 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 62 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 63 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 64 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 65 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 66 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 67 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 68 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 69 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 70 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT
+ ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 71 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT
+ ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 72 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 73 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 74 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 75 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 76 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 77 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 78 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 79 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 80 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ; PWD=/usr/src/local/millert/hg/sudo/build
+ ; USER=root ; TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 120 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ; PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 140 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ; PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ; COMMAND=/bin/rm
+ /root/.bash_profile
diff --git a/lib/iolog/Makefile.in b/lib/iolog/Makefile.in
new file mode 100644
index 0000000..2ae9c1b
--- /dev/null
+++ b/lib/iolog/Makefile.in
@@ -0,0 +1,368 @@
+#
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2011-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+# @configure_input@
+#
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+abs_srcdir = @abs_srcdir@
+top_srcdir = @top_srcdir@
+abs_top_srcdir = @abs_top_srcdir@
+top_builddir = @top_builddir@
+abs_top_builddir = @abs_top_builddir@
+devdir = @devdir@
+incdir = $(top_srcdir)/include
+scriptdir = $(top_srcdir)/scripts
+cross_compiling = @CROSS_COMPILING@
+
+# Compiler & tools to use
+CC = @CC@
+LIBTOOL = @LIBTOOL@
+
+# Libraries
+LT_LIBS = $(top_builddir)/lib/util/libsudo_util.la
+LIBS = @LIBS@ @ZLIB@ $(LT_LIBS)
+
+# C preprocessor flags
+CPPFLAGS = -I$(incdir) -I$(top_builddir) -I$(srcdir) @CPPFLAGS@
+
+# Usually -O and/or -g
+CFLAGS = @CFLAGS@
+
+# Flags to pass to the link stage
+LDFLAGS = @LDFLAGS@
+
+# 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@
+
+# cppcheck options, usually set in the top-level Makefile
+CPPCHECK_OPTS = -q --enable=warning,performance,portability --suppress=constStatement --suppress=compareBoolExpressionWithInt --error-exitcode=1 --inline-suppr -Dva_copy=va_copy -U__cplusplus -UQUAD_MAX -UQUAD_MIN -UUQUAD_MAX -U_POSIX_HOST_NAME_MAX -U_POSIX_PATH_MAX -U__NBBY -DNSIG=64
+
+# splint options, usually set in the top-level Makefile
+SPLINT_OPTS = -D__restrict= -checks
+
+# PVS-studio options
+PVS_CFG = $(top_srcdir)/PVS-Studio.cfg
+PVS_IGNORE = 'V707,V011,V002,V536'
+PVS_LOG_OPTS = -a 'GA:1,2' -e -t errorfile -d $(PVS_IGNORE)
+
+# Regression tests
+TEST_PROGS = check_iolog_json check_iolog_mkpath check_iolog_path check_iolog_util host_port_test
+TEST_LIBS = @LIBS@ $(top_builddir)/lib/eventlog/libsudo_eventlog.la
+TEST_LDFLAGS = @LDFLAGS@
+
+# Set to non-empty for development mode
+DEVEL = @DEVEL@
+
+#### End of system configuration section. ####
+
+SHELL = @SHELL@
+
+LIBIOLOG_OBJS = iolog_fileio.lo iolog_json.lo iolog_path.lo iolog_util.lo \
+ host_port.lo hostcheck.lo
+
+IOBJS = $(LIBIOLOG_OBJS:.lo=.i)
+
+POBJS = $(IOBJS:.i=.plog)
+
+CHECK_IOLOG_MKPATH_OBJS = check_iolog_mkpath.lo iolog_fileio.lo
+
+CHECK_IOLOG_PATH_OBJS = check_iolog_path.lo iolog_path.lo
+
+CHECK_IOLOG_UTIL_OBJS = check_iolog_util.lo iolog_json.lo iolog_util.lo
+
+CHECK_IOLOG_JSON_OBJS = check_iolog_json.lo iolog_json.lo
+
+HOST_PORT_TEST_OBJS = host_port_test.lo host_port.lo
+
+all: libsudo_iolog.la
+
+pvs-log-files: $(POBJS)
+
+pvs-studio: $(POBJS)
+ plog-converter $(PVS_LOG_OPTS) $(POBJS)
+
+depend:
+ $(scriptdir)/mkdep.pl --srcdir=$(abs_top_srcdir) \
+ --builddir=$(abs_top_builddir) lib/iolog/Makefile.in
+ cd $(top_builddir) && ./config.status --file lib/iolog/Makefile
+
+Makefile: $(srcdir)/Makefile.in
+ cd $(top_builddir) && ./config.status --file lib/iolog/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 $@
+
+libsudo_iolog.la: $(LIBIOLOG_OBJS)
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(LIBIOLOG_OBJS) $(LT_LIBS) @ZLIB@ @NET_LIBS@
+
+check_iolog_path: $(CHECK_IOLOG_PATH_OBJS) libsudo_iolog.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_PATH_OBJS) libsudo_iolog.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+check_iolog_mkpath: $(CHECK_IOLOG_MKPATH_OBJS) libsudo_iolog.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_MKPATH_OBJS) libsudo_iolog.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+check_iolog_util: $(CHECK_IOLOG_UTIL_OBJS) libsudo_iolog.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_UTIL_OBJS) libsudo_iolog.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+check_iolog_json: $(CHECK_IOLOG_JSON_OBJS) libsudo_iolog.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_JSON_OBJS) libsudo_iolog.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+host_port_test: $(HOST_PORT_TEST_OBJS) libsudo_iolog.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(HOST_PORT_TEST_OBJS) libsudo_iolog.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+pre-install:
+
+install:
+
+install-binaries:
+
+install-includes:
+
+install-doc:
+
+install-plugin:
+
+uninstall:
+
+splint:
+ splint $(SPLINT_OPTS) -I$(incdir) -I$(top_builddir) $(srcdir)/*.c
+
+cppcheck:
+ cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) $(srcdir)/*.c
+
+pvs-log-files: $(POBJS)
+
+check: $(TEST_PROGS)
+ @if test X"$(cross_compiling)" != X"yes"; then \
+ LC_ALL=C; export LC_ALL; \
+ unset LANG || LANG=; \
+ rval=0; \
+ ./check_iolog_json $(srcdir)/regress/iolog_json/*.in || rval=`expr $$rval + $$?`; \
+ ./check_iolog_path $(srcdir)/regress/iolog_path/data || rval=`expr $$rval + $$?`; \
+ ./check_iolog_mkpath || rval=`expr $$rval + $$?`; \
+ ./check_iolog_util || rval=`expr $$rval + $$?`; \
+ ./host_port_test || rval=`expr $$rval + $$?`; \
+ exit $$rval; \
+ fi
+
+clean:
+ -$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f $(TEST_PROGS) *.lo *.o *.la
+ -rm -f *.i *.plog stamp-* core *.core core.* regress/*/*.out \
+ regress/*/*.err
+
+mostlyclean: clean
+
+distclean: clean
+ -rm -rf Makefile .libs
+
+clobber: distclean
+
+realclean: distclean
+ rm -f TAGS tags
+
+cleandir: realclean
+
+# Autogenerated dependencies, do not modify
+check_iolog_json.lo: $(srcdir)/regress/iolog_json/check_iolog_json.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_json.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/iolog_json.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_json/check_iolog_json.c
+check_iolog_json.i: $(srcdir)/regress/iolog_json/check_iolog_json.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_json.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/iolog_json.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+check_iolog_json.plog: check_iolog_json.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/iolog_json/check_iolog_json.c --i-file $< --output-file $@
+check_iolog_mkpath.lo: $(srcdir)/regress/iolog_mkpath/check_iolog_mkpath.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_iolog.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_mkpath/check_iolog_mkpath.c
+check_iolog_mkpath.i: $(srcdir)/regress/iolog_mkpath/check_iolog_mkpath.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_iolog.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+check_iolog_mkpath.plog: check_iolog_mkpath.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/iolog_mkpath/check_iolog_mkpath.c --i-file $< --output-file $@
+check_iolog_path.lo: $(srcdir)/regress/iolog_path/check_iolog_path.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_iolog.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_path/check_iolog_path.c
+check_iolog_path.i: $(srcdir)/regress/iolog_path/check_iolog_path.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_iolog.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+check_iolog_path.plog: check_iolog_path.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/iolog_path/check_iolog_path.c --i-file $< --output-file $@
+check_iolog_util.lo: $(srcdir)/regress/iolog_util/check_iolog_util.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_iolog.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_util/check_iolog_util.c
+check_iolog_util.i: $(srcdir)/regress/iolog_util/check_iolog_util.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_iolog.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+check_iolog_util.plog: check_iolog_util.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/iolog_util/check_iolog_util.c --i-file $< --output-file $@
+host_port.lo: $(srcdir)/host_port.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)/host_port.c
+host_port.i: $(srcdir)/host_port.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) $<
+host_port.plog: host_port.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/host_port.c --i-file $< --output-file $@
+host_port_test.lo: $(srcdir)/regress/host_port/host_port_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_iolog.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/host_port/host_port_test.c
+host_port_test.i: $(srcdir)/regress/host_port/host_port_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_iolog.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+host_port_test.plog: host_port_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/host_port/host_port_test.c --i-file $< --output-file $@
+hostcheck.lo: $(srcdir)/hostcheck.c $(incdir)/compat/stdbool.h \
+ $(incdir)/hostcheck.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)/hostcheck.c
+hostcheck.i: $(srcdir)/hostcheck.c $(incdir)/compat/stdbool.h \
+ $(incdir)/hostcheck.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) $<
+hostcheck.plog: hostcheck.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/hostcheck.c --i-file $< --output-file $@
+iolog_fileio.lo: $(srcdir)/iolog_fileio.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_eventlog.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_iolog.h $(incdir)/sudo_json.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)/iolog_fileio.c
+iolog_fileio.i: $(srcdir)/iolog_fileio.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_eventlog.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_iolog.h $(incdir)/sudo_json.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) $<
+iolog_fileio.plog: iolog_fileio.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_fileio.c --i-file $< --output-file $@
+iolog_json.lo: $(srcdir)/iolog_json.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_iolog.h \
+ $(incdir)/sudo_json.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/iolog_json.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/iolog_json.c
+iolog_json.i: $(srcdir)/iolog_json.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_iolog.h \
+ $(incdir)/sudo_json.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(srcdir)/iolog_json.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+iolog_json.plog: iolog_json.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_json.c --i-file $< --output-file $@
+iolog_path.lo: $(srcdir)/iolog_path.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_iolog.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)/iolog_path.c
+iolog_path.i: $(srcdir)/iolog_path.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_iolog.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+iolog_path.plog: iolog_path.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_path.c --i-file $< --output-file $@
+iolog_util.lo: $(srcdir)/iolog_util.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_iolog.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)/iolog_util.c
+iolog_util.i: $(srcdir)/iolog_util.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_iolog.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+iolog_util.plog: iolog_util.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_util.c --i-file $< --output-file $@
diff --git a/lib/iolog/host_port.c b/lib/iolog/host_port.c
new file mode 100644
index 0000000..b16c658
--- /dev/null
+++ b/lib/iolog/host_port.c
@@ -0,0 +1,102 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "config.h"
+
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <stdio.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+/*
+ * Parse a string in the form host[:port] where host can also be
+ * an IPv4 address or an IPv6 address in square brackets.
+ * Fills in hostp and portp which may point within str, which is modified.
+ */
+bool
+iolog_parse_host_port(char *str, char **hostp, char **portp, bool *tlsp,
+ char *defport, char *defport_tls)
+{
+ char *flags, *port, *host = str;
+ bool ret = false;
+ bool tls = false;
+ debug_decl(iolog_parse_host_port, SUDO_DEBUG_UTIL);
+
+ /* Check for IPv6 address like [::0] followed by optional port */
+ if (*host == '[') {
+ host++;
+ port = strchr(host, ']');
+ if (port == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "invalid IPv6 address %s", str);
+ goto done;
+ }
+ *port++ = '\0';
+ switch (*port) {
+ case ':':
+ port++;
+ break;
+ case '\0':
+ port = NULL; /* no port specified */
+ break;
+ case '(':
+ /* flag, handled below */
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "invalid IPv6 address %s", str);
+ goto done;
+ }
+ } else {
+ port = strrchr(host, ':');
+ if (port != NULL)
+ *port++ = '\0';
+ }
+
+ /* Check for optional tls flag at the end. */
+ flags = strchr(port ? port : host, '(');
+ if (flags != NULL) {
+ if (strcasecmp(flags, "(tls)") == 0)
+ tls = true;
+ *flags = '\0';
+ if (port == flags)
+ port = NULL;
+ }
+
+ if (port == NULL)
+ port = tls ? defport_tls : defport;
+ else if (*port == '\0')
+ goto done;
+
+ *hostp = host;
+ *portp = port;
+ *tlsp = tls;
+
+ ret = true;
+
+done:
+ debug_return_bool(ret);
+}
diff --git a/lib/iolog/hostcheck.c b/lib/iolog/hostcheck.c
new file mode 100644
index 0000000..0dee14b
--- /dev/null
+++ b/lib/iolog/hostcheck.c
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2020 Laszlo Orban <laszlo.orban@oneidentity.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include "config.h"
+
+#if defined(HAVE_OPENSSL)
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <arpa/inet.h>
+# include <stdlib.h>
+# include <string.h>
+# include <netdb.h>
+
+# define NEED_INET_NTOP /* to expose sudo_inet_ntop in sudo_compat.h */
+
+# include "sudo_compat.h"
+# include "sudo_debug.h"
+# include "sudo_util.h"
+# include "hostcheck.h"
+
+/**
+ * @brief Checks if given hostname resolves to the given IP address.
+ *
+ * @param hostname hostname to be resolved
+ * @param ipaddr ip address to be checked
+ *
+ * @return 1 if hostname resolves to the given IP address
+ * 0 otherwise
+ */
+static int
+forward_lookup_match(const char *hostname, const char *ipaddr)
+{
+ int rc, ret = 0;
+ struct addrinfo *res = NULL, *p;
+ void *addr;
+ struct sockaddr_in *ipv4;
+#if defined(HAVE_STRUCT_IN6_ADDR)
+ struct sockaddr_in6 *ipv6;
+ char ipstr[INET6_ADDRSTRLEN];
+#else
+ char ipstr[INET_ADDRSTRLEN];
+#endif
+ debug_decl(forward_lookup_match, SUDO_DEBUG_UTIL);
+
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "verify %s resolves to %s", hostname, ipaddr);
+
+ if ((rc = getaddrinfo(hostname, NULL, NULL, &res)) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to resolve %s: %s", hostname, gai_strerror(rc));
+ goto exit;
+ }
+
+ for (p = res; p != NULL; p = p->ai_next) {
+ if (p->ai_family == AF_INET) {
+ ipv4 = (struct sockaddr_in *)p->ai_addr;
+ addr = &(ipv4->sin_addr);
+#if defined(HAVE_STRUCT_IN6_ADDR)
+ } else if (p->ai_family == AF_INET6) {
+ ipv6 = (struct sockaddr_in6 *)p->ai_addr;
+ addr = &(ipv6->sin6_addr);
+#endif
+ } else {
+ goto exit;
+ }
+
+ if (inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr)) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "comparing %s to %s", ipstr, ipaddr);
+ if (strcmp(ipaddr, ipstr) == 0) {
+ ret = 1;
+ break;
+ }
+ }
+ }
+
+exit:
+ if (res != NULL) {
+ freeaddrinfo(res);
+ }
+ debug_return_int(ret);
+}
+
+/**
+ * @brief Compares the given hostname with a DNS entry in a certificate.
+ *
+ * The certificate DNS name can contain wildcards in the left-most label.
+ * A wildcard can match only one label.
+ * Accepted names:
+ * - foo.bar.example.com
+ * - *.example.com
+ * - *.bar.example.com
+ *
+ * @param hostname peer's name
+ * @param certname_asn1 hostname in the certificate
+ *
+ * @return MatchFound
+ * MatchNotFound
+ */
+static HostnameValidationResult
+validate_name(const char *hostname, ASN1_STRING *certname_asn1)
+{
+ char *certname_s = (char *) ASN1_STRING_get0_data(certname_asn1);
+ int certname_len = ASN1_STRING_length(certname_asn1);
+ int hostname_len = strlen(hostname);
+ debug_decl(validate_name, SUDO_DEBUG_UTIL);
+
+ /* remove last '.' from hostname if exists */
+ if (hostname_len != 0 && hostname[hostname_len - 1] == '.') {
+ --hostname_len;
+ }
+
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "comparing %.*s to %.*s in cert", hostname_len, hostname,
+ certname_len, certname_s);
+
+ /* skip the first label if wildcard */
+ if (certname_len > 2 && certname_s[0] == '*' && certname_s[1] == '.') {
+ if (hostname_len != 0) {
+ do {
+ --hostname_len;
+ if (*hostname++ == '.') {
+ break;
+ }
+ } while (hostname_len != 0);
+ }
+ certname_s += 2;
+ certname_len -= 2;
+ }
+ /* Compare expected hostname with the DNS name */
+ if (certname_len != hostname_len) {
+ debug_return_int(MatchNotFound);
+ }
+ if (strncasecmp(hostname, certname_s, hostname_len) != 0) {
+ debug_return_int(MatchNotFound);
+ }
+
+ debug_return_int(MatchFound);
+}
+
+/**
+ * @brief Matches a hostname with the cert's CN.
+ *
+ * @param hostname peer's name
+ * on client side: it is the name where the client is connected to
+ * on server side, it is in fact an IP address of the remote client
+ * @param ipaddr peer's IP address
+ * @param cert peer's X509 certificate
+ * @param resolve if the value is not 0, the function checks that the value of the CN
+ * resolves to the given ipaddr or not.
+ *
+ * @return MatchFound
+ * MatchNotFound
+ * MalformedCertificate
+ * Error
+ */
+static HostnameValidationResult
+matches_common_name(const char *hostname, const char *ipaddr, const X509 *cert, int resolve)
+{
+ X509_NAME_ENTRY *common_name_entry = NULL;
+ ASN1_STRING *common_name_asn1 = NULL;
+ int common_name_loc = -1;
+ debug_decl(matches_common_name, SUDO_DEBUG_UTIL);
+
+ /* Find the position of the CN field in the Subject field of the certificate */
+ common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) cert), NID_commonName, -1);
+ if (common_name_loc < 0) {
+ debug_return_int(Error);
+ }
+
+ /* Extract the CN field */
+ common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) cert), common_name_loc);
+ if (common_name_entry == NULL) {
+ debug_return_int(Error);
+ }
+
+ /* Convert the CN field to a C string */
+ common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
+ if (common_name_asn1 == NULL) {
+ debug_return_int(Error);
+ }
+ const unsigned char *common_name_str = ASN1_STRING_get0_data(common_name_asn1);
+
+ /* Make sure there isn't an embedded NUL character in the CN */
+ if (memchr(common_name_str, '\0', ASN1_STRING_length(common_name_asn1)) != NULL) {
+ debug_return_int(MalformedCertificate);
+ }
+
+ /* Compare expected hostname with the CN */
+ if (validate_name(hostname, common_name_asn1) == MatchFound) {
+ debug_return_int(MatchFound);
+ }
+
+ int common_name_length = ASN1_STRING_length(common_name_asn1);
+ char *nullterm_common_name = malloc(common_name_length + 1);
+
+ if (nullterm_common_name == NULL) {
+ debug_return_int(Error);
+ }
+
+ memcpy(nullterm_common_name, common_name_str, common_name_length);
+ nullterm_common_name[common_name_length] = '\0';
+
+
+ /* check if hostname in the CN field resolves to the given ip address */
+ if (resolve && forward_lookup_match(nullterm_common_name, ipaddr)) {
+ free(nullterm_common_name);
+ debug_return_int(MatchFound);
+ }
+
+ free(nullterm_common_name);
+ debug_return_int(MatchNotFound);
+}
+
+/**
+ * @brief Matches a hostname or ipaddr with the cert's corresponding SAN field.
+ *
+ * SAN can have different fields. For hostname matching, the GEN_DNS field is used,
+ * for IP address matching, the GEN_IPADD field is used.
+ * Since SAN is an X503 v3 extension, it can happen that the cert does
+ * not contain SAN at all.
+ *
+ * @param hostname remote peer's name
+ * on client side: it is the name where the client is connected to
+ * on server side, it is in fact an IP address of the remote client
+ * @param ipaddr remote peer's IP address
+ * @param cert peer's X509 certificate
+ * @param resolve if the value is not 0, the function checks that the value of the
+ * SAN GEN_DNS resolves to the given ipaddr or not.
+ *
+ * @return MatchFound
+ * MatchNotFound
+ * NoSANPresent
+ * MalformedCertificate
+ * Error
+ */
+static HostnameValidationResult
+matches_subject_alternative_name(const char *hostname, const char *ipaddr, const X509 *cert, int resolve)
+{
+ HostnameValidationResult result = MatchNotFound;
+ int i;
+ int san_names_nb = -1;
+ STACK_OF(GENERAL_NAME) *san_names = NULL;
+ debug_decl(matches_subject_alternative_name, SUDO_DEBUG_UTIL);
+
+ /* Try to extract the names within the SAN extension from the certificate */
+ san_names = X509_get_ext_d2i((X509 *) cert, NID_subject_alt_name, NULL, NULL);
+ if (san_names == NULL) {
+ debug_return_int(NoSANPresent);
+ }
+ san_names_nb = sk_GENERAL_NAME_num(san_names);
+
+ /* Check each name within the extension */
+ for (i=0; i<san_names_nb; i++) {
+ const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
+
+ if (current_name->type == GEN_DNS) {
+ const unsigned char *dns_name = ASN1_STRING_get0_data(current_name->d.dNSName);
+
+ /* Make sure there isn't an embedded NUL character in the DNS name */
+ if (memchr(dns_name, '\0', ASN1_STRING_length(current_name->d.dNSName)) != NULL) {
+ result = MalformedCertificate;
+ break;
+ } else {
+ /* Compare expected hostname with the DNS name */
+ if (validate_name(hostname, current_name->d.dNSName) == MatchFound) {
+ result = MatchFound;
+ break;
+ }
+
+ int dns_name_length = ASN1_STRING_length(current_name->d.dNSName);
+ char *nullterm_dns_name = malloc(dns_name_length + 1);
+
+ if (nullterm_dns_name == NULL) {
+ debug_return_int(Error);
+ }
+
+ memcpy(nullterm_dns_name, dns_name, dns_name_length);
+ nullterm_dns_name[dns_name_length] = '\0';
+
+ if (resolve && forward_lookup_match(nullterm_dns_name, ipaddr)) {
+ free(nullterm_dns_name);
+ result = MatchFound;
+ break;
+ }
+ free(nullterm_dns_name);
+ }
+ } else if (current_name->type == GEN_IPADD) {
+ const unsigned char *san_ip = ASN1_STRING_get0_data(current_name->d.iPAddress);
+#if defined(HAVE_STRUCT_IN6_ADDR)
+ char san_ip_str[INET6_ADDRSTRLEN];
+#else
+ char san_ip_str[INET_ADDRSTRLEN];
+#endif
+
+ /* IPV4 address */
+ if(current_name->d.iPAddress->length == 4) {
+ if (inet_ntop(AF_INET, san_ip, san_ip_str, INET_ADDRSTRLEN) == NULL) {
+ result = MalformedCertificate;
+ break;
+ }
+#if defined(HAVE_STRUCT_IN6_ADDR)
+ /* IPV6 address */
+ } else if (current_name->d.iPAddress->length == 16) {
+ if (inet_ntop(AF_INET6, san_ip, san_ip_str, INET6_ADDRSTRLEN) == NULL) {
+ result = MalformedCertificate;
+ break;
+ }
+# endif
+ } else {
+ result = MalformedCertificate;
+ break;
+ }
+
+ if (strcasecmp(ipaddr, san_ip_str) == 0) {
+ result = MatchFound;
+ break;
+ }
+ }
+ }
+ sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
+
+ debug_return_int(result);
+}
+
+/**
+ * @brief Do hostname/IP validation on the given X509 certificate.
+ *
+ * According to RFC 6125 section 6.4.4, first the certificate's SAN field
+ * has to be checked. If there is no SAN field, the certificate's CN field
+ * has to be checked.
+ *
+ * @param cert X509 certificate
+ * @param hostname remote peer's name
+ * on client side: it is the name where the client is connected to
+ * on server side, it is in fact an IP address of the remote client
+ * @param ipaddr remote peer's IP address
+ * @param resolve if the value is not 0, the function checks that the value of the
+ * SAN GEN_DNS or the value of CN resolves to the given ipaddr or not.
+ *
+ * @return MatchFound
+ * MatchNotFound
+ * MalformedCertificate
+ * Error
+ */
+HostnameValidationResult
+validate_hostname(const X509 *cert, const char *hostname, const char *ipaddr, int resolve)
+{
+ HostnameValidationResult res = MatchFound;
+ debug_decl(validate_hostname, SUDO_DEBUG_UTIL);
+
+ /* hostname can be also an ip address, if client connects
+ * to ip instead of FQDN
+ */
+ if((ipaddr == NULL) || (cert == NULL)) {
+ debug_return_int(Error);
+ }
+
+ /* check SAN first if exists */
+ res = matches_subject_alternative_name(hostname, ipaddr, cert, resolve);
+
+ /* According to RFC 6125 section 6.4.4, check CN only,
+ * if no SAN name was provided
+ */
+ if (res == NoSANPresent) {
+ res = matches_common_name(hostname, ipaddr, cert, resolve);
+ }
+
+ debug_return_int(res);
+}
+#endif /* HAVE_OPENSSL */
diff --git a/lib/iolog/iolog_fileio.c b/lib/iolog/iolog_fileio.c
new file mode 100644
index 0000000..84aeaa8
--- /dev/null
+++ b/lib/iolog/iolog_fileio.c
@@ -0,0 +1,1061 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include "pathnames.h"
+#include "sudo_compat.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_eventlog.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_iolog.h"
+#include "sudo_json.h"
+#include "sudo_queue.h"
+#include "sudo_util.h"
+
+static unsigned char const gzip_magic[2] = {0x1f, 0x8b};
+static unsigned int sessid_max = SESSID_MAX;
+static mode_t iolog_filemode = S_IRUSR|S_IWUSR;
+static mode_t iolog_dirmode = S_IRWXU;
+static uid_t iolog_uid = ROOT_UID;
+static gid_t iolog_gid = ROOT_GID;
+static bool iolog_gid_set;
+static bool iolog_compress;
+static bool iolog_flush;
+
+/*
+ * Set effective user and group-IDs to iolog_uid and iolog_gid.
+ * If restore flag is set, swap them back.
+ */
+static bool
+io_swapids(bool restore)
+{
+#ifdef HAVE_SETEUID
+ static uid_t user_euid = (uid_t)-1;
+ static gid_t user_egid = (gid_t)-1;
+ debug_decl(io_swapids, SUDO_DEBUG_UTIL);
+
+ if (user_euid == (uid_t)-1)
+ user_euid = geteuid();
+ if (user_egid == (gid_t)-1)
+ user_egid = getegid();
+
+ if (restore) {
+ if (seteuid(user_euid) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to restore effective uid to %d", __func__,
+ (int)user_euid);
+ sudo_warn("seteuid() %d -> %d", (int)iolog_uid, (int)user_euid);
+ debug_return_bool(false);
+ }
+ if (setegid(user_egid) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to restore effective gid to %d", __func__,
+ (int)user_egid);
+ sudo_warn("setegid() %d -> %d", (int)iolog_gid, (int)user_egid);
+ debug_return_bool(false);
+ }
+ } else {
+ /* Fail silently if the user has insufficient privileges. */
+ if (setegid(iolog_gid) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to set effective gid to %d", __func__,
+ (int)iolog_gid);
+ debug_return_bool(false);
+ }
+ if (seteuid(iolog_uid) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to set effective uid to %d", __func__,
+ (int)iolog_uid);
+ debug_return_bool(false);
+ }
+ }
+ debug_return_bool(true);
+#else
+ return false;
+#endif
+}
+
+/*
+ * Create directory and any parent directories as needed.
+ */
+static bool
+iolog_mkdirs(char *path)
+{
+ mode_t omask;
+ struct stat sb;
+ int dfd;
+ bool ok = true, uid_changed = false;
+ debug_decl(iolog_mkdirs, SUDO_DEBUG_UTIL);
+
+ dfd = open(path, O_RDONLY|O_NONBLOCK);
+ if (dfd == -1 && errno == EACCES) {
+ /* Try again as the I/O log owner (for NFS). */
+ if (io_swapids(false)) {
+ dfd = open(path, O_RDONLY|O_NONBLOCK);
+ if (!io_swapids(true)) {
+ ok = false;
+ goto done;
+ }
+ }
+ }
+ if (dfd != -1 && fstat(dfd, &sb) != -1) {
+ if (S_ISDIR(sb.st_mode)) {
+ if (sb.st_uid != iolog_uid || sb.st_gid != iolog_gid) {
+ if (fchown(dfd, iolog_uid, iolog_gid) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to chown %d:%d %s", __func__,
+ (int)iolog_uid, (int)iolog_gid, path);
+ }
+ }
+ if ((sb.st_mode & ALLPERMS) != iolog_dirmode) {
+ if (fchmod(dfd, iolog_dirmode) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to chmod 0%o %s", __func__,
+ (int)iolog_dirmode, path);
+ }
+ }
+ } else {
+ sudo_warnx(U_("%s exists but is not a directory (0%o)"),
+ path, (unsigned int) sb.st_mode);
+ ok = false;
+ }
+ goto done;
+ }
+
+ /* umask must not be more restrictive than the file modes. */
+ omask = umask(ACCESSPERMS & ~(iolog_filemode|iolog_dirmode));
+
+ ok = sudo_mkdir_parents(path, iolog_uid, iolog_gid, iolog_dirmode, true);
+ if (!ok && errno == EACCES) {
+ /* Try again as the I/O log owner (for NFS). */
+ uid_changed = io_swapids(false);
+ if (uid_changed)
+ ok = sudo_mkdir_parents(path, -1, -1, iolog_dirmode, false);
+ }
+ if (ok) {
+ /* Create final path component. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "mkdir %s, mode 0%o", path, (unsigned int) iolog_dirmode);
+ ok = mkdir(path, iolog_dirmode) == 0 || errno == EEXIST;
+ if (!ok) {
+ if (errno == EACCES && !uid_changed) {
+ /* Try again as the I/O log owner (for NFS). */
+ uid_changed = io_swapids(false);
+ if (uid_changed)
+ ok = mkdir(path, iolog_dirmode) == 0 || errno == EEXIST;
+ }
+ if (!ok)
+ sudo_warn(U_("unable to mkdir %s"), path);
+ } else {
+ if (chown(path, iolog_uid, iolog_gid) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to chown %d:%d %s", __func__,
+ (int)iolog_uid, (int)iolog_gid, path);
+ }
+ }
+ }
+ if (uid_changed) {
+ if (!io_swapids(true))
+ ok = false;
+ }
+
+ umask(omask);
+
+done:
+ if (dfd != -1)
+ close(dfd);
+ debug_return_bool(ok);
+}
+
+/*
+ * Create temporary directory and any parent directories as needed.
+ */
+bool
+iolog_mkdtemp(char *path)
+{
+ bool ok, uid_changed = false;
+ debug_decl(iolog_mkdtemp, SUDO_DEBUG_UTIL);
+
+ ok = sudo_mkdir_parents(path, iolog_uid, iolog_gid, iolog_dirmode, true);
+ if (!ok && errno == EACCES) {
+ /* Try again as the I/O log owner (for NFS). */
+ uid_changed = io_swapids(false);
+ if (uid_changed)
+ ok = sudo_mkdir_parents(path, -1, -1, iolog_dirmode, false);
+ }
+ if (ok) {
+ /* Create final path component. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "mkdtemp %s", path);
+ /* We cannot retry mkdtemp() so always open as iolog user */
+ if (!uid_changed)
+ uid_changed = io_swapids(false);
+ if (mkdtemp(path) == NULL) {
+ sudo_warn(U_("unable to mkdir %s"), path);
+ ok = false;
+ } else {
+ if (chmod(path, iolog_dirmode) != 0) {
+ sudo_warn(U_("unable to change mode of %s to 0%o"),
+ path, (unsigned int)iolog_dirmode);
+ }
+ }
+ }
+
+ if (uid_changed) {
+ if (!io_swapids(true))
+ ok = false;
+ }
+ debug_return_bool(ok);
+}
+
+/*
+ * Like rename(2) but changes UID as needed.
+ */
+bool
+iolog_rename(const char *from, const char *to)
+{
+ bool ok, uid_changed = false;
+ debug_decl(iolog_rename, SUDO_DEBUG_UTIL);
+
+ ok = rename(from, to) == 0;
+ if (!ok && errno == EACCES) {
+ uid_changed = io_swapids(false);
+ if (uid_changed)
+ ok = rename(from, to) == 0;
+ }
+
+ if (uid_changed) {
+ if (!io_swapids(true))
+ ok = false;
+ }
+ debug_return_bool(ok);
+}
+
+/*
+ * Reset I/O log settings to default values.
+ */
+void
+iolog_set_defaults(void)
+{
+ sessid_max = SESSID_MAX;
+ iolog_filemode = S_IRUSR|S_IWUSR;
+ iolog_dirmode = S_IRWXU;
+ iolog_uid = ROOT_UID;
+ iolog_gid = ROOT_GID;
+ iolog_gid_set = false;
+ iolog_compress = false;
+ iolog_flush = false;
+}
+
+/*
+ * Set max sequence number (aka session ID)
+ */
+void
+iolog_set_maxseq(unsigned int newval)
+{
+ debug_decl(iolog_set_maxseq, SUDO_DEBUG_UTIL);
+
+ /* Clamp to SESSID_MAX as documented. */
+ if (newval > SESSID_MAX)
+ newval = SESSID_MAX;
+ sessid_max = newval;
+
+ debug_return;
+}
+
+/*
+ * Set iolog_uid (and iolog_gid if gid not explicitly set).
+ */
+void
+iolog_set_owner(uid_t uid, gid_t gid)
+{
+ debug_decl(iolog_set_owner, SUDO_DEBUG_UTIL);
+
+ iolog_uid = uid;
+ if (!iolog_gid_set)
+ iolog_gid = gid;
+
+ debug_return;
+}
+
+/*
+ * Set iolog_gid.
+ */
+void
+iolog_set_gid(gid_t gid)
+{
+ debug_decl(iolog_set_gid, SUDO_DEBUG_UTIL);
+
+ iolog_gid = gid;
+ iolog_gid_set = true;
+
+ debug_return;
+}
+
+/*
+ * Set iolog_filemode and iolog_dirmode.
+ */
+void
+iolog_set_mode(mode_t mode)
+{
+ debug_decl(iolog_set_mode, SUDO_DEBUG_UTIL);
+
+ /* I/O log files must be readable and writable by owner. */
+ iolog_filemode = S_IRUSR|S_IWUSR;
+
+ /* Add in group and other read/write if specified. */
+ iolog_filemode |= mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
+
+ /* For directory mode, add execute bits as needed. */
+ iolog_dirmode = iolog_filemode | S_IXUSR;
+ if (iolog_dirmode & (S_IRGRP|S_IWGRP))
+ iolog_dirmode |= S_IXGRP;
+ if (iolog_dirmode & (S_IROTH|S_IWOTH))
+ iolog_dirmode |= S_IXOTH;
+
+ debug_return;
+}
+
+/*
+ * Set iolog_compress
+ */
+void
+iolog_set_compress(bool newval)
+{
+ debug_decl(iolog_set_compress, SUDO_DEBUG_UTIL);
+ iolog_compress = newval;
+ debug_return;
+}
+
+/*
+ * Set iolog_flush
+ */
+void
+iolog_set_flush(bool newval)
+{
+ debug_decl(iolog_set_flush, SUDO_DEBUG_UTIL);
+ iolog_flush = newval;
+ debug_return;
+}
+
+/*
+ * Wrapper for openat(2) that sets umask and retries as iolog_uid/iolog_gid
+ * if openat(2) returns EACCES.
+ */
+int
+iolog_openat(int dfd, const char *path, int flags)
+{
+ int fd;
+ mode_t omask = S_IRWXG|S_IRWXO;
+ debug_decl(iolog_openat, SUDO_DEBUG_UTIL);
+
+ if (ISSET(flags, O_CREAT)) {
+ /* umask must not be more restrictive than the file modes. */
+ omask = umask(ACCESSPERMS & ~(iolog_filemode|iolog_dirmode));
+ }
+ fd = openat(dfd, path, flags, iolog_filemode);
+ if (fd == -1 && errno == EACCES) {
+ /* Enable write bit if it is missing. */
+ struct stat sb;
+ if (fstatat(dfd, path, &sb, 0) == 0) {
+ mode_t write_bits = iolog_filemode & (S_IWUSR|S_IWGRP|S_IWOTH);
+ if ((sb.st_mode & write_bits) != write_bits) {
+ if (fchmodat(dfd, path, iolog_filemode, 0) == 0)
+ fd = openat(dfd, path, flags, iolog_filemode);
+ }
+ }
+ }
+ if (fd == -1 && errno == EACCES) {
+ /* Try again as the I/O log owner (for NFS). */
+ if (io_swapids(false)) {
+ fd = openat(dfd, path, flags, iolog_filemode);
+ if (!io_swapids(true)) {
+ /* io_swapids() warns on error. */
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+ }
+ }
+ }
+ if (ISSET(flags, O_CREAT))
+ umask(omask);
+ debug_return_int(fd);
+}
+
+/*
+ * Read the on-disk sequence number, set sessid to the next
+ * number, and update the on-disk copy.
+ * Uses file locking to avoid sequence number collisions.
+ */
+bool
+iolog_nextid(char *iolog_dir, char sessid[7])
+{
+ char buf[32], *ep;
+ int i, len, fd = -1;
+ unsigned long id = 0;
+ ssize_t nread;
+ bool ret = false;
+ char pathbuf[PATH_MAX];
+ static const char b36char[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ debug_decl(iolog_nextid, SUDO_DEBUG_UTIL);
+
+ /*
+ * Create I/O log directory if it doesn't already exist.
+ */
+ if (!iolog_mkdirs(iolog_dir))
+ goto done;
+
+ /*
+ * Open sequence file
+ */
+ len = snprintf(pathbuf, sizeof(pathbuf), "%s/seq", iolog_dir);
+ if (len < 0 || len >= ssizeof(pathbuf)) {
+ errno = ENAMETOOLONG;
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: %s/seq", __func__, iolog_dir);
+ goto done;
+ }
+ fd = iolog_openat(AT_FDCWD, pathbuf, O_RDWR|O_CREAT);
+ if (fd == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to open %s", __func__, pathbuf);
+ goto done;
+ }
+ if (!sudo_lock_file(fd, SUDO_LOCK)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to lock %s", pathbuf);
+ goto done;
+ }
+ if (fchown(fd, iolog_uid, iolog_gid) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to fchown %d:%d %s", __func__,
+ (int)iolog_uid, (int)iolog_gid, pathbuf);
+ }
+
+ /* Read current seq number (base 36). */
+ nread = read(fd, buf, sizeof(buf) - 1);
+ if (nread != 0) {
+ if (nread == -1) {
+ goto done;
+ }
+ if (buf[nread - 1] == '\n')
+ nread--;
+ buf[nread] = '\0';
+ id = strtoul(buf, &ep, 36);
+ if (ep == buf || *ep != '\0' || id >= sessid_max) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: bad sequence number: %s", pathbuf, buf);
+ id = 0;
+ }
+ }
+ id++;
+
+ /*
+ * Convert id to a string and stash in sessid.
+ * Note that that least significant digits go at the end of the string.
+ */
+ for (i = 5; i >= 0; i--) {
+ buf[i] = b36char[id % 36];
+ id /= 36;
+ }
+ buf[6] = '\n';
+
+ /* Stash id for logging purposes. */
+ memcpy(sessid, buf, 6);
+ sessid[6] = '\0';
+
+ /* Rewind and overwrite old seq file, including the NUL byte. */
+#ifdef HAVE_PWRITE
+ if (pwrite(fd, buf, 7, 0) != 7) {
+#else
+ if (lseek(fd, 0, SEEK_SET) == -1 || write(fd, buf, 7) != 7) {
+#endif
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to write %s", __func__, pathbuf);
+ goto done;
+ }
+ ret = true;
+
+done:
+ if (fd != -1)
+ close(fd);
+ debug_return_bool(ret);
+}
+
+/*
+ * Create path and any intermediate directories.
+ * If path ends in 'XXXXXX', use mkdtemp().
+ */
+bool
+iolog_mkpath(char *path)
+{
+ size_t len;
+ bool ret;
+ debug_decl(iolog_mkpath, SUDO_DEBUG_UTIL);
+
+ /*
+ * Create path and intermediate subdirs as needed.
+ * If path ends in at least 6 Xs (ala POSIX mktemp), use mkdtemp().
+ * Sets iolog_gid (if it is not already set) as a side effect.
+ */
+ len = strlen(path);
+ if (len >= 6 && strcmp(&path[len - 6], "XXXXXX") == 0)
+ ret = iolog_mkdtemp(path);
+ else
+ ret = iolog_mkdirs(path);
+
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "iolog path %s", path);
+
+ debug_return_bool(ret);
+}
+
+/*
+ * Append suffix to pathbuf after len chars and open the resulting file.
+ * Note that the size of pathbuf is assumed to be PATH_MAX.
+ * Stores the open file handle which has the close-on-exec flag set.
+ * XXX - move enabled logic into caller?
+ */
+bool
+iolog_open(struct iolog_file *iol, int dfd, int iofd, const char *mode)
+{
+ int flags;
+ const char *file;
+ unsigned char magic[2];
+ debug_decl(iolog_open, SUDO_DEBUG_UTIL);
+
+ if (mode[0] == 'r') {
+ flags = mode[1] == '+' ? O_RDWR : O_RDONLY;
+ } else if (mode[0] == 'w') {
+ flags = O_CREAT|O_TRUNC;
+ flags |= mode[1] == '+' ? O_RDWR : O_WRONLY;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: invalid I/O mode %s", __func__, mode);
+ debug_return_bool(false);
+ }
+ if ((file = iolog_fd_to_name(iofd)) == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: invalid iofd %d", __func__, iofd);
+ debug_return_bool(false);
+ }
+
+ iol->writable = false;
+ iol->compressed = false;
+ if (iol->enabled) {
+ int fd = iolog_openat(dfd, file, flags);
+ if (fd != -1) {
+ if (*mode == 'w') {
+ if (fchown(fd, iolog_uid, iolog_gid) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to fchown %d:%d %s", __func__,
+ (int)iolog_uid, (int)iolog_gid, file);
+ }
+ iol->compressed = iolog_compress;
+ } else {
+ /* check for gzip magic number */
+ if (pread(fd, magic, sizeof(magic), 0) == ssizeof(magic)) {
+ if (magic[0] == gzip_magic[0] && magic[1] == gzip_magic[1])
+ iol->compressed = true;
+ }
+ }
+ if (fcntl(fd, F_SETFD, FD_CLOEXEC) != -1) {
+#ifdef HAVE_ZLIB_H
+ if (iol->compressed)
+ iol->fd.g = gzdopen(fd, mode);
+ else
+#endif
+ iol->fd.f = fdopen(fd, mode);
+ }
+ if (iol->fd.v != NULL) {
+ switch ((flags & O_ACCMODE)) {
+ case O_WRONLY:
+ case O_RDWR:
+ iol->writable = true;
+ break;
+ }
+ } else {
+ int save_errno = errno;
+ close(fd);
+ errno = save_errno;
+ fd = -1;
+ }
+ }
+ if (fd == -1) {
+ iol->enabled = false;
+ debug_return_bool(false);
+ }
+ } else {
+ if (*mode == 'w') {
+ /* Remove old log file in case we recycled sequence numbers. */
+ (void)unlinkat(dfd, file, 0);
+ }
+ }
+ debug_return_bool(true);
+}
+
+#ifdef HAVE_ZLIB_H
+static const char *
+gzstrerror(gzFile file)
+{
+ const char *errstr;
+ int errnum;
+
+ errstr = gzerror(file, &errnum);
+ if (errnum == Z_ERRNO)
+ errstr = strerror(errno);
+
+ return errstr;
+}
+#endif /* HAVE_ZLIB_H */
+
+/*
+ * Close an I/O log.
+ */
+bool
+iolog_close(struct iolog_file *iol, const char **errstr)
+{
+ bool ret = true;
+ debug_decl(iolog_close, SUDO_DEBUG_UTIL);
+
+#ifdef HAVE_ZLIB_H
+ if (iol->compressed) {
+ int errnum;
+
+ /* Must check error indicator before closing. */
+ if (iol->writable) {
+ if (gzflush(iol->fd.g, Z_SYNC_FLUSH) != Z_OK) {
+ ret = false;
+ if (errstr != NULL)
+ *errstr = gzstrerror(iol->fd.g);
+ }
+ }
+ errnum = gzclose(iol->fd.g);
+ if (ret && errnum != Z_OK) {
+ ret = false;
+ if (errstr != NULL)
+ *errstr = errnum == Z_ERRNO ? strerror(errno) : "unknown error";
+ }
+ } else
+#endif
+ if (fclose(iol->fd.f) != 0) {
+ ret = false;
+ if (errstr != NULL)
+ *errstr = strerror(errno);
+ }
+
+ debug_return_bool(ret);
+}
+
+/*
+ * I/O log wrapper for fseek/gzseek.
+ */
+off_t
+iolog_seek(struct iolog_file *iol, off_t offset, int whence)
+{
+ off_t ret;
+ //debug_decl(iolog_seek, SUDO_DEBUG_UTIL);
+
+#ifdef HAVE_ZLIB_H
+ if (iol->compressed)
+ ret = gzseek(iol->fd.g, offset, whence);
+ else
+#endif
+ ret = fseeko(iol->fd.f, offset, whence);
+
+ //debug_return_off_t(ret);
+ return ret;
+}
+
+/*
+ * I/O log wrapper for rewind/gzrewind.
+ */
+void
+iolog_rewind(struct iolog_file *iol)
+{
+ debug_decl(iolog_rewind, SUDO_DEBUG_UTIL);
+
+#ifdef HAVE_ZLIB_H
+ if (iol->compressed)
+ (void)gzrewind(iol->fd.g);
+ else
+#endif
+ rewind(iol->fd.f);
+
+ debug_return;
+}
+
+/*
+ * Read from a (possibly compressed) I/O log file.
+ */
+ssize_t
+iolog_read(struct iolog_file *iol, void *buf, size_t nbytes,
+ const char **errstr)
+{
+ ssize_t nread;
+ debug_decl(iolog_read, SUDO_DEBUG_UTIL);
+
+ if (nbytes > UINT_MAX) {
+ errno = EINVAL;
+ if (errstr != NULL)
+ *errstr = strerror(errno);
+ debug_return_ssize_t(-1);
+ }
+
+#ifdef HAVE_ZLIB_H
+ if (iol->compressed) {
+ if ((nread = gzread(iol->fd.g, buf, nbytes)) == -1) {
+ if (errstr != NULL)
+ *errstr = gzstrerror(iol->fd.g);
+ }
+ } else
+#endif
+ {
+ nread = (ssize_t)fread(buf, 1, nbytes, iol->fd.f);
+ if (nread == 0 && ferror(iol->fd.f)) {
+ nread = -1;
+ if (errstr != NULL)
+ *errstr = strerror(errno);
+ }
+ }
+ debug_return_ssize_t(nread);
+}
+
+/*
+ * Write to an I/O log, optionally compressing.
+ */
+ssize_t
+iolog_write(struct iolog_file *iol, const void *buf, size_t len,
+ const char **errstr)
+{
+ ssize_t ret;
+ debug_decl(iolog_write, SUDO_DEBUG_UTIL);
+
+ if (len > UINT_MAX) {
+ errno = EINVAL;
+ if (errstr != NULL)
+ *errstr = strerror(errno);
+ debug_return_ssize_t(-1);
+ }
+
+#ifdef HAVE_ZLIB_H
+ if (iol->compressed) {
+ ret = gzwrite(iol->fd.g, (const voidp)buf, len);
+ if (ret == 0) {
+ ret = -1;
+ if (errstr != NULL)
+ *errstr = gzstrerror(iol->fd.g);
+ goto done;
+ }
+ if (iolog_flush) {
+ if (gzflush(iol->fd.g, Z_SYNC_FLUSH) != Z_OK) {
+ ret = -1;
+ if (errstr != NULL)
+ *errstr = gzstrerror(iol->fd.g);
+ goto done;
+ }
+ }
+ } else
+#endif
+ {
+ ret = fwrite(buf, 1, len, iol->fd.f);
+ if (ret == 0) {
+ ret = -1;
+ if (errstr != NULL)
+ *errstr = strerror(errno);
+ goto done;
+ }
+ if (iolog_flush) {
+ if (fflush(iol->fd.f) != 0) {
+ ret = -1;
+ if (errstr != NULL)
+ *errstr = strerror(errno);
+ goto done;
+ }
+ }
+ }
+
+done:
+ debug_return_ssize_t(ret);
+}
+
+/*
+ * Returns true if at end of I/O log file, else false.
+ */
+bool
+iolog_eof(struct iolog_file *iol)
+{
+ bool ret;
+ debug_decl(iolog_eof, SUDO_DEBUG_UTIL);
+
+#ifdef HAVE_ZLIB_H
+ if (iol->compressed)
+ ret = gzeof(iol->fd.g) == 1;
+ else
+#endif
+ ret = feof(iol->fd.f) == 1;
+ debug_return_int(ret);
+}
+
+void
+iolog_clearerr(struct iolog_file *iol)
+{
+ debug_decl(iolog_eof, SUDO_DEBUG_UTIL);
+
+#ifdef HAVE_ZLIB_H
+ if (iol->compressed)
+ gzclearerr(iol->fd.g);
+ else
+#endif
+ clearerr(iol->fd.f);
+ debug_return;
+}
+
+/*
+ * Like gets() but for struct iolog_file.
+ */
+char *
+iolog_gets(struct iolog_file *iol, char *buf, size_t nbytes,
+ const char **errstr)
+{
+ char *str;
+ debug_decl(iolog_gets, SUDO_DEBUG_UTIL);
+
+ if (nbytes > UINT_MAX) {
+ errno = EINVAL;
+ if (errstr != NULL)
+ *errstr = strerror(errno);
+ debug_return_str(NULL);
+ }
+
+#ifdef HAVE_ZLIB_H
+ if (iol->compressed) {
+ if ((str = gzgets(iol->fd.g, buf, nbytes)) == NULL) {
+ if (errstr != NULL)
+ *errstr = gzstrerror(iol->fd.g);
+ }
+ } else
+#endif
+ {
+ if ((str = fgets(buf, nbytes, iol->fd.f)) == NULL) {
+ if (errstr != NULL)
+ *errstr = strerror(errno);
+ }
+ }
+ debug_return_str(str);
+}
+
+/*
+ * Write the legacy I/O log file that contains the user and command info.
+ * This file is not compressed.
+ */
+static bool
+iolog_write_info_file_legacy(int dfd, struct eventlog *evlog)
+{
+ char * const *av;
+ FILE *fp;
+ int error, fd;
+ debug_decl(iolog_info_write_log, SUDO_DEBUG_UTIL);
+
+ fd = iolog_openat(dfd, "log", O_CREAT|O_TRUNC|O_WRONLY);
+ if (fd == -1 || (fp = fdopen(fd, "w")) == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to open %s/log", evlog->iolog_path);
+ if (fd != -1)
+ close(fd);
+ debug_return_bool(false);
+ }
+ if (fchown(fd, iolog_uid, iolog_gid) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to fchown %d:%d %s/log", __func__,
+ (int)iolog_uid, (int)iolog_gid, evlog->iolog_path);
+ }
+
+ fprintf(fp, "%lld:%s:%s:%s:%s:%d:%d\n%s\n",
+ (long long)evlog->submit_time.tv_sec,
+ evlog->submituser ? evlog->submituser : "unknown",
+ evlog->runuser ? evlog->runuser : RUNAS_DEFAULT,
+ evlog->rungroup ? evlog->rungroup : "",
+ evlog->ttyname ? evlog->ttyname : "unknown",
+ evlog->lines, evlog->columns,
+ evlog->cwd ? evlog->cwd : "unknown");
+ fputs(evlog->command ? evlog->command : "unknown", fp);
+ for (av = evlog->argv + 1; *av != NULL; av++) {
+ fputc(' ', fp);
+ fputs(*av, fp);
+ }
+ fputc('\n', fp);
+ fflush(fp);
+ if ((error = ferror(fp))) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to write to I/O log file %s/log", evlog->iolog_path);
+ }
+ fclose(fp);
+
+ debug_return_bool(!error);
+}
+
+/*
+ * Write the "log.json" file that contains the user and command info.
+ * This file is not compressed.
+ */
+static bool
+iolog_write_info_file_json(int dfd, struct eventlog *evlog)
+{
+ struct json_container json;
+ struct json_value json_value;
+ bool ret = false;
+ FILE *fp = NULL;
+ int fd = -1;
+ debug_decl(iolog_write_info_file_json, SUDO_DEBUG_UTIL);
+
+ if (!sudo_json_init(&json, 4, false, false))
+ debug_return_bool(false);
+
+ /* Timestamp */
+ if (!sudo_json_open_object(&json, "timestamp"))
+ goto oom;
+
+ json_value.type = JSON_NUMBER;
+ json_value.u.number = evlog->submit_time.tv_sec;
+ if (!sudo_json_add_value(&json, "seconds", &json_value))
+ goto oom;
+
+ json_value.type = JSON_NUMBER;
+ json_value.u.number = evlog->submit_time.tv_nsec;
+ if (!sudo_json_add_value(&json, "nanoseconds", &json_value))
+ goto oom;
+
+ if (!sudo_json_close_object(&json))
+ goto oom;
+
+ if (!eventlog_store_json(&json, evlog))
+ goto done;
+
+ fd = iolog_openat(dfd, "log.json", O_CREAT|O_TRUNC|O_WRONLY);
+ if (fd == -1 || (fp = fdopen(fd, "w")) == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to open %s/log.json", evlog->iolog_path);
+ goto done;
+ }
+
+ if (fchown(fd, iolog_uid, iolog_gid) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to fchown %d:%d %s/log", __func__,
+ (int)iolog_uid, (int)iolog_gid, evlog->iolog_path);
+ }
+ fd = -1;
+
+ fprintf(fp, "{%s\n}\n", sudo_json_get_buf(&json));
+ fflush(fp);
+ if (ferror(fp)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to write to I/O log file %s/log.json", evlog->iolog_path);
+ goto done;
+ }
+
+ ret = true;
+ goto done;
+
+oom:
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+done:
+ sudo_json_free(&json);
+ if (fp != NULL)
+ fclose(fp);
+ if (fd != -1)
+ close(fd);
+
+ debug_return_bool(ret);
+}
+
+/*
+ * Write the I/O log and log.json files that contain user and command info.
+ * These files are not compressed.
+ */
+bool
+iolog_write_info_file(int dfd, struct eventlog *evlog)
+{
+ debug_decl(iolog_write_info_file, SUDO_DEBUG_UTIL);
+
+ if (!iolog_write_info_file_legacy(dfd, evlog))
+ debug_return_bool(false);
+ if (!iolog_write_info_file_json(dfd, evlog))
+ debug_return_bool(false);
+
+ debug_return_bool(true);
+}
+
+/*
+ * Map IOFD_* -> name.
+ */
+const char *
+iolog_fd_to_name(int iofd)
+{
+ const char *ret;
+ debug_decl(iolog_fd_to_name, SUDO_DEBUG_UTIL);
+
+ switch (iofd) {
+ case IOFD_STDIN:
+ ret = "stdin";
+ break;
+ case IOFD_STDOUT:
+ ret = "stdout";
+ break;
+ case IOFD_STDERR:
+ ret = "stderr";
+ break;
+ case IOFD_TTYIN:
+ ret = "ttyin";
+ break;
+ case IOFD_TTYOUT:
+ ret = "ttyout";
+ break;
+ case IOFD_TIMING:
+ ret = "timing";
+ break;
+ default:
+ ret = "unknown";
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: unexpected iofd %d",
+ __func__, iofd);
+ break;
+ }
+ debug_return_const_str(ret);
+}
diff --git a/lib/iolog/iolog_json.c b/lib/iolog/iolog_json.c
new file mode 100644
index 0000000..684f938
--- /dev/null
+++ b/lib/iolog/iolog_json.c
@@ -0,0 +1,789 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_eventlog.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_iolog.h"
+#include "sudo_util.h"
+
+#include "iolog_json.h"
+
+struct json_stack {
+ unsigned int depth;
+ unsigned int maxdepth;
+ struct json_object *frames[64];
+};
+#define JSON_STACK_INTIALIZER(s) { 0, nitems((s).frames) };
+
+static bool
+json_store_columns(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_columns, SUDO_DEBUG_UTIL);
+
+ if (item->u.number < 1 || item->u.number > INT_MAX) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "tty cols %lld: out of range", item->u.number);
+ evlog->columns = 0;
+ debug_return_bool(false);
+ }
+
+ evlog->columns = item->u.number;
+ debug_return_bool(true);
+}
+
+static bool
+json_store_command(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_command, SUDO_DEBUG_UTIL);
+
+ /*
+ * Note: struct eventlog must store command + args.
+ * We don't have argv yet so we append the args later.
+ */
+ evlog->command = item->u.string;
+ item->u.string = NULL;
+ debug_return_bool(true);
+}
+
+static bool
+json_store_lines(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_lines, SUDO_DEBUG_UTIL);
+
+ if (item->u.number < 1 || item->u.number > INT_MAX) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "tty lines %lld: out of range", item->u.number);
+ evlog->lines = 0;
+ debug_return_bool(false);
+ }
+
+ evlog->lines = item->u.number;
+ debug_return_bool(true);
+}
+
+char **
+json_array_to_strvec(struct json_object *array)
+{
+ struct json_item *item;
+ int len = 0;
+ char **ret;
+ debug_decl(json_array_to_strvec, SUDO_DEBUG_UTIL);
+
+ TAILQ_FOREACH(item, &array->items, entries) {
+ /* Can only convert arrays of string. */
+ if (item->type != JSON_STRING) {
+ sudo_warnx(U_("expected JSON_STRING, got %d"), item->type);
+ debug_return_ptr(NULL);
+ }
+ len++;
+ }
+ if ((ret = reallocarray(NULL, len + 1, sizeof(char *))) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_ptr(NULL);
+ }
+ len = 0;
+ TAILQ_FOREACH(item, &array->items, entries) {
+ ret[len++] = item->u.string;
+ item->u.string = NULL;
+ }
+ ret[len] = NULL;
+
+ debug_return_ptr(ret);
+}
+
+static bool
+json_store_runargv(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_runargv, SUDO_DEBUG_UTIL);
+
+ evlog->argv = json_array_to_strvec(&item->u.child);
+
+ debug_return_bool(evlog->argv != NULL);
+}
+
+static bool
+json_store_runenv(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_runenv, SUDO_DEBUG_UTIL);
+
+ evlog->envp = json_array_to_strvec(&item->u.child);
+
+ debug_return_bool(evlog->envp != NULL);
+}
+
+static bool
+json_store_rungid(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_rungid, SUDO_DEBUG_UTIL);
+
+ evlog->rungid = (gid_t)item->u.number;
+ debug_return_bool(true);
+}
+
+static bool
+json_store_rungroup(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_rungroup, SUDO_DEBUG_UTIL);
+
+ evlog->rungroup = item->u.string;
+ item->u.string = NULL;
+ debug_return_bool(true);
+}
+
+static bool
+json_store_runuid(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_runuid, SUDO_DEBUG_UTIL);
+
+ evlog->runuid = (uid_t)item->u.number;
+ debug_return_bool(true);
+}
+
+static bool
+json_store_runuser(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_runuser, SUDO_DEBUG_UTIL);
+
+ evlog->runuser = item->u.string;
+ item->u.string = NULL;
+ debug_return_bool(true);
+}
+
+static bool
+json_store_runchroot(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_runchroot, SUDO_DEBUG_UTIL);
+
+ evlog->runchroot = item->u.string;
+ item->u.string = NULL;
+ debug_return_bool(true);
+}
+
+static bool
+json_store_runcwd(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_runcwd, SUDO_DEBUG_UTIL);
+
+ evlog->runcwd = item->u.string;
+ item->u.string = NULL;
+ debug_return_bool(true);
+}
+
+static bool
+json_store_submitcwd(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_submitcwd, SUDO_DEBUG_UTIL);
+
+ evlog->cwd = item->u.string;
+ item->u.string = NULL;
+ debug_return_bool(true);
+}
+
+static bool
+json_store_submithost(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_submithost, SUDO_DEBUG_UTIL);
+
+ evlog->submithost = item->u.string;
+ item->u.string = NULL;
+ debug_return_bool(true);
+}
+
+static bool
+json_store_submituser(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_submituser, SUDO_DEBUG_UTIL);
+
+ evlog->submituser = item->u.string;
+ item->u.string = NULL;
+ debug_return_bool(true);
+}
+
+static bool
+json_store_timestamp(struct json_item *item, struct eventlog *evlog)
+{
+ struct json_object *object;
+ debug_decl(json_store_timestamp, SUDO_DEBUG_UTIL);
+
+ object = &item->u.child;
+ TAILQ_FOREACH(item, &object->items, entries) {
+ if (item->type != JSON_NUMBER)
+ continue;
+ if (strcmp(item->name, "seconds") == 0) {
+ evlog->submit_time.tv_sec = item->u.number;
+ continue;
+ }
+ if (strcmp(item->name, "nanoseconds") == 0) {
+ evlog->submit_time.tv_nsec = item->u.number;
+ continue;
+ }
+ }
+ debug_return_bool(true);
+}
+
+static bool
+json_store_ttyname(struct json_item *item, struct eventlog *evlog)
+{
+ debug_decl(json_store_ttyname, SUDO_DEBUG_UTIL);
+
+ evlog->ttyname = item->u.string;
+ item->u.string = NULL;
+ debug_return_bool(true);
+}
+
+static struct iolog_json_key {
+ const char *name;
+ enum json_value_type type;
+ bool (*setter)(struct json_item *, struct eventlog *);
+} iolog_json_keys[] = {
+ { "columns", JSON_NUMBER, json_store_columns },
+ { "command", JSON_STRING, json_store_command },
+ { "lines", JSON_NUMBER, json_store_lines },
+ { "runargv", JSON_ARRAY, json_store_runargv },
+ { "runenv", JSON_ARRAY, json_store_runenv },
+ { "rungid", JSON_ID, json_store_rungid },
+ { "rungroup", JSON_STRING, json_store_rungroup },
+ { "runuid", JSON_ID, json_store_runuid },
+ { "runuser", JSON_STRING, json_store_runuser },
+ { "runchroot", JSON_STRING, json_store_runchroot },
+ { "runcwd", JSON_STRING, json_store_runcwd },
+ { "submitcwd", JSON_STRING, json_store_submitcwd },
+ { "submithost", JSON_STRING, json_store_submithost },
+ { "submituser", JSON_STRING, json_store_submituser },
+ { "timestamp", JSON_OBJECT, json_store_timestamp },
+ { "ttyname", JSON_STRING, json_store_ttyname },
+ { NULL }
+};
+
+static struct json_item *
+new_json_item(enum json_value_type type, char *name, unsigned int lineno)
+{
+ struct json_item *item;
+ debug_decl(new_json_item, SUDO_DEBUG_UTIL);
+
+ if ((item = malloc(sizeof(*item))) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__,
+ U_("unable to allocate memory"));
+ debug_return_ptr(NULL);
+ }
+ item->name = name;
+ item->type = type;
+ item->lineno = lineno;
+
+ debug_return_ptr(item);
+}
+
+static char *
+json_parse_string(char **strp)
+{
+ char *dst, *end, *ret, *src = *strp + 1;
+ size_t len;
+ debug_decl(json_parse_string, SUDO_DEBUG_UTIL);
+
+ for (end = src; *end != '"' && *end != '\0'; end++) {
+ if (end[0] == '\\' && end[1] == '"')
+ end++;
+ }
+ if (*end != '"') {
+ sudo_warnx("%s", U_("missing double quote in name"));
+ debug_return_str(NULL);
+ }
+ len = (size_t)(end - src);
+
+ /* Copy string, flattening escaped chars. */
+ dst = ret = malloc(len + 1);
+ if (dst == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ while (src < end) {
+ char ch = *src++;
+ /* TODO: handle unicode escapes */
+ if (ch == '\\') {
+ switch (*src) {
+ case 'b':
+ ch = '\b';
+ break;
+ case 'f':
+ ch = '\f';
+ break;
+ case 'n':
+ ch = '\n';
+ break;
+ case 'r':
+ ch = '\r';
+ break;
+ case 't':
+ ch = '\t';
+ break;
+ case '"':
+ case '\\':
+ default:
+ /* Note: a bare \ at the end of a string will be removed. */
+ ch = *src;
+ break;
+ }
+ src++;
+ }
+ *dst++ = ch;
+ }
+ *dst = '\0';
+
+ /* Trim trailing whitespace. */
+ do {
+ end++;
+ } while (isspace((unsigned char)*end));
+ *strp = end;
+
+ debug_return_str(ret);
+}
+
+void
+free_json_items(struct json_item_list *items)
+{
+ struct json_item *item;
+ debug_decl(free_json_items, SUDO_DEBUG_UTIL);
+
+ while ((item = TAILQ_FIRST(items)) != NULL) {
+ TAILQ_REMOVE(items, item, entries);
+ switch (item->type) {
+ case JSON_STRING:
+ free(item->u.string);
+ break;
+ case JSON_ARRAY:
+ case JSON_OBJECT:
+ free_json_items(&item->u.child.items);
+ break;
+ default:
+ break;
+ }
+ free(item->name);
+ free(item);
+ }
+
+ debug_return;
+}
+
+static bool
+iolog_parse_json_object(struct json_object *object, struct eventlog *evlog)
+{
+ struct json_item *item;
+ bool ret = false;
+ debug_decl(iolog_parse_json_object, SUDO_DEBUG_UTIL);
+
+ /* First object holds all the actual data. */
+ item = TAILQ_FIRST(&object->items);
+ if (item->type != JSON_OBJECT) {
+ sudo_warnx(U_("expected JSON_OBJECT, got %d"), item->type);
+ goto done;
+ }
+ object = &item->u.child;
+
+ TAILQ_FOREACH(item, &object->items, entries) {
+ struct iolog_json_key *key;
+
+ /* lookup name */
+ for (key = iolog_json_keys; key->name != NULL; key++) {
+ if (strcmp(item->name, key->name) == 0)
+ break;
+ }
+ if (key->name == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
+ "%s: unknown key %s", __func__, item->name);
+ } else if (key->type != item->type &&
+ (key->type != JSON_ID || item->type != JSON_NUMBER)) {
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
+ "%s: key mismatch %s type %d, expected %d", __func__,
+ item->name, item->type, key->type);
+ goto done;
+ } else {
+ /* Matched name and type. */
+ if (!key->setter(item, evlog)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to store %s", key->name);
+ goto done;
+ }
+ }
+ }
+
+ /* Merge cmd and argv as sudoreplay expects. */
+ if (evlog->command != NULL && evlog->argv != NULL) {
+ size_t len = strlen(evlog->command) + 1;
+ char *newcmd;
+ int ac;
+
+ /* Skip argv[0], we use evlog->command instead. */
+ for (ac = 1; evlog->argv[ac] != NULL; ac++)
+ len += strlen(evlog->argv[ac]) + 1;
+
+ if ((newcmd = malloc(len)) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ goto done;
+ }
+
+ /* TODO: optimize this. */
+ if (strlcpy(newcmd, evlog->command, len) >= len)
+ sudo_fatalx(U_("internal error, %s overflow"), __func__);
+ for (ac = 1; evlog->argv[ac] != NULL; ac++) {
+ if (strlcat(newcmd, " ", len) >= len)
+ sudo_fatalx(U_("internal error, %s overflow"), __func__);
+ if (strlcat(newcmd, evlog->argv[ac], len) >= len)
+ sudo_fatalx(U_("internal error, %s overflow"), __func__);
+ }
+
+ free(evlog->command);
+ evlog->command = newcmd;
+ }
+
+ ret = true;
+
+done:
+ debug_return_bool(ret);
+}
+
+static bool
+json_insert_bool(struct json_item_list *items, char *name, bool value,
+ unsigned int lineno)
+{
+ struct json_item *item;
+ debug_decl(json_insert_bool, SUDO_DEBUG_UTIL);
+
+ if ((item = new_json_item(JSON_BOOL, name, lineno)) == NULL)
+ debug_return_bool(false);
+ item->u.boolean = value;
+ TAILQ_INSERT_TAIL(items, item, entries);
+
+ debug_return_bool(true);
+}
+
+static bool
+json_insert_null(struct json_item_list *items, char *name, unsigned int lineno)
+{
+ struct json_item *item;
+ debug_decl(json_insert_null, SUDO_DEBUG_UTIL);
+
+ if ((item = new_json_item(JSON_NULL, name, lineno)) == NULL)
+ debug_return_bool(false);
+ TAILQ_INSERT_TAIL(items, item, entries);
+
+ debug_return_bool(true);
+}
+
+static bool
+json_insert_num(struct json_item_list *items, char *name, long long value,
+ unsigned int lineno)
+{
+ struct json_item *item;
+ debug_decl(json_insert_num, SUDO_DEBUG_UTIL);
+
+ if ((item = new_json_item(JSON_NUMBER, name, lineno)) == NULL)
+ debug_return_bool(false);
+ item->u.number = value;
+ TAILQ_INSERT_TAIL(items, item, entries);
+
+ debug_return_bool(true);
+}
+
+static bool
+json_insert_str(struct json_item_list *items, char *name, char **strp,
+ unsigned int lineno)
+{
+ struct json_item *item;
+ debug_decl(json_insert_str, SUDO_DEBUG_UTIL);
+
+ if ((item = new_json_item(JSON_STRING, name, lineno)) == NULL)
+ debug_return_bool(false);
+ item->u.string = json_parse_string(strp);
+ if (item->u.string == NULL) {
+ free(item);
+ debug_return_bool(false);
+ }
+ TAILQ_INSERT_TAIL(items, item, entries);
+
+ debug_return_bool(true);
+}
+
+static struct json_object *
+json_stack_push(struct json_stack *stack, struct json_item_list *items,
+ struct json_object *frame, enum json_value_type type, char *name,
+ unsigned int lineno)
+{
+ struct json_item *item;
+ debug_decl(iolog_parse_loginfo_json, SUDO_DEBUG_UTIL);
+
+ /* Allocate a new item and insert it into the list. */
+ if ((item = new_json_item(type, name, lineno)) == NULL)
+ debug_return_ptr(NULL);
+ TAILQ_INIT(&item->u.child.items);
+ item->u.child.parent = item;
+ TAILQ_INSERT_TAIL(items, item, entries);
+
+ /* Push the current frame onto the stack. */
+ if (stack->depth == stack->maxdepth)
+ sudo_fatalx(U_("internal error, %s overflow"), __func__);
+ stack->frames[stack->depth++] = frame;
+
+ /* Return the new frame */
+ debug_return_ptr(&item->u.child);
+}
+
+/* Only expect a value if a name is defined or we are in an array. */
+#define expect_value (name != NULL || (frame->parent != NULL && frame->parent->type == JSON_ARRAY))
+
+bool
+iolog_parse_json(FILE *fp, const char *filename, struct json_object *root)
+{
+ struct json_object *frame = root;
+ struct json_stack stack = JSON_STACK_INTIALIZER(stack);
+ unsigned int lineno = 0;
+ char *name = NULL;
+ char *buf = NULL;
+ size_t bufsize = 0;
+ ssize_t len;
+ bool ret = false;
+ long long num;
+ char ch;
+ debug_decl(iolog_parse_json, SUDO_DEBUG_UTIL);
+
+ root->parent = NULL;
+ TAILQ_INIT(&root->items);
+
+ while ((len = getdelim(&buf, &bufsize, '\n', fp)) != -1) {
+ char *cp = buf;
+ char *ep = buf + len - 1;
+
+ lineno++;
+
+ /* Trim trailing whitespace. */
+ while (ep > cp && isspace((unsigned char)*ep))
+ ep--;
+ ep[1] = '\0';
+
+ for (;;) {
+ const char *errstr;
+
+ /* Trim leading whitespace, skip blank lines. */
+ while (isspace((unsigned char)*cp))
+ cp++;
+
+ /* Strip out commas. TODO: require commas between values. */
+ if (*cp == ',') {
+ cp++;
+ while (isspace((unsigned char)*cp))
+ cp++;
+ }
+
+ if (*cp == '\0')
+ break;
+
+ switch (*cp) {
+ case '{':
+ cp++;
+ frame = json_stack_push(&stack, &frame->items, frame,
+ JSON_OBJECT, name, lineno);
+ if (frame == NULL)
+ goto parse_error;
+ name = NULL;
+ break;
+ case '}':
+ cp++;
+ if (stack.depth == 0 || frame->parent == NULL ||
+ frame->parent->type != JSON_OBJECT) {
+ sudo_warnx("%s", U_("unmatched close brace"));
+ goto parse_error;
+ }
+ frame = stack.frames[--stack.depth];
+ break;
+ case '[':
+ cp++;
+ if (frame->parent == NULL) {
+ /* Must have an enclosing object. */
+ sudo_warnx("%s", U_("unexpected array"));
+ goto parse_error;
+ }
+ frame = json_stack_push(&stack, &frame->items, frame,
+ JSON_ARRAY, name, lineno);
+ if (frame == NULL)
+ goto parse_error;
+ name = NULL;
+ break;
+ case ']':
+ cp++;
+ if (stack.depth == 0 || frame->parent == NULL ||
+ frame->parent->type != JSON_ARRAY) {
+ sudo_warnx("%s", U_("unmatched close bracket"));
+ goto parse_error;
+ }
+ frame = stack.frames[--stack.depth];
+ break;
+ case '"':
+ if (frame->parent == NULL) {
+ /* Must have an enclosing object. */
+ sudo_warnx("%s", U_("unexpected string"));
+ goto parse_error;
+ }
+
+ if (!expect_value) {
+ /* Parse "name": */
+ if ((name = json_parse_string(&cp)) == NULL)
+ goto parse_error;
+ /* TODO: allow colon on next line? */
+ if (*cp++ != ':') {
+ sudo_warnx("%s", U_("missing colon after name"));
+ goto parse_error;
+ }
+ } else {
+ if (!json_insert_str(&frame->items, name, &cp, lineno))
+ goto parse_error;
+ name = NULL;
+ }
+ break;
+ case 't':
+ if (!expect_value) {
+ sudo_warnx("%s", U_("unexpected boolean"));
+ goto parse_error;
+ }
+ if (strncmp(cp, "true", sizeof("true") - 1) != 0)
+ goto parse_error;
+ cp += sizeof("true") - 1;
+ if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
+ goto parse_error;
+
+ if (!json_insert_bool(&frame->items, name, true, lineno))
+ goto parse_error;
+ name = NULL;
+ break;
+ case 'f':
+ if (!expect_value) {
+ sudo_warnx("%s", U_("unexpected boolean"));
+ goto parse_error;
+ }
+ if (strncmp(cp, "false", sizeof("false") - 1) != 0)
+ goto parse_error;
+ cp += sizeof("false") - 1;
+ if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
+ goto parse_error;
+
+ if (!json_insert_bool(&frame->items, name, false, lineno))
+ goto parse_error;
+ name = NULL;
+ break;
+ case 'n':
+ if (!expect_value) {
+ sudo_warnx("%s", U_("unexpected boolean"));
+ goto parse_error;
+ }
+ if (strncmp(cp, "null", sizeof("null") - 1) != 0)
+ goto parse_error;
+ cp += sizeof("null") - 1;
+ if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
+ goto parse_error;
+
+ if (!json_insert_null(&frame->items, name, lineno))
+ goto parse_error;
+ name = NULL;
+ break;
+ case '+': case '-': case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7': case '8': case '9':
+ if (!expect_value) {
+ sudo_warnx("%s", U_("unexpected number"));
+ goto parse_error;
+ }
+ /* XXX - strtonumx() would be simpler here. */
+ len = strcspn(cp, " \f\n\r\t\v,");
+ ch = cp[len];
+ cp[len] = '\0';
+ num = sudo_strtonum(cp, LLONG_MIN, LLONG_MAX, &errstr);
+ if (errstr != NULL) {
+ sudo_warnx(U_("%s: %s"), cp, U_(errstr));
+ goto parse_error;
+ }
+ cp += len;
+ *cp = ch;
+
+ if (!json_insert_num(&frame->items, name, num, lineno))
+ goto parse_error;
+ name = NULL;
+ break;
+ default:
+ goto parse_error;
+ }
+ }
+ }
+ if (stack.depth != 0) {
+ frame = stack.frames[stack.depth - 1];
+ if (frame->parent == NULL || frame->parent->type == JSON_OBJECT)
+ sudo_warnx("%s", U_("unmatched close brace"));
+ else
+ sudo_warnx("%s", U_("unmatched close bracket"));
+ goto parse_error;
+ }
+
+ ret = true;
+ goto done;
+
+parse_error:
+ sudo_warnx(U_("%s:%u unable to parse \"%s\""), filename, lineno, buf);
+done:
+ free(buf);
+ free(name);
+ if (!ret)
+ free_json_items(&root->items);
+
+ debug_return_bool(ret);
+}
+
+bool
+iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct eventlog *evlog)
+{
+ struct json_object root;
+ bool ret = false;
+ debug_decl(iolog_parse_loginfo_json, SUDO_DEBUG_UTIL);
+
+ if (iolog_parse_json(fp, iolog_dir, &root)) {
+ /* Walk the stack and parse entries. */
+ ret = iolog_parse_json_object(&root, evlog);
+
+ /* Cleanup. */
+ free_json_items(&root.items);
+ }
+
+ debug_return_bool(ret);
+}
diff --git a/lib/iolog/iolog_json.h b/lib/iolog/iolog_json.h
new file mode 100644
index 0000000..bc28dfa
--- /dev/null
+++ b/lib/iolog/iolog_json.h
@@ -0,0 +1,50 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IOLOG_JSON_H
+#define IOLOG_JSON_H
+
+#include "sudo_json.h"
+#include "sudo_queue.h"
+
+TAILQ_HEAD(json_item_list, json_item);
+
+struct json_object {
+ struct json_item *parent;
+ struct json_item_list items;
+};
+
+struct json_item {
+ TAILQ_ENTRY(json_item) entries;
+ char *name; /* may be NULL for first brace */
+ unsigned int lineno;
+ enum json_value_type type;
+ union {
+ struct json_object child;
+ char *string;
+ long long number;
+ id_t id;
+ bool boolean;
+ } u;
+};
+
+void free_json_items(struct json_item_list *items);
+bool iolog_parse_json(FILE *fp, const char *filename, struct json_object *root);
+char **json_array_to_strvec(struct json_object *array);
+
+#endif /* IOLOG_JSON_H */
diff --git a/lib/iolog/iolog_path.c b/lib/iolog/iolog_path.c
new file mode 100644
index 0000000..1f34c0b
--- /dev/null
+++ b/lib/iolog/iolog_path.c
@@ -0,0 +1,130 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011-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 <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_iolog.h"
+#include "sudo_util.h"
+
+/*
+ * Expand any escape sequences in inpath, returning the expanded path.
+ */
+bool
+expand_iolog_path(const char *inpath, char *path, size_t pathlen,
+ const struct iolog_path_escape *escapes, void *closure)
+{
+ char *dst, *pathend, tmpbuf[PATH_MAX];
+ const char *endbrace, *src;
+ bool strfit = false;
+ size_t len;
+ debug_decl(expand_iolog_path, SUDO_DEBUG_UTIL);
+
+ /* Collapse multiple leading slashes. */
+ while (inpath[0] == '/' && inpath[1] == '/')
+ inpath++;
+
+ pathend = path + pathlen;
+ for (src = inpath, dst = path; *src != '\0'; src++) {
+ if (src[0] == '%') {
+ if (src[1] == '{') {
+ endbrace = strchr(src + 2, '}');
+ if (endbrace != NULL) {
+ const struct iolog_path_escape *esc;
+ len = (size_t)(endbrace - src - 2);
+ for (esc = escapes; esc->name != NULL; esc++) {
+ if (strncmp(src + 2, esc->name, len) == 0 &&
+ esc->name[len] == '\0')
+ break;
+ }
+ if (esc->name != NULL) {
+ len = esc->copy_fn(dst, (size_t)(pathend - dst),
+ closure);
+ if (len >= (size_t)(pathend - dst))
+ goto bad;
+ dst += len;
+ src = endbrace;
+ continue;
+ }
+ }
+ } else if (src[1] == '%') {
+ /* Collapse %% -> % */
+ src++;
+ } else {
+ /* May need strftime() */
+ strfit = true;
+ }
+ }
+ /* Need at least 2 chars, including the NUL terminator. */
+ if (dst + 1 >= pathend)
+ goto bad;
+ *dst++ = *src;
+ }
+
+ /* Trim trailing slashes and NUL terminate. */
+ while (dst > path && dst[-1] == '/')
+ dst--;
+ *dst = '\0';
+
+ /* Expand strftime escapes as needed. */
+ if (strfit) {
+ time_t now;
+ struct tm *timeptr;
+
+ time(&now);
+ if ((timeptr = localtime(&now)) == NULL)
+ goto bad;
+
+ /* We only call strftime() on the current part of the buffer. */
+ tmpbuf[sizeof(tmpbuf) - 1] = '\0';
+ len = strftime(tmpbuf, sizeof(tmpbuf), path, timeptr);
+
+ if (len == 0 || tmpbuf[sizeof(tmpbuf) - 1] != '\0')
+ goto bad; /* strftime() failed, buf too small? */
+
+ if (len >= (size_t)(pathend - path))
+ goto bad; /* expanded buffer too big to fit. */
+ memcpy(path, tmpbuf, len);
+ dst = path + len;
+ *dst = '\0';
+ }
+
+ debug_return_bool(true);
+bad:
+ debug_return_bool(false);
+}
diff --git a/lib/iolog/iolog_util.c b/lib/iolog/iolog_util.c
new file mode 100644
index 0000000..7af33d9
--- /dev/null
+++ b/lib/iolog/iolog_util.c
@@ -0,0 +1,431 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_eventlog.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_iolog.h"
+#include "sudo_util.h"
+
+static int timing_event_adj;
+
+static bool
+iolog_parse_loginfo_legacy(FILE *fp, const char *iolog_dir,
+ struct eventlog *evlog)
+{
+ char *buf = NULL, *cp, *ep;
+ const char *errstr;
+ size_t bufsize = 0, cwdsize = 0, cmdsize = 0;
+ bool ret = false;
+ debug_decl(iolog_parse_loginfo_legacy, SUDO_DEBUG_UTIL);
+
+ /*
+ * Info file has three lines:
+ * 1) a log info line
+ * 2) cwd
+ * 3) command with args
+ */
+ if (getdelim(&buf, &bufsize, '\n', fp) == -1 ||
+ getdelim(&evlog->cwd, &cwdsize, '\n', fp) == -1 ||
+ getdelim(&evlog->command, &cmdsize, '\n', fp) == -1) {
+ sudo_warn(U_("%s: invalid log file"), iolog_dir);
+ goto done;
+ }
+
+ /* Strip the newline from the cwd and command. */
+ evlog->cwd[strcspn(evlog->cwd, "\n")] = '\0';
+ evlog->command[strcspn(evlog->command, "\n")] = '\0';
+
+ /*
+ * Crack the log line (lines and cols not present in old versions).
+ * timestamp:user:runas_user:runas_group:tty:lines:cols
+ * XXX - probably better to use strtok and switch on the state.
+ */
+ buf[strcspn(buf, "\n")] = '\0';
+ cp = buf;
+
+ /* timestamp */
+ if ((ep = strchr(cp, ':')) == NULL) {
+ sudo_warn(U_("%s: time stamp field is missing"), iolog_dir);
+ goto done;
+ }
+ *ep = '\0';
+ evlog->submit_time.tv_sec = sudo_strtonum(cp, 0, TIME_T_MAX, &errstr);
+ if (errstr != NULL) {
+ sudo_warn(U_("%s: time stamp %s: %s"), iolog_dir, cp, errstr);
+ goto done;
+ }
+
+ /* submit user */
+ cp = ep + 1;
+ if ((ep = strchr(cp, ':')) == NULL) {
+ sudo_warn(U_("%s: user field is missing"), iolog_dir);
+ goto done;
+ }
+ if ((evlog->submituser = strndup(cp, (size_t)(ep - cp))) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ /* runas user */
+ cp = ep + 1;
+ if ((ep = strchr(cp, ':')) == NULL) {
+ sudo_warn(U_("%s: runas user field is missing"), iolog_dir);
+ goto done;
+ }
+ if ((evlog->runuser = strndup(cp, (size_t)(ep - cp))) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ /* runas group */
+ cp = ep + 1;
+ if ((ep = strchr(cp, ':')) == NULL) {
+ sudo_warn(U_("%s: runas group field is missing"), iolog_dir);
+ goto done;
+ }
+ if (cp != ep) {
+ if ((evlog->rungroup = strndup(cp, (size_t)(ep - cp))) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ }
+
+ /* tty, followed by optional lines + cols */
+ cp = ep + 1;
+ if ((ep = strchr(cp, ':')) == NULL) {
+ /* just the tty */
+ if ((evlog->ttyname = strdup(cp)) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ } else {
+ /* tty followed by lines + cols */
+ if ((evlog->ttyname = strndup(cp, (size_t)(ep - cp))) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ cp = ep + 1;
+ /* need to NULL out separator to use sudo_strtonum() */
+ /* XXX - use sudo_strtonumx */
+ if ((ep = strchr(cp, ':')) != NULL) {
+ *ep = '\0';
+ }
+ evlog->lines = sudo_strtonum(cp, 1, INT_MAX, &errstr);
+ if (errstr != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: tty lines %s: %s", iolog_dir, cp, errstr);
+ }
+ if (ep != NULL) {
+ cp = ep + 1;
+ evlog->columns = sudo_strtonum(cp, 1, INT_MAX, &errstr);
+ if (errstr != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: tty cols %s: %s", iolog_dir, cp, errstr);
+ }
+ }
+ }
+
+ ret = true;
+
+done:
+ free(buf);
+ debug_return_bool(ret);
+}
+
+struct eventlog *
+iolog_parse_loginfo(int dfd, const char *iolog_dir)
+{
+ struct eventlog *evlog = NULL;
+ FILE *fp = NULL;
+ int fd = -1;
+ int tmpfd = -1;
+ bool ok, legacy = false;
+ debug_decl(iolog_parse_loginfo, SUDO_DEBUG_UTIL);
+
+ if (dfd == -1) {
+ if ((tmpfd = open(iolog_dir, O_RDONLY)) == -1) {
+ sudo_warn("%s", iolog_dir);
+ goto bad;
+ }
+ dfd = tmpfd;
+ }
+ if ((fd = openat(dfd, "log.json", O_RDONLY, 0)) == -1) {
+ fd = openat(dfd, "log", O_RDONLY, 0);
+ legacy = true;
+ }
+ if (tmpfd != -1)
+ close(tmpfd);
+ if (fd == -1 || (fp = fdopen(fd, "r")) == NULL) {
+ sudo_warn("%s/log", iolog_dir);
+ goto bad;
+ }
+ fd = -1;
+
+ if ((evlog = calloc(1, sizeof(*evlog))) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ evlog->runuid = (uid_t)-1;
+ evlog->rungid = (gid_t)-1;
+
+ ok = legacy ? iolog_parse_loginfo_legacy(fp, iolog_dir, evlog) :
+ iolog_parse_loginfo_json(fp, iolog_dir, evlog);
+ if (ok) {
+ fclose(fp);
+ debug_return_ptr(evlog);
+ }
+
+bad:
+ if (fd != -1)
+ close(fd);
+ if (fp != NULL)
+ fclose(fp);
+ eventlog_free(evlog);
+ debug_return_ptr(NULL);
+}
+
+void
+iolog_adjust_delay(struct timespec *delay, struct timespec *max_delay,
+ double scale_factor)
+{
+ double seconds;
+ debug_decl(iolog_adjust_delay, SUDO_DEBUG_UTIL);
+
+ if (scale_factor != 1.0) {
+ /* Order is important: we don't want to double the remainder. */
+ seconds = (double)delay->tv_sec / scale_factor;
+ delay->tv_sec = (time_t)seconds;
+ delay->tv_nsec /= scale_factor;
+ delay->tv_nsec += (seconds - delay->tv_sec) * 1000000000;
+ while (delay->tv_nsec >= 1000000000) {
+ delay->tv_sec++;
+ delay->tv_nsec -= 1000000000;
+ }
+ }
+
+ /* Clamp to max delay. */
+ if (max_delay != NULL) {
+ if (sudo_timespeccmp(delay, max_delay, >)) {
+ delay->tv_sec = max_delay->tv_sec;
+ delay->tv_nsec = max_delay->tv_nsec;
+ }
+ }
+
+ debug_return;
+}
+
+/*
+ * Parse the delay as seconds and nanoseconds: %lld.%09ld
+ * Sudo used to write this as a double, but since timing data is logged
+ * in the C locale this may not match the current locale.
+ */
+char *
+iolog_parse_delay(const char *cp, struct timespec *delay,
+ const char *decimal_point)
+{
+ char numbuf[(((sizeof(long long) * 8) + 2) / 3) + 2];
+ const char *errstr, *ep;
+ long long llval;
+ size_t len;
+ debug_decl(iolog_parse_delay, SUDO_DEBUG_UTIL);
+
+ /* Parse seconds (whole number portion). */
+ for (ep = cp; isdigit((unsigned char)*ep); ep++)
+ continue;
+ len = (size_t)(ep - cp);
+ if (len >= sizeof(numbuf)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: number of seconds is too large", cp);
+ debug_return_ptr(NULL);
+ }
+ memcpy(numbuf, cp, len);
+ numbuf[len] = '\0';
+ delay->tv_sec = sudo_strtonum(numbuf, 0, TIME_T_MAX, &errstr);
+ if (errstr != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: number of seconds is %s", numbuf, errstr);
+ debug_return_ptr(NULL);
+ }
+
+ /* Radix may be in user's locale for sudo < 1.7.4 so accept that too. */
+ if (*ep != '.' && *ep != *decimal_point) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "invalid characters after seconds: %s", ep);
+ debug_return_ptr(NULL);
+ }
+ cp = ep + 1;
+
+ /* Parse fractional part, we may read more precision than we can store. */
+ for (ep = cp; isdigit((unsigned char)*ep); ep++)
+ continue;
+ len = (size_t)(ep - cp);
+ if (len >= sizeof(numbuf)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: number of nanoseconds is too large", cp);
+ debug_return_ptr(NULL);
+ }
+ memcpy(numbuf, cp, len);
+ numbuf[len] = '\0';
+ llval = sudo_strtonum(numbuf, 0, LLONG_MAX, &errstr);
+ if (errstr != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: number of nanoseconds is %s", numbuf, errstr);
+ debug_return_ptr(NULL);
+ }
+
+ /* Adjust fractional part to nanosecond precision. */
+ if (len < 9) {
+ /* Convert to nanosecond precision. */
+ do {
+ llval *= 10;
+ } while (++len < 9);
+ } else if (len > 9) {
+ /* Clamp to nanoseconds. */
+ do {
+ llval /= 10;
+ } while (--len > 9);
+ }
+ delay->tv_nsec = (long)llval;
+
+ /* Advance to the next field. */
+ while (isspace((unsigned char)*ep))
+ ep++;
+
+ debug_return_str((char *)ep);
+}
+
+/*
+ * Parse a timing line, which is formatted as:
+ * IO_EVENT_TTYOUT sleep_time num_bytes
+ * IO_EVENT_WINSIZE sleep_time lines cols
+ * IO_EVENT_SUSPEND sleep_time signo
+ * Where type is IO_EVENT_*, sleep_time is the number of seconds to sleep
+ * before writing the data and num_bytes is the number of bytes to output.
+ * Returns true on success and false on failure.
+ */
+bool
+iolog_parse_timing(const char *line, struct timing_closure *timing)
+{
+ unsigned long ulval;
+ char *cp, *ep;
+ debug_decl(iolog_parse_timing, SUDO_DEBUG_UTIL);
+
+ /* Clear iolog descriptor. */
+ timing->iol = NULL;
+
+ /* Parse event type. */
+ ulval = strtoul(line, &ep, 10);
+ if (ep == line || !isspace((unsigned char) *ep))
+ goto bad;
+ if (ulval >= IO_EVENT_COUNT)
+ goto bad;
+ if (ulval == IO_EVENT_TTYOUT_1_8_7) {
+ /* work around a bug in timing files generated by sudo 1.8.7 */
+ timing_event_adj = 2;
+ }
+ timing->event = (int)ulval - timing_event_adj;
+ for (cp = ep + 1; isspace((unsigned char) *cp); cp++)
+ continue;
+
+ /* Parse delay, returns the next field or NULL on error. */
+ if ((cp = iolog_parse_delay(cp, &timing->delay, timing->decimal)) == NULL)
+ goto bad;
+
+ switch (timing->event) {
+ case IO_EVENT_SUSPEND:
+ /* Signal name (no leading SIG prefix) or number. */
+ if (str2sig(cp, &timing->u.signo) == -1)
+ goto bad;
+ break;
+ case IO_EVENT_WINSIZE:
+ ulval = strtoul(cp, &ep, 10);
+ if (ep == cp || !isspace((unsigned char) *ep))
+ goto bad;
+ if (ulval > INT_MAX)
+ goto bad;
+ timing->u.winsize.lines = (int)ulval;
+ for (cp = ep + 1; isspace((unsigned char) *cp); cp++)
+ continue;
+
+ ulval = strtoul(cp, &ep, 10);
+ if (ep == cp || *ep != '\0')
+ goto bad;
+ if (ulval > INT_MAX)
+ goto bad;
+ timing->u.winsize.cols = (int)ulval;
+ break;
+ default:
+ errno = 0;
+ ulval = strtoul(cp, &ep, 10);
+ if (ep == cp || *ep != '\0')
+ goto bad;
+ /* Note: assumes SIZE_MAX == ULONG_MAX */
+ if (errno == ERANGE && ulval == ULONG_MAX)
+ goto bad;
+ timing->u.nbytes = (size_t)ulval;
+ break;
+ }
+
+ debug_return_bool(true);
+bad:
+ debug_return_bool(false);
+}
+
+/*
+ * Read the next record from the timing file.
+ * Return 0 on success, 1 on EOF and -1 on error.
+ */
+int
+iolog_read_timing_record(struct iolog_file *iol, struct timing_closure *timing)
+{
+ char line[LINE_MAX];
+ const char *errstr;
+ debug_decl(iolog_read_timing_record, SUDO_DEBUG_UTIL);
+
+ /* Read next record from timing file. */
+ if (iolog_gets(iol, line, sizeof(line), &errstr) == NULL) {
+ /* EOF or error reading timing file, we are done. */
+ if (iolog_eof(iol))
+ debug_return_int(1);
+ sudo_warnx(U_("error reading timing file: %s"), errstr);
+ debug_return_int(-1);
+ }
+
+ /* Parse timing file record. */
+ line[strcspn(line, "\n")] = '\0';
+ if (!iolog_parse_timing(line, timing)) {
+ sudo_warnx(U_("invalid timing file line: %s"), line);
+ debug_return_int(-1);
+ }
+
+ debug_return_int(0);
+}
diff --git a/lib/iolog/regress/host_port/host_port_test.c b/lib/iolog/regress/host_port/host_port_test.c
new file mode 100644
index 0000000..109393a
--- /dev/null
+++ b/lib/iolog/regress/host_port/host_port_test.c
@@ -0,0 +1,145 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <time.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_iolog.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that iolog_parse_host_port() works as expected.
+ */
+
+struct host_port_test {
+ const char *str; /* input string */
+ const char *host; /* parsed host */
+ const char *port; /* parsed port */
+ bool tls; /* parsed TLS flag */
+ char *defport; /* default port */
+ char *defport_tls; /* default port */
+ bool ret; /* return value */
+};
+
+static struct host_port_test test_data[] = {
+ /* No TLS */
+ { "xerxes", "xerxes", "12345", false, "12345", NULL, true },
+ { "xerxes:12345", "xerxes", "12345", false, "67890", NULL, true },
+ { "127.0.0.1", "127.0.0.1", "12345", false, "12345", NULL, true },
+ { "127.0.0.1:12345", "127.0.0.1", "12345", false, "67890", NULL, true },
+ { "[::1]", "::1", "12345", false, "12345", NULL, true },
+ { "[::1]:12345", "::1", "12345", false, "67890", NULL, true },
+
+ /* With TLS */
+ { "xerxes(tls)", "xerxes", "12345", true, "5678", "12345", true },
+ { "xerxes:12345(tls)", "xerxes", "12345", true, "5678", "67890", true },
+ { "127.0.0.1(tls)", "127.0.0.1", "12345", true, "5678", "12345", true },
+ { "127.0.0.1:12345(tls)", "127.0.0.1", "12345", true, "5678", "67890", true },
+ { "[::1](tls)", "::1", "12345", true, "5678", "12345", true },
+ { "[::1]:12345(tls)", "::1", "12345", true, "5678", "67890", true },
+
+ /* Errors */
+ { "xerxes:", NULL, NULL, false, "12345", NULL, false }, /* missing port */
+ { "127.0.0.1:", NULL, NULL, false, "12345", NULL, false }, /* missing port */
+ { "[::1:12345", NULL, NULL, false, "67890", NULL, false }, /* missing bracket */
+ { "[::1]:", NULL, NULL, false, "12345", NULL, false }, /* missing port */
+ { NULL }
+};
+
+int
+main(int argc, char *argv[])
+{
+ int i, errors = 0, ntests = 0;
+ char *host, *port, *copy = NULL;
+ bool ret, tls;
+
+ initprogname(argc > 0 ? argv[0] : "host_port_test");
+
+ for (i = 0; test_data[i].str != NULL; i++) {
+ host = port = NULL;
+ tls = false;
+ free(copy);
+ if ((copy = strdup(test_data[i].str)) == NULL)
+ sudo_fatal_nodebug(NULL);
+
+ ntests++;
+ ret = iolog_parse_host_port(copy, &host, &port, &tls,
+ test_data[i].defport, test_data[i].defport_tls);
+ if (ret != test_data[i].ret) {
+ sudo_warnx_nodebug("test #%d: %s: returned %s, expected %s",
+ ntests, test_data[i].str, ret ? "true" : "false",
+ test_data[i].ret ? "true" : "false");
+ errors++;
+ continue;
+ }
+ if (!ret)
+ continue;
+
+ if (host == NULL) {
+ sudo_warnx_nodebug("test #%d: %s: NULL host",
+ ntests, test_data[i].str);
+ errors++;
+ continue;
+ }
+ if (strcmp(host, test_data[i].host) != 0) {
+ sudo_warnx_nodebug("test #%d: %s: bad host, expected %s, got %s",
+ ntests, test_data[i].str, test_data[i].host, host);
+ errors++;
+ continue;
+ }
+ if (port == NULL) {
+ sudo_warnx_nodebug("test #%d: %s: NULL port",
+ ntests, test_data[i].str);
+ errors++;
+ continue;
+ }
+ if (strcmp(port, test_data[i].port) != 0) {
+ sudo_warnx_nodebug("test #%d: %s: bad port, expected %s, got %s",
+ ntests, test_data[i].str, test_data[i].port, port);
+ errors++;
+ continue;
+ }
+ if (tls != test_data[i].tls) {
+ sudo_warnx_nodebug("test #%d: %s: bad tls, expected %s, got %s",
+ ntests, test_data[i].str, test_data[i].tls ? "true" : "false",
+ tls ? "true" : "false");
+ errors++;
+ continue;
+ }
+ }
+ free(copy);
+ 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/iolog/regress/iolog_json/check_iolog_json.c b/lib/iolog/regress/iolog_json/check_iolog_json.c
new file mode 100644
index 0000000..a967ba6
--- /dev/null
+++ b/lib/iolog/regress/iolog_json/check_iolog_json.c
@@ -0,0 +1,265 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+#include "iolog_json.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+bool
+json_print_object(struct json_container *json, struct json_object *object)
+{
+ struct json_item *item;
+ struct json_value json_value;
+ bool ret = false;
+
+ TAILQ_FOREACH(item, &object->items, entries) {
+ switch (item->type) {
+ case JSON_STRING:
+ json_value.type = JSON_STRING;
+ json_value.u.string = item->u.string;
+ if (!sudo_json_add_value(json, item->name, &json_value))
+ goto oom;
+ break;
+ case JSON_NUMBER:
+ json_value.type = JSON_NUMBER;
+ json_value.u.number = item->u.number;
+ if (!sudo_json_add_value(json, item->name, &json_value))
+ goto oom;
+ break;
+ case JSON_OBJECT:
+ if (!sudo_json_open_object(json, item->name))
+ goto oom;
+ if (!json_print_object(json, &item->u.child))
+ goto done;
+ if (!sudo_json_close_object(json))
+ goto oom;
+ break;
+ case JSON_ARRAY:
+ if (!sudo_json_open_array(json, item->name))
+ goto oom;
+ if (!json_print_object(json, &item->u.child))
+ goto done;
+ if (!sudo_json_close_array(json))
+ goto oom;
+ break;
+ case JSON_BOOL:
+ json_value.type = JSON_BOOL;
+ json_value.u.boolean = item->u.boolean;
+ if (!sudo_json_add_value(json, item->name, &json_value))
+ goto oom;
+ break;
+ case JSON_NULL:
+ json_value.type = JSON_NULL;
+ if (!sudo_json_add_value(json, item->name, &json_value))
+ goto oom;
+ break;
+ default:
+ sudo_warnx("unsupported JSON type %d", item->type);
+ goto done;
+ }
+ }
+
+ ret = true;
+ goto done;
+
+oom:
+ sudo_warnx("%s: %s", __func__, "unable to allocate memory");
+done:
+ return ret;
+}
+
+static bool
+json_format(struct json_container *json, struct json_object *object)
+{
+ struct json_item *item;
+ bool ret = false;
+
+ /* First object holds all the actual data. */
+ item = TAILQ_FIRST(&object->items);
+ if (item->type != JSON_OBJECT) {
+ sudo_warnx("expected JSON_OBJECT, got %d", item->type);
+ goto done;
+ }
+ object = &item->u.child;
+
+ if (!json_print_object(json, object))
+ goto done;
+
+ ret = true;
+
+done:
+ return ret;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-c] input_file ...\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static bool
+compare(FILE *fp, const char *infile, struct json_container *json)
+{
+ const char *cp;
+ unsigned int lineno = 0;
+ size_t linesize = 0;
+ char *line = NULL;
+ ssize_t len;
+
+ cp = sudo_json_get_buf(json);
+
+ while ((len = getdelim(&line, &linesize, '\n', fp)) != -1) {
+ lineno++;
+
+ /* skip open/close brace, not present in formatted output */
+ if (lineno == 1 && strcmp(line, "{\n") == 0)
+ continue;
+ if (*cp == '\0' && strcmp(line, "}\n") == 0)
+ continue;
+
+ /* Ignore newlines in output to make comparison easier. */
+ if (*cp == '\n')
+ cp++;
+ if (line[len - 1] == '\n')
+ len--;
+
+ if (strncmp(line, cp, len) != 0) {
+ fprintf(stderr, "%s: mismatch on line %u\n", infile, lineno);
+ fprintf(stderr, "expected: %s", line);
+ fprintf(stderr, "got : %.*s\n", (int)len, cp);
+ return false;
+ }
+ cp += len;
+ }
+ free(line);
+
+ return true;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct json_object root;
+ int ch, i, tests = 0, errors = 0;
+ bool cat = false;
+
+ initprogname(argc > 0 ? argv[0] : "check_iolog_json");
+
+ while ((ch = getopt(argc, argv, "c")) != -1) {
+ switch (ch) {
+ case 'c':
+ cat = true;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ for (i = 0; i < argc; i++) {
+ struct json_container json;
+ const char *infile = argv[i];
+ const char *outfile = argv[i];
+ const char *cp;
+ char pathbuf[PATH_MAX];
+ FILE *infp = NULL;
+ FILE *outfp = NULL;
+
+ tests++;
+
+ if (!sudo_json_init(&json, 4, false, true)) {
+ errors++;
+ continue;
+ }
+
+ /* Parse input file. */
+ if ((infp = fopen(infile, "r")) == NULL) {
+ sudo_warn("%s", argv[1]);
+ errors++;
+ goto next;
+ }
+ if (!iolog_parse_json(infp, infile, &root)) {
+ errors++;
+ goto next;
+ }
+
+ /* Format as pretty-printed JSON */
+ if (!json_format(&json, &root)) {
+ errors++;
+ goto next;
+ }
+
+ /* Check for a .out.ok file in the same location as the .in file. */
+ cp = strrchr(infile, '.');
+ if (cp != NULL && strcmp(cp, ".in") == 0) {
+ snprintf(pathbuf, sizeof(pathbuf), "%.*s.out.ok",
+ (int)(cp - infile), infile);
+ if ((outfp = fopen(pathbuf, "r")) != NULL)
+ outfile = pathbuf;
+ }
+ if (outfp == NULL)
+ outfp = infp;
+
+ /* Compare output to expected output. */
+ rewind(outfp);
+ if (!compare(outfp, outfile, &json))
+ errors++;
+
+ /* Write the formatted output to stdout for -c (cat) */
+ if (cat) {
+ fprintf(stdout, "{%s\n}\n", sudo_json_get_buf(&json));
+ fflush(stdout);
+ }
+
+next:
+ free_json_items(&root.items);
+ sudo_json_free(&json);
+ if (infp != NULL)
+ fclose(infp);
+ if (outfp != NULL && outfp != infp)
+ fclose(outfp);
+ }
+
+ if (tests != 0) {
+ printf("iolog_json: %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/iolog/regress/iolog_json/test1.in b/lib/iolog/regress/iolog_json/test1.in
new file mode 100644
index 0000000..8ad3689
--- /dev/null
+++ b/lib/iolog/regress/iolog_json/test1.in
@@ -0,0 +1,34 @@
+{
+ "timestamp": {
+ "seconds": 1584993067,
+ "nanoseconds": 880288287
+ },
+ "columns": 80,
+ "command": "/usr/bin/make",
+ "lines": 24,
+ "runargv": [
+ "make",
+ "test"
+ ],
+ "runenv": [
+ "LANG=en_US.UTF-8",
+ "PATH=/bin:/sbin:/usr/games:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin",
+ "TERM=vt100",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/ksh",
+ "SUDO_COMMAND=/usr/bin/make test",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20",
+ "A__z=\"*SHLVL"
+ ],
+ "runuid": 0,
+ "runuser": "root",
+ "submitcwd": "/home/test",
+ "submithost": "sudo.ws",
+ "submituser": "millert",
+ "ttyname": "/dev/console"
+}
diff --git a/lib/iolog/regress/iolog_json/test2.in b/lib/iolog/regress/iolog_json/test2.in
new file mode 100644
index 0000000..df7170f
--- /dev/null
+++ b/lib/iolog/regress/iolog_json/test2.in
@@ -0,0 +1,28 @@
+{
+ "timestamp": { "seconds": 1584993067, "nanoseconds": 880288287 },
+ "columns": 80,
+ "command": "/usr/bin/make",
+ "lines": 24,
+ "runargv": [ "make", "test" ],
+ "runenv": [
+ "LANG=en_US.UTF-8",
+ "PATH=/bin:/sbin:/usr/games:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin",
+ "TERM=vt100",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/ksh",
+ "SUDO_COMMAND=/usr/bin/make test",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20",
+ "A__z=\"*SHLVL"
+ ],
+ "runuid": 0,
+ "runuser": "root",
+ "submitcwd": "/home/test",
+ "submithost": "sudo.ws",
+ "submituser": "millert",
+ "ttyname": "/dev/console"
+}
diff --git a/lib/iolog/regress/iolog_json/test2.out.ok b/lib/iolog/regress/iolog_json/test2.out.ok
new file mode 100644
index 0000000..8ad3689
--- /dev/null
+++ b/lib/iolog/regress/iolog_json/test2.out.ok
@@ -0,0 +1,34 @@
+{
+ "timestamp": {
+ "seconds": 1584993067,
+ "nanoseconds": 880288287
+ },
+ "columns": 80,
+ "command": "/usr/bin/make",
+ "lines": 24,
+ "runargv": [
+ "make",
+ "test"
+ ],
+ "runenv": [
+ "LANG=en_US.UTF-8",
+ "PATH=/bin:/sbin:/usr/games:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin",
+ "TERM=vt100",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/ksh",
+ "SUDO_COMMAND=/usr/bin/make test",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20",
+ "A__z=\"*SHLVL"
+ ],
+ "runuid": 0,
+ "runuser": "root",
+ "submitcwd": "/home/test",
+ "submithost": "sudo.ws",
+ "submituser": "millert",
+ "ttyname": "/dev/console"
+}
diff --git a/lib/iolog/regress/iolog_json/test3.in b/lib/iolog/regress/iolog_json/test3.in
new file mode 100644
index 0000000..ea2df89
--- /dev/null
+++ b/lib/iolog/regress/iolog_json/test3.in
@@ -0,0 +1,22 @@
+{
+ "true": false,
+ "false": true,
+ "number": 1234567890,
+ "null": null,
+ "string": "nonsense",
+ "scope": {
+ "a": "b",
+ "bah": null
+ },
+ "array1": [
+ "foo",
+ "bar",
+ [
+ 123,
+ null,
+ false,
+ "fizz",
+ "buzz"
+ ]
+ ]
+}
diff --git a/lib/iolog/regress/iolog_mkpath/check_iolog_mkpath.c b/lib/iolog/regress/iolog_mkpath/check_iolog_mkpath.c
new file mode 100644
index 0000000..bb13787
--- /dev/null
+++ b/lib/iolog/regress/iolog_mkpath/check_iolog_mkpath.c
@@ -0,0 +1,91 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+#include "sudo_iolog.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static const char *test_paths[] = {
+ "testdir/a/b/c/user", /* create new */
+ "testdir/a/b/c/user", /* open existing */
+ "testdir/a/b/c/user.XXXXXX", /* mkdtemp new */
+ NULL
+};
+
+static void
+test_iolog_mkpath(const char *testdir, int *ntests, int *nerrors)
+{
+ const char **tp;
+ char *path;
+
+ iolog_set_owner(geteuid(), getegid());
+
+ for (tp = test_paths; *tp != NULL; tp++) {
+ if (asprintf(&path, "%s/%s", testdir, *tp) == -1)
+ sudo_fatalx("unable to allocate memory");
+
+ (*ntests)++;
+ if (!iolog_mkpath(path)) {
+ sudo_warnx("unable to mkpath %s", path);
+ (*nerrors)++;
+ }
+ free(path);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ char testdir[] = "mkpath.XXXXXX";
+ char *rmargs[] = { "rm", "-rf", NULL, NULL };
+ int status, tests = 0, errors = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_iolog_mkpath");
+
+ if (mkdtemp(testdir) == NULL)
+ sudo_fatal("unable to create test dir");
+ rmargs[2] = testdir;
+
+ test_iolog_mkpath(testdir, &tests, &errors);
+
+ if (tests != 0) {
+ printf("iolog_mkpath: %d test%s run, %d errors, %d%% success rate\n",
+ tests, tests == 1 ? "" : "s", errors,
+ (tests - errors) * 100 / tests);
+ }
+
+ /* Clean up (avoid running via shell) */
+ execvp("rm", rmargs);
+ wait(&status);
+
+ exit(errors);
+}
diff --git a/lib/iolog/regress/iolog_path/check_iolog_path.c b/lib/iolog/regress/iolog_path/check_iolog_path.c
new file mode 100644
index 0000000..8598384
--- /dev/null
+++ b/lib/iolog/regress/iolog_path/check_iolog_path.c
@@ -0,0 +1,273 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+#include "sudo_iolog.h"
+
+static struct iolog_escape_data {
+ char sessid[7];
+ char *user;
+ char *group;
+ char *runas_user;
+ char *runas_group;
+ char *host;
+ char *command;
+} escape_data;
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s datafile\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static void
+reset_escape_data(struct iolog_escape_data *data)
+{
+ free(data->user);
+ free(data->group);
+ free(data->runas_user);
+ free(data->runas_group);
+ free(data->host);
+ free(data->command);
+ memset(data, 0, sizeof(*data));
+}
+
+static size_t
+fill_seq(char *str, size_t strsize, void *unused)
+{
+ int len;
+
+ /* Path is of the form /var/log/sudo-io/00/00/01. */
+ len = snprintf(str, strsize, "%c%c/%c%c/%c%c", escape_data.sessid[0],
+ escape_data.sessid[1], escape_data.sessid[2], escape_data.sessid[3],
+ escape_data.sessid[4], escape_data.sessid[5]);
+ if (len < 0)
+ return strsize; /* handle non-standard snprintf() */
+ return len;
+}
+
+static size_t
+fill_user(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.user, strsize);
+}
+
+static size_t
+fill_group(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.group, strsize);
+}
+
+static size_t
+fill_runas_user(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.runas_user, strsize);
+}
+
+static size_t
+fill_runas_group(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.runas_group, strsize);
+}
+
+static size_t
+fill_hostname(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.host, strsize);
+}
+
+static size_t
+fill_command(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.command, strsize);
+}
+
+/* Note: "seq" must be first in the list. */
+static struct iolog_path_escape path_escapes[] = {
+ { "seq", fill_seq },
+ { "user", fill_user },
+ { "group", fill_group },
+ { "runas_user", fill_runas_user },
+ { "runas_group", fill_runas_group },
+ { "hostname", fill_hostname },
+ { "command", fill_command },
+ { NULL, NULL }
+};
+
+static int
+do_check(char *dir_in, char *file_in, char *tdir_out, char *tfile_out)
+{
+ char dir[PATH_MAX], dir_out[PATH_MAX];
+ char file[PATH_MAX], file_out[PATH_MAX];
+ struct tm *timeptr;
+ time_t now;
+ int error = 0;
+
+ /*
+ * Expand any strftime(3) escapes
+ * XXX - want to pass timeptr to expand_iolog_path
+ */
+ time(&now);
+ timeptr = localtime(&now);
+ if (timeptr == NULL)
+ sudo_fatalx("localtime returned NULL");
+ strftime(dir_out, sizeof(dir_out), tdir_out, timeptr);
+ strftime(file_out, sizeof(file_out), tfile_out, timeptr);
+
+ if (!expand_iolog_path(dir_in, dir, sizeof(dir), &path_escapes[1], NULL))
+ sudo_fatalx("unable to expand I/O log dir");
+ if (!expand_iolog_path(file_in, file, sizeof(file), &path_escapes[0], dir))
+ sudo_fatalx("unable to expand I/O log file");
+
+ if (strcmp(dir, dir_out) != 0) {
+ sudo_warnx("%s: expected %s, got %s", dir_in, dir_out, dir);
+ error = 1;
+ }
+ if (strcmp(file, file_out) != 0) {
+ sudo_warnx("%s: expected %s, got %s", file_in, file_out, file);
+ error = 1;
+ }
+
+ return error;
+}
+
+#define MAX_STATE 12
+
+int
+main(int argc, char *argv[])
+{
+ size_t len;
+ FILE *fp;
+ char line[2048];
+ char *file_in = NULL, *file_out = NULL;
+ char *dir_in = NULL, *dir_out = NULL;
+ int state = 0;
+ int errors = 0;
+ int tests = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_iolog_path");
+
+ if (argc != 2)
+ usage();
+
+ fp = fopen(argv[1], "r");
+ if (fp == NULL)
+ sudo_fatalx("unable to open %s", argv[1]);
+
+ /*
+ * Input consists of 12 lines:
+ * sequence number
+ * user name
+ * user gid
+ * runas user name
+ * runas gid
+ * hostname [short form]
+ * command
+ * dir [with escapes]
+ * file [with escapes]
+ * expanded dir
+ * expanded file
+ * empty line
+ */
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ len = strcspn(line, "\n");
+ line[len] = '\0';
+
+ switch (state) {
+ case 0:
+ strlcpy(escape_data.sessid, line, sizeof(escape_data.sessid));
+ break;
+ case 1:
+ if ((escape_data.user = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 2:
+ if ((escape_data.group = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 3:
+ if ((escape_data.runas_user = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 4:
+ if ((escape_data.runas_group = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 5:
+ if ((escape_data.host = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 6:
+ if ((escape_data.command = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 7:
+ if (dir_in != NULL)
+ free(dir_in);
+ dir_in = strdup(line);
+ break;
+ case 8:
+ if (file_in != NULL)
+ free(file_in);
+ file_in = strdup(line);
+ break;
+ case 9:
+ if (dir_out != NULL)
+ free(dir_out);
+ dir_out = strdup(line);
+ break;
+ case 10:
+ if (file_out != NULL)
+ free(file_out);
+ file_out = strdup(line);
+ break;
+ case 11:
+ errors += do_check(dir_in, file_in, dir_out, file_out);
+ tests++;
+ reset_escape_data(&escape_data);
+ break;
+ default:
+ sudo_fatalx("internal error, invalid state %d", state);
+ }
+ state = (state + 1) % MAX_STATE;
+ }
+
+ if (tests != 0) {
+ printf("iolog_path: %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/iolog/regress/iolog_path/data b/lib/iolog/regress/iolog_path/data
new file mode 100644
index 0000000..48dc87e
--- /dev/null
+++ b/lib/iolog/regress/iolog_path/data
@@ -0,0 +1,96 @@
+000001
+nobody
+nogroup
+root
+root
+somehost
+id
+/var/log/sudo-io
+%%{bogus}
+/var/log/sudo-io
+%%{bogus}
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+id
+/var/log/sudo-io
+%%{seq}
+/var/log/sudo-io
+%%{seq}
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+id
+/var/log/sudo-io
+%{seq}
+/var/log/sudo-io
+00/00/01
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+id
+/var/log/sudo-io/%{user}
+%{seq}
+/var/log/sudo-io/nobody
+00/00/01
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+su
+/var/log/sudo-io/%{user}/%{runas_user}
+%{command}_%Y%m%s_%H%M
+/var/log/sudo-io/nobody/root
+su_%Y%m%s_%H%M
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+su
+/var/log/sudo-io/
+//%{user}/%{runas_user}/%{command}_%Y%m%s_%H%M
+/var/log/sudo-io
+/nobody/root/su_%Y%m%s_%H%M
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+su
+/var/log/sudo-io/%d%m%Y
+%{user}/%{runas_user}/%{command}
+/var/log/sudo-io/%d%m%Y
+nobody/root/su
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+su
+////////
+%{user}/%{runas_user}/%{command}
+
+nobody/root/su
+
diff --git a/lib/iolog/regress/iolog_util/check_iolog_util.c b/lib/iolog/regress/iolog_util/check_iolog_util.c
new file mode 100644
index 0000000..de94ee1
--- /dev/null
+++ b/lib/iolog/regress/iolog_util/check_iolog_util.c
@@ -0,0 +1,148 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+#include "sudo_iolog.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static struct parse_delay_test {
+ const char *input;
+ const char *next_field;
+ struct timespec expected_delay;
+} parse_delay_tests[] = {
+ { "10.99999999999 X", "X", { 10, 999999999 } }, /* clamp to nsec */
+ { "10.999999999 X", "X", { 10, 999999999 } }, /* nsec */
+ { "10.999999 X", "X", { 10, 999999000 } }, /* usec -> nsec */
+ { "10.000999999 X", "X", { 10, 999999 } },
+ { "10.9 X", "X", { 10, 900000000 } },
+ { "10.0 X", "X", { 10, 0 } }
+};
+
+/*
+ * Test iolog_parse_delay()
+ */
+void
+test_parse_delay(int *ntests, int *nerrors)
+{
+ unsigned int i;
+
+ for (i = 0; i < nitems(parse_delay_tests); i++) {
+ struct timespec delay;
+ struct parse_delay_test *test = &parse_delay_tests[i];
+ char *cp = iolog_parse_delay(test->input, &delay, ".");
+ if (cp == NULL) {
+ sudo_warnx("%s:%u failed to parse delay: %s", __func__,
+ i, test->input);
+ (*nerrors)++;
+ continue;
+ }
+ if (strcmp(cp, test->next_field) != 0) {
+ sudo_warnx("%s:%u next field (want \"%s\", got \"%s\"", __func__,
+ i, test->next_field, cp);
+ (*nerrors)++;
+ continue;
+ }
+ if (delay.tv_sec != test->expected_delay.tv_sec) {
+ sudo_warnx("%s:%u wrong seconds (want %lld, got %lld)", __func__,
+ i, (long long)test->expected_delay.tv_sec,
+ (long long)delay.tv_sec);
+ (*nerrors)++;
+ continue;
+ }
+ if (delay.tv_nsec != test->expected_delay.tv_nsec) {
+ sudo_warnx("%s:%u wrong nanoseconds (want %ld, got %ld)", __func__,
+ i, test->expected_delay.tv_nsec, delay.tv_nsec);
+ (*nerrors)++;
+ continue;
+ }
+ }
+ (*ntests) += i;
+}
+
+static struct adjust_delay_test {
+ struct timespec in_delay;
+ struct timespec out_delay;
+ struct timespec max_delay;
+ double scale_factor;
+} adjust_delay_tests[] = {
+ { { 10, 300 }, { 10, 300 }, { 0, 0 }, 1.0 },
+ { { 10, 300 }, { 5, 150 }, { 0, 0 }, 2.0 },
+ { { 5, 300 }, { 2, 500000150 }, { 0, 0 }, 2.0 },
+ { { 0, 1000000 }, { 0, 333333 }, { 0, 0 }, 3 },
+ { { 10, 1000000 }, { 3, 333666666 }, { 0, 0 }, 3 },
+ { { 5, 150 }, { 10, 300 }, { 0, 0 }, 0.5 },
+ { { 5, 500000000 }, { 11, 0 }, { 0, 0 }, 0.5 },
+ { { 5, 150 }, { 5, 0 }, { 5, 0 }, 0.5 }
+};
+
+/*
+ * Test iolog_adjust_delay()
+ */
+void
+test_adjust_delay(int *ntests, int *nerrors)
+{
+ unsigned int i;
+
+ for (i = 0; i < nitems(adjust_delay_tests); i++) {
+ struct adjust_delay_test *test = &adjust_delay_tests[i];
+
+ iolog_adjust_delay(&test->in_delay,
+ sudo_timespecisset(&test->max_delay) ? &test->max_delay : NULL,
+ test->scale_factor);
+ if (!sudo_timespeccmp(&test->in_delay, &test->out_delay, ==)) {
+ sudo_warnx("%s:%u want {%lld, %ld}, got {%lld, %ld}", __func__, i,
+ (long long)test->out_delay.tv_sec, test->out_delay.tv_nsec,
+ (long long)test->in_delay.tv_sec, test->in_delay.tv_nsec);
+ (*nerrors)++;
+ }
+ }
+ (*ntests) += i;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int tests = 0, errors = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_iolog_util");
+
+ test_parse_delay(&tests, &errors);
+
+ test_adjust_delay(&tests, &errors);
+
+ if (tests != 0) {
+ printf("iolog_util: %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/logsrv/Makefile.in b/lib/logsrv/Makefile.in
new file mode 100644
index 0000000..76f7d4c
--- /dev/null
+++ b/lib/logsrv/Makefile.in
@@ -0,0 +1,187 @@
+#
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+# @configure_input@
+#
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+abs_srcdir = @abs_srcdir@
+top_srcdir = @top_srcdir@
+abs_top_srcdir = @abs_top_srcdir@
+top_builddir = @top_builddir@
+abs_top_builddir = @abs_top_builddir@
+devdir = @devdir@
+scriptdir = $(top_srcdir)/scripts
+incdir = $(top_srcdir)/include
+
+# Compiler & tools to use
+CC = @CC@
+LIBTOOL = @LIBTOOL@
+
+# C preprocessor flags
+CPPFLAGS = -I$(incdir) -I$(top_builddir) -I$(srcdir) -I$(top_srcdir) @CPPFLAGS@
+
+# Usually -O and/or -g
+CFLAGS = @CFLAGS@
+
+# 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@
+
+# cppcheck options, usually set in the top-level Makefile
+CPPCHECK_OPTS = -q --enable=warning,performance,portability --suppress=constStatement --suppress=compareBoolExpressionWithInt --error-exitcode=1 --inline-suppr -Dva_copy=va_copy -U__cplusplus -UQUAD_MAX -UQUAD_MIN -UUQUAD_MAX -U_POSIX_HOST_NAME_MAX -U_POSIX_PATH_MAX -U__NBBY -DNSIG=64
+
+# splint options, usually set in the top-level Makefile
+SPLINT_OPTS = -D__restrict= -checks
+
+# PVS-studio options
+PVS_CFG = $(top_srcdir)/PVS-Studio.cfg
+PVS_IGNORE = 'V707,V011,V002,V536'
+PVS_LOG_OPTS = -a 'GA:1,2' -e -t errorfile -d $(PVS_IGNORE)
+
+# Set to non-empty for development mode
+DEVEL = @DEVEL@
+
+#### End of system configuration section. ####
+
+SHELL = @SHELL@
+
+LIBLOGSRV_OBJS = protobuf-c.lo log_server.pb-c.lo
+
+IOBJS = $(LIBLOGSRV_OBJS:.lo=.i)
+
+POBJS = $(IOBJS:.i=.plog)
+
+GENERATED = log_server.pb-c.h log_server.pb-c.c
+
+all: liblogsrv.la
+
+pvs-log-files: $(POBJS)
+
+pvs-studio: $(POBJS)
+ plog-converter $(PVS_LOG_OPTS) $(POBJS)
+
+depend:
+ $(scriptdir)/mkdep.pl --srcdir=$(abs_top_srcdir) \
+ --builddir=$(abs_top_builddir) lib/logsrv/Makefile.in
+ cd $(top_builddir) && ./config.status --file lib/logsrv/Makefile
+
+Makefile: $(srcdir)/Makefile.in
+ cd $(top_builddir) && ./config.status --file lib/logsrv/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 $@
+
+$(devdir)/log_server.pb-c.c: $(srcdir)/log_server.proto
+ @if [ -n "$(DEVEL)" ]; then \
+ cmd='protoc-c --c_out=$(devdir) --proto_path=$(srcdir) $(srcdir)/log_server.proto'; \
+ echo "$$cmd"; eval $$cmd; \
+ cmd='$(scriptdir)/unanon $(devdir)/log_server.pb-c.h $(devdir)/log_server.pb-c.c'; \
+ echo "$$cmd"; eval $$cmd; \
+ if [ "$(devdir)" == "$(srcdir)" ]; then \
+ cmd='mv -f $(devdir)/log_server.pb-c.h $(incdir)/log_server.pb-c.h'; \
+ else \
+ cmd='mv -f $(devdir)/log_server.pb-c.h $(top_builddir)/log_server.pb-c.h'; \
+ fi; \
+ echo "$$cmd"; eval $$cmd; \
+ fi
+
+liblogsrv.la: $(LIBLOGSRV_OBJS)
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(LIBLOGSRV_OBJS)
+
+pre-install:
+
+install:
+
+install-binaries:
+
+install-includes:
+
+install-doc:
+
+install-plugin:
+
+uninstall:
+
+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)
+
+check:
+
+clean:
+ -$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f *.lo *.o *.la
+ -rm -f *.i *.plog stamp-* core *.core core.*
+
+mostlyclean: clean
+
+distclean: clean
+ -rm -rf Makefile .libs
+ @if [ -n "$(DEVEL)" -a "$(devdir)" != "$(srcdir)" ]; then \
+ cmd='rm -rf $(GENERATED)'; \
+ echo "$$cmd"; eval $$cmd; \
+ fi
+
+clobber: distclean
+
+realclean: distclean
+ rm -f TAGS tags
+
+cleandir: realclean
+
+# Autogenerated dependencies, do not modify
+log_server.pb-c.lo: $(srcdir)/log_server.pb-c.c $(incdir)/log_server.pb-c.h \
+ $(incdir)/protobuf-c/protobuf-c.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/log_server.pb-c.c
+log_server.pb-c.i: $(srcdir)/log_server.pb-c.c $(incdir)/log_server.pb-c.h \
+ $(incdir)/protobuf-c/protobuf-c.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+log_server.pb-c.plog: log_server.pb-c.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/log_server.pb-c.c --i-file $< --output-file $@
+protobuf-c.lo: $(srcdir)/protobuf-c.c $(incdir)/protobuf-c/protobuf-c.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/protobuf-c.c
+protobuf-c.i: $(srcdir)/protobuf-c.c $(incdir)/protobuf-c/protobuf-c.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+protobuf-c.plog: protobuf-c.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/protobuf-c.c --i-file $< --output-file $@
diff --git a/lib/logsrv/log_server.pb-c.c b/lib/logsrv/log_server.pb-c.c
new file mode 100644
index 0000000..51f4ef0
--- /dev/null
+++ b/lib/logsrv/log_server.pb-c.c
@@ -0,0 +1,1753 @@
+/* Generated by the protocol buffer compiler. DO NOT EDIT! */
+/* Generated from: log_server.proto */
+
+/* Do not generate deprecated warnings for self */
+#ifndef PROTOBUF_C__NO_DEPRECATED
+#define PROTOBUF_C__NO_DEPRECATED
+#endif
+
+#include "log_server.pb-c.h"
+void client_message__init
+ (ClientMessage *message)
+{
+ static const ClientMessage init_value = CLIENT_MESSAGE__INIT;
+ *message = init_value;
+}
+size_t client_message__get_packed_size
+ (const ClientMessage *message)
+{
+ assert(message->base.descriptor == &client_message__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t client_message__pack
+ (const ClientMessage *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &client_message__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t client_message__pack_to_buffer
+ (const ClientMessage *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &client_message__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+ClientMessage *
+ client_message__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (ClientMessage *)
+ protobuf_c_message_unpack (&client_message__descriptor,
+ allocator, len, data);
+}
+void client_message__free_unpacked
+ (ClientMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &client_message__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void time_spec__init
+ (TimeSpec *message)
+{
+ static const TimeSpec init_value = TIME_SPEC__INIT;
+ *message = init_value;
+}
+size_t time_spec__get_packed_size
+ (const TimeSpec *message)
+{
+ assert(message->base.descriptor == &time_spec__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t time_spec__pack
+ (const TimeSpec *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &time_spec__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t time_spec__pack_to_buffer
+ (const TimeSpec *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &time_spec__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+TimeSpec *
+ time_spec__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (TimeSpec *)
+ protobuf_c_message_unpack (&time_spec__descriptor,
+ allocator, len, data);
+}
+void time_spec__free_unpacked
+ (TimeSpec *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &time_spec__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void io_buffer__init
+ (IoBuffer *message)
+{
+ static const IoBuffer init_value = IO_BUFFER__INIT;
+ *message = init_value;
+}
+size_t io_buffer__get_packed_size
+ (const IoBuffer *message)
+{
+ assert(message->base.descriptor == &io_buffer__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t io_buffer__pack
+ (const IoBuffer *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &io_buffer__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t io_buffer__pack_to_buffer
+ (const IoBuffer *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &io_buffer__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+IoBuffer *
+ io_buffer__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (IoBuffer *)
+ protobuf_c_message_unpack (&io_buffer__descriptor,
+ allocator, len, data);
+}
+void io_buffer__free_unpacked
+ (IoBuffer *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &io_buffer__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void info_message__string_list__init
+ (InfoMessage__StringList *message)
+{
+ static const InfoMessage__StringList init_value = INFO_MESSAGE__STRING_LIST__INIT;
+ *message = init_value;
+}
+void info_message__number_list__init
+ (InfoMessage__NumberList *message)
+{
+ static const InfoMessage__NumberList init_value = INFO_MESSAGE__NUMBER_LIST__INIT;
+ *message = init_value;
+}
+void info_message__init
+ (InfoMessage *message)
+{
+ static const InfoMessage init_value = INFO_MESSAGE__INIT;
+ *message = init_value;
+}
+size_t info_message__get_packed_size
+ (const InfoMessage *message)
+{
+ assert(message->base.descriptor == &info_message__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t info_message__pack
+ (const InfoMessage *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &info_message__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t info_message__pack_to_buffer
+ (const InfoMessage *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &info_message__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+InfoMessage *
+ info_message__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (InfoMessage *)
+ protobuf_c_message_unpack (&info_message__descriptor,
+ allocator, len, data);
+}
+void info_message__free_unpacked
+ (InfoMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &info_message__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void accept_message__init
+ (AcceptMessage *message)
+{
+ static const AcceptMessage init_value = ACCEPT_MESSAGE__INIT;
+ *message = init_value;
+}
+size_t accept_message__get_packed_size
+ (const AcceptMessage *message)
+{
+ assert(message->base.descriptor == &accept_message__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t accept_message__pack
+ (const AcceptMessage *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &accept_message__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t accept_message__pack_to_buffer
+ (const AcceptMessage *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &accept_message__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+AcceptMessage *
+ accept_message__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (AcceptMessage *)
+ protobuf_c_message_unpack (&accept_message__descriptor,
+ allocator, len, data);
+}
+void accept_message__free_unpacked
+ (AcceptMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &accept_message__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void reject_message__init
+ (RejectMessage *message)
+{
+ static const RejectMessage init_value = REJECT_MESSAGE__INIT;
+ *message = init_value;
+}
+size_t reject_message__get_packed_size
+ (const RejectMessage *message)
+{
+ assert(message->base.descriptor == &reject_message__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t reject_message__pack
+ (const RejectMessage *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &reject_message__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t reject_message__pack_to_buffer
+ (const RejectMessage *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &reject_message__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+RejectMessage *
+ reject_message__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (RejectMessage *)
+ protobuf_c_message_unpack (&reject_message__descriptor,
+ allocator, len, data);
+}
+void reject_message__free_unpacked
+ (RejectMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &reject_message__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void exit_message__init
+ (ExitMessage *message)
+{
+ static const ExitMessage init_value = EXIT_MESSAGE__INIT;
+ *message = init_value;
+}
+size_t exit_message__get_packed_size
+ (const ExitMessage *message)
+{
+ assert(message->base.descriptor == &exit_message__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t exit_message__pack
+ (const ExitMessage *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &exit_message__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t exit_message__pack_to_buffer
+ (const ExitMessage *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &exit_message__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+ExitMessage *
+ exit_message__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (ExitMessage *)
+ protobuf_c_message_unpack (&exit_message__descriptor,
+ allocator, len, data);
+}
+void exit_message__free_unpacked
+ (ExitMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &exit_message__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void alert_message__init
+ (AlertMessage *message)
+{
+ static const AlertMessage init_value = ALERT_MESSAGE__INIT;
+ *message = init_value;
+}
+size_t alert_message__get_packed_size
+ (const AlertMessage *message)
+{
+ assert(message->base.descriptor == &alert_message__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t alert_message__pack
+ (const AlertMessage *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &alert_message__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t alert_message__pack_to_buffer
+ (const AlertMessage *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &alert_message__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+AlertMessage *
+ alert_message__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (AlertMessage *)
+ protobuf_c_message_unpack (&alert_message__descriptor,
+ allocator, len, data);
+}
+void alert_message__free_unpacked
+ (AlertMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &alert_message__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void restart_message__init
+ (RestartMessage *message)
+{
+ static const RestartMessage init_value = RESTART_MESSAGE__INIT;
+ *message = init_value;
+}
+size_t restart_message__get_packed_size
+ (const RestartMessage *message)
+{
+ assert(message->base.descriptor == &restart_message__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t restart_message__pack
+ (const RestartMessage *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &restart_message__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t restart_message__pack_to_buffer
+ (const RestartMessage *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &restart_message__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+RestartMessage *
+ restart_message__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (RestartMessage *)
+ protobuf_c_message_unpack (&restart_message__descriptor,
+ allocator, len, data);
+}
+void restart_message__free_unpacked
+ (RestartMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &restart_message__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void change_window_size__init
+ (ChangeWindowSize *message)
+{
+ static const ChangeWindowSize init_value = CHANGE_WINDOW_SIZE__INIT;
+ *message = init_value;
+}
+size_t change_window_size__get_packed_size
+ (const ChangeWindowSize *message)
+{
+ assert(message->base.descriptor == &change_window_size__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t change_window_size__pack
+ (const ChangeWindowSize *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &change_window_size__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t change_window_size__pack_to_buffer
+ (const ChangeWindowSize *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &change_window_size__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+ChangeWindowSize *
+ change_window_size__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (ChangeWindowSize *)
+ protobuf_c_message_unpack (&change_window_size__descriptor,
+ allocator, len, data);
+}
+void change_window_size__free_unpacked
+ (ChangeWindowSize *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &change_window_size__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void command_suspend__init
+ (CommandSuspend *message)
+{
+ static const CommandSuspend init_value = COMMAND_SUSPEND__INIT;
+ *message = init_value;
+}
+size_t command_suspend__get_packed_size
+ (const CommandSuspend *message)
+{
+ assert(message->base.descriptor == &command_suspend__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t command_suspend__pack
+ (const CommandSuspend *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &command_suspend__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t command_suspend__pack_to_buffer
+ (const CommandSuspend *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &command_suspend__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+CommandSuspend *
+ command_suspend__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (CommandSuspend *)
+ protobuf_c_message_unpack (&command_suspend__descriptor,
+ allocator, len, data);
+}
+void command_suspend__free_unpacked
+ (CommandSuspend *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &command_suspend__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void client_hello__init
+ (ClientHello *message)
+{
+ static const ClientHello init_value = CLIENT_HELLO__INIT;
+ *message = init_value;
+}
+size_t client_hello__get_packed_size
+ (const ClientHello *message)
+{
+ assert(message->base.descriptor == &client_hello__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t client_hello__pack
+ (const ClientHello *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &client_hello__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t client_hello__pack_to_buffer
+ (const ClientHello *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &client_hello__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+ClientHello *
+ client_hello__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (ClientHello *)
+ protobuf_c_message_unpack (&client_hello__descriptor,
+ allocator, len, data);
+}
+void client_hello__free_unpacked
+ (ClientHello *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &client_hello__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void server_message__init
+ (ServerMessage *message)
+{
+ static const ServerMessage init_value = SERVER_MESSAGE__INIT;
+ *message = init_value;
+}
+size_t server_message__get_packed_size
+ (const ServerMessage *message)
+{
+ assert(message->base.descriptor == &server_message__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t server_message__pack
+ (const ServerMessage *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &server_message__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t server_message__pack_to_buffer
+ (const ServerMessage *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &server_message__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+ServerMessage *
+ server_message__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (ServerMessage *)
+ protobuf_c_message_unpack (&server_message__descriptor,
+ allocator, len, data);
+}
+void server_message__free_unpacked
+ (ServerMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &server_message__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void server_hello__init
+ (ServerHello *message)
+{
+ static const ServerHello init_value = SERVER_HELLO__INIT;
+ *message = init_value;
+}
+size_t server_hello__get_packed_size
+ (const ServerHello *message)
+{
+ assert(message->base.descriptor == &server_hello__descriptor);
+ return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t server_hello__pack
+ (const ServerHello *message,
+ uint8_t *out)
+{
+ assert(message->base.descriptor == &server_hello__descriptor);
+ return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t server_hello__pack_to_buffer
+ (const ServerHello *message,
+ ProtobufCBuffer *buffer)
+{
+ assert(message->base.descriptor == &server_hello__descriptor);
+ return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+ServerHello *
+ server_hello__unpack
+ (ProtobufCAllocator *allocator,
+ size_t len,
+ const uint8_t *data)
+{
+ return (ServerHello *)
+ protobuf_c_message_unpack (&server_hello__descriptor,
+ allocator, len, data);
+}
+void server_hello__free_unpacked
+ (ServerHello *message,
+ ProtobufCAllocator *allocator)
+{
+ if(!message)
+ return;
+ assert(message->base.descriptor == &server_hello__descriptor);
+ protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+static const ProtobufCFieldDescriptor client_message__field_descriptors[13] =
+{
+ {
+ "accept_msg",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.accept_msg),
+ &accept_message__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "reject_msg",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.reject_msg),
+ &reject_message__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "exit_msg",
+ 3,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.exit_msg),
+ &exit_message__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "restart_msg",
+ 4,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.restart_msg),
+ &restart_message__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "alert_msg",
+ 5,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.alert_msg),
+ &alert_message__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "ttyin_buf",
+ 6,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.ttyin_buf),
+ &io_buffer__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "ttyout_buf",
+ 7,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.ttyout_buf),
+ &io_buffer__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "stdin_buf",
+ 8,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.stdin_buf),
+ &io_buffer__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "stdout_buf",
+ 9,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.stdout_buf),
+ &io_buffer__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "stderr_buf",
+ 10,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.stderr_buf),
+ &io_buffer__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "winsize_event",
+ 11,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.winsize_event),
+ &change_window_size__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "suspend_event",
+ 12,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.suspend_event),
+ &command_suspend__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "hello_msg",
+ 13,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ClientMessage, type_case),
+ offsetof(ClientMessage, u.hello_msg),
+ &client_hello__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned client_message__field_indices_by_name[] = {
+ 0, /* field[0] = accept_msg */
+ 4, /* field[4] = alert_msg */
+ 2, /* field[2] = exit_msg */
+ 12, /* field[12] = hello_msg */
+ 1, /* field[1] = reject_msg */
+ 3, /* field[3] = restart_msg */
+ 9, /* field[9] = stderr_buf */
+ 7, /* field[7] = stdin_buf */
+ 8, /* field[8] = stdout_buf */
+ 11, /* field[11] = suspend_event */
+ 5, /* field[5] = ttyin_buf */
+ 6, /* field[6] = ttyout_buf */
+ 10, /* field[10] = winsize_event */
+};
+static const ProtobufCIntRange client_message__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 13 }
+};
+const ProtobufCMessageDescriptor client_message__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "ClientMessage",
+ "ClientMessage",
+ "ClientMessage",
+ "",
+ sizeof(ClientMessage),
+ 13,
+ client_message__field_descriptors,
+ client_message__field_indices_by_name,
+ 1, client_message__number_ranges,
+ (ProtobufCMessageInit) client_message__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor time_spec__field_descriptors[2] =
+{
+ {
+ "tv_sec",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_INT64,
+ 0, /* quantifier_offset */
+ offsetof(TimeSpec, tv_sec),
+ NULL,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "tv_nsec",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_INT32,
+ 0, /* quantifier_offset */
+ offsetof(TimeSpec, tv_nsec),
+ NULL,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned time_spec__field_indices_by_name[] = {
+ 1, /* field[1] = tv_nsec */
+ 0, /* field[0] = tv_sec */
+};
+static const ProtobufCIntRange time_spec__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 2 }
+};
+const ProtobufCMessageDescriptor time_spec__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "TimeSpec",
+ "TimeSpec",
+ "TimeSpec",
+ "",
+ sizeof(TimeSpec),
+ 2,
+ time_spec__field_descriptors,
+ time_spec__field_indices_by_name,
+ 1, time_spec__number_ranges,
+ (ProtobufCMessageInit) time_spec__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor io_buffer__field_descriptors[2] =
+{
+ {
+ "delay",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(IoBuffer, delay),
+ &time_spec__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "data",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_BYTES,
+ 0, /* quantifier_offset */
+ offsetof(IoBuffer, data),
+ NULL,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned io_buffer__field_indices_by_name[] = {
+ 1, /* field[1] = data */
+ 0, /* field[0] = delay */
+};
+static const ProtobufCIntRange io_buffer__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 2 }
+};
+const ProtobufCMessageDescriptor io_buffer__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "IoBuffer",
+ "IoBuffer",
+ "IoBuffer",
+ "",
+ sizeof(IoBuffer),
+ 2,
+ io_buffer__field_descriptors,
+ io_buffer__field_indices_by_name,
+ 1, io_buffer__number_ranges,
+ (ProtobufCMessageInit) io_buffer__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor info_message__string_list__field_descriptors[1] =
+{
+ {
+ "strings",
+ 1,
+ PROTOBUF_C_LABEL_REPEATED,
+ PROTOBUF_C_TYPE_STRING,
+ offsetof(InfoMessage__StringList, n_strings),
+ offsetof(InfoMessage__StringList, strings),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned info_message__string_list__field_indices_by_name[] = {
+ 0, /* field[0] = strings */
+};
+static const ProtobufCIntRange info_message__string_list__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 1 }
+};
+const ProtobufCMessageDescriptor info_message__string_list__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "InfoMessage.StringList",
+ "StringList",
+ "InfoMessage__StringList",
+ "",
+ sizeof(InfoMessage__StringList),
+ 1,
+ info_message__string_list__field_descriptors,
+ info_message__string_list__field_indices_by_name,
+ 1, info_message__string_list__number_ranges,
+ (ProtobufCMessageInit) info_message__string_list__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor info_message__number_list__field_descriptors[1] =
+{
+ {
+ "numbers",
+ 1,
+ PROTOBUF_C_LABEL_REPEATED,
+ PROTOBUF_C_TYPE_INT64,
+ offsetof(InfoMessage__NumberList, n_numbers),
+ offsetof(InfoMessage__NumberList, numbers),
+ NULL,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_PACKED, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned info_message__number_list__field_indices_by_name[] = {
+ 0, /* field[0] = numbers */
+};
+static const ProtobufCIntRange info_message__number_list__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 1 }
+};
+const ProtobufCMessageDescriptor info_message__number_list__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "InfoMessage.NumberList",
+ "NumberList",
+ "InfoMessage__NumberList",
+ "",
+ sizeof(InfoMessage__NumberList),
+ 1,
+ info_message__number_list__field_descriptors,
+ info_message__number_list__field_indices_by_name,
+ 1, info_message__number_list__number_ranges,
+ (ProtobufCMessageInit) info_message__number_list__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor info_message__field_descriptors[5] =
+{
+ {
+ "key",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ 0, /* quantifier_offset */
+ offsetof(InfoMessage, key),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "numval",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_INT64,
+ offsetof(InfoMessage, value_case),
+ offsetof(InfoMessage, u.numval),
+ NULL,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "strval",
+ 3,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ offsetof(InfoMessage, value_case),
+ offsetof(InfoMessage, u.strval),
+ NULL,
+ &protobuf_c_empty_string,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "strlistval",
+ 4,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(InfoMessage, value_case),
+ offsetof(InfoMessage, u.strlistval),
+ &info_message__string_list__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "numlistval",
+ 5,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(InfoMessage, value_case),
+ offsetof(InfoMessage, u.numlistval),
+ &info_message__number_list__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned info_message__field_indices_by_name[] = {
+ 0, /* field[0] = key */
+ 4, /* field[4] = numlistval */
+ 1, /* field[1] = numval */
+ 3, /* field[3] = strlistval */
+ 2, /* field[2] = strval */
+};
+static const ProtobufCIntRange info_message__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 5 }
+};
+const ProtobufCMessageDescriptor info_message__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "InfoMessage",
+ "InfoMessage",
+ "InfoMessage",
+ "",
+ sizeof(InfoMessage),
+ 5,
+ info_message__field_descriptors,
+ info_message__field_indices_by_name,
+ 1, info_message__number_ranges,
+ (ProtobufCMessageInit) info_message__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor accept_message__field_descriptors[3] =
+{
+ {
+ "submit_time",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(AcceptMessage, submit_time),
+ &time_spec__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "info_msgs",
+ 2,
+ PROTOBUF_C_LABEL_REPEATED,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(AcceptMessage, n_info_msgs),
+ offsetof(AcceptMessage, info_msgs),
+ &info_message__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "expect_iobufs",
+ 3,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_BOOL,
+ 0, /* quantifier_offset */
+ offsetof(AcceptMessage, expect_iobufs),
+ NULL,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned accept_message__field_indices_by_name[] = {
+ 2, /* field[2] = expect_iobufs */
+ 1, /* field[1] = info_msgs */
+ 0, /* field[0] = submit_time */
+};
+static const ProtobufCIntRange accept_message__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 3 }
+};
+const ProtobufCMessageDescriptor accept_message__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "AcceptMessage",
+ "AcceptMessage",
+ "AcceptMessage",
+ "",
+ sizeof(AcceptMessage),
+ 3,
+ accept_message__field_descriptors,
+ accept_message__field_indices_by_name,
+ 1, accept_message__number_ranges,
+ (ProtobufCMessageInit) accept_message__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor reject_message__field_descriptors[3] =
+{
+ {
+ "submit_time",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(RejectMessage, submit_time),
+ &time_spec__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "reason",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ 0, /* quantifier_offset */
+ offsetof(RejectMessage, reason),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "info_msgs",
+ 3,
+ PROTOBUF_C_LABEL_REPEATED,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(RejectMessage, n_info_msgs),
+ offsetof(RejectMessage, info_msgs),
+ &info_message__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned reject_message__field_indices_by_name[] = {
+ 2, /* field[2] = info_msgs */
+ 1, /* field[1] = reason */
+ 0, /* field[0] = submit_time */
+};
+static const ProtobufCIntRange reject_message__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 3 }
+};
+const ProtobufCMessageDescriptor reject_message__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "RejectMessage",
+ "RejectMessage",
+ "RejectMessage",
+ "",
+ sizeof(RejectMessage),
+ 3,
+ reject_message__field_descriptors,
+ reject_message__field_indices_by_name,
+ 1, reject_message__number_ranges,
+ (ProtobufCMessageInit) reject_message__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor exit_message__field_descriptors[5] =
+{
+ {
+ "run_time",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(ExitMessage, run_time),
+ &time_spec__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "exit_value",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_INT32,
+ 0, /* quantifier_offset */
+ offsetof(ExitMessage, exit_value),
+ NULL,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "dumped_core",
+ 3,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_BOOL,
+ 0, /* quantifier_offset */
+ offsetof(ExitMessage, dumped_core),
+ NULL,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "signal",
+ 4,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ 0, /* quantifier_offset */
+ offsetof(ExitMessage, signal),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "error",
+ 5,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ 0, /* quantifier_offset */
+ offsetof(ExitMessage, error),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned exit_message__field_indices_by_name[] = {
+ 2, /* field[2] = dumped_core */
+ 4, /* field[4] = error */
+ 1, /* field[1] = exit_value */
+ 0, /* field[0] = run_time */
+ 3, /* field[3] = signal */
+};
+static const ProtobufCIntRange exit_message__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 5 }
+};
+const ProtobufCMessageDescriptor exit_message__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "ExitMessage",
+ "ExitMessage",
+ "ExitMessage",
+ "",
+ sizeof(ExitMessage),
+ 5,
+ exit_message__field_descriptors,
+ exit_message__field_indices_by_name,
+ 1, exit_message__number_ranges,
+ (ProtobufCMessageInit) exit_message__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor alert_message__field_descriptors[3] =
+{
+ {
+ "alert_time",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(AlertMessage, alert_time),
+ &time_spec__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "reason",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ 0, /* quantifier_offset */
+ offsetof(AlertMessage, reason),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "info_msgs",
+ 3,
+ PROTOBUF_C_LABEL_REPEATED,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(AlertMessage, n_info_msgs),
+ offsetof(AlertMessage, info_msgs),
+ &info_message__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned alert_message__field_indices_by_name[] = {
+ 0, /* field[0] = alert_time */
+ 2, /* field[2] = info_msgs */
+ 1, /* field[1] = reason */
+};
+static const ProtobufCIntRange alert_message__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 3 }
+};
+const ProtobufCMessageDescriptor alert_message__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "AlertMessage",
+ "AlertMessage",
+ "AlertMessage",
+ "",
+ sizeof(AlertMessage),
+ 3,
+ alert_message__field_descriptors,
+ alert_message__field_indices_by_name,
+ 1, alert_message__number_ranges,
+ (ProtobufCMessageInit) alert_message__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor restart_message__field_descriptors[2] =
+{
+ {
+ "log_id",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ 0, /* quantifier_offset */
+ offsetof(RestartMessage, log_id),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "resume_point",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(RestartMessage, resume_point),
+ &time_spec__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned restart_message__field_indices_by_name[] = {
+ 0, /* field[0] = log_id */
+ 1, /* field[1] = resume_point */
+};
+static const ProtobufCIntRange restart_message__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 2 }
+};
+const ProtobufCMessageDescriptor restart_message__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "RestartMessage",
+ "RestartMessage",
+ "RestartMessage",
+ "",
+ sizeof(RestartMessage),
+ 2,
+ restart_message__field_descriptors,
+ restart_message__field_indices_by_name,
+ 1, restart_message__number_ranges,
+ (ProtobufCMessageInit) restart_message__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor change_window_size__field_descriptors[3] =
+{
+ {
+ "delay",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(ChangeWindowSize, delay),
+ &time_spec__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "rows",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_INT32,
+ 0, /* quantifier_offset */
+ offsetof(ChangeWindowSize, rows),
+ NULL,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "cols",
+ 3,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_INT32,
+ 0, /* quantifier_offset */
+ offsetof(ChangeWindowSize, cols),
+ NULL,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned change_window_size__field_indices_by_name[] = {
+ 2, /* field[2] = cols */
+ 0, /* field[0] = delay */
+ 1, /* field[1] = rows */
+};
+static const ProtobufCIntRange change_window_size__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 3 }
+};
+const ProtobufCMessageDescriptor change_window_size__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "ChangeWindowSize",
+ "ChangeWindowSize",
+ "ChangeWindowSize",
+ "",
+ sizeof(ChangeWindowSize),
+ 3,
+ change_window_size__field_descriptors,
+ change_window_size__field_indices_by_name,
+ 1, change_window_size__number_ranges,
+ (ProtobufCMessageInit) change_window_size__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor command_suspend__field_descriptors[2] =
+{
+ {
+ "delay",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ 0, /* quantifier_offset */
+ offsetof(CommandSuspend, delay),
+ &time_spec__descriptor,
+ NULL,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "signal",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ 0, /* quantifier_offset */
+ offsetof(CommandSuspend, signal),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned command_suspend__field_indices_by_name[] = {
+ 0, /* field[0] = delay */
+ 1, /* field[1] = signal */
+};
+static const ProtobufCIntRange command_suspend__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 2 }
+};
+const ProtobufCMessageDescriptor command_suspend__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "CommandSuspend",
+ "CommandSuspend",
+ "CommandSuspend",
+ "",
+ sizeof(CommandSuspend),
+ 2,
+ command_suspend__field_descriptors,
+ command_suspend__field_indices_by_name,
+ 1, command_suspend__number_ranges,
+ (ProtobufCMessageInit) command_suspend__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor client_hello__field_descriptors[1] =
+{
+ {
+ "client_id",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ 0, /* quantifier_offset */
+ offsetof(ClientHello, client_id),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned client_hello__field_indices_by_name[] = {
+ 0, /* field[0] = client_id */
+};
+static const ProtobufCIntRange client_hello__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 1 }
+};
+const ProtobufCMessageDescriptor client_hello__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "ClientHello",
+ "ClientHello",
+ "ClientHello",
+ "",
+ sizeof(ClientHello),
+ 1,
+ client_hello__field_descriptors,
+ client_hello__field_indices_by_name,
+ 1, client_hello__number_ranges,
+ (ProtobufCMessageInit) client_hello__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor server_message__field_descriptors[5] =
+{
+ {
+ "hello",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ServerMessage, type_case),
+ offsetof(ServerMessage, u.hello),
+ &server_hello__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "commit_point",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_MESSAGE,
+ offsetof(ServerMessage, type_case),
+ offsetof(ServerMessage, u.commit_point),
+ &time_spec__descriptor,
+ NULL,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "log_id",
+ 3,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ offsetof(ServerMessage, type_case),
+ offsetof(ServerMessage, u.log_id),
+ NULL,
+ &protobuf_c_empty_string,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "error",
+ 4,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ offsetof(ServerMessage, type_case),
+ offsetof(ServerMessage, u.error),
+ NULL,
+ &protobuf_c_empty_string,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "abort",
+ 5,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ offsetof(ServerMessage, type_case),
+ offsetof(ServerMessage, u.abort),
+ NULL,
+ &protobuf_c_empty_string,
+ 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned server_message__field_indices_by_name[] = {
+ 4, /* field[4] = abort */
+ 1, /* field[1] = commit_point */
+ 3, /* field[3] = error */
+ 0, /* field[0] = hello */
+ 2, /* field[2] = log_id */
+};
+static const ProtobufCIntRange server_message__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 5 }
+};
+const ProtobufCMessageDescriptor server_message__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "ServerMessage",
+ "ServerMessage",
+ "ServerMessage",
+ "",
+ sizeof(ServerMessage),
+ 5,
+ server_message__field_descriptors,
+ server_message__field_indices_by_name,
+ 1, server_message__number_ranges,
+ (ProtobufCMessageInit) server_message__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor server_hello__field_descriptors[3] =
+{
+ {
+ "server_id",
+ 1,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ 0, /* quantifier_offset */
+ offsetof(ServerHello, server_id),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "redirect",
+ 2,
+ PROTOBUF_C_LABEL_NONE,
+ PROTOBUF_C_TYPE_STRING,
+ 0, /* quantifier_offset */
+ offsetof(ServerHello, redirect),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+ {
+ "servers",
+ 3,
+ PROTOBUF_C_LABEL_REPEATED,
+ PROTOBUF_C_TYPE_STRING,
+ offsetof(ServerHello, n_servers),
+ offsetof(ServerHello, servers),
+ NULL,
+ &protobuf_c_empty_string,
+ 0, /* flags */
+ 0,NULL,NULL /* reserved1,reserved2, etc */
+ },
+};
+static const unsigned server_hello__field_indices_by_name[] = {
+ 1, /* field[1] = redirect */
+ 0, /* field[0] = server_id */
+ 2, /* field[2] = servers */
+};
+static const ProtobufCIntRange server_hello__number_ranges[1 + 1] =
+{
+ { 1, 0 },
+ { 0, 3 }
+};
+const ProtobufCMessageDescriptor server_hello__descriptor =
+{
+ PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+ "ServerHello",
+ "ServerHello",
+ "ServerHello",
+ "",
+ sizeof(ServerHello),
+ 3,
+ server_hello__field_descriptors,
+ server_hello__field_indices_by_name,
+ 1, server_hello__number_ranges,
+ (ProtobufCMessageInit) server_hello__init,
+ NULL,NULL,NULL /* reserved[123] */
+};
diff --git a/lib/logsrv/log_server.proto b/lib/logsrv/log_server.proto
new file mode 100644
index 0000000..9c79737
--- /dev/null
+++ b/lib/logsrv/log_server.proto
@@ -0,0 +1,135 @@
+syntax = "proto3";
+
+/*
+ * Client message to the server. Messages on the wire are
+ * prefixed with a 32-bit size in network byte order.
+ */
+message ClientMessage {
+ oneof type {
+ AcceptMessage accept_msg = 1;
+ RejectMessage reject_msg = 2;
+ ExitMessage exit_msg = 3;
+ RestartMessage restart_msg = 4;
+ AlertMessage alert_msg = 5;
+ IoBuffer ttyin_buf = 6;
+ IoBuffer ttyout_buf = 7;
+ IoBuffer stdin_buf = 8;
+ IoBuffer stdout_buf = 9;
+ IoBuffer stderr_buf = 10;
+ ChangeWindowSize winsize_event = 11;
+ CommandSuspend suspend_event = 12;
+ ClientHello hello_msg = 13;
+ }
+}
+
+/* Equivalent of POSIX struct timespec */
+message TimeSpec {
+ int64 tv_sec = 1; /* seconds */
+ int32 tv_nsec = 2; /* nanoseconds */
+}
+
+/* I/O buffer with keystroke data */
+message IoBuffer {
+ TimeSpec delay = 1; /* elapsed time since last record */
+ bytes data = 2; /* keystroke data */
+}
+
+/*
+ * Key/value pairs, like Privilege Manager struct info.
+ * The value may be a number, a string, or a list of strings.
+ */
+message InfoMessage {
+ message StringList {
+ repeated string strings = 1;
+ }
+ message NumberList {
+ repeated int64 numbers = 1;
+ }
+ string key = 1;
+ oneof value {
+ int64 numval = 2;
+ string strval = 3;
+ StringList strlistval = 4;
+ NumberList numlistval = 5;
+ }
+}
+
+/*
+ * Event log data for command accepted by the policy.
+ */
+message AcceptMessage {
+ TimeSpec submit_time = 1; /* when command was submitted */
+ repeated InfoMessage info_msgs = 2; /* key,value event log data */
+ bool expect_iobufs = 3; /* true if I/O logging enabled */
+}
+
+/*
+ * Event log data for command rejected by the policy.
+ */
+message RejectMessage {
+ TimeSpec submit_time = 1; /* when command was submitted */
+ string reason = 2; /* reason command was rejected */
+ repeated InfoMessage info_msgs = 3; /* key,value event log data */
+}
+
+/* Message sent by client when command exits. */
+/* Might revisit runtime and use end_time instead */
+message ExitMessage {
+ TimeSpec run_time = 1; /* total elapsed run time */
+ int32 exit_value = 2; /* 0-255 */
+ bool dumped_core = 3; /* true if command dumped core */
+ string signal = 4; /* signal name if killed by signal */
+ string error = 5; /* if killed due to other error */
+}
+
+/* Alert message, policy module-specific. */
+message AlertMessage {
+ TimeSpec alert_time = 1; /* time alert message occurred */
+ string reason = 2; /* policy alert error string */
+ repeated InfoMessage info_msgs = 3; /* optional key,value event log data */
+}
+
+/* Used to restart an existing I/O log on the server. */
+message RestartMessage {
+ string log_id = 1; /* ID of log being restarted */
+ TimeSpec resume_point = 2; /* resume point (elapsed time) */
+}
+
+/* Window size change event. */
+message ChangeWindowSize {
+ TimeSpec delay = 1; /* elapsed time since last record */
+ int32 rows = 2; /* new number of rows */
+ int32 cols = 3; /* new number of columns */
+}
+
+/* Command suspend/resume event. */
+message CommandSuspend {
+ TimeSpec delay = 1; /* elapsed time since last record */
+ string signal = 2; /* signal that caused suspend/resume */
+}
+
+/* Hello message from client when connecting to server. */
+message ClientHello {
+ string client_id = 1; /* free-form client description */
+}
+
+/*
+ * Server messages to the client. Messages on the wire are
+ * prefixed with a 32-bit size in network byte order.
+ */
+message ServerMessage {
+ oneof type {
+ ServerHello hello = 1; /* server hello message */
+ TimeSpec commit_point = 2; /* cumulative time of records stored */
+ string log_id = 3; /* ID of server-side I/O log */
+ string error = 4; /* error message from server */
+ string abort = 5; /* abort message, kill command */
+ }
+}
+
+/* Hello message from server when client connects. */
+message ServerHello {
+ string server_id = 1; /* free-form server description */
+ string redirect = 2; /* optional redirect if busy */
+ repeated string servers = 3; /* optional list of known servers */
+}
diff --git a/lib/logsrv/protobuf-c.c b/lib/logsrv/protobuf-c.c
new file mode 100644
index 0000000..0325bc0
--- /dev/null
+++ b/lib/logsrv/protobuf-c.c
@@ -0,0 +1,3662 @@
+/*
+ * Copyright (c) 2008-2015, Dave Benson and the protobuf-c authors.
+ * 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.
+ *
+ * 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 THE COPYRIGHT
+ * OWNER 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.
+ */
+
+/*! \file
+ * Support library for `protoc-c` generated code.
+ *
+ * This file implements the public API used by the code generated
+ * by `protoc-c`.
+ *
+ * \authors Dave Benson and the protobuf-c authors
+ *
+ * \copyright 2008-2014. Licensed under the terms of the [BSD-2-Clause] license.
+ */
+
+/**
+ * \todo 64-BIT OPTIMIZATION: certain implementations use 32-bit math
+ * even on 64-bit platforms (uint64_size, uint64_pack, parse_uint64).
+ *
+ * \todo Use size_t consistently.
+ */
+
+#include <config.h>
+
+#include <stdlib.h> /* for malloc, free */
+#include <string.h> /* for strcmp, strlen, memcpy, memmove, memset */
+
+#include "protobuf-c/protobuf-c.h"
+
+#define TRUE 1
+#define FALSE 0
+
+#define PROTOBUF_C__ASSERT_NOT_REACHED() assert(0)
+
+/* Workaround for Microsoft compilers. */
+#ifdef _MSC_VER
+# define inline __inline
+#endif
+
+/**
+ * \defgroup internal Internal functions and macros
+ *
+ * These are not exported by the library but are useful to developers working
+ * on `libprotobuf-c` itself.
+ */
+
+/**
+ * \defgroup macros Utility macros for manipulating structures
+ *
+ * Macros and constants used to manipulate the base "classes" generated by
+ * `protobuf-c`. They also define limits and check correctness.
+ *
+ * \ingroup internal
+ * @{
+ */
+
+/** The maximum length of a 64-bit integer in varint encoding. */
+#define MAX_UINT64_ENCODED_SIZE 10
+
+#ifndef PROTOBUF_C_UNPACK_ERROR
+# define PROTOBUF_C_UNPACK_ERROR(...)
+#endif
+
+const char protobuf_c_empty_string[] = "";
+
+/**
+ * Internal `ProtobufCMessage` manipulation macro.
+ *
+ * Base macro for manipulating a `ProtobufCMessage`. Used by STRUCT_MEMBER() and
+ * STRUCT_MEMBER_PTR().
+ */
+#define STRUCT_MEMBER_P(struct_p, struct_offset) \
+ ((void *) ((uint8_t *) (struct_p) + (struct_offset)))
+
+/**
+ * Return field in a `ProtobufCMessage` based on offset.
+ *
+ * Take a pointer to a `ProtobufCMessage` and find the field at the offset.
+ * Cast it to the passed type.
+ */
+#define STRUCT_MEMBER(member_type, struct_p, struct_offset) \
+ (*(member_type *) STRUCT_MEMBER_P((struct_p), (struct_offset)))
+
+/**
+ * Return field in a `ProtobufCMessage` based on offset.
+ *
+ * Take a pointer to a `ProtobufCMessage` and find the field at the offset. Cast
+ * it to a pointer to the passed type.
+ */
+#define STRUCT_MEMBER_PTR(member_type, struct_p, struct_offset) \
+ ((member_type *) STRUCT_MEMBER_P((struct_p), (struct_offset)))
+
+/* Assertions for magic numbers. */
+
+#define ASSERT_IS_ENUM_DESCRIPTOR(desc) \
+ assert((desc)->magic == PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC)
+
+#define ASSERT_IS_MESSAGE_DESCRIPTOR(desc) \
+ assert((desc)->magic == PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC)
+
+#define ASSERT_IS_MESSAGE(message) \
+ ASSERT_IS_MESSAGE_DESCRIPTOR((message)->descriptor)
+
+#define ASSERT_IS_SERVICE_DESCRIPTOR(desc) \
+ assert((desc)->magic == PROTOBUF_C__SERVICE_DESCRIPTOR_MAGIC)
+
+/**@}*/
+
+/* --- version --- */
+
+const char *
+protobuf_c_version(void)
+{
+ return PROTOBUF_C_VERSION;
+}
+
+uint32_t
+protobuf_c_version_number(void)
+{
+ return PROTOBUF_C_VERSION_NUMBER;
+}
+
+/* --- allocator --- */
+
+static void *
+system_alloc(void *allocator_data, size_t size)
+{
+ return malloc(size);
+}
+
+static void
+system_free(void *allocator_data, void *data)
+{
+ free(data);
+}
+
+static inline void *
+do_alloc(ProtobufCAllocator *allocator, size_t size)
+{
+ return allocator->alloc(allocator->allocator_data, size);
+}
+
+static inline void
+do_free(ProtobufCAllocator *allocator, void *data)
+{
+ if (data != NULL)
+ allocator->free(allocator->allocator_data, data);
+}
+
+/*
+ * This allocator uses the system's malloc() and free(). It is the default
+ * allocator used if NULL is passed as the ProtobufCAllocator to an exported
+ * function.
+ */
+static ProtobufCAllocator protobuf_c__allocator = {
+ .alloc = &system_alloc,
+ .free = &system_free,
+ .allocator_data = NULL,
+};
+
+/* === buffer-simple === */
+
+void
+protobuf_c_buffer_simple_append(ProtobufCBuffer *buffer,
+ size_t len, const uint8_t *data)
+{
+ ProtobufCBufferSimple *simp = (ProtobufCBufferSimple *) buffer;
+ size_t new_len = simp->len + len;
+
+ if (new_len > simp->alloced) {
+ ProtobufCAllocator *allocator = simp->allocator;
+ size_t new_alloced = simp->alloced * 2;
+ uint8_t *new_data;
+
+ if (allocator == NULL)
+ allocator = &protobuf_c__allocator;
+ while (new_alloced < new_len)
+ new_alloced += new_alloced;
+ new_data = do_alloc(allocator, new_alloced);
+ if (!new_data)
+ return;
+ memcpy(new_data, simp->data, simp->len);
+ if (simp->must_free_data)
+ do_free(allocator, simp->data);
+ else
+ simp->must_free_data = TRUE;
+ simp->data = new_data;
+ simp->alloced = new_alloced;
+ }
+ memcpy(simp->data + simp->len, data, len);
+ simp->len = new_len;
+}
+
+/**
+ * \defgroup packedsz protobuf_c_message_get_packed_size() implementation
+ *
+ * Routines mainly used by protobuf_c_message_get_packed_size().
+ *
+ * \ingroup internal
+ * @{
+ */
+
+/**
+ * Return the number of bytes required to store the tag for the field. Includes
+ * 3 bits for the wire-type, and a single bit that denotes the end-of-tag.
+ *
+ * \param number
+ * Field tag to encode.
+ * \return
+ * Number of bytes required.
+ */
+static inline size_t
+get_tag_size(uint32_t number)
+{
+ if (number < (1UL << 4)) {
+ return 1;
+ } else if (number < (1UL << 11)) {
+ return 2;
+ } else if (number < (1UL << 18)) {
+ return 3;
+ } else if (number < (1UL << 25)) {
+ return 4;
+ } else {
+ return 5;
+ }
+}
+
+/**
+ * Return the number of bytes required to store a variable-length unsigned
+ * 32-bit integer in base-128 varint encoding.
+ *
+ * \param v
+ * Value to encode.
+ * \return
+ * Number of bytes required.
+ */
+static inline size_t
+uint32_size(uint32_t v)
+{
+ if (v < (1UL << 7)) {
+ return 1;
+ } else if (v < (1UL << 14)) {
+ return 2;
+ } else if (v < (1UL << 21)) {
+ return 3;
+ } else if (v < (1UL << 28)) {
+ return 4;
+ } else {
+ return 5;
+ }
+}
+
+/**
+ * Return the number of bytes required to store a variable-length signed 32-bit
+ * integer in base-128 varint encoding.
+ *
+ * \param v
+ * Value to encode.
+ * \return
+ * Number of bytes required.
+ */
+static inline size_t
+int32_size(int32_t v)
+{
+ if (v < 0) {
+ return 10;
+ } else if (v < (1L << 7)) {
+ return 1;
+ } else if (v < (1L << 14)) {
+ return 2;
+ } else if (v < (1L << 21)) {
+ return 3;
+ } else if (v < (1L << 28)) {
+ return 4;
+ } else {
+ return 5;
+ }
+}
+
+/**
+ * Return the ZigZag-encoded 32-bit unsigned integer form of a 32-bit signed
+ * integer.
+ *
+ * \param v
+ * Value to encode.
+ * \return
+ * ZigZag encoded integer.
+ */
+static inline uint32_t
+zigzag32(int32_t v)
+{
+ // Note: the right-shift must be arithmetic
+ // Note: left shift must be unsigned because of overflow
+ return ((uint32_t)(v) << 1) ^ (uint32_t)(v >> 31);
+}
+
+/**
+ * Return the number of bytes required to store a signed 32-bit integer,
+ * converted to an unsigned 32-bit integer with ZigZag encoding, using base-128
+ * varint encoding.
+ *
+ * \param v
+ * Value to encode.
+ * \return
+ * Number of bytes required.
+ */
+static inline size_t
+sint32_size(int32_t v)
+{
+ return uint32_size(zigzag32(v));
+}
+
+/**
+ * Return the number of bytes required to store a 64-bit unsigned integer in
+ * base-128 varint encoding.
+ *
+ * \param v
+ * Value to encode.
+ * \return
+ * Number of bytes required.
+ */
+static inline size_t
+uint64_size(uint64_t v)
+{
+ uint32_t upper_v = (uint32_t) (v >> 32);
+
+ if (upper_v == 0) {
+ return uint32_size((uint32_t) v);
+ } else if (upper_v < (1UL << 3)) {
+ return 5;
+ } else if (upper_v < (1UL << 10)) {
+ return 6;
+ } else if (upper_v < (1UL << 17)) {
+ return 7;
+ } else if (upper_v < (1UL << 24)) {
+ return 8;
+ } else if (upper_v < (1UL << 31)) {
+ return 9;
+ } else {
+ return 10;
+ }
+}
+
+/**
+ * Return the ZigZag-encoded 64-bit unsigned integer form of a 64-bit signed
+ * integer.
+ *
+ * \param v
+ * Value to encode.
+ * \return
+ * ZigZag encoded integer.
+ */
+static inline uint64_t
+zigzag64(int64_t v)
+{
+ // Note: the right-shift must be arithmetic
+ // Note: left shift must be unsigned because of overflow
+ return ((uint64_t)(v) << 1) ^ (uint64_t)(v >> 63);
+}
+
+/**
+ * Return the number of bytes required to store a signed 64-bit integer,
+ * converted to an unsigned 64-bit integer with ZigZag encoding, using base-128
+ * varint encoding.
+ *
+ * \param v
+ * Value to encode.
+ * \return
+ * Number of bytes required.
+ */
+static inline size_t
+sint64_size(int64_t v)
+{
+ return uint64_size(zigzag64(v));
+}
+
+/**
+ * Calculate the serialized size of a single required message field, including
+ * the space needed by the preceding tag.
+ *
+ * \param field
+ * Field descriptor for member.
+ * \param member
+ * Field to encode.
+ * \return
+ * Number of bytes required.
+ */
+static size_t
+required_field_get_packed_size(const ProtobufCFieldDescriptor *field,
+ const void *member)
+{
+ size_t rv = get_tag_size(field->id);
+
+ switch (field->type) {
+ case PROTOBUF_C_TYPE_SINT32:
+ return rv + sint32_size(*(const int32_t *) member);
+ case PROTOBUF_C_TYPE_ENUM:
+ case PROTOBUF_C_TYPE_INT32:
+ return rv + int32_size(*(const int32_t *) member);
+ case PROTOBUF_C_TYPE_UINT32:
+ return rv + uint32_size(*(const uint32_t *) member);
+ case PROTOBUF_C_TYPE_SINT64:
+ return rv + sint64_size(*(const int64_t *) member);
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_UINT64:
+ return rv + uint64_size(*(const uint64_t *) member);
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ return rv + 4;
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ return rv + 8;
+ case PROTOBUF_C_TYPE_BOOL:
+ return rv + 1;
+ case PROTOBUF_C_TYPE_FLOAT:
+ return rv + 4;
+ case PROTOBUF_C_TYPE_DOUBLE:
+ return rv + 8;
+ case PROTOBUF_C_TYPE_STRING: {
+ const char *str = *(char * const *) member;
+ size_t len = str ? strlen(str) : 0;
+ return rv + uint32_size(len) + len;
+ }
+ case PROTOBUF_C_TYPE_BYTES: {
+ size_t len = ((const ProtobufCBinaryData *) member)->len;
+ return rv + uint32_size(len) + len;
+ }
+ case PROTOBUF_C_TYPE_MESSAGE: {
+ const ProtobufCMessage *msg = *(ProtobufCMessage * const *) member;
+ size_t subrv = msg ? protobuf_c_message_get_packed_size(msg) : 0;
+ return rv + uint32_size(subrv) + subrv;
+ }
+ }
+ PROTOBUF_C__ASSERT_NOT_REACHED();
+ return 0;
+}
+
+/**
+ * Calculate the serialized size of a single oneof message field, including
+ * the space needed by the preceding tag. Returns 0 if the oneof field isn't
+ * selected or is not set.
+ *
+ * \param field
+ * Field descriptor for member.
+ * \param oneof_case
+ * Enum value that selects the field in the oneof.
+ * \param member
+ * Field to encode.
+ * \return
+ * Number of bytes required.
+ */
+static size_t
+oneof_field_get_packed_size(const ProtobufCFieldDescriptor *field,
+ uint32_t oneof_case,
+ const void *member)
+{
+ if (oneof_case != field->id) {
+ return 0;
+ }
+ if (field->type == PROTOBUF_C_TYPE_MESSAGE ||
+ field->type == PROTOBUF_C_TYPE_STRING)
+ {
+ const void *ptr = *(const void * const *) member;
+ if (ptr == NULL || ptr == field->default_value)
+ return 0;
+ }
+ return required_field_get_packed_size(field, member);
+}
+
+/**
+ * Calculate the serialized size of a single optional message field, including
+ * the space needed by the preceding tag. Returns 0 if the optional field isn't
+ * set.
+ *
+ * \param field
+ * Field descriptor for member.
+ * \param has
+ * True if the field exists, false if not.
+ * \param member
+ * Field to encode.
+ * \return
+ * Number of bytes required.
+ */
+static size_t
+optional_field_get_packed_size(const ProtobufCFieldDescriptor *field,
+ const protobuf_c_boolean has,
+ const void *member)
+{
+ if (field->type == PROTOBUF_C_TYPE_MESSAGE ||
+ field->type == PROTOBUF_C_TYPE_STRING)
+ {
+ const void *ptr = *(const void * const *) member;
+ if (ptr == NULL || ptr == field->default_value)
+ return 0;
+ } else {
+ if (!has)
+ return 0;
+ }
+ return required_field_get_packed_size(field, member);
+}
+
+static protobuf_c_boolean
+field_is_zeroish(const ProtobufCFieldDescriptor *field,
+ const void *member)
+{
+ protobuf_c_boolean ret = FALSE;
+
+ switch (field->type) {
+ case PROTOBUF_C_TYPE_BOOL:
+ ret = (0 == *(const protobuf_c_boolean *) member);
+ break;
+ case PROTOBUF_C_TYPE_ENUM:
+ case PROTOBUF_C_TYPE_SINT32:
+ case PROTOBUF_C_TYPE_INT32:
+ case PROTOBUF_C_TYPE_UINT32:
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ ret = (0 == *(const uint32_t *) member);
+ break;
+ case PROTOBUF_C_TYPE_SINT64:
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_UINT64:
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ ret = (0 == *(const uint64_t *) member);
+ break;
+ case PROTOBUF_C_TYPE_FLOAT:
+ ret = (0 == *(const float *) member);
+ break;
+ case PROTOBUF_C_TYPE_DOUBLE:
+ ret = (0 == *(const double *) member);
+ break;
+ case PROTOBUF_C_TYPE_STRING:
+ ret = (NULL == *(const char * const *) member) ||
+ ('\0' == **(const char * const *) member);
+ break;
+ case PROTOBUF_C_TYPE_BYTES:
+ case PROTOBUF_C_TYPE_MESSAGE:
+ ret = (NULL == *(const void * const *) member);
+ break;
+ default:
+ ret = TRUE;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * Calculate the serialized size of a single unlabeled message field, including
+ * the space needed by the preceding tag. Returns 0 if the field isn't set or
+ * if it is set to a "zeroish" value (null pointer or 0 for numerical values).
+ * Unlabeled fields are supported only in proto3.
+ *
+ * \param field
+ * Field descriptor for member.
+ * \param member
+ * Field to encode.
+ * \return
+ * Number of bytes required.
+ */
+static size_t
+unlabeled_field_get_packed_size(const ProtobufCFieldDescriptor *field,
+ const void *member)
+{
+ if (field_is_zeroish(field, member))
+ return 0;
+ return required_field_get_packed_size(field, member);
+}
+
+/**
+ * Calculate the serialized size of repeated message fields, which may consist
+ * of any number of values (including 0). Includes the space needed by the
+ * preceding tags (as needed).
+ *
+ * \param field
+ * Field descriptor for member.
+ * \param count
+ * Number of repeated field members.
+ * \param member
+ * Field to encode.
+ * \return
+ * Number of bytes required.
+ */
+static size_t
+repeated_field_get_packed_size(const ProtobufCFieldDescriptor *field,
+ size_t count, const void *member)
+{
+ size_t header_size;
+ size_t rv = 0;
+ unsigned i;
+ void *array = *(void * const *) member;
+
+ if (count == 0)
+ return 0;
+ header_size = get_tag_size(field->id);
+ if (0 == (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED))
+ header_size *= count;
+
+ switch (field->type) {
+ case PROTOBUF_C_TYPE_SINT32:
+ for (i = 0; i < count; i++)
+ rv += sint32_size(((int32_t *) array)[i]);
+ break;
+ case PROTOBUF_C_TYPE_ENUM:
+ case PROTOBUF_C_TYPE_INT32:
+ for (i = 0; i < count; i++)
+ rv += int32_size(((int32_t *) array)[i]);
+ break;
+ case PROTOBUF_C_TYPE_UINT32:
+ for (i = 0; i < count; i++)
+ rv += uint32_size(((uint32_t *) array)[i]);
+ break;
+ case PROTOBUF_C_TYPE_SINT64:
+ for (i = 0; i < count; i++)
+ rv += sint64_size(((int64_t *) array)[i]);
+ break;
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_UINT64:
+ for (i = 0; i < count; i++)
+ rv += uint64_size(((uint64_t *) array)[i]);
+ break;
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ case PROTOBUF_C_TYPE_FLOAT:
+ rv += 4 * count;
+ break;
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ case PROTOBUF_C_TYPE_DOUBLE:
+ rv += 8 * count;
+ break;
+ case PROTOBUF_C_TYPE_BOOL:
+ rv += count;
+ break;
+ case PROTOBUF_C_TYPE_STRING:
+ for (i = 0; i < count; i++) {
+ size_t len = strlen(((char **) array)[i]);
+ rv += uint32_size(len) + len;
+ }
+ break;
+ case PROTOBUF_C_TYPE_BYTES:
+ for (i = 0; i < count; i++) {
+ size_t len = ((ProtobufCBinaryData *) array)[i].len;
+ rv += uint32_size(len) + len;
+ }
+ break;
+ case PROTOBUF_C_TYPE_MESSAGE:
+ for (i = 0; i < count; i++) {
+ size_t len = protobuf_c_message_get_packed_size(
+ ((ProtobufCMessage **) array)[i]);
+ rv += uint32_size(len) + len;
+ }
+ break;
+ }
+
+ if (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED))
+ header_size += uint32_size(rv);
+ return header_size + rv;
+}
+
+/**
+ * Calculate the serialized size of an unknown field, i.e. one that is passed
+ * through mostly uninterpreted. This is required for forward compatibility if
+ * new fields are added to the message descriptor.
+ *
+ * \param field
+ * Unknown field type.
+ * \return
+ * Number of bytes required.
+ */
+static inline size_t
+unknown_field_get_packed_size(const ProtobufCMessageUnknownField *field)
+{
+ return get_tag_size(field->tag) + field->len;
+}
+
+/**@}*/
+
+/*
+ * Calculate the serialized size of the message.
+ */
+size_t protobuf_c_message_get_packed_size(const ProtobufCMessage *message)
+{
+ unsigned i;
+ size_t rv = 0;
+
+ ASSERT_IS_MESSAGE(message);
+ for (i = 0; i < message->descriptor->n_fields; i++) {
+ const ProtobufCFieldDescriptor *field =
+ message->descriptor->fields + i;
+ const void *member =
+ ((const char *) message) + field->offset;
+ const void *qmember =
+ ((const char *) message) + field->quantifier_offset;
+
+ if (field->label == PROTOBUF_C_LABEL_REQUIRED) {
+ rv += required_field_get_packed_size(field, member);
+ } else if ((field->label == PROTOBUF_C_LABEL_OPTIONAL ||
+ field->label == PROTOBUF_C_LABEL_NONE) &&
+ (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_ONEOF))) {
+ rv += oneof_field_get_packed_size(
+ field,
+ *(const uint32_t *) qmember,
+ member
+ );
+ } else if (field->label == PROTOBUF_C_LABEL_OPTIONAL) {
+ rv += optional_field_get_packed_size(
+ field,
+ *(protobuf_c_boolean *) qmember,
+ member
+ );
+ } else if (field->label == PROTOBUF_C_LABEL_NONE) {
+ rv += unlabeled_field_get_packed_size(
+ field,
+ member
+ );
+ } else {
+ rv += repeated_field_get_packed_size(
+ field,
+ *(const size_t *) qmember,
+ member
+ );
+ }
+ }
+ for (i = 0; i < message->n_unknown_fields; i++)
+ rv += unknown_field_get_packed_size(&message->unknown_fields[i]);
+ return rv;
+}
+
+/**
+ * \defgroup pack protobuf_c_message_pack() implementation
+ *
+ * Routines mainly used by protobuf_c_message_pack().
+ *
+ * \ingroup internal
+ * @{
+ */
+
+/**
+ * Pack an unsigned 32-bit integer in base-128 varint encoding and return the
+ * number of bytes written, which must be 5 or less.
+ *
+ * \param value
+ * Value to encode.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static inline size_t
+uint32_pack(uint32_t value, uint8_t *out)
+{
+ unsigned rv = 0;
+
+ if (value >= 0x80) {
+ out[rv++] = value | 0x80;
+ value >>= 7;
+ if (value >= 0x80) {
+ out[rv++] = value | 0x80;
+ value >>= 7;
+ if (value >= 0x80) {
+ out[rv++] = value | 0x80;
+ value >>= 7;
+ if (value >= 0x80) {
+ out[rv++] = value | 0x80;
+ value >>= 7;
+ }
+ }
+ }
+ }
+ /* assert: value<128 */
+ out[rv++] = value;
+ return rv;
+}
+
+/**
+ * Pack a signed 32-bit integer and return the number of bytes written.
+ * Negative numbers are encoded as two's complement 64-bit integers.
+ *
+ * \param value
+ * Value to encode.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static inline size_t
+int32_pack(int32_t value, uint8_t *out)
+{
+ if (value < 0) {
+ out[0] = value | 0x80;
+ out[1] = (value >> 7) | 0x80;
+ out[2] = (value >> 14) | 0x80;
+ out[3] = (value >> 21) | 0x80;
+ out[4] = (value >> 28) | 0x80;
+ out[5] = out[6] = out[7] = out[8] = 0xff;
+ out[9] = 0x01;
+ return 10;
+ } else {
+ return uint32_pack(value, out);
+ }
+}
+
+/**
+ * Pack a signed 32-bit integer using ZigZag encoding and return the number of
+ * bytes written.
+ *
+ * \param value
+ * Value to encode.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static inline size_t
+sint32_pack(int32_t value, uint8_t *out)
+{
+ return uint32_pack(zigzag32(value), out);
+}
+
+/**
+ * Pack a 64-bit unsigned integer using base-128 varint encoding and return the
+ * number of bytes written.
+ *
+ * \param value
+ * Value to encode.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static size_t
+uint64_pack(uint64_t value, uint8_t *out)
+{
+ uint32_t hi = (uint32_t) (value >> 32);
+ uint32_t lo = (uint32_t) value;
+ unsigned rv;
+
+ if (hi == 0)
+ return uint32_pack((uint32_t) lo, out);
+ out[0] = (lo) | 0x80;
+ out[1] = (lo >> 7) | 0x80;
+ out[2] = (lo >> 14) | 0x80;
+ out[3] = (lo >> 21) | 0x80;
+ if (hi < 8) {
+ out[4] = (hi << 4) | (lo >> 28);
+ return 5;
+ } else {
+ out[4] = ((hi & 7) << 4) | (lo >> 28) | 0x80;
+ hi >>= 3;
+ }
+ rv = 5;
+ while (hi >= 128) {
+ out[rv++] = hi | 0x80;
+ hi >>= 7;
+ }
+ out[rv++] = hi;
+ return rv;
+}
+
+/**
+ * Pack a 64-bit signed integer in ZigZag encoding and return the number of
+ * bytes written.
+ *
+ * \param value
+ * Value to encode.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static inline size_t
+sint64_pack(int64_t value, uint8_t *out)
+{
+ return uint64_pack(zigzag64(value), out);
+}
+
+/**
+ * Pack a 32-bit quantity in little-endian byte order. Used for protobuf wire
+ * types fixed32, sfixed32, float. Similar to "htole32".
+ *
+ * \param value
+ * Value to encode.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static inline size_t
+fixed32_pack(uint32_t value, void *out)
+{
+#if !defined(WORDS_BIGENDIAN)
+ memcpy(out, &value, 4);
+#else
+ uint8_t *buf = out;
+
+ buf[0] = value;
+ buf[1] = value >> 8;
+ buf[2] = value >> 16;
+ buf[3] = value >> 24;
+#endif
+ return 4;
+}
+
+/**
+ * Pack a 64-bit quantity in little-endian byte order. Used for protobuf wire
+ * types fixed64, sfixed64, double. Similar to "htole64".
+ *
+ * \todo The big-endian impl is really only good for 32-bit machines, a 64-bit
+ * version would be appreciated, plus a way to decide to use 64-bit math where
+ * convenient.
+ *
+ * \param value
+ * Value to encode.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static inline size_t
+fixed64_pack(uint64_t value, void *out)
+{
+#if !defined(WORDS_BIGENDIAN)
+ memcpy(out, &value, 8);
+#else
+ fixed32_pack(value, out);
+ fixed32_pack(value >> 32, ((char *) out) + 4);
+#endif
+ return 8;
+}
+
+/**
+ * Pack a boolean value as an integer and return the number of bytes written.
+ *
+ * \todo Perhaps on some platforms *out = !!value would be a better impl, b/c
+ * that is idiomatic C++ in some STL implementations.
+ *
+ * \param value
+ * Value to encode.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static inline size_t
+boolean_pack(protobuf_c_boolean value, uint8_t *out)
+{
+ *out = value ? TRUE : FALSE;
+ return 1;
+}
+
+/**
+ * Pack a NUL-terminated C string and return the number of bytes written. The
+ * output includes a length delimiter.
+ *
+ * The NULL pointer is treated as an empty string. This isn't really necessary,
+ * but it allows people to leave required strings blank. (See Issue #13 in the
+ * bug tracker for a little more explanation).
+ *
+ * \param str
+ * String to encode.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static inline size_t
+string_pack(const char *str, uint8_t *out)
+{
+ if (str == NULL) {
+ out[0] = 0;
+ return 1;
+ } else {
+ size_t len = strlen(str);
+ size_t rv = uint32_pack(len, out);
+ memcpy(out + rv, str, len);
+ return rv + len;
+ }
+}
+
+/**
+ * Pack a ProtobufCBinaryData and return the number of bytes written. The output
+ * includes a length delimiter.
+ *
+ * \param bd
+ * ProtobufCBinaryData to encode.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static inline size_t
+binary_data_pack(const ProtobufCBinaryData *bd, uint8_t *out)
+{
+ size_t len = bd->len;
+ size_t rv = uint32_pack(len, out);
+ memcpy(out + rv, bd->data, len);
+ return rv + len;
+}
+
+/**
+ * Pack a ProtobufCMessage and return the number of bytes written. The output
+ * includes a length delimiter.
+ *
+ * \param message
+ * ProtobufCMessage object to pack.
+ * \param[out] out
+ * Packed message.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static inline size_t
+prefixed_message_pack(const ProtobufCMessage *message, uint8_t *out)
+{
+ if (message == NULL) {
+ out[0] = 0;
+ return 1;
+ } else {
+ size_t rv = protobuf_c_message_pack(message, out + 1);
+ uint32_t rv_packed_size = uint32_size(rv);
+ if (rv_packed_size != 1)
+ memmove(out + rv_packed_size, out + 1, rv);
+ return uint32_pack(rv, out) + rv;
+ }
+}
+
+/**
+ * Pack a field tag.
+ *
+ * Wire-type will be added in required_field_pack().
+ *
+ * \todo Just call uint64_pack on 64-bit platforms.
+ *
+ * \param id
+ * Tag value to encode.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static size_t
+tag_pack(uint32_t id, uint8_t *out)
+{
+ if (id < (1UL << (32 - 3)))
+ return uint32_pack(id << 3, out);
+ else
+ return uint64_pack(((uint64_t) id) << 3, out);
+}
+
+/**
+ * Pack a required field and return the number of bytes written.
+ *
+ * \param field
+ * Field descriptor.
+ * \param member
+ * The field member.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static size_t
+required_field_pack(const ProtobufCFieldDescriptor *field,
+ const void *member, uint8_t *out)
+{
+ size_t rv = tag_pack(field->id, out);
+
+ switch (field->type) {
+ case PROTOBUF_C_TYPE_SINT32:
+ out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ return rv + sint32_pack(*(const int32_t *) member, out + rv);
+ case PROTOBUF_C_TYPE_ENUM:
+ case PROTOBUF_C_TYPE_INT32:
+ out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ return rv + int32_pack(*(const int32_t *) member, out + rv);
+ case PROTOBUF_C_TYPE_UINT32:
+ out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ return rv + uint32_pack(*(const uint32_t *) member, out + rv);
+ case PROTOBUF_C_TYPE_SINT64:
+ out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ return rv + sint64_pack(*(const int64_t *) member, out + rv);
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_UINT64:
+ out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ return rv + uint64_pack(*(const uint64_t *) member, out + rv);
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ case PROTOBUF_C_TYPE_FLOAT:
+ out[0] |= PROTOBUF_C_WIRE_TYPE_32BIT;
+ return rv + fixed32_pack(*(const uint32_t *) member, out + rv);
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ case PROTOBUF_C_TYPE_DOUBLE:
+ out[0] |= PROTOBUF_C_WIRE_TYPE_64BIT;
+ return rv + fixed64_pack(*(const uint64_t *) member, out + rv);
+ case PROTOBUF_C_TYPE_BOOL:
+ out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ return rv + boolean_pack(*(const protobuf_c_boolean *) member, out + rv);
+ case PROTOBUF_C_TYPE_STRING:
+ out[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED;
+ return rv + string_pack(*(char *const *) member, out + rv);
+ case PROTOBUF_C_TYPE_BYTES:
+ out[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED;
+ return rv + binary_data_pack((const ProtobufCBinaryData *) member, out + rv);
+ case PROTOBUF_C_TYPE_MESSAGE:
+ out[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED;
+ return rv + prefixed_message_pack(*(ProtobufCMessage * const *) member, out + rv);
+ }
+ PROTOBUF_C__ASSERT_NOT_REACHED();
+ return 0;
+}
+
+/**
+ * Pack a oneof field and return the number of bytes written. Only packs the
+ * field that is selected by the case enum.
+ *
+ * \param field
+ * Field descriptor.
+ * \param oneof_case
+ * Enum value that selects the field in the oneof.
+ * \param member
+ * The field member.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static size_t
+oneof_field_pack(const ProtobufCFieldDescriptor *field,
+ uint32_t oneof_case,
+ const void *member, uint8_t *out)
+{
+ if (oneof_case != field->id) {
+ return 0;
+ }
+ if (field->type == PROTOBUF_C_TYPE_MESSAGE ||
+ field->type == PROTOBUF_C_TYPE_STRING)
+ {
+ const void *ptr = *(const void * const *) member;
+ if (ptr == NULL || ptr == field->default_value)
+ return 0;
+ }
+ return required_field_pack(field, member, out);
+}
+
+/**
+ * Pack an optional field and return the number of bytes written.
+ *
+ * \param field
+ * Field descriptor.
+ * \param has
+ * Whether the field is set.
+ * \param member
+ * The field member.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static size_t
+optional_field_pack(const ProtobufCFieldDescriptor *field,
+ const protobuf_c_boolean has,
+ const void *member, uint8_t *out)
+{
+ if (field->type == PROTOBUF_C_TYPE_MESSAGE ||
+ field->type == PROTOBUF_C_TYPE_STRING)
+ {
+ const void *ptr = *(const void * const *) member;
+ if (ptr == NULL || ptr == field->default_value)
+ return 0;
+ } else {
+ if (!has)
+ return 0;
+ }
+ return required_field_pack(field, member, out);
+}
+
+/**
+ * Pack an unlabeled field and return the number of bytes written.
+ *
+ * \param field
+ * Field descriptor.
+ * \param member
+ * The field member.
+ * \param[out] out
+ * Packed value.
+ * \return
+ * Number of bytes written to `out`.
+ */
+static size_t
+unlabeled_field_pack(const ProtobufCFieldDescriptor *field,
+ const void *member, uint8_t *out)
+{
+ if (field_is_zeroish(field, member))
+ return 0;
+ return required_field_pack(field, member, out);
+}
+
+/**
+ * Given a field type, return the in-memory size.
+ *
+ * \todo Implement as a table lookup.
+ *
+ * \param type
+ * Field type.
+ * \return
+ * Size of the field.
+ */
+static inline size_t
+sizeof_elt_in_repeated_array(ProtobufCType type)
+{
+ switch (type) {
+ case PROTOBUF_C_TYPE_SINT32:
+ case PROTOBUF_C_TYPE_INT32:
+ case PROTOBUF_C_TYPE_UINT32:
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ case PROTOBUF_C_TYPE_FLOAT:
+ case PROTOBUF_C_TYPE_ENUM:
+ return 4;
+ case PROTOBUF_C_TYPE_SINT64:
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_UINT64:
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ case PROTOBUF_C_TYPE_DOUBLE:
+ return 8;
+ case PROTOBUF_C_TYPE_BOOL:
+ return sizeof(protobuf_c_boolean);
+ case PROTOBUF_C_TYPE_STRING:
+ case PROTOBUF_C_TYPE_MESSAGE:
+ return sizeof(void *);
+ case PROTOBUF_C_TYPE_BYTES:
+ return sizeof(ProtobufCBinaryData);
+ }
+ PROTOBUF_C__ASSERT_NOT_REACHED();
+ return 0;
+}
+
+/**
+ * Pack an array of 32-bit quantities.
+ *
+ * \param[out] out
+ * Destination.
+ * \param[in] in
+ * Source.
+ * \param[in] n
+ * Number of elements in the source array.
+ */
+static void
+copy_to_little_endian_32(void *out, const void *in, const unsigned n)
+{
+#if !defined(WORDS_BIGENDIAN)
+ memcpy(out, in, n * 4);
+#else
+ unsigned i;
+ const uint32_t *ini = in;
+ for (i = 0; i < n; i++)
+ fixed32_pack(ini[i], (uint32_t *) out + i);
+#endif
+}
+
+/**
+ * Pack an array of 64-bit quantities.
+ *
+ * \param[out] out
+ * Destination.
+ * \param[in] in
+ * Source.
+ * \param[in] n
+ * Number of elements in the source array.
+ */
+static void
+copy_to_little_endian_64(void *out, const void *in, const unsigned n)
+{
+#if !defined(WORDS_BIGENDIAN)
+ memcpy(out, in, n * 8);
+#else
+ unsigned i;
+ const uint64_t *ini = in;
+ for (i = 0; i < n; i++)
+ fixed64_pack(ini[i], (uint64_t *) out + i);
+#endif
+}
+
+/**
+ * Get the minimum number of bytes required to pack a field value of a
+ * particular type.
+ *
+ * \param type
+ * Field type.
+ * \return
+ * Number of bytes.
+ */
+static unsigned
+get_type_min_size(ProtobufCType type)
+{
+ if (type == PROTOBUF_C_TYPE_SFIXED32 ||
+ type == PROTOBUF_C_TYPE_FIXED32 ||
+ type == PROTOBUF_C_TYPE_FLOAT)
+ {
+ return 4;
+ }
+ if (type == PROTOBUF_C_TYPE_SFIXED64 ||
+ type == PROTOBUF_C_TYPE_FIXED64 ||
+ type == PROTOBUF_C_TYPE_DOUBLE)
+ {
+ return 8;
+ }
+ return 1;
+}
+
+/**
+ * Packs the elements of a repeated field and returns the serialised field and
+ * its length.
+ *
+ * \param field
+ * Field descriptor.
+ * \param count
+ * Number of elements in the repeated field array.
+ * \param member
+ * Pointer to the elements for this repeated field.
+ * \param[out] out
+ * Serialised representation of the repeated field.
+ * \return
+ * Number of bytes serialised to `out`.
+ */
+static size_t
+repeated_field_pack(const ProtobufCFieldDescriptor *field,
+ size_t count, const void *member, uint8_t *out)
+{
+ void *array = *(void * const *) member;
+ unsigned i;
+
+ if (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED)) {
+ unsigned header_len;
+ unsigned len_start;
+ unsigned min_length;
+ unsigned payload_len;
+ unsigned length_size_min;
+ unsigned actual_length_size;
+ uint8_t *payload_at;
+
+ if (count == 0)
+ return 0;
+ header_len = tag_pack(field->id, out);
+ out[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED;
+ len_start = header_len;
+ min_length = get_type_min_size(field->type) * count;
+ length_size_min = uint32_size(min_length);
+ header_len += length_size_min;
+ payload_at = out + header_len;
+
+ switch (field->type) {
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ case PROTOBUF_C_TYPE_FLOAT:
+ copy_to_little_endian_32(payload_at, array, count);
+ payload_at += count * 4;
+ break;
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ case PROTOBUF_C_TYPE_DOUBLE:
+ copy_to_little_endian_64(payload_at, array, count);
+ payload_at += count * 8;
+ break;
+ case PROTOBUF_C_TYPE_ENUM:
+ case PROTOBUF_C_TYPE_INT32: {
+ const int32_t *arr = (const int32_t *) array;
+ for (i = 0; i < count; i++)
+ payload_at += int32_pack(arr[i], payload_at);
+ break;
+ }
+ case PROTOBUF_C_TYPE_SINT32: {
+ const int32_t *arr = (const int32_t *) array;
+ for (i = 0; i < count; i++)
+ payload_at += sint32_pack(arr[i], payload_at);
+ break;
+ }
+ case PROTOBUF_C_TYPE_SINT64: {
+ const int64_t *arr = (const int64_t *) array;
+ for (i = 0; i < count; i++)
+ payload_at += sint64_pack(arr[i], payload_at);
+ break;
+ }
+ case PROTOBUF_C_TYPE_UINT32: {
+ const uint32_t *arr = (const uint32_t *) array;
+ for (i = 0; i < count; i++)
+ payload_at += uint32_pack(arr[i], payload_at);
+ break;
+ }
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_UINT64: {
+ const uint64_t *arr = (const uint64_t *) array;
+ for (i = 0; i < count; i++)
+ payload_at += uint64_pack(arr[i], payload_at);
+ break;
+ }
+ case PROTOBUF_C_TYPE_BOOL: {
+ const protobuf_c_boolean *arr = (const protobuf_c_boolean *) array;
+ for (i = 0; i < count; i++)
+ payload_at += boolean_pack(arr[i], payload_at);
+ break;
+ }
+ default:
+ PROTOBUF_C__ASSERT_NOT_REACHED();
+ }
+
+ payload_len = payload_at - (out + header_len);
+ actual_length_size = uint32_size(payload_len);
+ if (length_size_min != actual_length_size) {
+ assert(actual_length_size == length_size_min + 1);
+ memmove(out + header_len + 1, out + header_len,
+ payload_len);
+ header_len++;
+ }
+ uint32_pack(payload_len, out + len_start);
+ return header_len + payload_len;
+ } else {
+ /* not "packed" cased */
+ /* CONSIDER: optimize this case a bit (by putting the loop inside the switch) */
+ size_t rv = 0;
+ unsigned siz = sizeof_elt_in_repeated_array(field->type);
+
+ for (i = 0; i < count; i++) {
+ rv += required_field_pack(field, array, out + rv);
+ array = (char *)array + siz;
+ }
+ return rv;
+ }
+}
+
+static size_t
+unknown_field_pack(const ProtobufCMessageUnknownField *field, uint8_t *out)
+{
+ size_t rv = tag_pack(field->tag, out);
+ out[0] |= field->wire_type;
+ memcpy(out + rv, field->data, field->len);
+ return rv + field->len;
+}
+
+/**@}*/
+
+size_t
+protobuf_c_message_pack(const ProtobufCMessage *message, uint8_t *out)
+{
+ unsigned i;
+ size_t rv = 0;
+
+ ASSERT_IS_MESSAGE(message);
+ for (i = 0; i < message->descriptor->n_fields; i++) {
+ const ProtobufCFieldDescriptor *field =
+ message->descriptor->fields + i;
+ const void *member = ((const char *) message) + field->offset;
+
+ /*
+ * It doesn't hurt to compute qmember (a pointer to the
+ * quantifier field of the structure), but the pointer is only
+ * valid if the field is:
+ * - a repeated field, or
+ * - a field that is part of a oneof
+ * - an optional field that isn't a pointer type
+ * (Meaning: not a message or a string).
+ */
+ const void *qmember =
+ ((const char *) message) + field->quantifier_offset;
+
+ if (field->label == PROTOBUF_C_LABEL_REQUIRED) {
+ rv += required_field_pack(field, member, out + rv);
+ } else if ((field->label == PROTOBUF_C_LABEL_OPTIONAL ||
+ field->label == PROTOBUF_C_LABEL_NONE) &&
+ (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_ONEOF))) {
+ rv += oneof_field_pack(
+ field,
+ *(const uint32_t *) qmember,
+ member,
+ out + rv
+ );
+ } else if (field->label == PROTOBUF_C_LABEL_OPTIONAL) {
+ rv += optional_field_pack(
+ field,
+ *(const protobuf_c_boolean *) qmember,
+ member,
+ out + rv
+ );
+ } else if (field->label == PROTOBUF_C_LABEL_NONE) {
+ rv += unlabeled_field_pack(field, member, out + rv);
+ } else {
+ rv += repeated_field_pack(field, *(const size_t *) qmember,
+ member, out + rv);
+ }
+ }
+ for (i = 0; i < message->n_unknown_fields; i++)
+ rv += unknown_field_pack(&message->unknown_fields[i], out + rv);
+ return rv;
+}
+
+/**
+ * \defgroup packbuf protobuf_c_message_pack_to_buffer() implementation
+ *
+ * Routines mainly used by protobuf_c_message_pack_to_buffer().
+ *
+ * \ingroup internal
+ * @{
+ */
+
+/**
+ * Pack a required field to a virtual buffer.
+ *
+ * \param field
+ * Field descriptor.
+ * \param member
+ * The element to be packed.
+ * \param[out] buffer
+ * Virtual buffer to append data to.
+ * \return
+ * Number of bytes packed.
+ */
+static size_t
+required_field_pack_to_buffer(const ProtobufCFieldDescriptor *field,
+ const void *member, ProtobufCBuffer *buffer)
+{
+ size_t rv;
+ uint8_t scratch[MAX_UINT64_ENCODED_SIZE * 2];
+
+ rv = tag_pack(field->id, scratch);
+ switch (field->type) {
+ case PROTOBUF_C_TYPE_SINT32:
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ rv += sint32_pack(*(const int32_t *) member, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ break;
+ case PROTOBUF_C_TYPE_ENUM:
+ case PROTOBUF_C_TYPE_INT32:
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ rv += int32_pack(*(const int32_t *) member, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ break;
+ case PROTOBUF_C_TYPE_UINT32:
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ rv += uint32_pack(*(const uint32_t *) member, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ break;
+ case PROTOBUF_C_TYPE_SINT64:
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ rv += sint64_pack(*(const int64_t *) member, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ break;
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_UINT64:
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ rv += uint64_pack(*(const uint64_t *) member, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ break;
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ case PROTOBUF_C_TYPE_FLOAT:
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_32BIT;
+ rv += fixed32_pack(*(const uint32_t *) member, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ break;
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ case PROTOBUF_C_TYPE_DOUBLE:
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_64BIT;
+ rv += fixed64_pack(*(const uint64_t *) member, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ break;
+ case PROTOBUF_C_TYPE_BOOL:
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT;
+ rv += boolean_pack(*(const protobuf_c_boolean *) member, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ break;
+ case PROTOBUF_C_TYPE_STRING: {
+ const char *str = *(char *const *) member;
+ size_t sublen = str ? strlen(str) : 0;
+
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED;
+ rv += uint32_pack(sublen, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ buffer->append(buffer, sublen, (const uint8_t *) str);
+ rv += sublen;
+ break;
+ }
+ case PROTOBUF_C_TYPE_BYTES: {
+ const ProtobufCBinaryData *bd = ((const ProtobufCBinaryData *) member);
+ size_t sublen = bd->len;
+
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED;
+ rv += uint32_pack(sublen, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ buffer->append(buffer, sublen, bd->data);
+ rv += sublen;
+ break;
+ }
+ case PROTOBUF_C_TYPE_MESSAGE: {
+ uint8_t simple_buffer_scratch[256];
+ size_t sublen;
+ const ProtobufCMessage *msg = *(ProtobufCMessage * const *) member;
+ ProtobufCBufferSimple simple_buffer =
+ PROTOBUF_C_BUFFER_SIMPLE_INIT(simple_buffer_scratch);
+
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED;
+ if (msg == NULL)
+ sublen = 0;
+ else
+ sublen = protobuf_c_message_pack_to_buffer(msg, &simple_buffer.base);
+ rv += uint32_pack(sublen, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ buffer->append(buffer, sublen, simple_buffer.data);
+ rv += sublen;
+ PROTOBUF_C_BUFFER_SIMPLE_CLEAR(&simple_buffer);
+ break;
+ }
+ default:
+ PROTOBUF_C__ASSERT_NOT_REACHED();
+ }
+ return rv;
+}
+
+/**
+ * Pack a oneof field to a buffer. Only packs the field that is selected by the case enum.
+ *
+ * \param field
+ * Field descriptor.
+ * \param oneof_case
+ * Enum value that selects the field in the oneof.
+ * \param member
+ * The element to be packed.
+ * \param[out] buffer
+ * Virtual buffer to append data to.
+ * \return
+ * Number of bytes serialised to `buffer`.
+ */
+static size_t
+oneof_field_pack_to_buffer(const ProtobufCFieldDescriptor *field,
+ uint32_t oneof_case,
+ const void *member, ProtobufCBuffer *buffer)
+{
+ if (oneof_case != field->id) {
+ return 0;
+ }
+ if (field->type == PROTOBUF_C_TYPE_MESSAGE ||
+ field->type == PROTOBUF_C_TYPE_STRING)
+ {
+ const void *ptr = *(const void *const *) member;
+ if (ptr == NULL || ptr == field->default_value)
+ return 0;
+ }
+ return required_field_pack_to_buffer(field, member, buffer);
+}
+
+/**
+ * Pack an optional field to a buffer.
+ *
+ * \param field
+ * Field descriptor.
+ * \param has
+ * Whether the field is set.
+ * \param member
+ * The element to be packed.
+ * \param[out] buffer
+ * Virtual buffer to append data to.
+ * \return
+ * Number of bytes serialised to `buffer`.
+ */
+static size_t
+optional_field_pack_to_buffer(const ProtobufCFieldDescriptor *field,
+ const protobuf_c_boolean has,
+ const void *member, ProtobufCBuffer *buffer)
+{
+ if (field->type == PROTOBUF_C_TYPE_MESSAGE ||
+ field->type == PROTOBUF_C_TYPE_STRING)
+ {
+ const void *ptr = *(const void *const *) member;
+ if (ptr == NULL || ptr == field->default_value)
+ return 0;
+ } else {
+ if (!has)
+ return 0;
+ }
+ return required_field_pack_to_buffer(field, member, buffer);
+}
+
+/**
+ * Pack an unlabeled field to a buffer.
+ *
+ * \param field
+ * Field descriptor.
+ * \param member
+ * The element to be packed.
+ * \param[out] buffer
+ * Virtual buffer to append data to.
+ * \return
+ * Number of bytes serialised to `buffer`.
+ */
+static size_t
+unlabeled_field_pack_to_buffer(const ProtobufCFieldDescriptor *field,
+ const void *member, ProtobufCBuffer *buffer)
+{
+ if (field_is_zeroish(field, member))
+ return 0;
+ return required_field_pack_to_buffer(field, member, buffer);
+}
+
+/**
+ * Get the packed size of an array of same field type.
+ *
+ * \param field
+ * Field descriptor.
+ * \param count
+ * Number of elements of this type.
+ * \param array
+ * The elements to get the size of.
+ * \return
+ * Number of bytes required.
+ */
+static size_t
+get_packed_payload_length(const ProtobufCFieldDescriptor *field,
+ unsigned count, const void *array)
+{
+ unsigned rv = 0;
+ unsigned i;
+
+ switch (field->type) {
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ case PROTOBUF_C_TYPE_FLOAT:
+ return count * 4;
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ case PROTOBUF_C_TYPE_DOUBLE:
+ return count * 8;
+ case PROTOBUF_C_TYPE_ENUM:
+ case PROTOBUF_C_TYPE_INT32: {
+ const int32_t *arr = (const int32_t *) array;
+ for (i = 0; i < count; i++)
+ rv += int32_size(arr[i]);
+ break;
+ }
+ case PROTOBUF_C_TYPE_SINT32: {
+ const int32_t *arr = (const int32_t *) array;
+ for (i = 0; i < count; i++)
+ rv += sint32_size(arr[i]);
+ break;
+ }
+ case PROTOBUF_C_TYPE_UINT32: {
+ const uint32_t *arr = (const uint32_t *) array;
+ for (i = 0; i < count; i++)
+ rv += uint32_size(arr[i]);
+ break;
+ }
+ case PROTOBUF_C_TYPE_SINT64: {
+ const int64_t *arr = (const int64_t *) array;
+ for (i = 0; i < count; i++)
+ rv += sint64_size(arr[i]);
+ break;
+ }
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_UINT64: {
+ const uint64_t *arr = (const uint64_t *) array;
+ for (i = 0; i < count; i++)
+ rv += uint64_size(arr[i]);
+ break;
+ }
+ case PROTOBUF_C_TYPE_BOOL:
+ return count;
+ default:
+ PROTOBUF_C__ASSERT_NOT_REACHED();
+ }
+ return rv;
+}
+
+/**
+ * Pack an array of same field type to a virtual buffer.
+ *
+ * \param field
+ * Field descriptor.
+ * \param count
+ * Number of elements of this type.
+ * \param array
+ * The elements to get the size of.
+ * \param[out] buffer
+ * Virtual buffer to append data to.
+ * \return
+ * Number of bytes packed.
+ */
+static size_t
+pack_buffer_packed_payload(const ProtobufCFieldDescriptor *field,
+ unsigned count, const void *array,
+ ProtobufCBuffer *buffer)
+{
+ uint8_t scratch[16];
+ size_t rv = 0;
+ unsigned i;
+
+ switch (field->type) {
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ case PROTOBUF_C_TYPE_FLOAT:
+#if !defined(WORDS_BIGENDIAN)
+ rv = count * 4;
+ goto no_packing_needed;
+#else
+ for (i = 0; i < count; i++) {
+ unsigned len = fixed32_pack(((uint32_t *) array)[i], scratch);
+ buffer->append(buffer, len, scratch);
+ rv += len;
+ }
+ break;
+#endif
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ case PROTOBUF_C_TYPE_DOUBLE:
+#if !defined(WORDS_BIGENDIAN)
+ rv = count * 8;
+ goto no_packing_needed;
+#else
+ for (i = 0; i < count; i++) {
+ unsigned len = fixed64_pack(((uint64_t *) array)[i], scratch);
+ buffer->append(buffer, len, scratch);
+ rv += len;
+ }
+ break;
+#endif
+ case PROTOBUF_C_TYPE_ENUM:
+ case PROTOBUF_C_TYPE_INT32:
+ for (i = 0; i < count; i++) {
+ unsigned len = int32_pack(((int32_t *) array)[i], scratch);
+ buffer->append(buffer, len, scratch);
+ rv += len;
+ }
+ break;
+ case PROTOBUF_C_TYPE_SINT32:
+ for (i = 0; i < count; i++) {
+ unsigned len = sint32_pack(((int32_t *) array)[i], scratch);
+ buffer->append(buffer, len, scratch);
+ rv += len;
+ }
+ break;
+ case PROTOBUF_C_TYPE_UINT32:
+ for (i = 0; i < count; i++) {
+ unsigned len = uint32_pack(((uint32_t *) array)[i], scratch);
+ buffer->append(buffer, len, scratch);
+ rv += len;
+ }
+ break;
+ case PROTOBUF_C_TYPE_SINT64:
+ for (i = 0; i < count; i++) {
+ unsigned len = sint64_pack(((int64_t *) array)[i], scratch);
+ buffer->append(buffer, len, scratch);
+ rv += len;
+ }
+ break;
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_UINT64:
+ for (i = 0; i < count; i++) {
+ unsigned len = uint64_pack(((uint64_t *) array)[i], scratch);
+ buffer->append(buffer, len, scratch);
+ rv += len;
+ }
+ break;
+ case PROTOBUF_C_TYPE_BOOL:
+ for (i = 0; i < count; i++) {
+ unsigned len = boolean_pack(((protobuf_c_boolean *) array)[i], scratch);
+ buffer->append(buffer, len, scratch);
+ rv += len;
+ }
+ return count;
+ default:
+ PROTOBUF_C__ASSERT_NOT_REACHED();
+ }
+ return rv;
+
+#if !defined(WORDS_BIGENDIAN)
+no_packing_needed:
+ buffer->append(buffer, rv, array);
+ return rv;
+#endif
+}
+
+static size_t
+repeated_field_pack_to_buffer(const ProtobufCFieldDescriptor *field,
+ unsigned count, const void *member,
+ ProtobufCBuffer *buffer)
+{
+ char *array = *(char * const *) member;
+
+ if (count == 0)
+ return 0;
+ if (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED)) {
+ uint8_t scratch[MAX_UINT64_ENCODED_SIZE * 2];
+ size_t rv = tag_pack(field->id, scratch);
+ size_t payload_len = get_packed_payload_length(field, count, array);
+ size_t tmp;
+
+ scratch[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED;
+ rv += uint32_pack(payload_len, scratch + rv);
+ buffer->append(buffer, rv, scratch);
+ tmp = pack_buffer_packed_payload(field, count, array, buffer);
+ assert(tmp == payload_len);
+ return rv + payload_len;
+ } else {
+ size_t siz;
+ unsigned i;
+ /* CONSIDER: optimize this case a bit (by putting the loop inside the switch) */
+ unsigned rv = 0;
+
+ siz = sizeof_elt_in_repeated_array(field->type);
+ for (i = 0; i < count; i++) {
+ rv += required_field_pack_to_buffer(field, array, buffer);
+ array += siz;
+ }
+ return rv;
+ }
+}
+
+static size_t
+unknown_field_pack_to_buffer(const ProtobufCMessageUnknownField *field,
+ ProtobufCBuffer *buffer)
+{
+ uint8_t header[MAX_UINT64_ENCODED_SIZE];
+ size_t rv = tag_pack(field->tag, header);
+
+ header[0] |= field->wire_type;
+ buffer->append(buffer, rv, header);
+ buffer->append(buffer, field->len, field->data);
+ return rv + field->len;
+}
+
+/**@}*/
+
+size_t
+protobuf_c_message_pack_to_buffer(const ProtobufCMessage *message,
+ ProtobufCBuffer *buffer)
+{
+ unsigned i;
+ size_t rv = 0;
+
+ ASSERT_IS_MESSAGE(message);
+ for (i = 0; i < message->descriptor->n_fields; i++) {
+ const ProtobufCFieldDescriptor *field =
+ message->descriptor->fields + i;
+ const void *member =
+ ((const char *) message) + field->offset;
+ const void *qmember =
+ ((const char *) message) + field->quantifier_offset;
+
+ if (field->label == PROTOBUF_C_LABEL_REQUIRED) {
+ rv += required_field_pack_to_buffer(field, member, buffer);
+ } else if ((field->label == PROTOBUF_C_LABEL_OPTIONAL ||
+ field->label == PROTOBUF_C_LABEL_NONE) &&
+ (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_ONEOF))) {
+ rv += oneof_field_pack_to_buffer(
+ field,
+ *(const uint32_t *) qmember,
+ member,
+ buffer
+ );
+ } else if (field->label == PROTOBUF_C_LABEL_OPTIONAL) {
+ rv += optional_field_pack_to_buffer(
+ field,
+ *(const protobuf_c_boolean *) qmember,
+ member,
+ buffer
+ );
+ } else if (field->label == PROTOBUF_C_LABEL_NONE) {
+ rv += unlabeled_field_pack_to_buffer(
+ field,
+ member,
+ buffer
+ );
+ } else {
+ rv += repeated_field_pack_to_buffer(
+ field,
+ *(const size_t *) qmember,
+ member,
+ buffer
+ );
+ }
+ }
+ for (i = 0; i < message->n_unknown_fields; i++)
+ rv += unknown_field_pack_to_buffer(&message->unknown_fields[i], buffer);
+
+ return rv;
+}
+
+/**
+ * \defgroup unpack unpacking implementation
+ *
+ * Routines mainly used by the unpacking functions.
+ *
+ * \ingroup internal
+ * @{
+ */
+
+static inline int
+int_range_lookup(unsigned n_ranges, const ProtobufCIntRange *ranges, int value)
+{
+ unsigned n;
+ unsigned start;
+
+ if (n_ranges == 0)
+ return -1;
+ start = 0;
+ n = n_ranges;
+ while (n > 1) {
+ unsigned mid = start + n / 2;
+
+ if (value < ranges[mid].start_value) {
+ n = mid - start;
+ } else if (value >= ranges[mid].start_value +
+ (int) (ranges[mid + 1].orig_index -
+ ranges[mid].orig_index))
+ {
+ unsigned new_start = mid + 1;
+ n = start + n - new_start;
+ start = new_start;
+ } else
+ return (value - ranges[mid].start_value) +
+ ranges[mid].orig_index;
+ }
+ if (n > 0) {
+ unsigned start_orig_index = ranges[start].orig_index;
+ unsigned range_size =
+ ranges[start + 1].orig_index - start_orig_index;
+
+ if (ranges[start].start_value <= value &&
+ value < (int) (ranges[start].start_value + range_size))
+ {
+ return (value - ranges[start].start_value) +
+ start_orig_index;
+ }
+ }
+ return -1;
+}
+
+static size_t
+parse_tag_and_wiretype(size_t len,
+ const uint8_t *data,
+ uint32_t *tag_out,
+ ProtobufCWireType *wiretype_out)
+{
+ unsigned max_rv = len > 5 ? 5 : len;
+ uint32_t tag = (data[0] & 0x7f) >> 3;
+ unsigned shift = 4;
+ unsigned rv;
+
+ /* 0 is not a valid tag value */
+ if ((data[0] & 0xf8) == 0) {
+ return 0;
+ }
+
+ *wiretype_out = data[0] & 7;
+ if ((data[0] & 0x80) == 0) {
+ *tag_out = tag;
+ return 1;
+ }
+ for (rv = 1; rv < max_rv; rv++) {
+ if (data[rv] & 0x80) {
+ tag |= (data[rv] & 0x7f) << shift;
+ shift += 7;
+ } else {
+ tag |= data[rv] << shift;
+ *tag_out = tag;
+ return rv + 1;
+ }
+ }
+ return 0; /* error: bad header */
+}
+
+/* sizeof(ScannedMember) must be <= (1UL<<BOUND_SIZEOF_SCANNED_MEMBER_LOG2) */
+#define BOUND_SIZEOF_SCANNED_MEMBER_LOG2 5
+typedef struct _ScannedMember ScannedMember;
+/** Field as it's being read. */
+struct _ScannedMember {
+ uint32_t tag; /**< Field tag. */
+ uint8_t wire_type; /**< Field type. */
+ uint8_t length_prefix_len; /**< Prefix length. */
+ const ProtobufCFieldDescriptor *field; /**< Field descriptor. */
+ size_t len; /**< Field length. */
+ const uint8_t *data; /**< Pointer to field data. */
+};
+
+static inline size_t
+scan_length_prefixed_data(size_t len, const uint8_t *data,
+ size_t *prefix_len_out)
+{
+ unsigned hdr_max = len < 5 ? len : 5;
+ unsigned hdr_len;
+ size_t val = 0;
+ unsigned i;
+ unsigned shift = 0;
+
+ for (i = 0; i < hdr_max; i++) {
+ val |= ((size_t)data[i] & 0x7f) << shift;
+ shift += 7;
+ if ((data[i] & 0x80) == 0)
+ break;
+ }
+ if (i == hdr_max) {
+ PROTOBUF_C_UNPACK_ERROR("error parsing length for length-prefixed data");
+ return 0;
+ }
+ hdr_len = i + 1;
+ *prefix_len_out = hdr_len;
+ if (val > INT_MAX) {
+ // Protobuf messages should always be less than 2 GiB in size.
+ // We also want to return early here so that hdr_len + val does
+ // not overflow on 32-bit systems.
+ PROTOBUF_C_UNPACK_ERROR("length prefix of %lu is too large", val);
+ return 0;
+ }
+ if (hdr_len + val > len) {
+ PROTOBUF_C_UNPACK_ERROR("data too short after length-prefix of %lu", val);
+ return 0;
+ }
+ return hdr_len + val;
+}
+
+static size_t
+max_b128_numbers(size_t len, const uint8_t *data)
+{
+ size_t rv = 0;
+ while (len--)
+ if ((*data++ & 0x80) == 0)
+ ++rv;
+ return rv;
+}
+
+/**@}*/
+
+/**
+ * Merge earlier message into a latter message.
+ *
+ * For numeric types and strings, if the same value appears multiple
+ * times, the parser accepts the last value it sees. For embedded
+ * message fields, the parser merges multiple instances of the same
+ * field. That is, all singular scalar fields in the latter instance
+ * replace those in the former, singular embedded messages are merged,
+ * and repeated fields are concatenated.
+ *
+ * The earlier message should be freed after calling this function, as
+ * some of its fields may have been reused and changed to their default
+ * values during the merge.
+ */
+static protobuf_c_boolean
+merge_messages(ProtobufCMessage *earlier_msg,
+ ProtobufCMessage *latter_msg,
+ ProtobufCAllocator *allocator)
+{
+ unsigned i;
+ const ProtobufCFieldDescriptor *fields =
+ latter_msg->descriptor->fields;
+ for (i = 0; i < latter_msg->descriptor->n_fields; i++) {
+ if (fields[i].label == PROTOBUF_C_LABEL_REPEATED) {
+ size_t *n_earlier =
+ STRUCT_MEMBER_PTR(size_t, earlier_msg,
+ fields[i].quantifier_offset);
+ uint8_t **p_earlier =
+ STRUCT_MEMBER_PTR(uint8_t *, earlier_msg,
+ fields[i].offset);
+ size_t *n_latter =
+ STRUCT_MEMBER_PTR(size_t, latter_msg,
+ fields[i].quantifier_offset);
+ uint8_t **p_latter =
+ STRUCT_MEMBER_PTR(uint8_t *, latter_msg,
+ fields[i].offset);
+
+ if (*n_earlier > 0) {
+ if (*n_latter > 0) {
+ /* Concatenate the repeated field */
+ size_t el_size =
+ sizeof_elt_in_repeated_array(fields[i].type);
+ uint8_t *new_field;
+
+ new_field = do_alloc(allocator,
+ (*n_earlier + *n_latter) * el_size);
+ if (!new_field)
+ return FALSE;
+
+ memcpy(new_field, *p_earlier,
+ *n_earlier * el_size);
+ memcpy(new_field +
+ *n_earlier * el_size,
+ *p_latter,
+ *n_latter * el_size);
+
+ do_free(allocator, *p_latter);
+ do_free(allocator, *p_earlier);
+ *p_latter = new_field;
+ *n_latter = *n_earlier + *n_latter;
+ } else {
+ /* Zero copy the repeated field from the earlier message */
+ *n_latter = *n_earlier;
+ *p_latter = *p_earlier;
+ }
+ /* Make sure the field does not get double freed */
+ *n_earlier = 0;
+ *p_earlier = 0;
+ }
+ } else if (fields[i].label == PROTOBUF_C_LABEL_OPTIONAL ||
+ fields[i].label == PROTOBUF_C_LABEL_NONE) {
+ const ProtobufCFieldDescriptor *field;
+ uint32_t *earlier_case_p = STRUCT_MEMBER_PTR(uint32_t,
+ earlier_msg,
+ fields[i].
+ quantifier_offset);
+ uint32_t *latter_case_p = STRUCT_MEMBER_PTR(uint32_t,
+ latter_msg,
+ fields[i].
+ quantifier_offset);
+ protobuf_c_boolean need_to_merge = FALSE;
+ void *earlier_elem;
+ void *latter_elem;
+ const void *def_val;
+
+ if (fields[i].flags & PROTOBUF_C_FIELD_FLAG_ONEOF) {
+ if (*latter_case_p == 0) {
+ /* lookup correct oneof field */
+ int field_index =
+ int_range_lookup(
+ latter_msg->descriptor
+ ->n_field_ranges,
+ latter_msg->descriptor
+ ->field_ranges,
+ *earlier_case_p);
+ if (field_index < 0)
+ return FALSE;
+ field = latter_msg->descriptor->fields +
+ field_index;
+ } else {
+ /* Oneof is present in the latter message, move on */
+ continue;
+ }
+ } else {
+ field = &fields[i];
+ }
+
+ earlier_elem = STRUCT_MEMBER_P(earlier_msg, field->offset);
+ latter_elem = STRUCT_MEMBER_P(latter_msg, field->offset);
+ def_val = field->default_value;
+
+ switch (field->type) {
+ case PROTOBUF_C_TYPE_MESSAGE: {
+ ProtobufCMessage *em = *(ProtobufCMessage **) earlier_elem;
+ ProtobufCMessage *lm = *(ProtobufCMessage **) latter_elem;
+ if (em != NULL) {
+ if (lm != NULL) {
+ if (!merge_messages(em, lm, allocator))
+ return FALSE;
+ /* Already merged */
+ need_to_merge = FALSE;
+ } else {
+ /* Zero copy the message */
+ need_to_merge = TRUE;
+ }
+ }
+ break;
+ }
+ case PROTOBUF_C_TYPE_BYTES: {
+ uint8_t *e_data =
+ ((ProtobufCBinaryData *) earlier_elem)->data;
+ uint8_t *l_data =
+ ((ProtobufCBinaryData *) latter_elem)->data;
+ const ProtobufCBinaryData *d_bd =
+ (ProtobufCBinaryData *) def_val;
+
+ need_to_merge =
+ (e_data != NULL &&
+ (d_bd == NULL ||
+ e_data != d_bd->data)) &&
+ (l_data == NULL ||
+ (d_bd != NULL &&
+ l_data == d_bd->data));
+ break;
+ }
+ case PROTOBUF_C_TYPE_STRING: {
+ char *e_str = *(char **) earlier_elem;
+ char *l_str = *(char **) latter_elem;
+ const char *d_str = def_val;
+
+ need_to_merge = e_str != d_str && l_str == d_str;
+ break;
+ }
+ default: {
+ /* Could be has field or case enum, the logic is
+ * equivalent, since 0 (FALSE) means not set for
+ * oneof */
+ need_to_merge = (*earlier_case_p != 0) &&
+ (*latter_case_p == 0);
+ break;
+ }
+ }
+
+ if (need_to_merge) {
+ size_t el_size =
+ sizeof_elt_in_repeated_array(field->type);
+ memcpy(latter_elem, earlier_elem, el_size);
+ /*
+ * Reset the element from the old message to 0
+ * to make sure earlier message deallocation
+ * doesn't corrupt zero-copied data in the new
+ * message, earlier message will be freed after
+ * this function is called anyway
+ */
+ memset(earlier_elem, 0, el_size);
+
+ if (field->quantifier_offset != 0) {
+ /* Set the has field or the case enum,
+ * if applicable */
+ *latter_case_p = *earlier_case_p;
+ *earlier_case_p = 0;
+ }
+ }
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Count packed elements.
+ *
+ * Given a raw slab of packed-repeated values, determine the number of
+ * elements. This function detects certain kinds of errors but not
+ * others; the remaining error checking is done by
+ * parse_packed_repeated_member().
+ */
+static protobuf_c_boolean
+count_packed_elements(ProtobufCType type,
+ size_t len, const uint8_t *data, size_t *count_out)
+{
+ switch (type) {
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ case PROTOBUF_C_TYPE_FLOAT:
+ if (len % 4 != 0) {
+ PROTOBUF_C_UNPACK_ERROR("length must be a multiple of 4 for fixed-length 32-bit types");
+ return FALSE;
+ }
+ *count_out = len / 4;
+ return TRUE;
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ case PROTOBUF_C_TYPE_DOUBLE:
+ if (len % 8 != 0) {
+ PROTOBUF_C_UNPACK_ERROR("length must be a multiple of 8 for fixed-length 64-bit types");
+ return FALSE;
+ }
+ *count_out = len / 8;
+ return TRUE;
+ case PROTOBUF_C_TYPE_ENUM:
+ case PROTOBUF_C_TYPE_INT32:
+ case PROTOBUF_C_TYPE_SINT32:
+ case PROTOBUF_C_TYPE_UINT32:
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_SINT64:
+ case PROTOBUF_C_TYPE_UINT64:
+ *count_out = max_b128_numbers(len, data);
+ return TRUE;
+ case PROTOBUF_C_TYPE_BOOL:
+ *count_out = len;
+ return TRUE;
+ case PROTOBUF_C_TYPE_STRING:
+ case PROTOBUF_C_TYPE_BYTES:
+ case PROTOBUF_C_TYPE_MESSAGE:
+ default:
+ PROTOBUF_C_UNPACK_ERROR("bad protobuf-c type %u for packed-repeated", type);
+ return FALSE;
+ }
+}
+
+static inline uint32_t
+parse_uint32(unsigned len, const uint8_t *data)
+{
+ uint32_t rv = data[0] & 0x7f;
+ if (len > 1) {
+ rv |= ((uint32_t) (data[1] & 0x7f) << 7);
+ if (len > 2) {
+ rv |= ((uint32_t) (data[2] & 0x7f) << 14);
+ if (len > 3) {
+ rv |= ((uint32_t) (data[3] & 0x7f) << 21);
+ if (len > 4)
+ rv |= ((uint32_t) (data[4]) << 28);
+ }
+ }
+ }
+ return rv;
+}
+
+static inline uint32_t
+parse_int32(unsigned len, const uint8_t *data)
+{
+ return parse_uint32(len, data);
+}
+
+static inline int32_t
+unzigzag32(uint32_t v)
+{
+ // Note: Using unsigned types prevents undefined behavior
+ return (int32_t)((v >> 1) ^ (~(v & 1) + 1));
+}
+
+static inline uint32_t
+parse_fixed_uint32(const uint8_t *data)
+{
+#if !defined(WORDS_BIGENDIAN)
+ uint32_t t;
+ memcpy(&t, data, 4);
+ return t;
+#else
+ return data[0] |
+ ((uint32_t) (data[1]) << 8) |
+ ((uint32_t) (data[2]) << 16) |
+ ((uint32_t) (data[3]) << 24);
+#endif
+}
+
+static uint64_t
+parse_uint64(unsigned len, const uint8_t *data)
+{
+ unsigned shift, i;
+ uint64_t rv;
+
+ if (len < 5)
+ return parse_uint32(len, data);
+ rv = ((uint64_t) (data[0] & 0x7f)) |
+ ((uint64_t) (data[1] & 0x7f) << 7) |
+ ((uint64_t) (data[2] & 0x7f) << 14) |
+ ((uint64_t) (data[3] & 0x7f) << 21);
+ shift = 28;
+ for (i = 4; i < len; i++) {
+ rv |= (((uint64_t) (data[i] & 0x7f)) << shift);
+ shift += 7;
+ }
+ return rv;
+}
+
+static inline int64_t
+unzigzag64(uint64_t v)
+{
+ // Note: Using unsigned types prevents undefined behavior
+ return (int64_t)((v >> 1) ^ (~(v & 1) + 1));
+}
+
+static inline uint64_t
+parse_fixed_uint64(const uint8_t *data)
+{
+#if !defined(WORDS_BIGENDIAN)
+ uint64_t t;
+ memcpy(&t, data, 8);
+ return t;
+#else
+ return (uint64_t) parse_fixed_uint32(data) |
+ (((uint64_t) parse_fixed_uint32(data + 4)) << 32);
+#endif
+}
+
+static protobuf_c_boolean
+parse_boolean(unsigned len, const uint8_t *data)
+{
+ unsigned i;
+ for (i = 0; i < len; i++)
+ if (data[i] & 0x7f)
+ return TRUE;
+ return FALSE;
+}
+
+static protobuf_c_boolean
+parse_required_member(ScannedMember *scanned_member,
+ void *member,
+ ProtobufCAllocator *allocator,
+ protobuf_c_boolean maybe_clear)
+{
+ unsigned len = scanned_member->len;
+ const uint8_t *data = scanned_member->data;
+ ProtobufCWireType wire_type = scanned_member->wire_type;
+
+ switch (scanned_member->field->type) {
+ case PROTOBUF_C_TYPE_ENUM:
+ case PROTOBUF_C_TYPE_INT32:
+ if (wire_type != PROTOBUF_C_WIRE_TYPE_VARINT)
+ return FALSE;
+ *(int32_t *) member = parse_int32(len, data);
+ return TRUE;
+ case PROTOBUF_C_TYPE_UINT32:
+ if (wire_type != PROTOBUF_C_WIRE_TYPE_VARINT)
+ return FALSE;
+ *(uint32_t *) member = parse_uint32(len, data);
+ return TRUE;
+ case PROTOBUF_C_TYPE_SINT32:
+ if (wire_type != PROTOBUF_C_WIRE_TYPE_VARINT)
+ return FALSE;
+ *(int32_t *) member = unzigzag32(parse_uint32(len, data));
+ return TRUE;
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ case PROTOBUF_C_TYPE_FLOAT:
+ if (wire_type != PROTOBUF_C_WIRE_TYPE_32BIT)
+ return FALSE;
+ *(uint32_t *) member = parse_fixed_uint32(data);
+ return TRUE;
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_UINT64:
+ if (wire_type != PROTOBUF_C_WIRE_TYPE_VARINT)
+ return FALSE;
+ *(uint64_t *) member = parse_uint64(len, data);
+ return TRUE;
+ case PROTOBUF_C_TYPE_SINT64:
+ if (wire_type != PROTOBUF_C_WIRE_TYPE_VARINT)
+ return FALSE;
+ *(int64_t *) member = unzigzag64(parse_uint64(len, data));
+ return TRUE;
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ case PROTOBUF_C_TYPE_DOUBLE:
+ if (wire_type != PROTOBUF_C_WIRE_TYPE_64BIT)
+ return FALSE;
+ *(uint64_t *) member = parse_fixed_uint64(data);
+ return TRUE;
+ case PROTOBUF_C_TYPE_BOOL:
+ *(protobuf_c_boolean *) member = parse_boolean(len, data);
+ return TRUE;
+ case PROTOBUF_C_TYPE_STRING: {
+ char **pstr = member;
+ unsigned pref_len = scanned_member->length_prefix_len;
+
+ if (wire_type != PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED)
+ return FALSE;
+
+ if (maybe_clear && *pstr != NULL) {
+ const char *def = scanned_member->field->default_value;
+ if (*pstr != NULL && *pstr != def)
+ do_free(allocator, *pstr);
+ }
+ *pstr = do_alloc(allocator, len - pref_len + 1);
+ if (*pstr == NULL)
+ return FALSE;
+ memcpy(*pstr, data + pref_len, len - pref_len);
+ (*pstr)[len - pref_len] = 0;
+ return TRUE;
+ }
+ case PROTOBUF_C_TYPE_BYTES: {
+ ProtobufCBinaryData *bd = member;
+ const ProtobufCBinaryData *def_bd;
+ unsigned pref_len = scanned_member->length_prefix_len;
+
+ if (wire_type != PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED)
+ return FALSE;
+
+ def_bd = scanned_member->field->default_value;
+ if (maybe_clear &&
+ bd->data != NULL &&
+ (def_bd == NULL || bd->data != def_bd->data))
+ {
+ do_free(allocator, bd->data);
+ }
+ if (len - pref_len > 0) {
+ bd->data = do_alloc(allocator, len - pref_len);
+ if (bd->data == NULL)
+ return FALSE;
+ memcpy(bd->data, data + pref_len, len - pref_len);
+ } else {
+ bd->data = NULL;
+ }
+ bd->len = len - pref_len;
+ return TRUE;
+ }
+ case PROTOBUF_C_TYPE_MESSAGE: {
+ ProtobufCMessage **pmessage = member;
+ ProtobufCMessage *subm;
+ const ProtobufCMessage *def_mess;
+ protobuf_c_boolean merge_successful = TRUE;
+ unsigned pref_len = scanned_member->length_prefix_len;
+
+ if (wire_type != PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED)
+ return FALSE;
+
+ def_mess = scanned_member->field->default_value;
+ subm = protobuf_c_message_unpack(scanned_member->field->descriptor,
+ allocator,
+ len - pref_len,
+ data + pref_len);
+
+ if (maybe_clear &&
+ *pmessage != NULL &&
+ *pmessage != def_mess)
+ {
+ if (subm != NULL)
+ merge_successful = merge_messages(*pmessage, subm, allocator);
+ /* Delete the previous message */
+ protobuf_c_message_free_unpacked(*pmessage, allocator);
+ }
+ *pmessage = subm;
+ if (subm == NULL || !merge_successful)
+ return FALSE;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static protobuf_c_boolean
+parse_oneof_member (ScannedMember *scanned_member,
+ void *member,
+ ProtobufCMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ uint32_t *oneof_case = STRUCT_MEMBER_PTR(uint32_t, message,
+ scanned_member->field->quantifier_offset);
+
+ /* If we have already parsed a member of this oneof, free it. */
+ if (*oneof_case != 0) {
+ const ProtobufCFieldDescriptor *old_field;
+ size_t el_size;
+ /* lookup field */
+ int field_index =
+ int_range_lookup(message->descriptor->n_field_ranges,
+ message->descriptor->field_ranges,
+ *oneof_case);
+ if (field_index < 0)
+ return FALSE;
+ old_field = message->descriptor->fields + field_index;
+ el_size = sizeof_elt_in_repeated_array(old_field->type);
+
+ switch (old_field->type) {
+ case PROTOBUF_C_TYPE_STRING: {
+ char **pstr = member;
+ const char *def = old_field->default_value;
+ if (*pstr != NULL && *pstr != def)
+ do_free(allocator, *pstr);
+ break;
+ }
+ case PROTOBUF_C_TYPE_BYTES: {
+ ProtobufCBinaryData *bd = member;
+ const ProtobufCBinaryData *def_bd = old_field->default_value;
+ if (bd->data != NULL &&
+ (def_bd == NULL || bd->data != def_bd->data))
+ {
+ do_free(allocator, bd->data);
+ }
+ break;
+ }
+ case PROTOBUF_C_TYPE_MESSAGE: {
+ ProtobufCMessage **pmessage = member;
+ const ProtobufCMessage *def_mess = old_field->default_value;
+ if (*pmessage != NULL && *pmessage != def_mess)
+ protobuf_c_message_free_unpacked(*pmessage, allocator);
+ break;
+ }
+ default:
+ break;
+ }
+
+ memset (member, 0, el_size);
+ }
+ if (!parse_required_member (scanned_member, member, allocator, TRUE))
+ return FALSE;
+
+ *oneof_case = scanned_member->tag;
+ return TRUE;
+}
+
+
+static protobuf_c_boolean
+parse_optional_member(ScannedMember *scanned_member,
+ void *member,
+ ProtobufCMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ if (!parse_required_member(scanned_member, member, allocator, TRUE))
+ return FALSE;
+ if (scanned_member->field->quantifier_offset != 0)
+ STRUCT_MEMBER(protobuf_c_boolean,
+ message,
+ scanned_member->field->quantifier_offset) = TRUE;
+ return TRUE;
+}
+
+static protobuf_c_boolean
+parse_repeated_member(ScannedMember *scanned_member,
+ void *member,
+ ProtobufCMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ const ProtobufCFieldDescriptor *field = scanned_member->field;
+ size_t *p_n = STRUCT_MEMBER_PTR(size_t, message, field->quantifier_offset);
+ size_t siz = sizeof_elt_in_repeated_array(field->type);
+ char *array = *(char **) member;
+
+ if (!parse_required_member(scanned_member, array + siz * (*p_n),
+ allocator, FALSE))
+ {
+ return FALSE;
+ }
+ *p_n += 1;
+ return TRUE;
+}
+
+static unsigned
+scan_varint(unsigned len, const uint8_t *data)
+{
+ unsigned i;
+ if (len > 10)
+ len = 10;
+ for (i = 0; i < len; i++)
+ if ((data[i] & 0x80) == 0)
+ break;
+ if (i == len)
+ return 0;
+ return i + 1;
+}
+
+static protobuf_c_boolean
+parse_packed_repeated_member(ScannedMember *scanned_member,
+ void *member,
+ ProtobufCMessage *message)
+{
+ const ProtobufCFieldDescriptor *field = scanned_member->field;
+ size_t *p_n = STRUCT_MEMBER_PTR(size_t, message, field->quantifier_offset);
+ size_t siz = sizeof_elt_in_repeated_array(field->type);
+ void *array = *(char **) member + siz * (*p_n);
+ const uint8_t *at = scanned_member->data + scanned_member->length_prefix_len;
+ size_t rem = scanned_member->len - scanned_member->length_prefix_len;
+ size_t count = 0;
+ unsigned i;
+
+ switch (field->type) {
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ case PROTOBUF_C_TYPE_FLOAT:
+ count = (scanned_member->len - scanned_member->length_prefix_len) / 4;
+#if !defined(WORDS_BIGENDIAN)
+ goto no_unpacking_needed;
+#else
+ for (i = 0; i < count; i++) {
+ ((uint32_t *) array)[i] = parse_fixed_uint32(at);
+ at += 4;
+ }
+ break;
+#endif
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ case PROTOBUF_C_TYPE_DOUBLE:
+ count = (scanned_member->len - scanned_member->length_prefix_len) / 8;
+#if !defined(WORDS_BIGENDIAN)
+ goto no_unpacking_needed;
+#else
+ for (i = 0; i < count; i++) {
+ ((uint64_t *) array)[i] = parse_fixed_uint64(at);
+ at += 8;
+ }
+ break;
+#endif
+ case PROTOBUF_C_TYPE_ENUM:
+ case PROTOBUF_C_TYPE_INT32:
+ while (rem > 0) {
+ unsigned s = scan_varint(rem, at);
+ if (s == 0) {
+ PROTOBUF_C_UNPACK_ERROR("bad packed-repeated int32 value");
+ return FALSE;
+ }
+ ((int32_t *) array)[count++] = parse_int32(s, at);
+ at += s;
+ rem -= s;
+ }
+ break;
+ case PROTOBUF_C_TYPE_SINT32:
+ while (rem > 0) {
+ unsigned s = scan_varint(rem, at);
+ if (s == 0) {
+ PROTOBUF_C_UNPACK_ERROR("bad packed-repeated sint32 value");
+ return FALSE;
+ }
+ ((int32_t *) array)[count++] = unzigzag32(parse_uint32(s, at));
+ at += s;
+ rem -= s;
+ }
+ break;
+ case PROTOBUF_C_TYPE_UINT32:
+ while (rem > 0) {
+ unsigned s = scan_varint(rem, at);
+ if (s == 0) {
+ PROTOBUF_C_UNPACK_ERROR("bad packed-repeated enum or uint32 value");
+ return FALSE;
+ }
+ ((uint32_t *) array)[count++] = parse_uint32(s, at);
+ at += s;
+ rem -= s;
+ }
+ break;
+
+ case PROTOBUF_C_TYPE_SINT64:
+ while (rem > 0) {
+ unsigned s = scan_varint(rem, at);
+ if (s == 0) {
+ PROTOBUF_C_UNPACK_ERROR("bad packed-repeated sint64 value");
+ return FALSE;
+ }
+ ((int64_t *) array)[count++] = unzigzag64(parse_uint64(s, at));
+ at += s;
+ rem -= s;
+ }
+ break;
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_UINT64:
+ while (rem > 0) {
+ unsigned s = scan_varint(rem, at);
+ if (s == 0) {
+ PROTOBUF_C_UNPACK_ERROR("bad packed-repeated int64/uint64 value");
+ return FALSE;
+ }
+ ((int64_t *) array)[count++] = parse_uint64(s, at);
+ at += s;
+ rem -= s;
+ }
+ break;
+ case PROTOBUF_C_TYPE_BOOL:
+ count = rem;
+ for (i = 0; i < count; i++) {
+ if (at[i] > 1) {
+ PROTOBUF_C_UNPACK_ERROR("bad packed-repeated boolean value");
+ return FALSE;
+ }
+ ((protobuf_c_boolean *) array)[i] = at[i];
+ }
+ break;
+ default:
+ PROTOBUF_C__ASSERT_NOT_REACHED();
+ }
+ *p_n += count;
+ return TRUE;
+
+#if !defined(WORDS_BIGENDIAN)
+no_unpacking_needed:
+ memcpy(array, at, count * siz);
+ *p_n += count;
+ return TRUE;
+#endif
+}
+
+static protobuf_c_boolean
+is_packable_type(ProtobufCType type)
+{
+ return
+ type != PROTOBUF_C_TYPE_STRING &&
+ type != PROTOBUF_C_TYPE_BYTES &&
+ type != PROTOBUF_C_TYPE_MESSAGE;
+}
+
+static protobuf_c_boolean
+parse_member(ScannedMember *scanned_member,
+ ProtobufCMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ const ProtobufCFieldDescriptor *field = scanned_member->field;
+ void *member;
+
+ if (field == NULL) {
+ ProtobufCMessageUnknownField *ufield =
+ message->unknown_fields +
+ (message->n_unknown_fields++);
+ ufield->tag = scanned_member->tag;
+ ufield->wire_type = scanned_member->wire_type;
+ ufield->len = scanned_member->len;
+ ufield->data = do_alloc(allocator, scanned_member->len);
+ if (ufield->data == NULL)
+ return FALSE;
+ memcpy(ufield->data, scanned_member->data, ufield->len);
+ return TRUE;
+ }
+ member = (char *) message + field->offset;
+ switch (field->label) {
+ case PROTOBUF_C_LABEL_REQUIRED:
+ return parse_required_member(scanned_member, member,
+ allocator, TRUE);
+ case PROTOBUF_C_LABEL_OPTIONAL:
+ case PROTOBUF_C_LABEL_NONE:
+ if (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_ONEOF)) {
+ return parse_oneof_member(scanned_member, member,
+ message, allocator);
+ } else {
+ return parse_optional_member(scanned_member, member,
+ message, allocator);
+ }
+ case PROTOBUF_C_LABEL_REPEATED:
+ if (scanned_member->wire_type ==
+ PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED &&
+ (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED) ||
+ is_packable_type(field->type)))
+ {
+ return parse_packed_repeated_member(scanned_member,
+ member, message);
+ } else {
+ return parse_repeated_member(scanned_member,
+ member, message,
+ allocator);
+ }
+ }
+ PROTOBUF_C__ASSERT_NOT_REACHED();
+ return 0;
+}
+
+/**
+ * Initialise messages generated by old code.
+ *
+ * This function is used if desc->message_init == NULL (which occurs
+ * for old code, and which would be useful to support allocating
+ * descriptors dynamically).
+ */
+static void
+message_init_generic(const ProtobufCMessageDescriptor *desc,
+ ProtobufCMessage *message)
+{
+ unsigned i;
+
+ memset(message, 0, desc->sizeof_message);
+ message->descriptor = desc;
+ for (i = 0; i < desc->n_fields; i++) {
+ if (desc->fields[i].default_value != NULL &&
+ desc->fields[i].label != PROTOBUF_C_LABEL_REPEATED)
+ {
+ void *field =
+ STRUCT_MEMBER_P(message, desc->fields[i].offset);
+ const void *dv = desc->fields[i].default_value;
+
+ switch (desc->fields[i].type) {
+ case PROTOBUF_C_TYPE_INT32:
+ case PROTOBUF_C_TYPE_SINT32:
+ case PROTOBUF_C_TYPE_SFIXED32:
+ case PROTOBUF_C_TYPE_UINT32:
+ case PROTOBUF_C_TYPE_FIXED32:
+ case PROTOBUF_C_TYPE_FLOAT:
+ case PROTOBUF_C_TYPE_ENUM:
+ memcpy(field, dv, 4);
+ break;
+ case PROTOBUF_C_TYPE_INT64:
+ case PROTOBUF_C_TYPE_SINT64:
+ case PROTOBUF_C_TYPE_SFIXED64:
+ case PROTOBUF_C_TYPE_UINT64:
+ case PROTOBUF_C_TYPE_FIXED64:
+ case PROTOBUF_C_TYPE_DOUBLE:
+ memcpy(field, dv, 8);
+ break;
+ case PROTOBUF_C_TYPE_BOOL:
+ memcpy(field, dv, sizeof(protobuf_c_boolean));
+ break;
+ case PROTOBUF_C_TYPE_BYTES:
+ memcpy(field, dv, sizeof(ProtobufCBinaryData));
+ break;
+
+ case PROTOBUF_C_TYPE_STRING:
+ case PROTOBUF_C_TYPE_MESSAGE:
+ /*
+ * The next line essentially implements a cast
+ * from const, which is totally unavoidable.
+ */
+ *(const void **) field = dv;
+ break;
+ }
+ }
+ }
+}
+
+/**@}*/
+
+/*
+ * ScannedMember slabs (an unpacking implementation detail). Before doing real
+ * unpacking, we first scan through the elements to see how many there are (for
+ * repeated fields), and which field to use (for non-repeated fields given
+ * twice).
+ *
+ * In order to avoid allocations for small messages, we keep a stack-allocated
+ * slab of ScannedMembers of size FIRST_SCANNED_MEMBER_SLAB_SIZE (16). After we
+ * fill that up, we allocate each slab twice as large as the previous one.
+ */
+#define FIRST_SCANNED_MEMBER_SLAB_SIZE_LOG2 4
+
+/*
+ * The number of slabs, including the stack-allocated ones; choose the number so
+ * that we would overflow if we needed a slab larger than provided.
+ */
+#define MAX_SCANNED_MEMBER_SLAB \
+ (sizeof(unsigned int)*8 - 1 \
+ - BOUND_SIZEOF_SCANNED_MEMBER_LOG2 \
+ - FIRST_SCANNED_MEMBER_SLAB_SIZE_LOG2)
+
+#define REQUIRED_FIELD_BITMAP_SET(index) \
+ (required_fields_bitmap[(index)/8] |= (1UL<<((index)%8)))
+
+#define REQUIRED_FIELD_BITMAP_IS_SET(index) \
+ (required_fields_bitmap[(index)/8] & (1UL<<((index)%8)))
+
+ProtobufCMessage *
+protobuf_c_message_unpack(const ProtobufCMessageDescriptor *desc,
+ ProtobufCAllocator *allocator,
+ size_t len, const uint8_t *data)
+{
+ ProtobufCMessage *rv;
+ size_t rem = len;
+ const uint8_t *at = data;
+ const ProtobufCFieldDescriptor *last_field = desc->fields + 0;
+ ScannedMember first_member_slab[1UL <<
+ FIRST_SCANNED_MEMBER_SLAB_SIZE_LOG2];
+
+ /*
+ * scanned_member_slabs[i] is an array of arrays of ScannedMember.
+ * The first slab (scanned_member_slabs[0] is just a pointer to
+ * first_member_slab), above. All subsequent slabs will be allocated
+ * using the allocator.
+ */
+ ScannedMember *scanned_member_slabs[MAX_SCANNED_MEMBER_SLAB + 1];
+ unsigned which_slab = 0; /* the slab we are currently populating */
+ unsigned in_slab_index = 0; /* number of members in the slab */
+ size_t n_unknown = 0;
+ unsigned f;
+ unsigned j;
+ unsigned i_slab;
+ unsigned last_field_index = 0;
+ unsigned required_fields_bitmap_len;
+ unsigned char required_fields_bitmap_stack[16];
+ unsigned char *required_fields_bitmap = required_fields_bitmap_stack;
+ protobuf_c_boolean required_fields_bitmap_alloced = FALSE;
+
+ ASSERT_IS_MESSAGE_DESCRIPTOR(desc);
+
+ if (allocator == NULL)
+ allocator = &protobuf_c__allocator;
+
+ rv = do_alloc(allocator, desc->sizeof_message);
+ if (!rv)
+ return (NULL);
+ scanned_member_slabs[0] = first_member_slab;
+
+ required_fields_bitmap_len = (desc->n_fields + 7) / 8;
+ if (required_fields_bitmap_len > sizeof(required_fields_bitmap_stack)) {
+ required_fields_bitmap = do_alloc(allocator, required_fields_bitmap_len);
+ if (!required_fields_bitmap) {
+ do_free(allocator, rv);
+ return (NULL);
+ }
+ required_fields_bitmap_alloced = TRUE;
+ }
+ memset(required_fields_bitmap, 0, required_fields_bitmap_len);
+
+ /*
+ * Generated code always defines "message_init". However, we provide a
+ * fallback for (1) users of old protobuf-c generated-code that do not
+ * provide the function, and (2) descriptors constructed from some other
+ * source (most likely, direct construction from the .proto file).
+ */
+ if (desc->message_init != NULL)
+ protobuf_c_message_init(desc, rv);
+ else
+ message_init_generic(desc, rv);
+
+ while (rem > 0) {
+ uint32_t tag;
+ ProtobufCWireType wire_type;
+ size_t used = parse_tag_and_wiretype(rem, at, &tag, &wire_type);
+ const ProtobufCFieldDescriptor *field;
+ ScannedMember tmp;
+
+ if (used == 0) {
+ PROTOBUF_C_UNPACK_ERROR("error parsing tag/wiretype at offset %u",
+ (unsigned) (at - data));
+ goto error_cleanup_during_scan;
+ }
+ /*
+ * \todo Consider optimizing for field[1].id == tag, if field[1]
+ * exists!
+ */
+ if (last_field == NULL || last_field->id != tag) {
+ /* lookup field */
+ int field_index =
+ int_range_lookup(desc->n_field_ranges,
+ desc->field_ranges,
+ tag);
+ if (field_index < 0) {
+ field = NULL;
+ n_unknown++;
+ } else {
+ field = desc->fields + field_index;
+ last_field = field;
+ last_field_index = field_index;
+ }
+ } else {
+ field = last_field;
+ }
+
+ if (field != NULL && field->label == PROTOBUF_C_LABEL_REQUIRED)
+ REQUIRED_FIELD_BITMAP_SET(last_field_index);
+
+ at += used;
+ rem -= used;
+ tmp.tag = tag;
+ tmp.wire_type = wire_type;
+ tmp.field = field;
+ tmp.data = at;
+ tmp.length_prefix_len = 0;
+
+ switch (wire_type) {
+ case PROTOBUF_C_WIRE_TYPE_VARINT: {
+ unsigned max_len = rem < 10 ? rem : 10;
+ unsigned i;
+
+ for (i = 0; i < max_len; i++)
+ if ((at[i] & 0x80) == 0)
+ break;
+ if (i == max_len) {
+ PROTOBUF_C_UNPACK_ERROR("unterminated varint at offset %u",
+ (unsigned) (at - data));
+ goto error_cleanup_during_scan;
+ }
+ tmp.len = i + 1;
+ break;
+ }
+ case PROTOBUF_C_WIRE_TYPE_64BIT:
+ if (rem < 8) {
+ PROTOBUF_C_UNPACK_ERROR("too short after 64bit wiretype at offset %u",
+ (unsigned) (at - data));
+ goto error_cleanup_during_scan;
+ }
+ tmp.len = 8;
+ break;
+ case PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED: {
+ size_t pref_len;
+
+ tmp.len = scan_length_prefixed_data(rem, at, &pref_len);
+ if (tmp.len == 0) {
+ /* NOTE: scan_length_prefixed_data calls UNPACK_ERROR */
+ goto error_cleanup_during_scan;
+ }
+ tmp.length_prefix_len = pref_len;
+ break;
+ }
+ case PROTOBUF_C_WIRE_TYPE_32BIT:
+ if (rem < 4) {
+ PROTOBUF_C_UNPACK_ERROR("too short after 32bit wiretype at offset %u",
+ (unsigned) (at - data));
+ goto error_cleanup_during_scan;
+ }
+ tmp.len = 4;
+ break;
+ default:
+ PROTOBUF_C_UNPACK_ERROR("unsupported tag %u at offset %u",
+ wire_type, (unsigned) (at - data));
+ goto error_cleanup_during_scan;
+ }
+
+ if (in_slab_index == (1UL <<
+ (which_slab + FIRST_SCANNED_MEMBER_SLAB_SIZE_LOG2)))
+ {
+ size_t size;
+
+ in_slab_index = 0;
+ if (which_slab == MAX_SCANNED_MEMBER_SLAB) {
+ PROTOBUF_C_UNPACK_ERROR("too many fields");
+ goto error_cleanup_during_scan;
+ }
+ which_slab++;
+ size = sizeof(ScannedMember)
+ << (which_slab + FIRST_SCANNED_MEMBER_SLAB_SIZE_LOG2);
+ scanned_member_slabs[which_slab] = do_alloc(allocator, size);
+ if (scanned_member_slabs[which_slab] == NULL)
+ goto error_cleanup_during_scan;
+ }
+ scanned_member_slabs[which_slab][in_slab_index++] = tmp;
+
+ if (field != NULL && field->label == PROTOBUF_C_LABEL_REPEATED) {
+ size_t *n = STRUCT_MEMBER_PTR(size_t, rv,
+ field->quantifier_offset);
+ if (wire_type == PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED &&
+ (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED) ||
+ is_packable_type(field->type)))
+ {
+ size_t count;
+ if (!count_packed_elements(field->type,
+ tmp.len -
+ tmp.length_prefix_len,
+ tmp.data +
+ tmp.length_prefix_len,
+ &count))
+ {
+ PROTOBUF_C_UNPACK_ERROR("counting packed elements");
+ goto error_cleanup_during_scan;
+ }
+ *n += count;
+ } else {
+ *n += 1;
+ }
+ }
+
+ at += tmp.len;
+ rem -= tmp.len;
+ }
+
+ /* allocate space for repeated fields, also check that all required fields have been set */
+ for (f = 0; f < desc->n_fields; f++) {
+ const ProtobufCFieldDescriptor *field = desc->fields + f;
+ if (field->label == PROTOBUF_C_LABEL_REPEATED) {
+ size_t siz =
+ sizeof_elt_in_repeated_array(field->type);
+ size_t *n_ptr =
+ STRUCT_MEMBER_PTR(size_t, rv,
+ field->quantifier_offset);
+ if (*n_ptr != 0) {
+ unsigned n = *n_ptr;
+ void *a;
+ *n_ptr = 0;
+ assert(rv->descriptor != NULL);
+#define CLEAR_REMAINING_N_PTRS() \
+ for(f++;f < desc->n_fields; f++) \
+ { \
+ field = desc->fields + f; \
+ if (field->label == PROTOBUF_C_LABEL_REPEATED) \
+ STRUCT_MEMBER (size_t, rv, field->quantifier_offset) = 0; \
+ }
+ a = do_alloc(allocator, siz * n);
+ if (!a) {
+ CLEAR_REMAINING_N_PTRS();
+ goto error_cleanup;
+ }
+ STRUCT_MEMBER(void *, rv, field->offset) = a;
+ }
+ } else if (field->label == PROTOBUF_C_LABEL_REQUIRED) {
+ if (field->default_value == NULL &&
+ !REQUIRED_FIELD_BITMAP_IS_SET(f))
+ {
+ CLEAR_REMAINING_N_PTRS();
+ PROTOBUF_C_UNPACK_ERROR("message '%s': missing required field '%s'",
+ desc->name, field->name);
+ goto error_cleanup;
+ }
+ }
+ }
+#undef CLEAR_REMAINING_N_PTRS
+
+ /* allocate space for unknown fields */
+ if (n_unknown) {
+ rv->unknown_fields = do_alloc(allocator,
+ n_unknown * sizeof(ProtobufCMessageUnknownField));
+ if (rv->unknown_fields == NULL)
+ goto error_cleanup;
+ }
+
+ /* do real parsing */
+ for (i_slab = 0; i_slab <= which_slab; i_slab++) {
+ unsigned max = (i_slab == which_slab) ?
+ in_slab_index : (1UL << (i_slab + 4));
+ ScannedMember *slab = scanned_member_slabs[i_slab];
+
+ for (j = 0; j < max; j++) {
+ if (!parse_member(slab + j, rv, allocator)) {
+ PROTOBUF_C_UNPACK_ERROR("error parsing member %s of %s",
+ slab->field ? slab->field->name : "*unknown-field*",
+ desc->name);
+ goto error_cleanup;
+ }
+ }
+ }
+
+ /* cleanup */
+ for (j = 1; j <= which_slab; j++)
+ do_free(allocator, scanned_member_slabs[j]);
+ if (required_fields_bitmap_alloced)
+ do_free(allocator, required_fields_bitmap);
+ return rv;
+
+error_cleanup:
+ protobuf_c_message_free_unpacked(rv, allocator);
+ for (j = 1; j <= which_slab; j++)
+ do_free(allocator, scanned_member_slabs[j]);
+ if (required_fields_bitmap_alloced)
+ do_free(allocator, required_fields_bitmap);
+ return NULL;
+
+error_cleanup_during_scan:
+ do_free(allocator, rv);
+ for (j = 1; j <= which_slab; j++)
+ do_free(allocator, scanned_member_slabs[j]);
+ if (required_fields_bitmap_alloced)
+ do_free(allocator, required_fields_bitmap);
+ return NULL;
+}
+
+void
+protobuf_c_message_free_unpacked(ProtobufCMessage *message,
+ ProtobufCAllocator *allocator)
+{
+ const ProtobufCMessageDescriptor *desc;
+ unsigned f;
+
+ if (message == NULL)
+ return;
+
+ desc = message->descriptor;
+
+ ASSERT_IS_MESSAGE(message);
+
+ if (allocator == NULL)
+ allocator = &protobuf_c__allocator;
+ message->descriptor = NULL;
+ for (f = 0; f < desc->n_fields; f++) {
+ if (0 != (desc->fields[f].flags & PROTOBUF_C_FIELD_FLAG_ONEOF) &&
+ desc->fields[f].id !=
+ STRUCT_MEMBER(uint32_t, message, desc->fields[f].quantifier_offset))
+ {
+ /* This is not the selected oneof, skip it */
+ continue;
+ }
+
+ if (desc->fields[f].label == PROTOBUF_C_LABEL_REPEATED) {
+ size_t n = STRUCT_MEMBER(size_t,
+ message,
+ desc->fields[f].quantifier_offset);
+ void *arr = STRUCT_MEMBER(void *,
+ message,
+ desc->fields[f].offset);
+
+ if (arr != NULL) {
+ if (desc->fields[f].type == PROTOBUF_C_TYPE_STRING) {
+ unsigned i;
+ for (i = 0; i < n; i++)
+ do_free(allocator, ((char **) arr)[i]);
+ } else if (desc->fields[f].type == PROTOBUF_C_TYPE_BYTES) {
+ unsigned i;
+ for (i = 0; i < n; i++)
+ do_free(allocator, ((ProtobufCBinaryData *) arr)[i].data);
+ } else if (desc->fields[f].type == PROTOBUF_C_TYPE_MESSAGE) {
+ unsigned i;
+ for (i = 0; i < n; i++)
+ protobuf_c_message_free_unpacked(
+ ((ProtobufCMessage **) arr)[i],
+ allocator
+ );
+ }
+ do_free(allocator, arr);
+ }
+ } else if (desc->fields[f].type == PROTOBUF_C_TYPE_STRING) {
+ char *str = STRUCT_MEMBER(char *, message,
+ desc->fields[f].offset);
+
+ if (str && str != desc->fields[f].default_value)
+ do_free(allocator, str);
+ } else if (desc->fields[f].type == PROTOBUF_C_TYPE_BYTES) {
+ void *data = STRUCT_MEMBER(ProtobufCBinaryData, message,
+ desc->fields[f].offset).data;
+ const ProtobufCBinaryData *default_bd;
+
+ default_bd = desc->fields[f].default_value;
+ if (data != NULL &&
+ (default_bd == NULL ||
+ default_bd->data != data))
+ {
+ do_free(allocator, data);
+ }
+ } else if (desc->fields[f].type == PROTOBUF_C_TYPE_MESSAGE) {
+ ProtobufCMessage *sm;
+
+ sm = STRUCT_MEMBER(ProtobufCMessage *, message,
+ desc->fields[f].offset);
+ if (sm && sm != desc->fields[f].default_value)
+ protobuf_c_message_free_unpacked(sm, allocator);
+ }
+ }
+
+ for (f = 0; f < message->n_unknown_fields; f++)
+ do_free(allocator, message->unknown_fields[f].data);
+ if (message->unknown_fields != NULL)
+ do_free(allocator, message->unknown_fields);
+
+ do_free(allocator, message);
+}
+
+void
+protobuf_c_message_init(const ProtobufCMessageDescriptor * descriptor,
+ void *message)
+{
+ descriptor->message_init((ProtobufCMessage *) (message));
+}
+
+protobuf_c_boolean
+protobuf_c_message_check(const ProtobufCMessage *message)
+{
+ unsigned i;
+
+ if (!message ||
+ !message->descriptor ||
+ message->descriptor->magic != PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC)
+ {
+ return FALSE;
+ }
+
+ for (i = 0; i < message->descriptor->n_fields; i++) {
+ const ProtobufCFieldDescriptor *f = message->descriptor->fields + i;
+ ProtobufCType type = f->type;
+ ProtobufCLabel label = f->label;
+ void *field = STRUCT_MEMBER_P (message, f->offset);
+
+ if (f->flags & PROTOBUF_C_FIELD_FLAG_ONEOF) {
+ const uint32_t *oneof_case = STRUCT_MEMBER_P (message, f->quantifier_offset);
+ if (f->id != *oneof_case) {
+ continue; //Do not check if it is an unpopulated oneof member.
+ }
+ }
+
+ if (label == PROTOBUF_C_LABEL_REPEATED) {
+ size_t *quantity = STRUCT_MEMBER_P (message, f->quantifier_offset);
+
+ if (*quantity > 0 && *(void **) field == NULL) {
+ return FALSE;
+ }
+
+ if (type == PROTOBUF_C_TYPE_MESSAGE) {
+ ProtobufCMessage **submessage = *(ProtobufCMessage ***) field;
+ unsigned j;
+ for (j = 0; j < *quantity; j++) {
+ if (!protobuf_c_message_check(submessage[j]))
+ return FALSE;
+ }
+ } else if (type == PROTOBUF_C_TYPE_STRING) {
+ char **string = *(char ***) field;
+ unsigned j;
+ for (j = 0; j < *quantity; j++) {
+ if (!string[j])
+ return FALSE;
+ }
+ } else if (type == PROTOBUF_C_TYPE_BYTES) {
+ ProtobufCBinaryData *bd = *(ProtobufCBinaryData **) field;
+ unsigned j;
+ for (j = 0; j < *quantity; j++) {
+ if (bd[j].len > 0 && bd[j].data == NULL)
+ return FALSE;
+ }
+ }
+
+ } else { /* PROTOBUF_C_LABEL_REQUIRED or PROTOBUF_C_LABEL_OPTIONAL */
+
+ if (type == PROTOBUF_C_TYPE_MESSAGE) {
+ ProtobufCMessage *submessage = *(ProtobufCMessage **) field;
+ if (label == PROTOBUF_C_LABEL_REQUIRED || submessage != NULL) {
+ if (!protobuf_c_message_check(submessage))
+ return FALSE;
+ }
+ } else if (type == PROTOBUF_C_TYPE_STRING) {
+ char *string = *(char **) field;
+ if (label == PROTOBUF_C_LABEL_REQUIRED && string == NULL)
+ return FALSE;
+ } else if (type == PROTOBUF_C_TYPE_BYTES) {
+ protobuf_c_boolean *has = STRUCT_MEMBER_P (message, f->quantifier_offset);
+ ProtobufCBinaryData *bd = field;
+ if (label == PROTOBUF_C_LABEL_REQUIRED || *has == TRUE) {
+ if (bd->len > 0 && bd->data == NULL)
+ return FALSE;
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/* === services === */
+
+typedef void (*GenericHandler) (void *service,
+ const ProtobufCMessage *input,
+ ProtobufCClosure closure,
+ void *closure_data);
+void
+protobuf_c_service_invoke_internal(ProtobufCService *service,
+ unsigned method_index,
+ const ProtobufCMessage *input,
+ ProtobufCClosure closure,
+ void *closure_data)
+{
+ GenericHandler *handlers;
+ GenericHandler handler;
+
+ /*
+ * Verify that method_index is within range. If this fails, you are
+ * likely invoking a newly added method on an old service. (Although
+ * other memory corruption bugs can cause this assertion too.)
+ */
+ assert(method_index < service->descriptor->n_methods);
+
+ /*
+ * Get the array of virtual methods (which are enumerated by the
+ * generated code).
+ */
+ handlers = (GenericHandler *) (service + 1);
+
+ /*
+ * Get our method and invoke it.
+ * \todo Seems like handler == NULL is a situation that needs handling.
+ */
+ handler = handlers[method_index];
+ (*handler)(service, input, closure, closure_data);
+}
+
+void
+protobuf_c_service_generated_init(ProtobufCService *service,
+ const ProtobufCServiceDescriptor *descriptor,
+ ProtobufCServiceDestroy destroy)
+{
+ ASSERT_IS_SERVICE_DESCRIPTOR(descriptor);
+ service->descriptor = descriptor;
+ service->destroy = destroy;
+ service->invoke = protobuf_c_service_invoke_internal;
+ memset(service + 1, 0, descriptor->n_methods * sizeof(GenericHandler));
+}
+
+void protobuf_c_service_destroy(ProtobufCService *service)
+{
+ service->destroy(service);
+}
+
+/* --- querying the descriptors --- */
+
+const ProtobufCEnumValue *
+protobuf_c_enum_descriptor_get_value_by_name(const ProtobufCEnumDescriptor *desc,
+ const char *name)
+{
+ unsigned start = 0;
+ unsigned count;
+
+ if (desc == NULL || desc->values_by_name == NULL)
+ return NULL;
+
+ count = desc->n_value_names;
+
+ while (count > 1) {
+ unsigned mid = start + count / 2;
+ int rv = strcmp(desc->values_by_name[mid].name, name);
+ if (rv == 0)
+ return desc->values + desc->values_by_name[mid].index;
+ else if (rv < 0) {
+ count = start + count - (mid + 1);
+ start = mid + 1;
+ } else
+ count = mid - start;
+ }
+ if (count == 0)
+ return NULL;
+ if (strcmp(desc->values_by_name[start].name, name) == 0)
+ return desc->values + desc->values_by_name[start].index;
+ return NULL;
+}
+
+const ProtobufCEnumValue *
+protobuf_c_enum_descriptor_get_value(const ProtobufCEnumDescriptor *desc,
+ int value)
+{
+ int rv = int_range_lookup(desc->n_value_ranges, desc->value_ranges, value);
+ if (rv < 0)
+ return NULL;
+ return desc->values + rv;
+}
+
+const ProtobufCFieldDescriptor *
+protobuf_c_message_descriptor_get_field_by_name(const ProtobufCMessageDescriptor *desc,
+ const char *name)
+{
+ unsigned start = 0;
+ unsigned count;
+ const ProtobufCFieldDescriptor *field;
+
+ if (desc == NULL || desc->fields_sorted_by_name == NULL)
+ return NULL;
+
+ count = desc->n_fields;
+
+ while (count > 1) {
+ unsigned mid = start + count / 2;
+ int rv;
+ field = desc->fields + desc->fields_sorted_by_name[mid];
+ rv = strcmp(field->name, name);
+ if (rv == 0)
+ return field;
+ else if (rv < 0) {
+ count = start + count - (mid + 1);
+ start = mid + 1;
+ } else
+ count = mid - start;
+ }
+ if (count == 0)
+ return NULL;
+ field = desc->fields + desc->fields_sorted_by_name[start];
+ if (strcmp(field->name, name) == 0)
+ return field;
+ return NULL;
+}
+
+const ProtobufCFieldDescriptor *
+protobuf_c_message_descriptor_get_field(const ProtobufCMessageDescriptor *desc,
+ unsigned value)
+{
+ int rv = int_range_lookup(desc->n_field_ranges,desc->field_ranges, value);
+ if (rv < 0)
+ return NULL;
+ return desc->fields + rv;
+}
+
+const ProtobufCMethodDescriptor *
+protobuf_c_service_descriptor_get_method_by_name(const ProtobufCServiceDescriptor *desc,
+ const char *name)
+{
+ unsigned start = 0;
+ unsigned count;
+
+ if (desc == NULL || desc->method_indices_by_name == NULL)
+ return NULL;
+
+ count = desc->n_methods;
+
+ while (count > 1) {
+ unsigned mid = start + count / 2;
+ unsigned mid_index = desc->method_indices_by_name[mid];
+ const char *mid_name = desc->methods[mid_index].name;
+ int rv = strcmp(mid_name, name);
+
+ if (rv == 0)
+ return desc->methods + desc->method_indices_by_name[mid];
+ if (rv < 0) {
+ count = start + count - (mid + 1);
+ start = mid + 1;
+ } else {
+ count = mid - start;
+ }
+ }
+ if (count == 0)
+ return NULL;
+ if (strcmp(desc->methods[desc->method_indices_by_name[start]].name, name) == 0)
+ return desc->methods + desc->method_indices_by_name[start];
+ return NULL;
+}
diff --git a/lib/util/Makefile.in b/lib/util/Makefile.in
new file mode 100644
index 0000000..9b4ff8b
--- /dev/null
+++ b/lib/util/Makefile.in
@@ -0,0 +1,1399 @@
+#
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2011-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+# @configure_input@
+#
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+abs_srcdir = @abs_srcdir@
+top_srcdir = @top_srcdir@
+abs_top_srcdir = @abs_top_srcdir@
+top_builddir = @top_builddir@
+abs_top_builddir = @abs_top_builddir@
+devdir = @devdir@
+scriptdir = $(top_srcdir)/scripts
+incdir = $(top_srcdir)/include
+cross_compiling = @CROSS_COMPILING@
+
+# Where to install things...
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+sbindir = @sbindir@
+sysconfdir = @sysconfdir@
+libexecdir = @libexecdir@
+datarootdir = @datarootdir@
+localstatedir = @localstatedir@
+
+# File extension, mode and map file to use for shared libraries/objects
+shlib_enable = @SHLIB_ENABLE@
+shlib_mode = @SHLIB_MODE@
+shlib_exp = ./util.exp
+shlib_map = util.map
+shlib_opt = util.opt
+
+# Compiler & tools to use
+CC = @CC@
+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) $(CPPDEFS) @CPPFLAGS@ \
+ -DDEFAULT_TEXT_DOMAIN=\"@PACKAGE_NAME@\"
+
+# 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 --enable=warning,performance,portability --suppress=constStatement --suppress=compareBoolExpressionWithInt --error-exitcode=1 --inline-suppr -Dva_copy=va_copy -U__cplusplus -UQUAD_MAX -UQUAD_MIN -UUQUAD_MAX -U_POSIX_HOST_NAME_MAX -U_POSIX_PATH_MAX -U__NBBY -DNSIG=64
+
+# splint options, usually set in the top-level Makefile
+SPLINT_OPTS = -D__restrict= -checks
+
+# PVS-studio options
+PVS_CFG = $(top_srcdir)/PVS-Studio.cfg
+PVS_IGNORE = 'V707,V011,V002,V536'
+PVS_LOG_OPTS = -a 'GA:1,2' -e -t errorfile -d $(PVS_IGNORE)
+
+# Regression tests
+TEST_PROGS = conf_test hltq_test parseln_test progname_test strsplit_test \
+ strtobool_test strtoid_test strtomode_test strtonum_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 json.lo lbuf.lo locking.lo \
+ logfac.lo logpri.lo mkdir_parents.lo parseln.lo progname.lo \
+ roundup.lo secure_path.lo setgroups.lo strsplit.lo strtobool.lo \
+ strtoid.lo strtomode.lo strtonum.lo sudo_conf.lo \
+ sudo_debug.lo sudo_dso.lo term.lo ttyname_dev.lo \
+ ttysize.lo uuid.lo @COMMON_OBJS@ @LTLIBOBJS@
+
+IOBJS = $(LTOBJS:.lo=.i)
+
+POBJS = $(IOBJS:.i=.plog)
+
+MKTEMP_TEST_OBJS = mktemp_test.lo mktemp.lo
+
+PARSELN_TEST_OBJS = parseln_test.lo parseln.lo
+
+PROGNAME_TEST_OBJS = progname_test.lo progname.lo
+
+CONF_TEST_OBJS = conf_test.lo sudo_conf.lo
+
+HLTQ_TEST_OBJS = hltq_test.lo
+
+FNM_TEST_OBJS = fnm_test.lo fnmatch.lo
+
+GLOBTEST_OBJS = globtest.lo glob.lo
+
+GETDELIM_TEST_OBJS = getdelim_test.lo getdelim.lo
+
+STRTOBOOL_TEST_OBJS = strtobool_test.lo strtobool.lo
+
+STRTOMODE_TEST_OBJS = strtomode_test.lo strtomode.lo
+
+STRTOID_TEST_OBJS = strtoid_test.lo strtoid.lo strtonum.lo
+
+STRTONUM_TEST_OBJS = strtonum_test.lo strtonum.lo
+
+STRSPLIT_TEST_OBJS = strsplit_test.lo strsplit.lo
+
+PARSE_GIDS_TEST_OBJS = parse_gids_test.lo gidlist.lo
+
+GETGROUPLIST_TEST_OBJS = getgrouplist_test.lo getgrouplist.lo
+
+STRSIG_TEST_OBJS = strsig_test.lo sig2str.lo str2sig.lo @SIGNAME@
+
+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)
+
+depend: siglist.c signame.c
+ $(scriptdir)/mkdep.pl --srcdir=$(abs_top_srcdir) \
+ --builddir=$(abs_top_builddir) lib/util/Makefile.in
+ cd $(top_builddir) && ./config.status --file lib/util/Makefile
+
+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) @LT_DEP_LIBS@ @LIBINTL@ @LIBMD@ @LIBPTHREAD@ @LIBDL@ @LIBRT@ @NET_LIBS@;; \
+ *) \
+ $(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@ @NET_LIBS@;; \
+ 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
+
+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)
+
+getdelim_test: $(GETDELIM_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(GETDELIM_TEST_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)
+
+strsig_test: $(STRSIG_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(STRSIG_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+strtobool_test: $(STRTOBOOL_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(STRTOBOOL_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+strtomode_test: $(STRTOMODE_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(STRTOMODE_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+strtonum_test: $(STRTONUM_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(STRTONUM_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
+
+strtoid_test: $(STRTOID_TEST_OBJS) libsudo_util.la
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(STRTOID_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(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) $(scriptdir)/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) $(srcdir)/*.c
+
+cppcheck:
+ cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) $(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 \
+ MALLOC_OPTIONS=S; export MALLOC_OPTIONS; \
+ MALLOC_CONF="abort:true,junk:true"; export MALLOC_CONF; \
+ 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 getdelim_test; then \
+ ./getdelim_test || rval=`expr $$rval + $$?`; \
+ fi; \
+ if test -f mktemp_test; then \
+ ./mktemp_test || rval=`expr $$rval + $$?`; \
+ fi; \
+ if test -f strsig_test; then \
+ ./strsig_test || rval=`expr $$rval + $$?`; \
+ fi; \
+ ./getgrouplist_test || rval=`expr $$rval + $$?`; \
+ ./strtobool_test || rval=`expr $$rval + $$?`; \
+ ./strtoid_test || rval=`expr $$rval + $$?`; \
+ ./strtomode_test || rval=`expr $$rval + $$?`; \
+ ./strtonum_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
+ -rm -f *.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_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)/aix.c
+aix.i: $(srcdir)/aix.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+aix.plog: aix.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/aix.c --i-file $< --output-file $@
+arc4random.lo: $(srcdir)/arc4random.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_rand.h $(srcdir)/chacha_private.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/arc4random.c
+arc4random.i: $(srcdir)/arc4random.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_rand.h $(srcdir)/chacha_private.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+arc4random.plog: arc4random.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/arc4random.c --i-file $< --output-file $@
+arc4random_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 $@
+cfmakeraw.lo: $(srcdir)/cfmakeraw.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/cfmakeraw.c
+cfmakeraw.i: $(srcdir)/cfmakeraw.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+cfmakeraw.plog: cfmakeraw.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/cfmakeraw.c --i-file $< --output-file $@
+closefrom.lo: $(srcdir)/closefrom.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/closefrom.c
+closefrom.i: $(srcdir)/closefrom.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+closefrom.plog: closefrom.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/closefrom.c --i-file $< --output-file $@
+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 $@
+dup3.lo: $(srcdir)/dup3.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/dup3.c
+dup3.i: $(srcdir)/dup3.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+dup3.plog: dup3.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/dup3.c --i-file $< --output-file $@
+event.lo: $(srcdir)/event.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+event.plog: event.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/event.c --i-file $< --output-file $@
+event_poll.lo: $(srcdir)/event_poll.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+event_poll.plog: event_poll.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/event_poll.c --i-file $< --output-file $@
+event_select.lo: $(srcdir)/event_select.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+event_select.plog: event_select.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/event_select.c --i-file $< --output-file $@
+explicit_bzero.lo: $(srcdir)/explicit_bzero.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/explicit_bzero.c
+explicit_bzero.i: $(srcdir)/explicit_bzero.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+explicit_bzero.plog: explicit_bzero.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/explicit_bzero.c --i-file $< --output-file $@
+fatal.lo: $(srcdir)/fatal.c $(incdir)/compat/getaddrinfo.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+fchmodat.lo: $(srcdir)/fchmodat.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/fchmodat.c
+fchmodat.i: $(srcdir)/fchmodat.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+fchmodat.plog: fchmodat.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/fchmodat.c --i-file $< --output-file $@
+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 $@
+freezero.lo: $(srcdir)/freezero.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/freezero.c
+freezero.i: $(srcdir)/freezero.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+freezero.plog: freezero.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/freezero.c --i-file $< --output-file $@
+fstatat.lo: $(srcdir)/fstatat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/fstatat.c
+fstatat.i: $(srcdir)/fstatat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+fstatat.plog: fstatat.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/fstatat.c --i-file $< --output-file $@
+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 $@
+getdelim.lo: $(srcdir)/getdelim.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/getdelim.c
+getdelim.i: $(srcdir)/getdelim.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getdelim.plog: getdelim.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getdelim.c --i-file $< --output-file $@
+getdelim_test.lo: $(srcdir)/regress/getdelim/getdelim_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/getdelim/getdelim_test.c
+getdelim_test.i: $(srcdir)/regress/getdelim/getdelim_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getdelim_test.plog: getdelim_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/getdelim/getdelim_test.c --i-file $< --output-file $@
+getentropy.lo: $(srcdir)/getentropy.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_digest.h $(incdir)/sudo_rand.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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_plugin.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_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getgrouplist_test.plog: getgrouplist_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/getgrouplist/getgrouplist_test.c --i-file $< --output-file $@
+gethostname.lo: $(srcdir)/gethostname.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+getopt_long.lo: $(srcdir)/getopt_long.c $(incdir)/compat/getopt.h \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $(incdir)/sudo_plugin.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getopt_long.plog: getopt_long.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getopt_long.c --i-file $< --output-file $@
+gettime.lo: $(srcdir)/gettime.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+getusershell.lo: $(srcdir)/getusershell.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/getusershell.c
+getusershell.i: $(srcdir)/getusershell.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getusershell.plog: getusershell.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getusershell.c --i-file $< --output-file $@
+gidlist.lo: $(srcdir)/gidlist.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+gidlist.plog: gidlist.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/gidlist.c --i-file $< --output-file $@
+glob.lo: $(srcdir)/glob.c $(incdir)/compat/charclass.h $(incdir)/compat/glob.h \
+ $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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_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)/regress/tailq/hltq_test.c
+hltq_test.i: $(srcdir)/regress/tailq/hltq_test.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+hltq_test.plog: hltq_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/tailq/hltq_test.c --i-file $< --output-file $@
+inet_pton.lo: $(srcdir)/inet_pton.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+json.lo: $(srcdir)/json.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_json.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/json.c
+json.i: $(srcdir)/json.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_json.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+json.plog: json.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/json.c --i-file $< --output-file $@
+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 $@
+logfac.lo: $(srcdir)/logfac.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/logfac.c
+logfac.i: $(srcdir)/logfac.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+logfac.plog: logfac.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/logfac.c --i-file $< --output-file $@
+logpri.lo: $(srcdir)/logpri.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/logpri.c
+logpri.i: $(srcdir)/logpri.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+logpri.plog: logpri.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/logpri.c --i-file $< --output-file $@
+memrchr.lo: $(srcdir)/memrchr.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+mkdir_parents.lo: $(srcdir)/mkdir_parents.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/mkdir_parents.c
+mkdir_parents.i: $(srcdir)/mkdir_parents.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+mkdir_parents.plog: mkdir_parents.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/mkdir_parents.c --i-file $< --output-file $@
+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_plugin.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_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+mktemp_test.plog: mktemp_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/mktemp/mktemp_test.c --i-file $< --output-file $@
+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 $@
+openat.lo: $(srcdir)/openat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/openat.c
+openat.i: $(srcdir)/openat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+openat.plog: openat.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/openat.c --i-file $< --output-file $@
+parse_gids_test.lo: $(srcdir)/regress/parse_gids/parse_gids_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+parse_gids_test.plog: parse_gids_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/parse_gids/parse_gids_test.c --i-file $< --output-file $@
+parseln.lo: $(srcdir)/parseln.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+pread.lo: $(srcdir)/pread.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/pread.c
+pread.i: $(srcdir)/pread.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+pread.plog: pread.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/pread.c --i-file $< --output-file $@
+progname.lo: $(srcdir)/progname.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+pwrite.lo: $(srcdir)/pwrite.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/pwrite.c
+pwrite.i: $(srcdir)/pwrite.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+pwrite.plog: pwrite.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/pwrite.c --i-file $< --output-file $@
+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 $@
+roundup.lo: $(srcdir)/roundup.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/roundup.c
+roundup.i: $(srcdir)/roundup.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+roundup.plog: roundup.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/roundup.c --i-file $< --output-file $@
+secure_path.lo: $(srcdir)/secure_path.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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)/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)/sig2str.c
+sig2str.i: $(srcdir)/sig2str.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+sig2str.plog: sig2str.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sig2str.c --i-file $< --output-file $@
+siglist.lo: siglist.c $(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 $@
+str2sig.lo: $(srcdir)/str2sig.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/str2sig.c
+str2sig.i: $(srcdir)/str2sig.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+str2sig.plog: str2sig.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/str2sig.c --i-file $< --output-file $@
+strlcat.lo: $(srcdir)/strlcat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+strsig_test.lo: $(srcdir)/regress/strsig/strsig_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/strsig/strsig_test.c
+strsig_test.i: $(srcdir)/regress/strsig/strsig_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strsig_test.plog: strsig_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strsig/strsig_test.c --i-file $< --output-file $@
+strsignal.lo: $(srcdir)/strsignal.c $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_gettext.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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_plugin.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_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strsplit_test.plog: strsplit_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strsplit/strsplit_test.c --i-file $< --output-file $@
+strtobool.lo: $(srcdir)/strtobool.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+strtobool_test.lo: $(srcdir)/regress/strtofoo/strtobool_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/strtofoo/strtobool_test.c
+strtobool_test.i: $(srcdir)/regress/strtofoo/strtobool_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtobool_test.plog: strtobool_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strtofoo/strtobool_test.c --i-file $< --output-file $@
+strtoid.lo: $(srcdir)/strtoid.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+strtoid_test.lo: $(srcdir)/regress/strtofoo/strtoid_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/strtofoo/strtoid_test.c
+strtoid_test.i: $(srcdir)/regress/strtofoo/strtoid_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtoid_test.plog: strtoid_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strtofoo/strtoid_test.c --i-file $< --output-file $@
+strtomode.lo: $(srcdir)/strtomode.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+strtomode_test.lo: $(srcdir)/regress/strtofoo/strtomode_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/strtofoo/strtomode_test.c
+strtomode_test.i: $(srcdir)/regress/strtofoo/strtomode_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtomode_test.plog: strtomode_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strtofoo/strtomode_test.c --i-file $< --output-file $@
+strtonum.lo: $(srcdir)/strtonum.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/strtonum.c
+strtonum.i: $(srcdir)/strtonum.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtonum.plog: strtonum.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/strtonum.c --i-file $< --output-file $@
+strtonum_test.lo: $(srcdir)/regress/strtofoo/strtonum_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/strtofoo/strtonum_test.c
+strtonum_test.i: $(srcdir)/regress/strtofoo/strtonum_test.c \
+ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+strtonum_test.plog: strtonum_test.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/strtofoo/strtonum_test.c --i-file $< --output-file $@
+sudo_conf.lo: $(srcdir)/sudo_conf.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
+ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
+ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h $(top_builddir)/pathnames.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+unlinkat.lo: $(srcdir)/unlinkat.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/unlinkat.c
+unlinkat.i: $(srcdir)/unlinkat.c $(incdir)/sudo_compat.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+unlinkat.plog: unlinkat.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/unlinkat.c --i-file $< --output-file $@
+utimens.lo: $(srcdir)/utimens.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(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 $@
+uuid.lo: $(srcdir)/uuid.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_rand.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/uuid.c
+uuid.i: $(srcdir)/uuid.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+ $(incdir)/sudo_rand.h $(incdir)/sudo_util.h $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+uuid.plog: uuid.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/uuid.c --i-file $< --output-file $@
+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_plugin.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_plugin.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..4956373
--- /dev/null
+++ b/lib/util/aix.c
@@ -0,0 +1,290 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008, 2010-2016 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/resource.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <usersec.h>
+#include <uinfo.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+#ifdef HAVE_GETUSERATTR
+
+#ifndef HAVE_SETRLIMIT64
+# define setrlimit64(a, b) setrlimit(a, b)
+# define rlimit64 rlimit
+# define rlim64_t rlim_t
+# define RLIM64_INFINITY RLIM_INFINITY
+#endif /* HAVE_SETRLIMIT64 */
+
+#ifndef RLIM_SAVED_MAX
+# define RLIM_SAVED_MAX RLIM64_INFINITY
+#endif
+
+struct aix_limit {
+ int resource;
+ 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("%s", U_("unable to open userdb"));
+ debug_return_int(-1);
+ }
+
+ /*
+ * For each resource limit, get the soft/hard values for the user
+ * and set those values via setrlimit64(). Must be run as euid 0.
+ */
+ for (n = 0; n < nitems(aix_limits); n++) {
+ /*
+ * We have two strategies, depending on whether or not the
+ * hard limit has been defined.
+ */
+ if (aix_getlimit(user, aix_limits[n].hard, &val) == 0) {
+ rlim.rlim_max = val == -1 ? RLIM64_INFINITY : (rlim64_t)val * aix_limits[n].factor;
+ if (aix_getlimit(user, aix_limits[n].soft, &val) == 0)
+ rlim.rlim_cur = val == -1 ? RLIM64_INFINITY : (rlim64_t)val * aix_limits[n].factor;
+ else
+ rlim.rlim_cur = rlim.rlim_max; /* soft not specd, use hard */
+ } else {
+ /* No hard limit set, try soft limit, if it exists. */
+ if (aix_getlimit(user, aix_limits[n].soft, &val) == -1)
+ continue;
+ rlim.rlim_cur = val == -1 ? RLIM64_INFINITY : (rlim64_t)val * aix_limits[n].factor;
+
+ /* Set default hard limit as per limits(4). */
+ switch (aix_limits[n].resource) {
+ case RLIMIT_CPU:
+ case RLIMIT_FSIZE:
+ rlim.rlim_max = rlim.rlim_cur;
+ break;
+ case RLIMIT_STACK:
+ rlim.rlim_max = 4194304UL * aix_limits[n].factor;
+ break;
+ default:
+ rlim.rlim_max = RLIM64_INFINITY;
+ break;
+ }
+ }
+ (void)setrlimit64(aix_limits[n].resource, &rlim);
+ }
+ enduserdb();
+ debug_return_int(0);
+}
+
+#ifdef HAVE_SETAUTHDB
+
+# ifndef HAVE_AUTHDB_T
+typedef char authdb_t[16];
+# endif
+
+/* The empty string means to access all defined authentication registries. */
+static authdb_t old_registry;
+
+# if defined(HAVE_DECL_SETAUTHDB) && !HAVE_DECL_SETAUTHDB
+int setauthdb(authdb_t new, authdb_t old);
+int getauthdb(authdb_t val);
+# endif
+# if defined(HAVE_DECL_USRINFO) && !HAVE_DECL_USRINFO
+int usrinfo(int cmd, char *buf, int count);
+# endif
+
+/*
+ * Look up authentication registry for user (SYSTEM in /etc/security/user) and
+ * set it as the default for the process. This ensures that password and
+ * group lookups are made against the correct source (files, NIS, LDAP, etc).
+ * Does not modify errno even on error since callers do not check return value.
+ */
+int
+aix_getauthregistry_v1(char *user, char *saved_registry)
+{
+ int serrno = errno;
+ int ret = -1;
+ debug_decl(aix_getauthregistry, SUDO_DEBUG_UTIL);
+
+ saved_registry[0] = '\0';
+ if (user != NULL) {
+ char *registry;
+
+ if (setuserdb(S_READ) != 0) {
+ sudo_warn("%s", U_("unable to open userdb"));
+ goto done;
+ }
+ ret = getuserattr(user, S_REGISTRY, &registry, SEC_CHAR);
+ if (ret == 0) {
+ /* sizeof(authdb_t) is guaranteed to be 16 */
+ if (strlcpy(saved_registry, registry, 16) >= 16) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "registry for user %s too long: %s", user, registry);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: saved authentication registry for user %s is %s",
+ __func__, user, saved_registry);
+ }
+ enduserdb();
+ } else {
+ /* Get the process-wide registry. */
+ ret = getauthdb(saved_registry);
+ }
+done:
+ errno = serrno;
+ debug_return_int(ret);
+}
+
+/*
+ * Set the specified authentication registry for user (SYSTEM in
+ * /etc/security/user) and set it as the default for the process.
+ * This ensures that password and group lookups are made against
+ * the correct source (files, NIS, LDAP, etc).
+ * If registry is NULL, look it up based on the user name.
+ * Does not modify errno even on error since callers do not check return value.
+ */
+int
+aix_setauthdb_v1(char *user)
+{
+ return aix_setauthdb_v2(user, NULL);
+}
+
+int
+aix_setauthdb_v2(char *user, char *registry)
+{
+ authdb_t regbuf;
+ int serrno = errno;
+ int ret = -1;
+ debug_decl(aix_setauthdb, SUDO_DEBUG_UTIL);
+
+ if (user != NULL) {
+ /* Look up authentication registry if one is not provided. */
+ if (registry == NULL) {
+ if (aix_getauthregistry(user, regbuf) != 0)
+ goto done;
+ registry = regbuf;
+ }
+ ret = setauthdb(registry, old_registry);
+ if (ret != 0) {
+ sudo_warn(U_("unable to switch to registry \"%s\" for %s"),
+ registry, user);
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: setting authentication registry to %s",
+ __func__, registry);
+ }
+ }
+done:
+ errno = serrno;
+ debug_return_int(ret);
+}
+
+/*
+ * Restore the saved authentication registry, if any.
+ * Does not modify errno even on error since callers do not check return value.
+ */
+int
+aix_restoreauthdb_v1(void)
+{
+ int serrno = errno;
+ int ret = 0;
+ debug_decl(aix_setauthdb, SUDO_DEBUG_UTIL);
+
+ if (setauthdb(old_registry, NULL) != 0) {
+ sudo_warn("%s", U_("unable to restore registry"));
+ ret = -1;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: setting authentication registry to %s",
+ __func__, old_registry);
+ }
+ errno = serrno;
+ debug_return_int(ret);
+}
+#endif
+
+int
+aix_prep_user_v1(char *user, const char *tty)
+{
+ char *info;
+ int len;
+ debug_decl(aix_setauthdb, SUDO_DEBUG_UTIL);
+
+ /* set usrinfo, like login(1) does */
+ len = asprintf(&info, "NAME=%s%cLOGIN=%s%cLOGNAME=%s%cTTY=%s%c",
+ user, '\0', user, '\0', user, '\0', tty ? tty : "", '\0');
+ if (len == -1) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_int(-1);
+ }
+ (void)usrinfo(SETUINFO, info, len);
+ free(info);
+
+#ifdef HAVE_SETAUTHDB
+ /* set authentication registry */
+ if (aix_setauthdb(user, NULL) != 0)
+ debug_return_int(-1);
+#endif
+
+ /* set resource limits */
+ if (aix_setlimits(user) != 0)
+ debug_return_int(-1);
+
+ debug_return_int(0);
+}
+#endif /* HAVE_GETUSERATTR */
diff --git a/lib/util/arc4random.c b/lib/util/arc4random.c
new file mode 100644
index 0000000..8e19555
--- /dev/null
+++ b/lib/util/arc4random.c
@@ -0,0 +1,205 @@
+/* $OpenBSD: arc4random.c,v 1.54 2015/09/13 08:31:47 guenther Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1996, David Mazieres <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ * Copyright (c) 2013, Markus Friedl <markus@openbsd.org>
+ * Copyright (c) 2014, Theo de Raadt <deraadt@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+/*
+ * ChaCha based random number generator for OpenBSD.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_ARC4RANDOM
+
+#ifdef HAVE_SYS_RANDOM_H
+# include <sys/random.h>
+#endif
+
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_rand.h"
+
+#define KEYSTREAM_ONLY
+#include "chacha_private.h"
+
+#define minimum(a, b) ((a) < (b) ? (a) : (b))
+
+#ifdef __GNUC__
+# define inline __inline
+#else /* !__GNUC__ */
+# define inline
+#endif /* !__GNUC__ */
+
+/* Sudo isn't multithreaded */
+#define _ARC4_LOCK()
+#define _ARC4_UNLOCK()
+
+#define KEYSZ 32
+#define IVSZ 8
+#define BLOCKSZ 64
+#define RSBUFSZ (16*BLOCKSZ)
+static int rs_initialized;
+static pid_t rs_stir_pid;
+static chacha_ctx rs; /* chacha context for random keystream */
+static u_char rs_buf[RSBUFSZ]; /* keystream blocks */
+static size_t rs_have; /* valid bytes at end of rs_buf */
+static size_t rs_count; /* bytes till reseed */
+
+static inline void _rs_rekey(unsigned char *dat, size_t datlen);
+
+static inline void
+_rs_init(unsigned char *buf, size_t n)
+{
+ if (n < KEYSZ + IVSZ)
+ return;
+ chacha_keysetup(&rs, buf, KEYSZ * 8, 0);
+ chacha_ivsetup(&rs, buf + KEYSZ);
+}
+
+static void
+_rs_stir(void)
+{
+ unsigned char rnd[KEYSZ + IVSZ];
+
+ if (getentropy(rnd, sizeof rnd) == -1)
+ raise(SIGKILL);
+
+ if (!rs_initialized) {
+ rs_initialized = 1;
+ _rs_init(rnd, sizeof(rnd));
+ } else
+ _rs_rekey(rnd, sizeof(rnd));
+ explicit_bzero(rnd, sizeof(rnd)); /* discard source seed */
+
+ /* invalidate rs_buf */
+ rs_have = 0;
+ memset(rs_buf, 0, sizeof(rs_buf));
+
+ rs_count = 1600000;
+}
+
+static inline void
+_rs_stir_if_needed(size_t len)
+{
+ pid_t pid = getpid();
+
+ if (rs_count <= len || !rs_initialized || rs_stir_pid != pid) {
+ rs_stir_pid = pid;
+ _rs_stir();
+ } else
+ rs_count -= len;
+}
+
+static inline void
+_rs_rekey(unsigned char *dat, size_t datlen)
+{
+#ifndef KEYSTREAM_ONLY
+ memset(rs_buf, 0, sizeof(rs_buf));
+#endif
+ /* fill rs_buf with the keystream */
+ chacha_encrypt_bytes(&rs, rs_buf, rs_buf, sizeof(rs_buf));
+ /* mix in optional user provided data */
+ if (dat) {
+ size_t i, m;
+
+ m = minimum(datlen, KEYSZ + IVSZ);
+ for (i = 0; i < m; i++)
+ rs_buf[i] ^= dat[i];
+ }
+ /* immediately reinit for backtracking resistance */
+ _rs_init(rs_buf, KEYSZ + IVSZ);
+ memset(rs_buf, 0, KEYSZ + IVSZ); // -V512
+ rs_have = sizeof(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_have > 0) {
+ m = minimum(n, rs_have);
+ keystream = rs_buf + sizeof(rs_buf) - rs_have;
+ memcpy(buf, keystream, m);
+ memset(keystream, 0, m);
+ buf += m;
+ n -= m;
+ rs_have -= m;
+ }
+ if (rs_have == 0)
+ _rs_rekey(NULL, 0);
+ }
+}
+
+static inline void
+_rs_random_u32(uint32_t *val)
+{
+ unsigned char *keystream;
+
+ _rs_stir_if_needed(sizeof(*val));
+ if (rs_have < sizeof(*val))
+ _rs_rekey(NULL, 0);
+ keystream = rs_buf + sizeof(rs_buf) - rs_have;
+ memcpy(val, keystream, sizeof(*val));
+ memset(keystream, 0, sizeof(*val));
+ rs_have -= sizeof(*val);
+}
+
+uint32_t
+sudo_arc4random(void)
+{
+ uint32_t val;
+
+ _ARC4_LOCK();
+ _rs_random_u32(&val);
+ _ARC4_UNLOCK();
+ return val;
+}
+
+void
+sudo_arc4random_buf(void *buf, size_t n)
+{
+ _ARC4_LOCK();
+ _rs_random_buf(buf, n);
+ _ARC4_UNLOCK();
+}
+
+#endif /* HAVE_ARC4RANDOM */
diff --git a/lib/util/arc4random_buf.c b/lib/util/arc4random_buf.c
new file mode 100644
index 0000000..76f0fdc
--- /dev/null
+++ b/lib/util/arc4random_buf.c
@@ -0,0 +1,68 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_ARC4RANDOM_BUF
+
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_rand.h"
+
+/*
+ * Call arc4random() repeatedly to fill buf with n bytes of random data.
+ */
+void
+sudo_arc4random_buf(void *buf, size_t n)
+{
+ char *cp = buf;
+
+ while (n != 0) {
+ size_t m = minimum(n, 4);
+ uint32_t val = arc4random();
+
+ switch (m) {
+ case 4:
+ *cp++ = (val >> 24) & 0xff;
+ FALLTHROUGH;
+ case 3:
+ *cp++ = (val >> 16) & 0xff;
+ FALLTHROUGH;
+ case 2:
+ *cp++ = (val >> 8) & 0xff;
+ FALLTHROUGH;
+ case 1:
+ *cp++ = val & 0xff;
+ break;
+ }
+ n -= m;
+ }
+}
+
+#endif /* HAVE_ARC4RANDOM_BUF */
diff --git a/lib/util/arc4random_uniform.c b/lib/util/arc4random_uniform.c
new file mode 100644
index 0000000..3299a14
--- /dev/null
+++ b/lib/util/arc4random_uniform.c
@@ -0,0 +1,76 @@
+/* $OpenBSD: arc4random_uniform.c,v 1.2 2015/09/13 08:31:47 guenther Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_ARC4RANDOM_UNIFORM
+
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_rand.h"
+
+/*
+ * Calculate a uniformly distributed random number less than upper_bound
+ * avoiding "modulo bias".
+ *
+ * Uniformity is achieved by generating new random numbers until the one
+ * returned is outside the range [0, 2**32 % upper_bound). This
+ * guarantees the selected random number will be inside
+ * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
+ * after reduction modulo upper_bound.
+ */
+uint32_t
+sudo_arc4random_uniform(uint32_t upper_bound)
+{
+ uint32_t r, min;
+
+ if (upper_bound < 2)
+ return 0;
+
+ /* 2**32 % x == (2**32 - x) % x */
+ min = -upper_bound % upper_bound;
+
+ /*
+ * This could theoretically loop forever but each retry has
+ * p > 0.5 (worst case, usually far better) of selecting a
+ * number inside the range we need, so it should rarely need
+ * to re-roll.
+ */
+ for (;;) {
+ r = arc4random();
+ if (r >= min)
+ break;
+ }
+
+ return r % upper_bound;
+}
+
+#endif /* HAVE_ARC4RANDOM_UNIFORM */
diff --git a/lib/util/cfmakeraw.c b/lib/util/cfmakeraw.c
new file mode 100644
index 0000000..956001f
--- /dev/null
+++ b/lib/util/cfmakeraw.c
@@ -0,0 +1,58 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <termios.h>
+
+#include "sudo_compat.h"
+
+/* Non-standard termios input flags */
+#ifndef IUCLC
+# define IUCLC 0
+#endif
+#ifndef IMAXBEL
+# define IMAXBEL 0
+#endif
+
+/* Non-standard termios local flags */
+#ifndef IEXTEN
+# define IEXTEN 0
+#endif
+
+/*
+ * Set termios to raw mode (BSD extension).
+ */
+void
+sudo_cfmakeraw(struct termios *term)
+{
+ /* Set terminal to raw mode */
+ CLR(term->c_iflag,
+ IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IMAXBEL|IUCLC);
+ CLR(term->c_oflag, OPOST);
+ CLR(term->c_lflag, ECHO|ECHONL|ICANON|ISIG|IEXTEN);
+ CLR(term->c_cflag, CSIZE|PARENB);
+ SET(term->c_cflag, CS8);
+ term->c_cc[VMIN] = 1;
+ term->c_cc[VTIME] = 0;
+}
diff --git a/lib/util/chacha_private.h b/lib/util/chacha_private.h
new file mode 100644
index 0000000..7c3680f
--- /dev/null
+++ b/lib/util/chacha_private.h
@@ -0,0 +1,222 @@
+/*
+chacha-merged.c version 20080118
+D. J. Bernstein
+Public domain.
+*/
+
+/* $OpenBSD: chacha_private.h,v 1.2 2013/10/04 07:02:27 djm Exp $ */
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+typedef struct
+{
+ u32 input[16]; /* could be compressed */
+} chacha_ctx;
+
+#define U8C(v) (v##U)
+#define U32C(v) (v##U)
+
+#define U8V(v) ((u8)(v) & U8C(0xFF))
+#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF))
+
+#define ROTL32(v, n) \
+ (U32V((v) << (n)) | ((v) >> (32 - (n))))
+
+#define U8TO32_LITTLE(p) \
+ (((u32)((p)[0]) ) | \
+ ((u32)((p)[1]) << 8) | \
+ ((u32)((p)[2]) << 16) | \
+ ((u32)((p)[3]) << 24))
+
+#define U32TO8_LITTLE(p, v) \
+ do { \
+ (p)[0] = U8V((v) ); \
+ (p)[1] = U8V((v) >> 8); \
+ (p)[2] = U8V((v) >> 16); \
+ (p)[3] = U8V((v) >> 24); \
+ } while (0)
+
+#define ROTATE(v,c) (ROTL32(v,c))
+#define XOR(v,w) ((v) ^ (w))
+#define PLUS(v,w) (U32V((v) + (w)))
+#define PLUSONE(v) (PLUS((v),1))
+
+#define QUARTERROUND(a,b,c,d) \
+ a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \
+ c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \
+ a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \
+ c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
+
+static const char sigma[16] = "expand 32-byte k";
+static const char tau[16] = "expand 16-byte k";
+
+static void
+chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits,u32 ivbits)
+{
+ const char *constants;
+
+ x->input[4] = U8TO32_LITTLE(k + 0);
+ x->input[5] = U8TO32_LITTLE(k + 4);
+ x->input[6] = U8TO32_LITTLE(k + 8);
+ x->input[7] = U8TO32_LITTLE(k + 12);
+ if (kbits == 256) { /* recommended */
+ k += 16;
+ constants = sigma;
+ } else { /* kbits == 128 */
+ constants = tau;
+ }
+ x->input[8] = U8TO32_LITTLE(k + 0);
+ x->input[9] = U8TO32_LITTLE(k + 4);
+ x->input[10] = U8TO32_LITTLE(k + 8);
+ x->input[11] = U8TO32_LITTLE(k + 12);
+ x->input[0] = U8TO32_LITTLE(constants + 0);
+ x->input[1] = U8TO32_LITTLE(constants + 4);
+ x->input[2] = U8TO32_LITTLE(constants + 8);
+ x->input[3] = U8TO32_LITTLE(constants + 12);
+}
+
+static void
+chacha_ivsetup(chacha_ctx *x,const u8 *iv)
+{
+ x->input[12] = 0;
+ x->input[13] = 0;
+ x->input[14] = U8TO32_LITTLE(iv + 0);
+ x->input[15] = U8TO32_LITTLE(iv + 4);
+}
+
+static void
+chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes)
+{
+ u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
+ u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+ u8 *ctarget = NULL;
+ u8 tmp[64];
+ u_int i;
+
+ if (!bytes) return;
+
+ j0 = x->input[0];
+ j1 = x->input[1];
+ j2 = x->input[2];
+ j3 = x->input[3];
+ j4 = x->input[4];
+ j5 = x->input[5];
+ j6 = x->input[6];
+ j7 = x->input[7];
+ j8 = x->input[8];
+ j9 = x->input[9];
+ j10 = x->input[10];
+ j11 = x->input[11];
+ j12 = x->input[12];
+ j13 = x->input[13];
+ j14 = x->input[14];
+ j15 = x->input[15];
+
+ for (;;) {
+ if (bytes < 64) {
+ for (i = 0;i < bytes;++i) tmp[i] = m[i];
+ m = tmp;
+ ctarget = c;
+ c = tmp;
+ }
+ x0 = j0;
+ x1 = j1;
+ x2 = j2;
+ x3 = j3;
+ x4 = j4;
+ x5 = j5;
+ x6 = j6;
+ x7 = j7;
+ x8 = j8;
+ x9 = j9;
+ x10 = j10;
+ x11 = j11;
+ x12 = j12;
+ x13 = j13;
+ x14 = j14;
+ x15 = j15;
+ for (i = 20;i > 0;i -= 2) {
+ QUARTERROUND( x0, x4, x8,x12)
+ QUARTERROUND( x1, x5, x9,x13)
+ QUARTERROUND( x2, x6,x10,x14)
+ QUARTERROUND( x3, x7,x11,x15)
+ QUARTERROUND( x0, x5,x10,x15)
+ QUARTERROUND( x1, x6,x11,x12)
+ QUARTERROUND( x2, x7, x8,x13)
+ QUARTERROUND( x3, x4, x9,x14)
+ }
+ x0 = PLUS(x0,j0);
+ x1 = PLUS(x1,j1);
+ x2 = PLUS(x2,j2);
+ x3 = PLUS(x3,j3);
+ x4 = PLUS(x4,j4);
+ x5 = PLUS(x5,j5);
+ x6 = PLUS(x6,j6);
+ x7 = PLUS(x7,j7);
+ x8 = PLUS(x8,j8);
+ x9 = PLUS(x9,j9);
+ x10 = PLUS(x10,j10);
+ x11 = PLUS(x11,j11);
+ x12 = PLUS(x12,j12);
+ x13 = PLUS(x13,j13);
+ x14 = PLUS(x14,j14);
+ x15 = PLUS(x15,j15);
+
+#ifndef KEYSTREAM_ONLY
+ x0 = XOR(x0,U8TO32_LITTLE(m + 0));
+ x1 = XOR(x1,U8TO32_LITTLE(m + 4));
+ x2 = XOR(x2,U8TO32_LITTLE(m + 8));
+ x3 = XOR(x3,U8TO32_LITTLE(m + 12));
+ x4 = XOR(x4,U8TO32_LITTLE(m + 16));
+ x5 = XOR(x5,U8TO32_LITTLE(m + 20));
+ x6 = XOR(x6,U8TO32_LITTLE(m + 24));
+ x7 = XOR(x7,U8TO32_LITTLE(m + 28));
+ x8 = XOR(x8,U8TO32_LITTLE(m + 32));
+ x9 = XOR(x9,U8TO32_LITTLE(m + 36));
+ x10 = XOR(x10,U8TO32_LITTLE(m + 40));
+ x11 = XOR(x11,U8TO32_LITTLE(m + 44));
+ x12 = XOR(x12,U8TO32_LITTLE(m + 48));
+ x13 = XOR(x13,U8TO32_LITTLE(m + 52));
+ x14 = XOR(x14,U8TO32_LITTLE(m + 56));
+ x15 = XOR(x15,U8TO32_LITTLE(m + 60));
+#endif
+
+ j12 = PLUSONE(j12);
+ if (!j12) {
+ j13 = PLUSONE(j13);
+ /* stopping at 2^70 bytes per nonce is user's responsibility */
+ }
+
+ U32TO8_LITTLE(c + 0,x0);
+ U32TO8_LITTLE(c + 4,x1);
+ U32TO8_LITTLE(c + 8,x2);
+ U32TO8_LITTLE(c + 12,x3);
+ U32TO8_LITTLE(c + 16,x4);
+ U32TO8_LITTLE(c + 20,x5);
+ U32TO8_LITTLE(c + 24,x6);
+ U32TO8_LITTLE(c + 28,x7);
+ U32TO8_LITTLE(c + 32,x8);
+ U32TO8_LITTLE(c + 36,x9);
+ U32TO8_LITTLE(c + 40,x10);
+ U32TO8_LITTLE(c + 44,x11);
+ U32TO8_LITTLE(c + 48,x12);
+ U32TO8_LITTLE(c + 52,x13);
+ U32TO8_LITTLE(c + 56,x14);
+ U32TO8_LITTLE(c + 60,x15);
+
+ if (bytes <= 64) {
+ if (bytes < 64) {
+ for (i = 0;i < bytes;++i) ctarget[i] = c[i];
+ }
+ x->input[12] = j12;
+ x->input[13] = j13;
+ return;
+ }
+ bytes -= 64;
+ c += 64;
+#ifndef KEYSTREAM_ONLY
+ m += 64;
+#endif
+ }
+}
diff --git a/lib/util/closefrom.c b/lib/util/closefrom.c
new file mode 100644
index 0000000..1b212aa
--- /dev/null
+++ b/lib/util/closefrom.c
@@ -0,0 +1,143 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+#ifdef HAVE_PSTAT_GETPROC
+# include <sys/pstat.h>
+#else
+# include <dirent.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "pathnames.h"
+
+#ifndef OPEN_MAX
+# define OPEN_MAX 256
+#endif
+
+/*
+ * Close all file descriptors greater than or equal to lowfd.
+ * This is the expensive (fallback) method.
+ */
+static void
+closefrom_fallback(int lowfd)
+{
+ long fd, maxfd;
+
+ /*
+ * Fall back on sysconf(_SC_OPEN_MAX). This is equivalent to
+ * checking the RLIMIT_NOFILE soft limit. It is possible for
+ * there to be open file descriptors past this limit but there's
+ * not much we can do about that since the hard limit may be
+ * RLIM_INFINITY (LLONG_MAX or ULLONG_MAX on modern systems).
+ */
+ maxfd = sysconf(_SC_OPEN_MAX);
+ if (maxfd < OPEN_MAX)
+ maxfd = OPEN_MAX;
+
+ /* Make sure we didn't get RLIM_INFINITY as the upper limit. */
+ if (maxfd > INT_MAX)
+ maxfd = INT_MAX;
+
+ for (fd = lowfd; fd < maxfd; fd++) {
+#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 pst;
+#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)
+ /*
+ * EOVERFLOW is not a fatal error for the fields we use.
+ * See the "EOVERFLOW Error" section of pstat_getvminfo(3).
+ */
+ if (pstat_getproc(&pst, sizeof(pst), 0, getpid()) != -1 ||
+ errno == EOVERFLOW) {
+ int fd;
+
+ for (fd = lowfd; fd <= pst.pst_highestfd; fd++)
+ (void) close(fd);
+ return;
+ }
+#elif defined(HAVE_DIRFD)
+ /* Use /proc/self/fd (or /dev/fd on macOS) if it exists. */
+# ifdef __APPLE__
+ path = _PATH_DEV "fd";
+# else
+ path = "/proc/self/fd";
+# endif
+ if ((dirp = opendir(path)) != NULL) {
+ struct dirent *dent;
+ while ((dent = readdir(dirp)) != NULL) {
+ const char *errstr;
+ int fd = sudo_strtonum(dent->d_name, lowfd, INT_MAX, &errstr);
+ if (errstr == NULL && fd != dirfd(dirp)) {
+# 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..b48bc48
--- /dev/null
+++ b/lib/util/digest.c
@@ -0,0 +1,165 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <unistd.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_digest.h"
+
+#ifdef HAVE_SHA224UPDATE
+# include <sha2.h>
+#else
+# include "compat/sha2.h"
+#endif
+
+static struct digest_function {
+ const unsigned int digest_len;
+ void (*init)(SHA2_CTX *);
+#ifdef SHA2_VOID_PTR
+ void (*update)(SHA2_CTX *, const void *, size_t);
+ void (*final)(void *, SHA2_CTX *);
+#else
+ void (*update)(SHA2_CTX *, const unsigned char *, size_t);
+ void (*final)(unsigned char *, SHA2_CTX *);
+#endif
+} digest_functions[] = {
+ {
+ SHA224_DIGEST_LENGTH,
+ SHA224Init,
+ SHA224Update,
+ SHA224Final
+ }, {
+ SHA256_DIGEST_LENGTH,
+ SHA256Init,
+ SHA256Update,
+ SHA256Final
+ }, {
+ SHA384_DIGEST_LENGTH,
+ SHA384Init,
+ SHA384Update,
+ SHA384Final
+ }, {
+ SHA512_DIGEST_LENGTH,
+ SHA512Init,
+ SHA512Update,
+ SHA512Final
+ }, {
+ 0
+ }
+};
+
+struct sudo_digest {
+ struct digest_function *func;
+ SHA2_CTX ctx;
+};
+
+struct sudo_digest *
+sudo_digest_alloc_v1(int digest_type)
+{
+ debug_decl(sudo_digest_alloc, SUDO_DEBUG_UTIL);
+ struct digest_function *func = NULL;
+ struct sudo_digest *dig;
+ int i;
+
+ for (i = 0; digest_functions[i].digest_len != 0; i++) {
+ if (digest_type == i) {
+ func = &digest_functions[i];
+ break;
+ }
+ }
+ if (func == NULL) {
+ errno = EINVAL;
+ debug_return_ptr(NULL);
+ }
+
+ if ((dig = malloc(sizeof(*dig))) == NULL)
+ debug_return_ptr(NULL);
+ func->init(&dig->ctx);
+ dig->func = func;
+
+ debug_return_ptr(dig);
+}
+
+void
+sudo_digest_free_v1(struct sudo_digest *dig)
+{
+ debug_decl(sudo_digest_free, SUDO_DEBUG_UTIL);
+
+ free(dig);
+
+ debug_return;
+}
+
+void
+sudo_digest_reset_v1(struct sudo_digest *dig)
+{
+ debug_decl(sudo_digest_reset, SUDO_DEBUG_UTIL);
+
+ dig->func->init(&dig->ctx);
+
+ debug_return;
+}
+
+int
+sudo_digest_getlen_v1(int digest_type)
+{
+ debug_decl(sudo_digest_getlen, SUDO_DEBUG_UTIL);
+ int i;
+
+ for (i = 0; digest_functions[i].digest_len != 0; i++) {
+ if (digest_type == i)
+ debug_return_int(digest_functions[i].digest_len);
+ }
+
+ debug_return_int(-1);
+}
+
+void
+sudo_digest_update_v1(struct sudo_digest *dig, const void *data, size_t len)
+{
+ debug_decl(sudo_digest_update, SUDO_DEBUG_UTIL);
+
+ dig->func->update(&dig->ctx, data, len);
+
+ debug_return;
+}
+
+void
+sudo_digest_final_v1(struct sudo_digest *dig, unsigned char *md)
+{
+ debug_decl(sudo_digest_final, SUDO_DEBUG_UTIL);
+
+ dig->func->final(md, &dig->ctx);
+
+ debug_return;
+}
diff --git a/lib/util/digest_gcrypt.c b/lib/util/digest_gcrypt.c
new file mode 100644
index 0000000..3ec7152
--- /dev/null
+++ b/lib/util/digest_gcrypt.c
@@ -0,0 +1,141 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2017-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.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;
+ case SUDO_DIGEST_SHA256:
+ return GCRY_MD_SHA256;
+ case SUDO_DIGEST_SHA384:
+ return GCRY_MD_SHA384;
+ case SUDO_DIGEST_SHA512:
+ return GCRY_MD_SHA512;
+ default:
+ return -1;
+ }
+}
+
+struct sudo_digest *
+sudo_digest_alloc_v1(int digest_type)
+{
+ debug_decl(sudo_digest_alloc, SUDO_DEBUG_UTIL);
+ 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..aed796a
--- /dev/null
+++ b/lib/util/digest_openssl.c
@@ -0,0 +1,154 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#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/dup3.c b/lib/util/dup3.c
new file mode 100644
index 0000000..68d6ebf
--- /dev/null
+++ b/lib/util/dup3.c
@@ -0,0 +1,74 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_DUP3
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+int
+sudo_dup3(int oldd, int newd, int flags)
+{
+ int oflags;
+
+ if (oldd == newd) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (dup2(oldd, newd) == -1)
+ return -1;
+
+ oflags = fcntl(newd, F_GETFL, 0);
+ if (oflags == -1)
+ goto bad;
+
+ if (ISSET(flags, O_NONBLOCK)) {
+ if (!ISSET(oflags, O_NONBLOCK)) {
+ SET(oflags, O_NONBLOCK);
+ if (fcntl(newd, F_SETFL, oflags) == -1)
+ goto bad;
+ }
+ } else {
+ if (ISSET(oflags, O_NONBLOCK)) {
+ CLR(oflags, O_NONBLOCK);
+ if (fcntl(newd, F_SETFL, oflags) == -1)
+ goto bad;
+ }
+ }
+ if (ISSET(flags, O_CLOEXEC)) {
+ if (fcntl(newd, F_SETFD, FD_CLOEXEC) == -1)
+ goto bad;
+ }
+ return 0;
+bad:
+ close(newd);
+ return -1;
+}
+
+#endif /* HAVE_DUP3 */
diff --git a/lib/util/event.c b/lib/util/event.c
new file mode 100644
index 0000000..4192770
--- /dev/null
+++ b/lib/util/event.c
@@ -0,0 +1,862 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_debug.h"
+#include "sudo_event.h"
+#include "sudo_util.h"
+
+static void sudo_ev_init(struct sudo_event *ev, int fd, short events,
+ sudo_ev_callback_t callback, void *closure);
+
+/* Default event base when none is specified. */
+static struct sudo_event_base *default_base;
+
+/* We need the event base to be available from the signal handler. */
+static struct sudo_event_base *signal_base;
+
+/*
+ * Add an event to the base's active queue and mark it active.
+ * This is extern so sudo_ev_scan_impl() can call it.
+ */
+void
+sudo_ev_activate(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ TAILQ_INSERT_TAIL(&base->active, ev, active_entries);
+ SET(ev->flags, SUDO_EVQ_ACTIVE);
+}
+
+/*
+ * Remove an event from the base's active queue and mark it inactive.
+ */
+static inline void
+sudo_ev_deactivate(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ CLR(ev->flags, SUDO_EVQ_ACTIVE);
+ TAILQ_REMOVE(&base->active, ev, active_entries);
+}
+
+/*
+ * Clear out the base's active queue and mark all events as inactive.
+ */
+static void
+sudo_ev_deactivate_all(struct sudo_event_base *base)
+{
+ struct sudo_event *ev;
+ debug_decl(sudo_ev_deactivate_all, SUDO_DEBUG_EVENT);
+
+ while ((ev = TAILQ_FIRST(&base->active)) != NULL)
+ sudo_ev_deactivate(base, ev);
+
+ debug_return;
+}
+
+/*
+ * Activate all signal events for which the corresponding signal_pending[]
+ * flag is set.
+ */
+static void
+sudo_ev_activate_sigevents(struct sudo_event_base *base)
+{
+ struct sudo_event *ev;
+ sigset_t set, oset;
+ int i;
+ debug_decl(sudo_ev_activate_sigevents, SUDO_DEBUG_EVENT);
+
+ /*
+ * We treat this as a critical section since the signal handler
+ * could modify the siginfo[] entry.
+ */
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oset);
+ base->signal_caught = 0;
+ for (i = 0; i < NSIG; i++) {
+ if (!base->signal_pending[i])
+ continue;
+ base->signal_pending[i] = 0;
+ TAILQ_FOREACH(ev, &base->signals[i], entries) {
+ if (ISSET(ev->events, SUDO_EV_SIGINFO)) {
+ struct sudo_ev_siginfo_container *sc = ev->closure;
+ if (base->siginfo[i]->si_signo == 0) {
+ /* No siginfo available. */
+ sc->siginfo = NULL;
+ } else {
+ sc->siginfo = (siginfo_t *)sc->si_buf;
+ memcpy(sc->siginfo, base->siginfo[i], sizeof(siginfo_t));
+ }
+ }
+ /* Make event active. */
+ ev->revents = ev->events & (SUDO_EV_SIGNAL|SUDO_EV_SIGINFO);
+ TAILQ_INSERT_TAIL(&base->active, ev, active_entries);
+ SET(ev->flags, SUDO_EVQ_ACTIVE);
+ }
+ }
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+
+ debug_return;
+}
+
+/*
+ * Internal callback for SUDO_EV_SIGNAL and SUDO_EV_SIGINFO.
+ */
+static void
+signal_pipe_cb(int fd, int what, void *v)
+{
+ struct sudo_event_base *base = v;
+ unsigned char ch;
+ ssize_t nread;
+ debug_decl(signal_pipe_cb, SUDO_DEBUG_EVENT);
+
+ /*
+ * Drain signal_pipe, the signal handler updated base->signals_pending.
+ * Actual processing of signal events is done when poll/select is
+ * interrupted by a signal.
+ */
+ while ((nread = read(fd, &ch, 1)) > 0) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: received signal %d", __func__, (int)ch);
+ }
+ if (nread == -1 && errno != EAGAIN) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "%s: error reading from signal pipe fd %d", __func__, fd);
+ }
+
+ /* Activate signal events. */
+ sudo_ev_activate_sigevents(base);
+
+ debug_return;
+}
+
+static int
+sudo_ev_base_init(struct sudo_event_base *base)
+{
+ int i;
+ debug_decl(sudo_ev_base_init, SUDO_DEBUG_EVENT);
+
+ TAILQ_INIT(&base->events);
+ TAILQ_INIT(&base->timeouts);
+ for (i = 0; i < NSIG; i++)
+ TAILQ_INIT(&base->signals[i]);
+ if (sudo_ev_base_alloc_impl(base) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: unable to allocate impl base", __func__);
+ goto bad;
+ }
+ if (pipe2(base->signal_pipe, O_NONBLOCK|O_CLOEXEC) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: unable to create signal pipe", __func__);
+ goto bad;
+ }
+ sudo_ev_init(&base->signal_event, base->signal_pipe[0],
+ SUDO_EV_READ|SUDO_EV_PERSIST, signal_pipe_cb, base);
+
+ debug_return_int(0);
+bad:
+ /* Note: signal_pipe[] not filled in. */
+ sudo_ev_base_free_impl(base);
+ debug_return_int(-1);
+}
+
+struct sudo_event_base *
+sudo_ev_base_alloc_v1(void)
+{
+ struct sudo_event_base *base;
+ debug_decl(sudo_ev_base_alloc, SUDO_DEBUG_EVENT);
+
+ base = calloc(1, sizeof(*base));
+ if (base == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate base", __func__);
+ debug_return_ptr(NULL);
+ }
+ if (sudo_ev_base_init(base) != 0) {
+ free(base);
+ debug_return_ptr(NULL);
+ }
+ debug_return_ptr(base);
+}
+
+void
+sudo_ev_base_free_v1(struct sudo_event_base *base)
+{
+ struct sudo_event *ev, *next;
+ int i;
+ debug_decl(sudo_ev_base_free, SUDO_DEBUG_EVENT);
+
+ if (base == NULL)
+ debug_return;
+
+ /* Reset the default base if necessary. */
+ if (default_base == base)
+ default_base = NULL;
+
+ /* Remove any existing events before freeing the base. */
+ TAILQ_FOREACH_SAFE(ev, &base->events, entries, next) {
+ sudo_ev_del(base, ev);
+ ev->base = NULL;
+ }
+ for (i = 0; i < NSIG; i++) {
+ TAILQ_FOREACH_SAFE(ev, &base->signals[i], entries, next) {
+ sudo_ev_del(base, ev);
+ ev->base = NULL;
+ }
+ free(base->siginfo[i]);
+ free(base->orig_handlers[i]);
+ }
+ sudo_ev_base_free_impl(base);
+ close(base->signal_pipe[0]);
+ close(base->signal_pipe[1]);
+ free(base);
+
+ debug_return;
+}
+
+void
+sudo_ev_base_setdef_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_base_setdef, SUDO_DEBUG_EVENT);
+
+ default_base = base;
+
+ debug_return;
+}
+
+/*
+ * Clear and fill in a struct sudo_event.
+ */
+static void
+sudo_ev_init(struct sudo_event *ev, int fd, short events,
+ sudo_ev_callback_t callback, void *closure)
+{
+ debug_decl(sudo_ev_init, SUDO_DEBUG_EVENT);
+
+ memset(ev, 0, sizeof(*ev));
+ ev->fd = fd;
+ ev->events = events & SUDO_EV_MASK;
+ ev->pfd_idx = -1;
+ ev->callback = callback;
+ ev->closure = closure;
+
+ debug_return;
+}
+
+/*
+ * Set a pre-allocated struct sudo_event.
+ * Allocates space for siginfo_t for SUDO_EV_SIGINFO as needed.
+ */
+int
+sudo_ev_set_v1(struct sudo_event *ev, int fd, short events,
+ sudo_ev_callback_t callback, void *closure)
+{
+ debug_decl(sudo_ev_set, SUDO_DEBUG_EVENT);
+
+ /* For SUDO_EV_SIGINFO we use a container to store closure + siginfo_t */
+ if (ISSET(events, SUDO_EV_SIGINFO)) {
+ struct sudo_ev_siginfo_container *container =
+ malloc(sizeof(*container) + sizeof(siginfo_t) - 1);
+ if (container == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate siginfo container", __func__);
+ debug_return_int(-1);
+ }
+ container->closure = closure;
+ closure = container;
+ }
+ sudo_ev_init(ev, fd, events, callback, closure);
+
+ debug_return_int(0);
+}
+
+struct sudo_event *
+sudo_ev_alloc_v1(int fd, short events, sudo_ev_callback_t callback, void *closure)
+{
+ struct sudo_event *ev;
+ debug_decl(sudo_ev_alloc, SUDO_DEBUG_EVENT);
+
+ ev = malloc(sizeof(*ev));
+ if (ev == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate event", __func__);
+ debug_return_ptr(NULL);
+ }
+ if (sudo_ev_set(ev, fd, events, callback, closure) == -1) {
+ free(ev);
+ debug_return_ptr(NULL);
+ }
+ debug_return_ptr(ev);
+}
+
+void
+sudo_ev_free_v1(struct sudo_event *ev)
+{
+ debug_decl(sudo_ev_free, SUDO_DEBUG_EVENT);
+
+ if (ev == NULL)
+ debug_return;
+
+ /* Make sure ev is not in use before freeing it. */
+ if (ISSET(ev->flags, SUDO_EVQ_INSERTED))
+ (void)sudo_ev_del(NULL, ev);
+ if (ISSET(ev->events, SUDO_EV_SIGINFO))
+ free(ev->closure);
+ free(ev);
+
+ debug_return;
+}
+
+static void
+sudo_ev_handler(int signo, siginfo_t *info, void *context)
+{
+ unsigned char ch = (unsigned char)signo;
+
+ if (signal_base != NULL) {
+ /*
+ * Update signals_pending[] and siginfo[].
+ * All signals must be blocked any time siginfo[] is accessed.
+ * If no siginfo available, zero out the struct in base.
+ */
+ if (info == NULL)
+ memset(signal_base->siginfo[signo], 0, sizeof(*info));
+ else
+ memcpy(signal_base->siginfo[signo], info, sizeof(*info));
+ signal_base->signal_pending[signo] = 1;
+ signal_base->signal_caught = 1;
+
+ /* Wake up the other end of the pipe. */
+ ignore_result(write(signal_base->signal_pipe[1], &ch, 1));
+ }
+}
+
+static int
+sudo_ev_add_signal(struct sudo_event_base *base, struct sudo_event *ev,
+ bool tohead)
+{
+ const int signo = ev->fd;
+ debug_decl(sudo_ev_add_signal, SUDO_DEBUG_EVENT);
+
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: adding event %p to base %p, signal %d, events %d",
+ __func__, ev, base, signo, ev->events);
+ if (signo >= NSIG) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: signo %d larger than max %d", __func__, signo, NSIG - 1);
+ debug_return_int(-1);
+ }
+ if ((ev->events & ~(SUDO_EV_SIGNAL|SUDO_EV_SIGINFO|SUDO_EV_PERSIST)) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: invalid event set 0x%x", __func__, ev->events);
+ debug_return_int(-1);
+ }
+
+ /*
+ * Allocate base->siginfo[signo] and base->orig_handlers[signo] as needed.
+ */
+ if (base->siginfo[signo] == NULL) {
+ base->siginfo[signo] = malloc(sizeof(*base->siginfo[signo]));
+ if (base->siginfo[signo] == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate siginfo for signo %d",
+ __func__, signo);
+ debug_return_int(-1);
+ }
+ }
+ if (base->orig_handlers[signo] == NULL) {
+ base->orig_handlers[signo] =
+ malloc(sizeof(*base->orig_handlers[signo]));
+ if (base->orig_handlers[signo] == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate orig_handlers for signo %d",
+ __func__, signo);
+ debug_return_int(-1);
+ }
+ }
+
+ /* Install signal handler as needed, saving the original value. */
+ if (TAILQ_EMPTY(&base->signals[signo])) {
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART|SA_SIGINFO;
+ sa.sa_sigaction = sudo_ev_handler;
+ if (sigaction(signo, &sa, base->orig_handlers[signo]) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to install handler for signo %d", __func__, signo);
+ debug_return_int(-1);
+ }
+ base->num_handlers++;
+ }
+
+ /*
+ * Insert signal event into the proper tail queue.
+ * Signal events are always persistent.
+ */
+ ev->base = base;
+ if (tohead) {
+ TAILQ_INSERT_HEAD(&base->signals[signo], ev, entries);
+ } else {
+ TAILQ_INSERT_TAIL(&base->signals[signo], ev, entries);
+ }
+ SET(ev->events, SUDO_EV_PERSIST);
+ SET(ev->flags, SUDO_EVQ_INSERTED);
+
+ /* Add the internal signal_pipe event on demand. */
+ if (!ISSET(base->signal_event.flags, SUDO_EVQ_INSERTED))
+ sudo_ev_add(base, &base->signal_event, NULL, true);
+
+ /* Update global signal base so handler to update signals_pending[] */
+ signal_base = base;
+
+ debug_return_int(0);
+}
+
+int
+sudo_ev_add_v1(struct sudo_event_base *base, struct sudo_event *ev,
+ const struct timeval *timo, bool tohead)
+{
+ struct timespec tsbuf, *ts = NULL;
+
+ if (timo != NULL) {
+ TIMEVAL_TO_TIMESPEC(timo, &tsbuf);
+ ts = &tsbuf;
+ }
+
+ return sudo_ev_add_v2(base, ev, ts, tohead);
+}
+
+int
+sudo_ev_add_v2(struct sudo_event_base *base, struct sudo_event *ev,
+ const struct timespec *timo, bool tohead)
+{
+ debug_decl(sudo_ev_add, SUDO_DEBUG_EVENT);
+
+ /* If no base specified, use existing or default base. */
+ if (base == NULL) {
+ if (ev->base != NULL) {
+ base = ev->base;
+ } else if (default_base != NULL) {
+ base = default_base;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: no base specified",
+ __func__);
+ debug_return_int(-1);
+ }
+ }
+
+ /* Only add new events to the events list. */
+ if (ISSET(ev->flags, SUDO_EVQ_INSERTED)) {
+ /* If event no longer has a timeout, remove from timeouts queue. */
+ if (timo == NULL && ISSET(ev->flags, SUDO_EVQ_TIMEOUTS)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: removing event %p from timeouts queue", __func__, ev);
+ CLR(ev->flags, SUDO_EVQ_TIMEOUTS);
+ TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries);
+ }
+ } else {
+ /* Special handling for signal events. */
+ if (ev->events & (SUDO_EV_SIGNAL|SUDO_EV_SIGINFO))
+ debug_return_int(sudo_ev_add_signal(base, ev, tohead));
+
+ /* Add event to the base. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: adding event %p to base %p, fd %d, events %d",
+ __func__, ev, base, ev->fd, ev->events);
+ if (ev->events & (SUDO_EV_READ|SUDO_EV_WRITE)) {
+ if (sudo_ev_add_impl(base, ev) != 0)
+ debug_return_int(-1);
+ }
+ ev->base = base;
+ if (tohead) {
+ TAILQ_INSERT_HEAD(&base->events, ev, entries);
+ } else {
+ TAILQ_INSERT_TAIL(&base->events, ev, entries);
+ }
+ SET(ev->flags, SUDO_EVQ_INSERTED);
+ }
+ /* Timeouts can be changed for existing events. */
+ if (timo != NULL) {
+ struct sudo_event *evtmp;
+ if (ISSET(ev->flags, SUDO_EVQ_TIMEOUTS)) {
+ /* Remove from timeouts list, then add back. */
+ TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries);
+ }
+ /* Convert to absolute time and insert in sorted order; O(n). */
+ sudo_gettime_mono(&ev->timeout);
+ sudo_timespecadd(&ev->timeout, timo, &ev->timeout);
+ TAILQ_FOREACH(evtmp, &base->timeouts, timeouts_entries) {
+ if (sudo_timespeccmp(&ev->timeout, &evtmp->timeout, <))
+ break;
+ }
+ if (evtmp != NULL) {
+ TAILQ_INSERT_BEFORE(evtmp, ev, timeouts_entries);
+ } else {
+ TAILQ_INSERT_TAIL(&base->timeouts, ev, timeouts_entries);
+ }
+ SET(ev->flags, SUDO_EVQ_TIMEOUTS);
+ }
+ debug_return_int(0);
+}
+
+/*
+ * Remove an event from the base, if specified, or the base embedded
+ * in the event if not. Note that there are multiple tail queues.
+ */
+int
+sudo_ev_del_v1(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ debug_decl(sudo_ev_del, SUDO_DEBUG_EVENT);
+
+ /* Make sure event is really in the queue. */
+ if (!ISSET(ev->flags, SUDO_EVQ_INSERTED)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: event %p not in queue",
+ __func__, ev);
+ debug_return_int(0);
+ }
+
+ /* Check for event base mismatch, if one is specified. */
+ if (base == NULL) {
+ if (ev->base == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: no base specified",
+ __func__);
+ debug_return_int(-1);
+ }
+ base = ev->base;
+ } else if (base != ev->base) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: mismatch base %p, ev->base %p",
+ __func__, base, ev->base);
+ debug_return_int(-1);
+ }
+
+ if (ev->events & (SUDO_EV_SIGNAL|SUDO_EV_SIGINFO)) {
+ const int signo = ev->fd;
+
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: removing event %p from base %p, signo %d, events %d",
+ __func__, ev, base, signo, ev->events);
+
+ /* Unlink from signal event list. */
+ TAILQ_REMOVE(&base->signals[signo], ev, entries);
+ if (TAILQ_EMPTY(&base->signals[signo])) {
+ if (sigaction(signo, base->orig_handlers[signo], NULL) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to restore handler for signo %d",
+ __func__, signo);
+ debug_return_int(-1);
+ }
+ base->num_handlers--;
+ }
+ if (base->num_handlers == 0) {
+ /* No registered signal events, remove internal event. */
+ sudo_ev_del(base, &base->signal_event);
+ }
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: removing event %p from base %p, fd %d, events %d",
+ __func__, ev, base, ev->fd, ev->events);
+
+ /* Call backend. */
+ if (ev->events & (SUDO_EV_READ|SUDO_EV_WRITE)) {
+ if (sudo_ev_del_impl(base, ev) != 0)
+ debug_return_int(-1);
+ }
+
+ /* Unlink from event list. */
+ TAILQ_REMOVE(&base->events, ev, entries);
+
+ /* Unlink from timeouts list. */
+ if (ISSET(ev->flags, SUDO_EVQ_TIMEOUTS))
+ TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries);
+ }
+
+ /* Unlink from active list. */
+ if (ISSET(ev->flags, SUDO_EVQ_ACTIVE))
+ TAILQ_REMOVE(&base->active, ev, active_entries);
+
+ /* Mark event unused. */
+ ev->flags = 0;
+ ev->pfd_idx = -1;
+
+ debug_return_int(0);
+}
+
+int
+sudo_ev_dispatch_v1(struct sudo_event_base *base)
+{
+ return sudo_ev_loop_v1(base, 0);
+}
+
+/*
+ * Run main event loop.
+ * Returns 0 on success, 1 if no events registered and -1 on error
+ */
+int
+sudo_ev_loop_v1(struct sudo_event_base *base, int flags)
+{
+ struct timespec now;
+ struct sudo_event *ev;
+ int nready, rc = 0;
+ debug_decl(sudo_ev_loop, SUDO_DEBUG_EVENT);
+
+ /*
+ * If sudo_ev_loopexit() was called when events were not running
+ * the next invocation of sudo_ev_loop() only runs once.
+ * All other base flags are ignored unless we are running events.
+ * Note that SUDO_EVLOOP_ONCE and SUDO_EVBASE_LOOPONCE are equivalent.
+ */
+ base->flags |= (flags & SUDO_EVLOOP_ONCE);
+ base->flags &= (SUDO_EVBASE_LOOPEXIT|SUDO_EVBASE_LOOPONCE);
+
+ for (;;) {
+rescan:
+ /* Make sure we have some events. */
+ if (TAILQ_EMPTY(&base->events)) {
+ rc = 1;
+ break;
+ }
+
+ /* Call backend to scan for I/O events. */
+ TAILQ_INIT(&base->active);
+ nready = sudo_ev_scan_impl(base, flags);
+ switch (nready) {
+ case -1:
+ if (errno == ENOMEM || errno == EAGAIN)
+ continue;
+ if (errno == EINTR) {
+ /* Interrupted by signal, check for sigevents. */
+ if (base->signal_caught) {
+ signal_pipe_cb(base->signal_pipe[0], SUDO_EV_READ, base);
+ break;
+ }
+ continue;
+ }
+ rc = -1;
+ goto done;
+ case 0:
+ /* Timed out, activate timeout events. */
+ sudo_gettime_mono(&now);
+ while ((ev = TAILQ_FIRST(&base->timeouts)) != NULL) {
+ if (sudo_timespeccmp(&ev->timeout, &now, >))
+ break;
+ /* Remove from timeouts list. */
+ CLR(ev->flags, SUDO_EVQ_TIMEOUTS);
+ TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries);
+ /* Make event active. */
+ ev->revents = SUDO_EV_TIMEOUT;
+ TAILQ_INSERT_TAIL(&base->active, ev, active_entries);
+ SET(ev->flags, SUDO_EVQ_ACTIVE);
+ }
+ if (ISSET(flags, SUDO_EVLOOP_NONBLOCK)) {
+ /* If nonblocking, return immediately if no active events. */
+ if (TAILQ_EMPTY(&base->active))
+ goto done;
+ }
+ break;
+ default:
+ /* I/O events active, sudo_ev_scan_impl() already added them. */
+ break;
+ }
+
+ /*
+ * Service each event in the active queue.
+ * We store the current event pointer in the base so that
+ * it can be cleared by sudo_ev_del(). This prevents a use
+ * after free if the callback frees its own event.
+ */
+ while ((ev = TAILQ_FIRST(&base->active)) != NULL) {
+ /* Pop first event off the active queue. */
+ sudo_ev_deactivate(base, ev);
+ /* Remove from base unless persistent. */
+ if (!ISSET(ev->events, SUDO_EV_PERSIST))
+ sudo_ev_del(base, ev);
+ ev->callback(ev->fd, ev->revents,
+ ev->closure == sudo_ev_self_cbarg() ? ev : ev->closure);
+ if (ISSET(base->flags, SUDO_EVBASE_LOOPBREAK)) {
+ /* Stop processing events immediately. */
+ SET(base->flags, SUDO_EVBASE_GOT_BREAK);
+ sudo_ev_deactivate_all(base);
+ goto done;
+ }
+ if (ISSET(base->flags, SUDO_EVBASE_LOOPCONT)) {
+ /* Rescan events and start polling again. */
+ CLR(base->flags, SUDO_EVBASE_LOOPCONT);
+ sudo_ev_deactivate_all(base);
+ goto rescan;
+ }
+ }
+ if (ISSET(base->flags, SUDO_EVBASE_LOOPONCE)) {
+ /* SUDO_EVBASE_LOOPEXIT is always set w/ SUDO_EVBASE_LOOPONCE */
+ if (ISSET(base->flags, SUDO_EVBASE_LOOPEXIT))
+ SET(base->flags, SUDO_EVBASE_GOT_EXIT);
+ sudo_ev_deactivate_all(base);
+ break;
+ }
+ }
+done:
+ base->flags &= SUDO_EVBASE_GOT_MASK;
+ debug_return_int(rc);
+}
+
+void
+sudo_ev_loopexit_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_loopexit, SUDO_DEBUG_EVENT);
+
+ if (base == NULL) {
+ if ((base = default_base) == NULL)
+ debug_return;
+ }
+
+ /* SUDO_EVBASE_LOOPBREAK trumps SUDO_EVBASE_LOOPEXIT */
+ if (!ISSET(base->flags, SUDO_EVBASE_LOOPBREAK)) {
+ /* SUDO_EVBASE_LOOPEXIT trumps SUDO_EVBASE_LOOPCONT */
+ CLR(base->flags, SUDO_EVBASE_LOOPCONT);
+ SET(base->flags, (SUDO_EVBASE_LOOPEXIT|SUDO_EVBASE_LOOPONCE));
+ }
+ debug_return;
+}
+
+void
+sudo_ev_loopbreak_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_loopbreak, SUDO_DEBUG_EVENT);
+
+ if (base == NULL) {
+ if ((base = default_base) == NULL)
+ debug_return;
+ }
+
+ /* SUDO_EVBASE_LOOPBREAK trumps SUDO_EVBASE_LOOP{CONT,EXIT,ONCE}. */
+ CLR(base->flags, (SUDO_EVBASE_LOOPCONT|SUDO_EVBASE_LOOPEXIT|SUDO_EVBASE_LOOPONCE));
+ SET(base->flags, SUDO_EVBASE_LOOPBREAK);
+ debug_return;
+}
+
+void
+sudo_ev_loopcontinue_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_loopcontinue, SUDO_DEBUG_EVENT);
+
+ if (base == NULL) {
+ if ((base = default_base) == NULL)
+ debug_return;
+ }
+
+ /* SUDO_EVBASE_LOOP{BREAK,EXIT} trumps SUDO_EVBASE_LOOPCONT */
+ if (!ISSET(base->flags, SUDO_EVBASE_LOOPONCE|SUDO_EVBASE_LOOPBREAK)) {
+ SET(base->flags, SUDO_EVBASE_LOOPCONT);
+ }
+ debug_return;
+}
+
+bool
+sudo_ev_got_exit_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_got_exit, SUDO_DEBUG_EVENT);
+
+ if (base == NULL) {
+ if ((base = default_base) == NULL)
+ debug_return_bool(false);
+ }
+ debug_return_bool(ISSET(base->flags, SUDO_EVBASE_GOT_EXIT));
+}
+
+bool
+sudo_ev_got_break_v1(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_got_break, SUDO_DEBUG_EVENT);
+
+ if (base == NULL) {
+ if ((base = default_base) == NULL)
+ debug_return_bool(false);
+ }
+ debug_return_bool(ISSET(base->flags, SUDO_EVBASE_GOT_BREAK));
+}
+
+int
+sudo_ev_get_timeleft_v1(struct sudo_event *ev, struct timeval *tv)
+{
+ struct timespec ts;
+ int ret;
+
+ ret = sudo_ev_get_timeleft_v2(ev, &ts);
+ TIMESPEC_TO_TIMEVAL(tv, &ts);
+
+ return ret;
+}
+
+int
+sudo_ev_get_timeleft_v2(struct sudo_event *ev, struct timespec *ts)
+{
+ debug_decl(sudo_ev_get_timeleft, SUDO_DEBUG_EVENT);
+
+ sudo_timespecclear(ts);
+ if (sudo_ev_pending_v1(ev, SUDO_EV_TIMEOUT, ts) != SUDO_EV_TIMEOUT)
+ debug_return_int(-1);
+ debug_return_int(0);
+}
+
+int
+sudo_ev_pending_v1(struct sudo_event *ev, short events, struct timespec *ts)
+{
+ int ret;
+ debug_decl(sudo_ev_pending, SUDO_DEBUG_EVENT);
+
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: event %p, flags 0x%x, events 0x%x",
+ __func__, ev, ev->flags, ev->events);
+
+ if (!ISSET(ev->flags, SUDO_EVQ_INSERTED))
+ debug_return_int(0);
+
+ ret = ev->events & events;
+ CLR(ret, SUDO_EV_TIMEOUT);
+ if (ISSET(ev->flags, SUDO_EVQ_TIMEOUTS) && ISSET(events, SUDO_EV_TIMEOUT)) {
+ ret |= SUDO_EV_TIMEOUT;
+ if (ts != NULL) {
+ struct timespec now;
+
+ sudo_gettime_mono(&now);
+ sudo_timespecsub(&ev->timeout, &now, ts);
+ if (ts->tv_sec < 0)
+ sudo_timespecclear(ts);
+ }
+ }
+
+ debug_return_int(ret);
+}
diff --git a/lib/util/event_poll.c b/lib/util/event_poll.c
new file mode 100644
index 0000000..271719a
--- /dev/null
+++ b/lib/util/event_poll.c
@@ -0,0 +1,227 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/resource.h>
+
+#include <stdlib.h>
+#include <poll.h>
+#include <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+#include "sudo_debug.h"
+#include "sudo_event.h"
+
+int
+sudo_ev_base_alloc_impl(struct sudo_event_base *base)
+{
+ int i;
+ debug_decl(sudo_ev_base_alloc_impl, SUDO_DEBUG_EVENT);
+
+ base->pfd_high = -1;
+ base->pfd_max = 32;
+ base->pfds = reallocarray(NULL, base->pfd_max, sizeof(struct pollfd));
+ if (base->pfds == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate %d pollfds", __func__, base->pfd_max);
+ base->pfd_max = 0;
+ debug_return_int(-1);
+ }
+ for (i = 0; i < base->pfd_max; i++) {
+ base->pfds[i].fd = -1;
+ }
+
+ debug_return_int(0);
+}
+
+void
+sudo_ev_base_free_impl(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_base_free_impl, SUDO_DEBUG_EVENT);
+ free(base->pfds);
+ debug_return;
+}
+
+int
+sudo_ev_add_impl(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ static int nofile_max = -1;
+ struct pollfd *pfd;
+ debug_decl(sudo_ev_add_impl, SUDO_DEBUG_EVENT);
+
+ if (nofile_max == -1) {
+ struct rlimit rlim;
+ if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
+ nofile_max = rlim.rlim_cur;
+ }
+ }
+
+ /* If out of space in pfds array, realloc. */
+ if (base->pfd_free == base->pfd_max) {
+ struct pollfd *pfds;
+ int i, new_max;
+
+ /* Don't allow pfd_max to go over RLIM_NOFILE */
+ new_max = base->pfd_max * 2;
+ if (new_max > nofile_max)
+ new_max = nofile_max;
+ if (base->pfd_free == new_max) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: out of fds (max %d)", __func__, nofile_max);
+ debug_return_int(-1);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "%s: pfd_max %d -> %d", __func__, base->pfd_max, new_max);
+ pfds = reallocarray(base->pfds, new_max, sizeof(struct pollfd));
+ if (pfds == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to allocate %d pollfds", __func__, new_max);
+ debug_return_int(-1);
+ }
+ base->pfds = pfds;
+ base->pfd_max = new_max;
+ for (i = base->pfd_free; i < base->pfd_max; i++) {
+ base->pfds[i].fd = -1;
+ }
+ }
+
+ /* Fill in pfd entry. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "%s: choosing free slot %d", __func__, base->pfd_free);
+ ev->pfd_idx = base->pfd_free;
+ pfd = &base->pfds[ev->pfd_idx];
+ pfd->fd = ev->fd;
+ pfd->events = 0;
+ if (ISSET(ev->events, SUDO_EV_READ))
+ pfd->events |= POLLIN;
+ if (ISSET(ev->events, SUDO_EV_WRITE))
+ pfd->events |= POLLOUT;
+
+ /* Update pfd_high and pfd_free. */
+ if (ev->pfd_idx > base->pfd_high)
+ base->pfd_high = ev->pfd_idx;
+ for (;;) {
+ if (++base->pfd_free == base->pfd_max)
+ break;
+ if (base->pfds[base->pfd_free].fd == -1)
+ break;
+ }
+
+ debug_return_int(0);
+}
+
+int
+sudo_ev_del_impl(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ debug_decl(sudo_ev_del_impl, SUDO_DEBUG_EVENT);
+
+ /* Mark pfd entry unused, add to free list and adjust high slot. */
+ base->pfds[ev->pfd_idx].fd = -1;
+ if (ev->pfd_idx < base->pfd_free) {
+ base->pfd_free = ev->pfd_idx;
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "%s: new free slot %d", __func__, base->pfd_free);
+ }
+ while (base->pfd_high >= 0 && base->pfds[base->pfd_high].fd == -1)
+ base->pfd_high--;
+
+ debug_return_int(0);
+}
+
+#ifdef HAVE_PPOLL
+static int
+sudo_ev_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timo)
+{
+ return ppoll(fds, nfds, timo, NULL);
+}
+#else
+static int
+sudo_ev_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timo)
+{
+ const int timeout =
+ timo ? (timo->tv_sec * 1000) + (timo->tv_nsec / 1000000) : -1;
+
+ return poll(fds, nfds, timeout);
+}
+#endif /* HAVE_PPOLL */
+
+int
+sudo_ev_scan_impl(struct sudo_event_base *base, int flags)
+{
+ struct timespec now, ts, *timeout;
+ struct sudo_event *ev;
+ int nready;
+ debug_decl(sudo_ev_scan_impl, SUDO_DEBUG_EVENT);
+
+ if ((ev = TAILQ_FIRST(&base->timeouts)) != NULL) {
+ sudo_gettime_mono(&now);
+ sudo_timespecsub(&ev->timeout, &now, &ts);
+ if (ts.tv_sec < 0)
+ sudo_timespecclear(&ts);
+ timeout = &ts;
+ } else {
+ if (ISSET(flags, SUDO_EVLOOP_NONBLOCK)) {
+ sudo_timespecclear(&ts);
+ timeout = &ts;
+ } else {
+ timeout = NULL;
+ }
+ }
+
+ nready = sudo_ev_poll(base->pfds, base->pfd_high + 1, timeout);
+ switch (nready) {
+ case -1:
+ /* Error: EINTR (signal) or EINVAL (nfds > RLIMIT_NOFILE) */
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "sudo_ev_poll");
+ break;
+ case 0:
+ /* Front end will activate timeout events. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: timeout", __func__);
+ break;
+ default:
+ /* Activate each I/O event that fired. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d fds ready", __func__,
+ nready);
+ TAILQ_FOREACH(ev, &base->events, entries) {
+ if (ev->pfd_idx != -1 && base->pfds[ev->pfd_idx].revents) {
+ int what = 0;
+ if (base->pfds[ev->pfd_idx].revents & (POLLIN|POLLHUP|POLLNVAL|POLLERR))
+ what |= (ev->events & SUDO_EV_READ);
+ if (base->pfds[ev->pfd_idx].revents & (POLLOUT|POLLHUP|POLLNVAL|POLLERR))
+ what |= (ev->events & SUDO_EV_WRITE);
+ /* Make event active. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "%s: polled fd %d, events %d, activating %p",
+ __func__, ev->fd, what, ev);
+ ev->revents = what;
+ sudo_ev_activate(base, ev);
+ }
+ }
+ break;
+ }
+ debug_return_int(nready);
+}
diff --git a/lib/util/event_select.c b/lib/util/event_select.c
new file mode 100644
index 0000000..a2ee9d1
--- /dev/null
+++ b/lib/util/event_select.c
@@ -0,0 +1,250 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/param.h> /* for howmany() on Linux */
+#include <sys/time.h>
+#ifdef HAVE_SYS_SYSMACROS_H
+# include <sys/sysmacros.h> /* for howmany() on Solaris */
+#endif
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif /* HAVE_SYS_SELECT_H */
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+#include "sudo_debug.h"
+#include "sudo_event.h"
+
+int
+sudo_ev_base_alloc_impl(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_base_alloc_impl, SUDO_DEBUG_EVENT);
+
+ base->maxfd = NFDBITS - 1;
+ base->readfds_in = calloc(1, sizeof(fd_mask));
+ base->writefds_in = calloc(1, sizeof(fd_mask));
+ base->readfds_out = calloc(1, sizeof(fd_mask));
+ base->writefds_out = calloc(1, sizeof(fd_mask));
+
+ if (base->readfds_in == NULL || base->writefds_in == NULL ||
+ base->readfds_out == NULL || base->writefds_out == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to calloc(1, %zu)", __func__, sizeof(fd_mask));
+ sudo_ev_base_free_impl(base);
+ debug_return_int(-1);
+ }
+ debug_return_int(0);
+}
+
+void
+sudo_ev_base_free_impl(struct sudo_event_base *base)
+{
+ debug_decl(sudo_ev_base_free_impl, SUDO_DEBUG_EVENT);
+ free(base->readfds_in);
+ free(base->writefds_in);
+ free(base->readfds_out);
+ free(base->writefds_out);
+ debug_return;
+}
+
+int
+sudo_ev_add_impl(struct sudo_event_base *base, struct sudo_event *ev)
+{
+ debug_decl(sudo_ev_add_impl, SUDO_DEBUG_EVENT);
+
+ /* If out of space in fd sets, realloc. */
+ if (ev->fd > base->maxfd) {
+ const int o = (base->maxfd + 1) / NFDBITS;
+ const int n = howmany(ev->fd + 1, NFDBITS);
+ const size_t used_bytes = o * sizeof(fd_mask);
+ const size_t new_bytes = (n - o) * sizeof(fd_mask);
+ fd_set *rfds_in, *wfds_in, *rfds_out, *wfds_out;
+
+ rfds_in = reallocarray(base->readfds_in, n, sizeof(fd_mask));
+ wfds_in = reallocarray(base->writefds_in, n, sizeof(fd_mask));
+ rfds_out = reallocarray(base->readfds_out, n, sizeof(fd_mask));
+ wfds_out = reallocarray(base->writefds_out, n, sizeof(fd_mask));
+ if (rfds_in == NULL || wfds_in == NULL ||
+ rfds_out == NULL || wfds_out == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s: unable to reallocarray(%d, %zu)",
+ __func__, n, sizeof(fd_mask));
+ free(rfds_in);
+ free(wfds_in);
+ free(rfds_out);
+ free(wfds_out);
+ debug_return_int(-1);
+ }
+
+ /* Clear newly allocated space. */
+ memset((char *)rfds_in + used_bytes, 0, new_bytes);
+ memset((char *)wfds_in + used_bytes, 0, new_bytes);
+ memset((char *)rfds_out + used_bytes, 0, new_bytes);
+ memset((char *)wfds_out + used_bytes, 0, new_bytes);
+
+ /* Update base. */
+ base->readfds_in = rfds_in;
+ base->writefds_in = wfds_in;
+ base->readfds_out = rfds_out;
+ base->writefds_out = wfds_out;
+ base->maxfd = (n * NFDBITS) - 1;
+ }
+
+ /* Set events and adjust high fd as needed. */
+ if (ISSET(ev->events, SUDO_EV_READ)) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: added fd %d to readfs",
+ __func__, ev->fd);
+ FD_SET(ev->fd, 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. */
+ break;
+ case 0:
+ /* Front end will activate timeout events. */
+ break;
+ default:
+ /* Activate each I/O event that fired. */
+ TAILQ_FOREACH(ev, &base->events, entries) {
+ if (ev->fd >= 0) {
+ int what = 0;
+ if (FD_ISSET(ev->fd, 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/explicit_bzero.c b/lib/util/explicit_bzero.c
new file mode 100644
index 0000000..a7defbd
--- /dev/null
+++ b/lib/util/explicit_bzero.c
@@ -0,0 +1,77 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#define __STDC_WANT_LIB_EXT1__ 1 /* for memset_s() */
+
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_EXPLICIT_BZERO
+
+# if defined(HAVE_EXPLICIT_MEMSET)
+void
+sudo_explicit_bzero(void *s, size_t n)
+{
+ explicit_memset(s, 0, n);
+}
+# elif defined(HAVE_MEMSET_EXPLICIT)
+void
+sudo_explicit_bzero(void *s, size_t n)
+{
+ memset_explicit(s, 0, n);
+}
+# elif defined(HAVE_MEMSET_S)
+void
+sudo_explicit_bzero(void *s, size_t n)
+{
+ (void)memset_s(s, n, 0, n);
+}
+# elif defined(HAVE_BZERO)
+/* Jumping through a volatile function pointer should not be optimized away. */
+void (* volatile sudo_explicit_bzero_impl)(void *, size_t) =
+ (void (*)(void *, size_t))bzero;
+
+void
+sudo_explicit_bzero(void *s, size_t n)
+{
+ sudo_explicit_bzero_impl(s, n);
+}
+# else
+void
+sudo_explicit_bzero(void *v, size_t n)
+{
+ volatile unsigned char *s = v;
+
+ /* Updating through a volatile pointer should not be optimized away. */
+ while (n--)
+ *s++ = '\0';
+}
+# endif /* HAVE_BZERO */
+
+#endif /* HAVE_EXPLICIT_BZERO */
diff --git a/lib/util/fatal.c b/lib/util/fatal.c
new file mode 100644
index 0000000..a12879e
--- /dev/null
+++ b/lib/util/fatal.c
@@ -0,0 +1,339 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2004-2005, 2010-2015, 2017-2018
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <unistd.h>
+#ifndef HAVE_GETADDRINFO
+# include "compat/getaddrinfo.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_queue.h"
+#include "sudo_util.h"
+#include "sudo_plugin.h"
+
+struct sudo_fatal_callback {
+ SLIST_ENTRY(sudo_fatal_callback) entries;
+ void (*func)(void);
+};
+SLIST_HEAD(sudo_fatal_callback_list, sudo_fatal_callback);
+
+static struct sudo_fatal_callback_list callbacks = SLIST_HEAD_INITIALIZER(&callbacks);
+static sudo_conv_t sudo_warn_conversation;
+static sudo_warn_setlocale_t sudo_warn_setlocale;
+static sudo_warn_setlocale_t sudo_warn_setlocale_prev;
+
+static void warning(const char *errstr, const char *fmt, va_list ap);
+
+static void
+do_cleanup(void)
+{
+ struct sudo_fatal_callback *cb;
+
+ /* Run callbacks, removing them from the list as we go. */
+ while ((cb = SLIST_FIRST(&callbacks)) != NULL) {
+ SLIST_REMOVE_HEAD(&callbacks, entries);
+ cb->func();
+ free(cb);
+ }
+}
+
+void
+sudo_fatal_nodebug_v1(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ warning(strerror(errno), fmt, ap);
+ va_end(ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_fatalx_nodebug_v1(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ warning(NULL, fmt, ap);
+ va_end(ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_vfatal_nodebug_v1(const char *fmt, va_list ap)
+{
+ warning(strerror(errno), fmt, ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_vfatalx_nodebug_v1(const char *fmt, va_list ap)
+{
+ warning(NULL, fmt, ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_warn_nodebug_v1(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ warning(strerror(errno), fmt, ap);
+ va_end(ap);
+}
+
+void
+sudo_warnx_nodebug_v1(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ warning(NULL, fmt, ap);
+ va_end(ap);
+}
+
+void
+sudo_vwarn_nodebug_v1(const char *fmt, va_list ap)
+{
+ warning(strerror(errno), fmt, ap);
+}
+
+void
+sudo_vwarnx_nodebug_v1(const char *fmt, va_list ap)
+{
+ warning(NULL, fmt, ap);
+}
+
+void
+sudo_gai_fatal_nodebug_v1(int errnum, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ warning(gai_strerror(errnum), fmt, ap);
+ va_end(ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_gai_vfatal_nodebug_v1(int errnum, const char *fmt, va_list ap)
+{
+ warning(gai_strerror(errnum), fmt, ap);
+ do_cleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+sudo_gai_warn_nodebug_v1(int errnum, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ warning(gai_strerror(errnum), fmt, ap);
+ va_end(ap);
+}
+
+void
+sudo_gai_vwarn_nodebug_v1(int errnum, const char *fmt, va_list ap)
+{
+ warning(gai_strerror(errnum), fmt, ap);
+}
+
+static void
+warning(const char *errstr, const char *fmt, va_list ap)
+{
+ int cookie;
+
+ /* Set user locale if setter was specified. */
+ if (sudo_warn_setlocale != NULL)
+ sudo_warn_setlocale(false, &cookie);
+
+ if (sudo_warn_conversation != NULL) {
+ struct sudo_conv_message msgs[6];
+ char static_buf[1024], *buf = static_buf;
+ int nmsgs = 0;
+
+ /* Use conversation function. */
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = getprogname();
+ if (fmt != NULL) {
+ va_list ap2;
+ int buflen;
+
+ /* Use static buffer if possible, else dynamic. */
+ va_copy(ap2, ap);
+ buflen = vsnprintf(static_buf, sizeof(static_buf), fmt, ap2);
+ va_end(ap2);
+ if (buflen >= ssizeof(static_buf)) {
+ buf = malloc(++buflen);
+ if (buf != NULL)
+ (void)vsnprintf(buf, buflen, fmt, ap);
+ else
+ buf = static_buf;
+ }
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = ": ";
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = buf;
+ }
+ if (errstr != NULL) {
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = ": ";
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = errstr;
+ }
+ msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
+ msgs[nmsgs++].msg = "\n";
+ sudo_warn_conversation(nmsgs, msgs, NULL, NULL);
+ if (buf != static_buf)
+ free(buf);
+ } else {
+ /* Write to the standard error. */
+ fputs(getprogname(), stderr);
+ if (fmt != NULL) {
+ fputs(": ", stderr);
+ vfprintf(stderr, fmt, ap);
+ }
+ if (errstr != NULL) {
+ fputs(": ", stderr);
+ fputs(errstr, stderr);
+ }
+ if (isatty(fileno(stderr)))
+ putc('\r', 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 = NULL;
+
+ /* Search for callback and remove if found, dupes are not allowed. */
+ SLIST_FOREACH(cb, &callbacks, entries) {
+ if (cb->func == func) {
+ if (prev == NULL)
+ SLIST_REMOVE_HEAD(&callbacks, entries);
+ else
+ SLIST_REMOVE_AFTER(prev, entries);
+ free(cb);
+ return 0;
+ }
+ prev = cb;
+ }
+
+ return -1;
+}
+
+/*
+ * Set the conversation function to use for output insteaf of the
+ * standard error. If conv is NULL, switch back to standard error.
+ */
+void
+sudo_warn_set_conversation_v1(sudo_conv_t conv)
+{
+ sudo_warn_conversation = conv;
+}
+
+/*
+ * Set the locale function so the plugin can use a non-default
+ * locale for user warnings.
+ */
+void
+sudo_warn_set_locale_func_v1(sudo_warn_setlocale_t func)
+{
+ sudo_warn_setlocale_prev = sudo_warn_setlocale;
+ sudo_warn_setlocale = func;
+}
+
+#ifdef HAVE_LIBINTL_H
+char *
+sudo_warn_gettext_v1(const char *domainname, const char *msgid)
+{
+ int cookie;
+ char *msg;
+
+ /* Set user locale if setter was specified. */
+ if (sudo_warn_setlocale != NULL)
+ sudo_warn_setlocale(false, &cookie);
+
+ msg = dgettext(domainname, msgid);
+
+ /* Restore old locale as needed. */
+ if (sudo_warn_setlocale != NULL)
+ sudo_warn_setlocale(true, &cookie);
+
+ return msg;
+}
+#endif /* HAVE_LIBINTL_H */
diff --git a/lib/util/fchmodat.c b/lib/util/fchmodat.c
new file mode 100644
index 0000000..9800296
--- /dev/null
+++ b/lib/util/fchmodat.c
@@ -0,0 +1,69 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_FCHMODAT
+int
+sudo_fchmodat(int dfd, const char *path, mode_t mode, int flag)
+{
+ int odfd, ret = -1;
+
+ if (ISSET(flag, AT_SYMLINK_NOFOLLOW)) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (dfd == (int)AT_FDCWD)
+ return chmod(path, mode);
+
+ /* Save cwd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ goto done;
+
+ if (fchdir(dfd) == -1)
+ goto done;
+
+ ret = chmod(path, mode);
+
+ /* Restore cwd */
+ if (fchdir(odfd) == -1) {
+ /* Should not happen */
+ ret = -1;
+ }
+
+done:
+ if (odfd != -1)
+ close(odfd);
+
+ return ret;
+}
+#endif /* HAVE_FCHMODAT */
diff --git a/lib/util/fnmatch.c b/lib/util/fnmatch.c
new file mode 100644
index 0000000..f393bb4
--- /dev/null
+++ b/lib/util/fnmatch.c
@@ -0,0 +1,499 @@
+/* $OpenBSD: fnmatch.c,v 1.15 2011/02/10 21:31:59 stsp Exp $ */
+
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2011, VMware, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the VMware, Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008, 2016 Todd C. Miller <millert@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+/* Authored by William A. Rowe Jr. <wrowe; apache.org, vmware.com>, April 2011
+ *
+ * Derived from The Open Group Base Specifications Issue 7, IEEE Std 1003.1-2008
+ * as described in;
+ * http://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html
+ *
+ * Filename pattern matches defined in section 2.13, "Pattern Matching Notation"
+ * from chapter 2. "Shell Command Language"
+ * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13
+ * where; 1. A bracket expression starting with an unquoted <circumflex> '^'
+ * character CONTINUES to specify a non-matching list; 2. an explicit <period> '.'
+ * in a bracket expression matching list, e.g. "[.abc]" does NOT match a leading
+ * <period> in a filename; 3. a <left-square-bracket> '[' which does not introduce
+ * a valid bracket expression is treated as an ordinary character; 4. a differing
+ * number of consecutive slashes within pattern and string will NOT match;
+ * 5. a trailing '\' in FNM_ESCAPE mode is treated as an ordinary '\' character.
+ *
+ * Bracket expansion defined in section 9.3.5, "RE Bracket Expression",
+ * from chapter 9, "Regular Expressions"
+ * http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
+ * with no support for collating symbols, equivalence class expressions or
+ * character class expressions. A partial range expression with a leading
+ * hyphen following a valid range expression will match only the ordinary
+ * <hyphen> and the ending character (e.g. "[a-m-z]" will match characters
+ * 'a' through 'm', a <hyphen> '-', or a 'z').
+ *
+ * Supports BSD extensions FNM_LEADING_DIR to match pattern to the end of one
+ * path segment of string, and FNM_CASEFOLD to ignore alpha case.
+ *
+ * NOTE: Only POSIX/C single byte locales are correctly supported at this time.
+ * Notably, non-POSIX locales with FNM_CASEFOLD produce undefined results,
+ * particularly in ranges of mixed case (e.g. "[A-z]") or spanning alpha and
+ * nonalpha characters within a range.
+ *
+ * XXX comments below indicate porting required for multi-byte character sets
+ * and non-POSIX locale collation orders; requires mbr* APIs to track shift
+ * state of pattern and string (rewinding pattern and string repeatedly).
+ *
+ * Certain parts of the code assume 0x00-0x3F are unique with any MBCS (e.g.
+ * UTF-8, SHIFT-JIS, etc). Any implementation allowing '\' as an alternate
+ * path delimiter must be aware that 0x5C is NOT unique within SHIFT-JIS.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_FNMATCH
+
+#include <ctype.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "compat/charclass.h"
+#include "compat/fnmatch.h"
+
+#define RANGE_MATCH 1
+#define RANGE_NOMATCH 0
+#define RANGE_ERROR (-1)
+
+static int
+classmatch(const char *pattern, char test, int foldcase, const char **ep)
+{
+ const char * const mismatch = pattern;
+ const char *colon;
+ struct cclass *cc;
+ int result = RANGE_NOMATCH;
+ size_t len;
+
+ if (pattern[0] != '[' || pattern[1] != ':') {
+ *ep = mismatch;
+ return RANGE_ERROR;
+ }
+ pattern += 2;
+
+ if ((colon = strchr(pattern, ':')) == NULL || colon[1] != ']') {
+ *ep = mismatch;
+ return RANGE_ERROR;
+ }
+ *ep = colon + 2;
+ len = (size_t)(colon - pattern);
+
+ if (foldcase && strncmp(pattern, "upper:]", 7) == 0)
+ pattern = "lower:]";
+ for (cc = cclasses; cc->name != NULL; cc++) {
+ if (!strncmp(pattern, cc->name, len) && cc->name[len] == '\0') {
+ if (cc->isctype((unsigned char)test))
+ result = RANGE_MATCH;
+ break;
+ }
+ }
+ if (cc->name == NULL) {
+ /* invalid character class, treat as normal text */
+ *ep = mismatch;
+ result = RANGE_ERROR;
+ }
+ return result;
+}
+
+/* Most MBCS/collation/case issues handled here. Wildcard '*' is not handled.
+ * EOS '\0' and the FNM_PATHNAME '/' delimiters are not advanced over,
+ * however the "\/" sequence is advanced to '/'.
+ *
+ * Both pattern and string are **char to support pointer increment of arbitrary
+ * multibyte characters for the given locale, in a later iteration of this code
+ */
+static int fnmatch_ch(const char **pattern, const char **string, int flags)
+{
+ const char * const mismatch = *pattern;
+ const int nocase = !!(flags & FNM_CASEFOLD);
+ const int escape = !(flags & FNM_NOESCAPE);
+ const int slash = !!(flags & FNM_PATHNAME);
+ int result = FNM_NOMATCH;
+ const char *startch;
+ int negate;
+
+ if (**pattern == '[')
+ {
+ ++*pattern;
+
+ /* Handle negation, either leading ! or ^ operators (never both) */
+ negate = ((**pattern == '!') || (**pattern == '^'));
+ if (negate)
+ ++*pattern;
+
+ /* ']' is an ordinary character at the start of the range pattern */
+ if (**pattern == ']')
+ goto leadingclosebrace;
+
+ while (**pattern)
+ {
+ if (**pattern == ']') {
+ ++*pattern;
+ /* XXX: Fix for MBCS character width */
+ ++*string;
+ return (result ^ negate);
+ }
+
+ if (escape && (**pattern == '\\')) {
+ ++*pattern;
+
+ /* Patterns must be terminated with ']', not EOS */
+ if (!**pattern)
+ break;
+ }
+
+ /* Patterns must be terminated with ']' not '/' */
+ if (slash && (**pattern == '/'))
+ break;
+
+ /* Match character classes. */
+ switch (classmatch(*pattern, **string, nocase, pattern)) {
+ case RANGE_MATCH:
+ result = 0;
+ continue;
+ case RANGE_NOMATCH:
+ /* Valid character class but no match. */
+ continue;
+ default:
+ /* Not a valid character class. */
+ break;
+ }
+ if (!**pattern)
+ break;
+
+leadingclosebrace:
+ /* Look at only well-formed range patterns;
+ * "x-]" is not allowed unless escaped ("x-\]")
+ * XXX: Fix for locale/MBCS character width
+ */
+ if (((*pattern)[1] == '-') && ((*pattern)[2] != ']'))
+ {
+ startch = *pattern;
+ *pattern += (escape && ((*pattern)[2] == '\\')) ? 3 : 2;
+
+ /* NOT a properly balanced [expr] pattern, EOS terminated
+ * or ranges containing a slash in FNM_PATHNAME mode pattern
+ * fall out to to the rewind and test '[' literal code path
+ */
+ if (!**pattern || (slash && (**pattern == '/')))
+ break;
+
+ /* XXX: handle locale/MBCS comparison, advance by MBCS char width */
+ if ((**string >= *startch) && (**string <= **pattern))
+ result = 0;
+ else if (nocase && (isupper((unsigned char)**string) ||
+ isupper((unsigned char)*startch) ||
+ isupper((unsigned char)**pattern))
+ && (tolower((unsigned char)**string) >= tolower((unsigned char)*startch))
+ && (tolower((unsigned char)**string) <= tolower((unsigned char)**pattern)))
+ result = 0;
+
+ ++*pattern;
+ continue;
+ }
+
+ /* XXX: handle locale/MBCS comparison, advance by MBCS char width */
+ if ((**string == **pattern))
+ result = 0;
+ else if (nocase && (isupper((unsigned char)**string) ||
+ isupper((unsigned char)**pattern))
+ && (tolower((unsigned char)**string) == tolower((unsigned char)**pattern)))
+ result = 0;
+
+ ++*pattern;
+ }
+
+ /* NOT a properly balanced [expr] pattern; Rewind
+ * and reset result to test '[' literal
+ */
+ *pattern = mismatch;
+ result = FNM_NOMATCH;
+ }
+ else if (**pattern == '?') {
+ /* Optimize '?' match before unescaping **pattern */
+ if (!**string || (slash && (**string == '/')))
+ return FNM_NOMATCH;
+ result = 0;
+ goto fnmatch_ch_success;
+ }
+ else if (escape && (**pattern == '\\') && (*pattern)[1]) {
+ ++*pattern;
+ }
+
+ /* XXX: handle locale/MBCS comparison, advance by the MBCS char width */
+ if (**string == **pattern)
+ result = 0;
+ else if (nocase && (isupper((unsigned char)**string) || isupper((unsigned char)**pattern))
+ && (tolower((unsigned char)**string) == tolower((unsigned char)**pattern)))
+ result = 0;
+
+ /* Refuse to advance over trailing slash or nulls
+ */
+ if (!**string || !**pattern || (slash && ((**string == '/') || (**pattern == '/'))))
+ return result;
+
+fnmatch_ch_success:
+ ++*pattern;
+ ++*string;
+ return result;
+}
+
+int sudo_fnmatch(const char *pattern, const char *string, int flags)
+{
+ static const char dummystring[2] = {' ', 0};
+ const int escape = !(flags & FNM_NOESCAPE);
+ const int slash = !!(flags & FNM_PATHNAME);
+ const int leading_dir = !!(flags & FNM_LEADING_DIR);
+ const char *strendseg;
+ const char *dummyptr;
+ const char *matchptr;
+ int wild;
+ /* For '*' wild processing only; suppress 'used before initialization'
+ * warnings with dummy initialization values;
+ */
+ const char *strstartseg = NULL;
+ const char *mismatch = NULL;
+ int matchlen = 0;
+
+ if (*pattern == '*')
+ goto firstsegment;
+
+ while (*pattern && *string)
+ {
+ /* Pre-decode "\/" which has no special significance, and
+ * match balanced slashes, starting a new segment pattern
+ */
+ if (slash && escape && (*pattern == '\\') && (pattern[1] == '/'))
+ ++pattern;
+ if (slash && (*pattern == '/') && (*string == '/')) {
+ ++pattern;
+ ++string;
+ }
+
+firstsegment:
+ /* At the beginning of each segment, validate leading period behavior.
+ */
+ if ((flags & FNM_PERIOD) && (*string == '.'))
+ {
+ if (*pattern == '.')
+ ++pattern;
+ else if (escape && (*pattern == '\\') && (pattern[1] == '.'))
+ pattern += 2;
+ else
+ return FNM_NOMATCH;
+ ++string;
+ }
+
+ /* Determine the end of string segment
+ *
+ * Presumes '/' character is unique, not composite in any MBCS encoding
+ */
+ if (slash) {
+ strendseg = strchr(string, '/');
+ if (!strendseg)
+ strendseg = strchr(string, '\0');
+ }
+ else {
+ strendseg = strchr(string, '\0');
+ }
+
+ /* Allow pattern '*' to be consumed even with no remaining string to match
+ */
+ while (*pattern)
+ {
+ if ((string > strendseg)
+ || ((string == strendseg) && (*pattern != '*')))
+ break;
+
+ if (slash && ((*pattern == '/')
+ || (escape && (*pattern == '\\')
+ && (pattern[1] == '/'))))
+ break;
+
+ /* Reduce groups of '*' and '?' to n '?' matches
+ * followed by one '*' test for simplicity
+ */
+ for (wild = 0; ((*pattern == '*') || (*pattern == '?')); ++pattern)
+ {
+ if (*pattern == '*') {
+ wild = 1;
+ }
+ else if (string < strendseg) { /* && (*pattern == '?') */
+ /* XXX: Advance 1 char for MBCS locale */
+ ++string;
+ }
+ else { /* (string >= strendseg) && (*pattern == '?') */
+ return FNM_NOMATCH;
+ }
+ }
+
+ if (wild)
+ {
+ strstartseg = string;
+ mismatch = pattern;
+
+ /* Count fixed (non '*') char matches remaining in pattern
+ * excluding '/' (or "\/") and '*'
+ */
+ for (matchptr = pattern, matchlen = 0; 1; ++matchlen)
+ {
+ if ((*matchptr == '\0')
+ || (slash && ((*matchptr == '/')
+ || (escape && (*matchptr == '\\')
+ && (matchptr[1] == '/')))))
+ {
+ /* Compare precisely this many trailing string chars,
+ * the resulting match needs no wildcard loop
+ */
+ /* XXX: Adjust for MBCS */
+ if (string + matchlen > strendseg)
+ return FNM_NOMATCH;
+
+ string = strendseg - matchlen;
+ wild = 0;
+ break;
+ }
+
+ if (*matchptr == '*')
+ {
+ /* Ensure at least this many trailing string chars remain
+ * for the first comparison
+ */
+ /* XXX: Adjust for MBCS */
+ if (string + matchlen > strendseg)
+ return FNM_NOMATCH;
+
+ /* Begin first wild comparison at the current position */
+ break;
+ }
+
+ /* Skip forward in pattern by a single character match
+ * Use a dummy fnmatch_ch() test to count one "[range]" escape
+ */
+ /* XXX: Adjust for MBCS */
+ if (escape && (*matchptr == '\\') && matchptr[1]) {
+ matchptr += 2;
+ }
+ else if (*matchptr == '[') {
+ dummyptr = dummystring;
+ fnmatch_ch(&matchptr, &dummyptr, flags);
+ }
+ else {
+ ++matchptr;
+ }
+ }
+ }
+
+ /* Incrementally match string against the pattern
+ */
+ while (*pattern && (string < strendseg))
+ {
+ /* Success; begin a new wild pattern search
+ */
+ if (*pattern == '*')
+ break;
+
+ if (slash && ((*string == '/')
+ || (*pattern == '/')
+ || (escape && (*pattern == '\\')
+ && (pattern[1] == '/'))))
+ break;
+
+ /* Compare ch's (the pattern is advanced over "\/" to the '/',
+ * but slashes will mismatch, and are not consumed)
+ */
+ if (!fnmatch_ch(&pattern, &string, flags))
+ continue;
+
+ /* Failed to match, loop against next char offset of string segment
+ * until not enough string chars remain to match the fixed pattern
+ */
+ if (wild) {
+ /* XXX: Advance 1 char for MBCS locale */
+ string = ++strstartseg;
+ if (string + matchlen > strendseg)
+ return FNM_NOMATCH;
+
+ pattern = mismatch;
+ continue;
+ }
+ else
+ return FNM_NOMATCH;
+ }
+ }
+
+ if (*string && !((slash || leading_dir) && (*string == '/')))
+ return FNM_NOMATCH;
+
+ if (*pattern && !(slash && ((*pattern == '/')
+ || (escape && (*pattern == '\\')
+ && (pattern[1] == '/')))))
+ return FNM_NOMATCH;
+
+ if (leading_dir && !*pattern && *string == '/')
+ return 0;
+ }
+
+ /* Where both pattern and string are at EOS, declare success
+ */
+ if (!*string && !*pattern)
+ return 0;
+
+ /* pattern didn't match to the end of string */
+ return FNM_NOMATCH;
+}
+#endif /* HAVE_FNMATCH */
diff --git a/lib/util/freezero.c b/lib/util/freezero.c
new file mode 100644
index 0000000..e2d2e8a
--- /dev/null
+++ b/lib/util/freezero.c
@@ -0,0 +1,38 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_FREEZERO
+void
+sudo_freezero(void *p, size_t n)
+{
+ explicit_bzero(p, n);
+ free(p);
+}
+#endif /* HAVE_FREEZERO */
diff --git a/lib/util/fstatat.c b/lib/util/fstatat.c
new file mode 100644
index 0000000..0b342d1
--- /dev/null
+++ b/lib/util/fstatat.c
@@ -0,0 +1,70 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_FSTATAT
+int
+sudo_fstatat(int dfd, const char *path, struct stat *sb, int flag)
+{
+ int odfd, ret = -1;
+
+ if (dfd == (int)AT_FDCWD) {
+ if (ISSET(flag, AT_SYMLINK_NOFOLLOW))
+ return lstat(path, sb);
+ else
+ return stat(path, sb);
+ }
+
+ /* Save cwd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ goto done;
+
+ if (fchdir(dfd) == -1)
+ goto done;
+
+ if (ISSET(flag, AT_SYMLINK_NOFOLLOW))
+ ret = lstat(path, sb);
+ else
+ ret = stat(path, sb);
+
+ /* Restore cwd */
+ if (fchdir(odfd) == -1) {
+ /* Should not happen */
+ ret = -1;
+ }
+
+done:
+ if (odfd != -1)
+ close(odfd);
+
+ return ret;
+}
+#endif /* HAVE_FSTATAT */
diff --git a/lib/util/getaddrinfo.c b/lib/util/getaddrinfo.c
new file mode 100644
index 0000000..ad0ea5f
--- /dev/null
+++ b/lib/util/getaddrinfo.c
@@ -0,0 +1,406 @@
+/*
+ * Replacement for a missing getaddrinfo.
+ *
+ * This is an implementation of getaddrinfo for systems that don't have one so
+ * that networking code can use a consistent interface without #ifdef. It is
+ * a fairly minimal implementation, with the following limitations:
+ *
+ * - IPv4 support only. IPv6 is not supported.
+ * - AI_ADDRCONFIG is ignored.
+ * - Not thread-safe due to gethostbyname and getservbyname.
+ * - SOCK_DGRAM and SOCK_STREAM only.
+ * - Multiple possible socket types only generate one addrinfo struct.
+ * - Protocol hints aren't used correctly.
+ *
+ * The last four issues could probably be easily remedied, but haven't been
+ * needed to date. Adding IPv6 support isn't worth it; systems with IPv6
+ * support should already have getaddrinfo.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <rra@stanford.edu>
+ *
+ * The authors hereby relinquish any claim to any copyright that they may have
+ * in this work, whether granted under contract or by operation of law or
+ * international treaty, and hereby commit to the public, at large, that they
+ * shall not, at any time in the future, seek to enforce any copyright in this
+ * work against any person or entity, or prevent any person or entity from
+ * copying, publishing, distributing or creating derivative works of this
+ * work.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_GETADDRINFO
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#ifdef NEED_RESOLV_H
+# include <arpa/nameser.h>
+# include <resolv.h>
+#endif /* NEED_RESOLV_H */
+
+#include "sudo_compat.h"
+#include "compat/getaddrinfo.h"
+
+/* We need access to h_errno to map errors from gethostbyname. */
+#ifndef HAVE_DECL_H_ERRNO
+extern int h_errno;
+#endif
+
+/*
+ * The netdb constants, which aren't always defined (particularly if h_errno
+ * isn't declared). We also make sure that a few of the less-used ones are
+ * defined so that we can deal with them in case statements.
+ */
+#ifndef HOST_NOT_FOUND
+# define HOST_NOT_FOUND 1
+# define TRY_AGAIN 2
+# define NO_RECOVERY 3
+# define NO_DATA 4
+#endif
+#ifndef NETDB_INTERNAL
+# define NETDB_INTERNAL -1
+#endif
+
+/*
+ * If we're running the test suite, rename the functions to avoid conflicts
+ * with the system version. Note that we don't rename the structures and
+ * constants, but that should be okay (except possibly for gai_strerror).
+ */
+#ifdef TESTING
+# define gai_strerror test_gai_strerror
+# define freeaddrinfo test_freeaddrinfo
+# define getaddrinfo test_getaddrinfo
+const char *test_gai_strerror(int);
+void test_freeaddrinfo(struct addrinfo *);
+int test_getaddrinfo(const char *, const char *, const struct addrinfo *,
+ struct addrinfo **);
+#endif
+
+/*
+ * If the platform doesn't support AI_NUMERICSERV or AI_NUMERICHOST,
+ * pick some other values for them.
+ */
+#ifdef TESTING
+# if AI_NUMERICSERV == 0
+# undef AI_NUMERICSERV
+# define AI_NUMERICSERV 0x0080
+# endif
+# if AI_NUMERICHOST == 0
+# undef AI_NUMERICHOST
+# define AI_NUMERICHOST 0x0100
+# endif
+#endif
+
+/*
+ * Value representing all of the hint flags set. Linux uses flags up to
+ * 0x0400, so be sure not to break when testing on that platform.
+ */
+#ifdef TESTING
+# ifdef HAVE_GETADDRINFO
+# define AI_INTERNAL_ALL 0x04ff
+# else
+# define AI_INTERNAL_ALL 0x01ff
+# endif
+#else
+# define AI_INTERNAL_ALL 0x007f
+#endif
+
+/* Table of strings corresponding to the EAI_* error codes. */
+static const char * const gai_errors[] = {
+ "Host name lookup failure", /* 1 EAI_AGAIN */
+ "Invalid flag value", /* 2 EAI_BADFLAGS */
+ "Unknown server error", /* 3 EAI_FAIL */
+ "Unsupported address family", /* 4 EAI_FAMILY */
+ "Memory allocation failure", /* 5 EAI_MEMORY */
+ "Host unknown or not given", /* 6 EAI_NONAME */
+ "Service not supported for socket", /* 7 EAI_SERVICE */
+ "Unsupported socket type", /* 8 EAI_SOCKTYPE */
+ "System error", /* 9 EAI_SYSTEM */
+ "Supplied buffer too small", /* 10 EAI_OVERFLOW */
+};
+
+/* Macro to set the len attribute of sockaddr_in. */
+#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
+# define sin_set_length(s) ((s)->sin_len = sizeof(struct sockaddr_in))
+#else
+# define sin_set_length(s) /* empty */
+#endif
+
+/*
+ * Return a constant string for a given EAI_* error code or a string
+ * indicating an unknown error.
+ */
+const char *
+sudo_gai_strerror(int ecode)
+{
+ if (ecode < 1 || (size_t) ecode > nitems(gai_errors))
+ return "Unknown error";
+ else
+ return gai_errors[ecode - 1];
+}
+
+
+/*
+ * Free a linked list of addrinfo structs.
+ */
+void
+sudo_freeaddrinfo(struct addrinfo *ai)
+{
+ struct addrinfo *next;
+
+ while (ai != NULL) {
+ next = ai->ai_next;
+ if (ai->ai_addr != NULL)
+ free(ai->ai_addr);
+ if (ai->ai_canonname != NULL)
+ free(ai->ai_canonname);
+ free(ai);
+ ai = next;
+ }
+}
+
+
+/*
+ * Allocate a new addrinfo struct, setting some defaults given that this
+ * implementation is IPv4 only. Also allocates an attached sockaddr_in and
+ * zeroes it, per the requirement for getaddrinfo. Takes the socktype,
+ * canonical name (which is copied if not NULL), address, and port. Returns
+ * NULL on a memory allocation failure.
+ */
+static struct addrinfo *
+gai_addrinfo_new(int socktype, const char *canonical, struct in_addr addr,
+ unsigned short port)
+{
+ struct addrinfo *ai;
+
+ ai = malloc(sizeof(*ai));
+ if (ai == NULL)
+ return NULL;
+ ai->ai_addr = malloc(sizeof(struct sockaddr_in));
+ if (ai->ai_addr == NULL) {
+ free(ai);
+ return NULL;
+ }
+ ai->ai_next = NULL;
+ if (canonical == NULL)
+ ai->ai_canonname = NULL;
+ else {
+ ai->ai_canonname = strdup(canonical);
+ if (ai->ai_canonname == NULL) {
+ freeaddrinfo(ai);
+ return NULL;
+ }
+ }
+ memset(ai->ai_addr, 0, sizeof(struct sockaddr_in));
+ ai->ai_flags = 0;
+ ai->ai_family = AF_INET;
+ ai->ai_socktype = socktype;
+ ai->ai_protocol = (socktype == SOCK_DGRAM) ? IPPROTO_UDP : IPPROTO_TCP;
+ ai->ai_addrlen = sizeof(struct sockaddr_in);
+ ((struct sockaddr_in *) ai->ai_addr)->sin_family = AF_INET;
+ ((struct sockaddr_in *) ai->ai_addr)->sin_addr = addr;
+ ((struct sockaddr_in *) ai->ai_addr)->sin_port = htons(port);
+ sin_set_length((struct sockaddr_in *) ai->ai_addr);
+ return ai;
+}
+
+
+/*
+ * Look up a service. Takes the service name (which may be numeric), the hint
+ * flags, a pointer to the socket type (used to determine whether TCP or UDP
+ * services are of interest and, if 0, is filled in with the result of
+ * getservbyname if the service was not numeric), and a pointer to the
+ * addrinfo struct to fill in. Returns 0 on success or an EAI_* error on
+ * failure.
+ */
+static int
+gai_service(const char *servname, int flags, int *type, unsigned short *port)
+{
+ struct servent *servent;
+ const char *protocol;
+ const char *errstr;
+ unsigned short value;
+
+ value = sudo_strtonum(servname, 0, USHRT_MAX, &errstr);
+ if (errstr == NULL) {
+ *port = value;
+ } else if (errno == ERANGE) {
+ return EAI_SERVICE;
+ } else {
+ if (flags & AI_NUMERICSERV)
+ return EAI_NONAME;
+ if (*type != 0)
+ protocol = (*type == SOCK_DGRAM) ? "udp" : "tcp";
+ else
+ protocol = NULL;
+
+ /*
+ * We really technically should be generating an addrinfo struct for
+ * each possible protocol unless type is set, but this works well
+ * enough for what I need this for.
+ */
+ servent = getservbyname(servname, protocol);
+ if (servent == NULL)
+ return EAI_NONAME;
+ if (strcmp(servent->s_proto, "udp") == 0)
+ *type = SOCK_DGRAM;
+ else if (strcmp(servent->s_proto, "tcp") == 0)
+ *type = SOCK_STREAM;
+ else
+ return EAI_SERVICE;
+ *port = htons(servent->s_port);
+ }
+ return 0;
+}
+
+
+/*
+ * Look up a host and fill in a linked list of addrinfo structs with the
+ * results, one per IP address of the returned host. Takes the name or IP
+ * address of the host as a string, the lookup flags, the type of socket (to
+ * fill into the addrinfo structs), the port (likewise), and a pointer to
+ * where the head of the linked list should be put. Returns 0 on success or
+ * the appropriate EAI_* error.
+ */
+static int
+gai_lookup(const char *nodename, int flags, int socktype, unsigned short port,
+ struct addrinfo **res)
+{
+ struct addrinfo *ai, *first, *prev;
+ struct in_addr addr;
+ struct hostent *host;
+ const char *canonical;
+ int i;
+
+ if (inet_pton(AF_INET, nodename, &addr)) {
+ canonical = (flags & AI_CANONNAME) ? nodename : NULL;
+ ai = gai_addrinfo_new(socktype, canonical, addr, port);
+ if (ai == NULL)
+ return EAI_MEMORY;
+ *res = ai;
+ return 0;
+ } else {
+ if (flags & AI_NUMERICHOST)
+ return EAI_NONAME;
+ host = gethostbyname(nodename);
+ if (host == NULL)
+ switch (h_errno) {
+ case HOST_NOT_FOUND:
+ return EAI_NONAME;
+ case TRY_AGAIN:
+ case NO_DATA:
+ return EAI_AGAIN;
+ case NO_RECOVERY:
+ return EAI_FAIL;
+ case NETDB_INTERNAL:
+ default:
+ return EAI_SYSTEM;
+ }
+ if (host->h_addr_list[0] == NULL)
+ return EAI_FAIL;
+ canonical = (flags & AI_CANONNAME)
+ ? ((host->h_name != NULL) ? host->h_name : nodename)
+ : NULL;
+ first = NULL;
+ prev = NULL;
+ for (i = 0; host->h_addr_list[i] != NULL; i++) {
+ if (host->h_length != sizeof(addr)) {
+ freeaddrinfo(first);
+ return EAI_FAIL;
+ }
+ memcpy(&addr, host->h_addr_list[i], sizeof(addr));
+ ai = gai_addrinfo_new(socktype, canonical, addr, port);
+ if (ai == NULL) {
+ freeaddrinfo(first);
+ return EAI_MEMORY;
+ }
+ if (first == NULL) {
+ first = ai;
+ prev = ai;
+ } else {
+ prev->ai_next = ai;
+ prev = ai;
+ }
+ }
+ *res = first;
+ return 0;
+ }
+}
+
+
+/*
+ * The actual getaddrinfo implementation.
+ */
+int
+sudo_getaddrinfo(const char *nodename, const char *servname,
+ const struct addrinfo *hints, struct addrinfo **res)
+{
+ struct addrinfo *ai;
+ struct in_addr addr;
+ int flags, socktype, status;
+ unsigned short port;
+
+ /* Take the hints into account and check them for validity. */
+ if (hints != NULL) {
+ flags = hints->ai_flags;
+ socktype = hints->ai_socktype;
+ if ((flags & AI_INTERNAL_ALL) != flags)
+ return EAI_BADFLAGS;
+ if (hints->ai_family != AF_UNSPEC && hints->ai_family != AF_INET)
+ return EAI_FAMILY;
+ if (socktype != 0 && socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
+ return EAI_SOCKTYPE;
+
+ /* EAI_SOCKTYPE isn't quite right, but there isn't anything better. */
+ if (hints->ai_protocol != 0) {
+ int protocol = hints->ai_protocol;
+ if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP)
+ return EAI_SOCKTYPE;
+ }
+ } else {
+ flags = 0;
+ socktype = 0;
+ }
+
+ /*
+ * See what we're doing. If nodename is null, either AI_PASSIVE is set or
+ * we're getting information for connecting to a service on the loopback
+ * address. Otherwise, we're getting information for connecting to a
+ * remote system.
+ */
+ if (servname == NULL)
+ port = 0;
+ else {
+ status = gai_service(servname, flags, &socktype, &port);
+ if (status != 0)
+ return status;
+ }
+ if (nodename != NULL)
+ return gai_lookup(nodename, flags, socktype, port, res);
+ else {
+ if (servname == NULL)
+ return EAI_NONAME;
+ if ((flags & AI_PASSIVE) == AI_PASSIVE)
+ addr.s_addr = INADDR_ANY;
+ else
+ addr.s_addr = htonl(0x7f000001UL);
+ ai = gai_addrinfo_new(socktype, NULL, addr, port);
+ if (ai == NULL)
+ return EAI_MEMORY;
+ *res = ai;
+ return 0;
+ }
+}
+#endif /* HAVE_GETADDRINFO */
diff --git a/lib/util/getcwd.c b/lib/util/getcwd.c
new file mode 100644
index 0000000..2ad3d37
--- /dev/null
+++ b/lib/util/getcwd.c
@@ -0,0 +1,244 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_GETCWD
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <limits.h>
+
+#include "sudo_compat.h"
+
+#define ISDOT(dp) \
+ (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
+ (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
+
+#if defined(HAVE_STRUCT_DIRENT_D_NAMLEN) && HAVE_STRUCT_DIRENT_D_NAMLEN
+# define NAMLEN(dirent) (dirent)->d_namlen
+#else
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#endif
+
+char *
+sudo_getcwd(char *pt, size_t size)
+{
+ struct dirent *dp;
+ DIR *dir = NULL;
+ dev_t dev;
+ ino_t ino;
+ int first;
+ char *bpt, *bup;
+ struct stat s;
+ dev_t root_dev;
+ ino_t root_ino;
+ size_t ptsize, upsize;
+ int save_errno;
+ char *ept, *eup, *up;
+
+ /*
+ * If no buffer specified by the user, allocate one as necessary.
+ * If a buffer is specified, the size has to be non-zero. The path
+ * is built from the end of the buffer backwards.
+ */
+ if (pt) {
+ ptsize = 0;
+ if (!size) {
+ errno = EINVAL;
+ return NULL;
+ }
+ ept = pt + size;
+ } else {
+ if ((pt = malloc(ptsize = 1024 - 4)) == NULL)
+ return NULL;
+ ept = pt + ptsize;
+ }
+ bpt = ept - 1;
+ *bpt = '\0';
+
+ /*
+ * Allocate bytes (1024 - malloc space) for the string of "../"'s.
+ * Should always be enough (it's 340 levels). If it's not, allocate
+ * as necessary. Special * case the first stat, it's ".", not "..".
+ */
+ if ((up = malloc(upsize = 1024 - 4)) == NULL)
+ goto err;
+ eup = up + PATH_MAX;
+ bup = up;
+ up[0] = '.';
+ up[1] = '\0';
+
+ /* Save root values, so know when to stop. */
+ if (stat("/", &s))
+ goto err;
+ root_dev = s.st_dev;
+ root_ino = s.st_ino;
+
+ errno = 0; /* XXX readdir has no error return. */
+
+ for (first = 1;; first = 0) {
+ /* Stat the current level. */
+ if (lstat(up, &s))
+ goto err;
+
+ /* Save current node values. */
+ ino = s.st_ino;
+ dev = s.st_dev;
+
+ /* Check for reaching root. */
+ if (root_dev == dev && root_ino == ino) {
+ *--bpt = '/';
+ /*
+ * It's unclear that it's a requirement to copy the
+ * path to the beginning of the buffer, but it's always
+ * been that way and stuff would probably break.
+ */
+ memcpy(pt, bpt, ept - bpt);
+ free(up);
+ return pt;
+ }
+
+ /*
+ * Build pointer to the parent directory, allocating memory
+ * as necessary. Max length is 3 for "../", the largest
+ * possible component name, plus a trailing NULL.
+ */
+ if (bup + 3 + 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;
+ memcpy(bup, dp->d_name, NAMLEN(dp) + 1);
+
+ /* Save the first error for later. */
+ if (lstat(up, &s)) {
+ if (!save_errno)
+ save_errno = errno;
+ errno = 0;
+ continue;
+ }
+ if (s.st_dev == dev && s.st_ino == ino)
+ break;
+ }
+
+ /*
+ * Check for length of the current name, preceding slash,
+ * leading slash.
+ */
+ if (bpt - pt <= NAMLEN(dp) + (first ? 1 : 2)) {
+ size_t len, off;
+ char *npt;
+
+ if (!ptsize) {
+ errno = ERANGE;
+ goto err;
+ }
+ off = bpt - pt;
+ len = ept - bpt;
+ if ((npt = reallocarray(pt, ptsize, 2)) == NULL)
+ goto err;
+ ptsize *= 2;
+ pt = npt;
+ bpt = pt + off;
+ ept = pt + ptsize;
+ memcpy(ept - len, bpt, len);
+ bpt = ept - len;
+ }
+ if (!first)
+ *--bpt = '/';
+ bpt -= NAMLEN(dp);
+ memcpy(bpt, dp->d_name, NAMLEN(dp));
+ (void)closedir(dir);
+
+ /* Truncate any file name. */
+ *bup = '\0';
+ }
+
+notfound:
+ /*
+ * If readdir set errno, use it, not any saved error; otherwise,
+ * didn't find the current directory in its parent directory, set
+ * errno to ENOENT.
+ */
+ if (!errno)
+ errno = save_errno ? save_errno : ENOENT;
+ /* FALLTHROUGH */
+err:
+ if (ptsize)
+ free(pt);
+ if (up)
+ free(up);
+ if (dir)
+ (void)closedir(dir);
+ return NULL;
+}
+#endif /* HAVE_GETCWD */
diff --git a/lib/util/getdelim.c b/lib/util/getdelim.c
new file mode 100644
index 0000000..8daf620
--- /dev/null
+++ b/lib/util/getdelim.c
@@ -0,0 +1,81 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_GETDELIM
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "sudo_compat.h"
+
+ssize_t
+sudo_getdelim(char **buf, size_t *bufsize, int delim, FILE *fp)
+{
+ char *cp, *ep;
+ int ch;
+
+ if (*buf == NULL || *bufsize == 0) {
+ char *tmp = realloc(*buf, LINE_MAX);
+ if (tmp == NULL)
+ return -1;
+ *buf = tmp;
+ *bufsize = LINE_MAX;
+ }
+ cp = *buf;
+ ep = cp + *bufsize;
+
+ do {
+ if (cp + 1 >= ep) {
+ char *tmp = reallocarray(*buf, *bufsize, 2);
+ if (tmp == NULL)
+ goto bad;
+ cp = tmp + (cp - *buf);
+ *buf = tmp;
+ *bufsize *= 2;
+ }
+ if ((ch = getc(fp)) == EOF) {
+ if (feof(fp))
+ break;
+ goto bad;
+ }
+ *cp++ = ch;
+ } while (ch != delim);
+
+ /* getdelim(3) should never return a length of 0. */
+ if (cp != *buf) {
+ *cp = '\0';
+ return (ssize_t)(cp - *buf);
+ }
+bad:
+ /* Error, push back what was read if possible. */
+ while (cp > *buf) {
+ if (ungetc(*cp--, fp) == EOF)
+ break;
+ }
+ return -1;
+}
+#endif /* HAVE_GETDELIM */
diff --git a/lib/util/getentropy.c b/lib/util/getentropy.c
new file mode 100644
index 0000000..3a649b6
--- /dev/null
+++ b/lib/util/getentropy.c
@@ -0,0 +1,622 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014 Theo de Raadt <deraadt@openbsd.org>
+ * Copyright (c) 2014 Bob Beck <beck@obtuse.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Emulation of getentropy(2) as documented at:
+ * http://man.openbsd.org/getentropy.2
+ */
+
+#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
+
+static void *
+mmap_anon(void *addr, size_t len, int prot, int flags, off_t offset)
+{
+#ifdef MAP_ANON
+ return mmap(addr, len, prot, flags | MAP_ANON, -1, offset);
+#else
+ int fd;
+
+ if ((fd = open("/dev/zero", O_RDWR)) == -1)
+ return MAP_FAILED;
+ addr = mmap(addr, len, prot, flags, fd, offset);
+ close(fd);
+ return addr;
+#endif
+}
+
+int
+sudo_getentropy(void *buf, size_t len)
+{
+ int ret = -1;
+
+ if (len > 256) {
+ errno = EIO;
+ return (-1);
+ }
+
+ ret = getentropy_getrandom(buf, len);
+ if (ret != -1)
+ return (ret);
+
+ ret = getentropy_sysctl(buf, len);
+ if (ret != -1)
+ return (ret);
+
+ /*
+ * Try to get entropy with /dev/urandom
+ */
+ ret = getentropy_urandom(buf, len, "/dev/urandom", 0);
+ if (ret != -1)
+ return (ret);
+
+ /*
+ * Entropy collection via /dev/urandom has failed.
+ *
+ * No other API exists for collecting entropy, and we have no
+ * failsafe way to get it that is not sensitive to resource exhaustion.
+ *
+ * We have very few options:
+ * - Even syslog_r is unsafe to call at this low level, so
+ * there is no way to alert the user or program.
+ * - Cannot call abort() because some systems have unsafe
+ * corefiles.
+ * - Could raise(SIGKILL) resulting in silent program termination.
+ * - Return EIO, to hint that arc4random's stir function
+ * should raise(SIGKILL)
+ * - Do the best under the circumstances....
+ *
+ * This code path exists to bring light to the issue that the OS
+ * does not provide a failsafe API for entropy collection.
+ *
+ * We hope this demonstrates that the OS should consider
+ * providing a new failsafe API which works in a chroot or
+ * when file descriptors are exhausted.
+ */
+#undef FAIL_INSTEAD_OF_TRYING_FALLBACK
+#ifdef FAIL_INSTEAD_OF_TRYING_FALLBACK
+ raise(SIGKILL);
+#endif
+ ret = getentropy_fallback(buf, len);
+ if (ret != -1)
+ return (ret);
+
+ errno = EIO;
+ return (ret);
+}
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+/*
+ * Basic validity checking; wish we could do better.
+ */
+static int
+gotdata(char *buf, size_t len)
+{
+ char any_set = 0;
+ size_t i;
+
+ for (i = 0; i < len; ++i)
+ any_set |= buf[i];
+ if (any_set == 0)
+ return (-1);
+ return (0);
+}
+
+static int
+getentropy_urandom(void *buf, size_t len, const char *path, int devfscheck)
+{
+ struct stat st;
+ size_t i;
+ int fd, flags;
+ int save_errno = errno;
+
+start:
+
+ /* We do not use O_NOFOLLOW since /dev/urandom is a link on Solaris. */
+ flags = O_RDONLY;
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+ fd = open(path, flags, 0);
+ if (fd == -1) {
+ if (errno == EINTR)
+ goto start;
+ goto nodevrandom;
+ }
+#ifndef O_CLOEXEC
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
+
+ /* Lightly verify that the device node looks OK */
+ if (fstat(fd, &st) == -1 || !S_ISCHR(st.st_mode)) {
+ close(fd);
+ goto nodevrandom;
+ }
+ for (i = 0; i < len; ) {
+ size_t wanted = len - i;
+ ssize_t ret = read(fd, (char *)buf + i, wanted);
+
+ if (ret == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ close(fd);
+ goto nodevrandom;
+ }
+ i += ret;
+ }
+ close(fd);
+ if (gotdata(buf, len) == 0) {
+ errno = save_errno;
+ return (0); /* satisfied */
+ }
+nodevrandom:
+ errno = EIO;
+ return (-1);
+}
+
+#if defined(HAVE_SYSCTL) && defined(KERN_ARND)
+static int
+getentropy_sysctl(void *buf, size_t len)
+{
+ int save_errno = errno;
+ int mib[2];
+ size_t i;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_ARND;
+
+ for (i = 0; i < len; ) {
+ size_t chunk = len - i;
+
+ if (sysctl(mib, 2, (char *)buf + i, &chunk, NULL, 0) == -1)
+ goto sysctlfailed;
+ i += chunk;
+ }
+ if (gotdata(buf, len) == 0) {
+ errno = save_errno;
+ return (0); /* satisfied */
+ }
+sysctlfailed:
+ errno = EIO;
+ return (-1);
+}
+#elif defined(SYS__sysctl) && defined(RANDOM_UUID)
+static int
+getentropy_sysctl(void *buf, size_t len)
+{
+ static int mib[3];
+ size_t i;
+ int save_errno = errno;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_RANDOM;
+ mib[2] = RANDOM_UUID;
+
+ for (i = 0; i < len; ) {
+ size_t chunk = min(len - i, 16);
+
+ /* SYS__sysctl because some systems already removed sysctl() */
+ struct __sysctl_args args = {
+ .name = mib,
+ .nlen = 3,
+ .oldval = (char *)buf + i,
+ .oldlenp = &chunk,
+ };
+ if (syscall(SYS__sysctl, &args) != 0)
+ goto sysctlfailed;
+ i += chunk;
+ }
+ if (gotdata(buf, len) == 0) {
+ errno = save_errno;
+ return (0); /* satisfied */
+ }
+sysctlfailed:
+ errno = EIO;
+ return (-1);
+}
+#else
+static int
+getentropy_sysctl(void *buf, size_t len)
+{
+ errno = ENOTSUP;
+ return (-1);
+}
+#endif
+
+#if defined(SYS_getrandom) && defined(GRND_NONBLOCK)
+static int
+getentropy_getrandom(void *buf, size_t len)
+{
+ int pre_errno = errno;
+ int ret;
+
+ /*
+ * Try descriptor-less getrandom(), in non-blocking mode.
+ *
+ * The design of Linux getrandom is broken. It has an
+ * uninitialized phase coupled with blocking behaviour, which
+ * is unacceptable from within a library at boot time without
+ * possible recovery. See http://bugs.python.org/issue26839#msg267745
+ */
+ do {
+ ret = syscall(SYS_getrandom, buf, len, GRND_NONBLOCK);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret < 0 || (size_t)ret != len)
+ return (-1);
+ errno = pre_errno;
+ return (0);
+}
+#else
+static int
+getentropy_getrandom(void *buf, size_t len)
+{
+ errno = ENOTSUP;
+ return (-1);
+}
+#endif
+
+#ifdef HAVE_CLOCK_GETTIME
+static const int cl[] = {
+ CLOCK_REALTIME,
+#ifdef CLOCK_MONOTONIC
+ CLOCK_MONOTONIC,
+#endif
+#ifdef CLOCK_MONOTONIC_RAW
+ CLOCK_MONOTONIC_RAW,
+#endif
+#ifdef CLOCK_TAI
+ CLOCK_TAI,
+#endif
+#ifdef CLOCK_VIRTUAL
+ CLOCK_VIRTUAL,
+#endif
+#ifdef CLOCK_UPTIME
+ CLOCK_UPTIME,
+#endif
+#ifdef CLOCK_PROCESS_CPUTIME_ID
+ CLOCK_PROCESS_CPUTIME_ID,
+#endif
+#ifdef CLOCK_THREAD_CPUTIME_ID
+ CLOCK_THREAD_CPUTIME_ID,
+#endif
+};
+#endif /* HAVE_CLOCK_GETTIME */
+
+#ifdef HAVE_DL_ITERATE_PHDR
+static int
+getentropy_phdr(struct dl_phdr_info *info, size_t size, void *data)
+{
+ struct sudo_digest *ctx = data;
+
+ sudo_digest_update(ctx, &info->dlpi_addr, sizeof (info->dlpi_addr));
+ return (0);
+}
+#endif
+
+static int
+getentropy_fallback(void *buf, size_t len)
+{
+ unsigned char *results = NULL;
+ int save_errno = errno, e, pgs = sysconf(_SC_PAGESIZE), faster = 0, repeat;
+ int ret = -1;
+ static int cnt;
+ struct timespec ts;
+ struct timeval tv;
+ struct rusage ru;
+ sigset_t set;
+ struct stat st;
+ struct sudo_digest *ctx;
+ static pid_t lastpid;
+ pid_t pid;
+ size_t i, ii, m, digest_len;
+ char *p;
+
+ if ((ctx = sudo_digest_alloc(SUDO_DIGEST_SHA512)) == NULL)
+ goto done;
+ digest_len = sudo_digest_getlen(SUDO_DIGEST_SHA512);
+ if (digest_len == (size_t)-1 || (results = malloc(digest_len)) == NULL)
+ goto done;
+
+ pid = getpid();
+ if (lastpid == pid) {
+ faster = 1;
+ repeat = 2;
+ } else {
+ faster = 0;
+ lastpid = pid;
+ repeat = REPEAT;
+ }
+ for (i = 0; i < len; ) {
+ int j;
+ for (j = 0; j < repeat; j++) {
+ HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+ if (e != -1) {
+ cnt += (int)tv.tv_sec;
+ cnt += (int)tv.tv_usec;
+ }
+#ifdef HAVE_DL_ITERATE_PHDR
+ dl_iterate_phdr(getentropy_phdr, ctx);
+#endif
+
+#ifdef HAVE_CLOCK_GETTIME
+ for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]); ii++)
+ HX(clock_gettime(cl[ii], &ts) == -1, ts);
+#endif /* HAVE_CLOCK_GETTIME */
+
+ HX((pid = getpid()) == -1, pid);
+ HX((pid = getsid(pid)) == -1, pid);
+ HX((pid = getppid()) == -1, pid);
+ HX((pid = getpgid(0)) == -1, pid);
+ HX((e = getpriority(0, 0)) == -1, e);
+
+ if (!faster) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 1;
+ (void) nanosleep(&ts, NULL);
+ }
+
+ HX(sigpending(&set) == -1, set);
+ HX(sigprocmask(SIG_BLOCK, NULL, &set) == -1, set);
+
+ HF(sudo_getentropy); /* an addr in this library */
+ HF(printf); /* an addr in libc */
+ p = (char *)&p;
+ HD(p); /* an addr on stack */
+ p = (char *)&errno;
+ HD(p); /* the addr of errno */
+
+ if (i == 0) {
+#ifdef HAVE_SYS_STATVFS_H
+ struct statvfs stvfs;
+#endif
+ struct termios tios;
+ off_t off;
+
+ /*
+ * Prime-sized mappings encourage fragmentation;
+ * thus exposing some address entropy.
+ */
+ struct mm {
+ size_t npg;
+ void *p;
+ } mm[] = {
+ { 17, MAP_FAILED }, { 3, MAP_FAILED },
+ { 11, MAP_FAILED }, { 2, MAP_FAILED },
+ { 5, MAP_FAILED }, { 3, MAP_FAILED },
+ { 7, MAP_FAILED }, { 1, MAP_FAILED },
+ { 57, MAP_FAILED }, { 3, MAP_FAILED },
+ { 131, MAP_FAILED }, { 1, MAP_FAILED },
+ };
+
+ for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+ HX(mm[m].p = mmap_anon(NULL,
+ mm[m].npg * pgs,
+ PROT_READ|PROT_WRITE,
+ MAP_PRIVATE,
+ (off_t)0), mm[m].p);
+ if (mm[m].p != MAP_FAILED) {
+ size_t mo;
+
+ /* Touch some memory... */
+ p = mm[m].p;
+ mo = cnt %
+ (mm[m].npg * pgs - 1);
+ p[mo] = 1;
+ cnt += (int)((long)(mm[m].p)
+ / pgs);
+ }
+
+#ifdef HAVE_CLOCK_GETTIME
+ /* Check cnts and times... */
+ for (ii = 0; ii < sizeof(cl)/sizeof(cl[0]);
+ ii++) {
+ HX((e = clock_gettime(cl[ii],
+ &ts)) == -1, ts);
+ if (e != -1)
+ cnt += (int)ts.tv_nsec;
+ }
+#endif /* HAVE_CLOCK_GETTIME */
+
+ HX((e = getrusage(RUSAGE_SELF,
+ &ru)) == -1, ru);
+ if (e != -1) {
+ cnt += (int)ru.ru_utime.tv_sec;
+ cnt += (int)ru.ru_utime.tv_usec;
+ }
+ }
+
+ for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) {
+ if (mm[m].p != MAP_FAILED)
+ munmap(mm[m].p, mm[m].npg * pgs);
+ mm[m].p = MAP_FAILED;
+ }
+
+ HX(stat(".", &st) == -1, st);
+ HX(stat("/", &st) == -1, st);
+
+#ifdef HAVE_SYS_STATVFS_H
+ HX(statvfs(".", &stvfs) == -1, stvfs);
+ HX(statvfs("/", &stvfs) == -1, stvfs);
+#endif
+ HX((e = fstat(0, &st)) == -1, st);
+ if (e == -1) {
+ if (S_ISREG(st.st_mode) ||
+ S_ISFIFO(st.st_mode) ||
+ S_ISSOCK(st.st_mode)) {
+#ifdef HAVE_SYS_STATVFS_H
+ HX(fstatvfs(0, &stvfs) == -1,
+ stvfs);
+#endif
+ HX((off = lseek(0, (off_t)0,
+ SEEK_CUR)) < 0, off);
+ }
+ if (S_ISCHR(st.st_mode)) {
+ HX(tcgetattr(0, &tios) == -1,
+ tios);
+#if 0
+ } else if (S_ISSOCK(st.st_mode)) {
+ struct sockaddr_storage ss;
+ socklen_t ssl;
+ memset(&ss, 0, sizeof ss);
+ ssl = sizeof(ss);
+ HX(getpeername(0,
+ (void *)&ss, &ssl) == -1,
+ ss);
+#endif
+ }
+ }
+
+ HX((e = getrusage(RUSAGE_CHILDREN,
+ &ru)) == -1, ru);
+ if (e != -1) {
+ cnt += (int)ru.ru_utime.tv_sec;
+ cnt += (int)ru.ru_utime.tv_usec;
+ }
+ } else {
+ /* Subsequent hashes absorb previous result */
+ HR(results, digest_len);
+ }
+
+ HX((e = gettimeofday(&tv, NULL)) == -1, tv);
+ if (e != -1) {
+ cnt += (int)tv.tv_sec;
+ cnt += (int)tv.tv_usec;
+ }
+
+ HD(cnt);
+ }
+
+#ifdef HAVE_GETAUXVAL
+#ifdef AT_RANDOM
+ /* Not as random as you think but we take what we are given */
+ p = (char *) getauxval(AT_RANDOM);
+ if (p)
+ HR(p, 16);
+#endif
+#ifdef AT_SYSINFO_EHDR
+ p = (char *) getauxval(AT_SYSINFO_EHDR);
+ if (p)
+ HR(p, pgs);
+#endif
+#ifdef AT_BASE
+ p = (char *) getauxval(AT_BASE);
+ if (p)
+ HD(p);
+#endif
+#endif /* HAVE_GETAUXVAL */
+
+ sudo_digest_final(ctx, results);
+ sudo_digest_reset(ctx);
+ memcpy((char *)buf + i, results, min(digest_len, len - i));
+ i += min(digest_len, len - i);
+ }
+ if (gotdata(buf, len) == 0) {
+ errno = save_errno;
+ ret = 0; /* satisfied */
+ } else {
+ errno = EIO;
+ }
+done:
+ sudo_digest_free(ctx);
+ if (results != NULL)
+ freezero(results, sizeof(results));
+ return (ret);
+}
+
+#endif /* HAVE_GETENTROPY */
diff --git a/lib/util/getgrouplist.c b/lib/util/getgrouplist.c
new file mode 100644
index 0000000..ae67bc0
--- /dev/null
+++ b/lib/util/getgrouplist.c
@@ -0,0 +1,513 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <grp.h>
+#include <limits.h>
+#include <unistd.h>
+#ifdef HAVE_NSS_SEARCH
+# include <errno.h>
+# include <limits.h>
+# include <nsswitch.h>
+# ifdef HAVE_NSS_DBDEFS_H
+# include <nss_dbdefs.h>
+# else
+# include "compat/nss_dbdefs.h"
+# endif
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_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') {
+ grpsize++; /* at least one supplementary group */
+ 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, &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, &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..894aefa
--- /dev/null
+++ b/lib/util/gethostname.c
@@ -0,0 +1,59 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+/*
+ * Return a malloc()ed copy of the system hostname, or NULL if
+ * malloc() or gethostname() fails.
+ */
+char *
+sudo_gethostname_v1(void)
+{
+ char *hname;
+ size_t host_name_max;
+
+#ifdef _SC_HOST_NAME_MAX
+ host_name_max = (size_t)sysconf(_SC_HOST_NAME_MAX);
+ if (host_name_max == (size_t)-1)
+#endif
+ host_name_max = 255; /* POSIX and historic BSD */
+
+ hname = malloc(host_name_max + 1);
+ if (hname != NULL) {
+ if (gethostname(hname, host_name_max + 1) == 0 && *hname != '\0') {
+ /* Old gethostname() may not NUL-terminate if there is no room. */
+ hname[host_name_max] = '\0';
+ } else {
+ free(hname);
+ hname = NULL;
+ }
+ }
+ return hname;
+}
diff --git a/lib/util/getopt_long.c b/lib/util/getopt_long.c
new file mode 100644
index 0000000..3a48f0e
--- /dev/null
+++ b/lib/util/getopt_long.c
@@ -0,0 +1,624 @@
+/* $OpenBSD: getopt_long.c,v 1.26 2013/06/08 22:47:56 millert Exp $ */
+/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */
+/* $FreeBSD: head/lib/libc/stdlib/getopt_long.c 236936 2012-06-11 22:25:20Z delphij $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2002 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Dieter Baron and Thomas Klausner.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "compat/getopt.h"
+
+#define GNU_COMPATIBLE /* Be more compatible with GNU getopt. */
+
+#ifdef REPLACE_GETOPT
+int opterr = 1; /* if error message should be printed */
+int optind = 1; /* index into parent argv vector */
+int optopt = '?'; /* character checked for validity */
+char *optarg; /* argument associated with option */
+#else
+extern int opterr; /* if error message should be printed */
+extern int optind; /* index into parent argv vector */
+extern int optopt; /* character checked for validity */
+extern char *optarg; /* argument associated with option */
+#endif
+#if !defined(REPLACE_GETOPT) && !defined(HAVE_OPTRESET)
+int optreset; /* reset getopt */
+#endif
+
+#define PRINT_ERROR ((opterr) && (*options != ':'))
+
+#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */
+#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */
+#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */
+
+/* return values */
+#define BADCH (int)'?'
+#define BADARG ((*options == ':') ? (int)':' : (int)'?')
+#define INORDER (int)1
+
+#define EMSG ""
+
+#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..e7161b5
--- /dev/null
+++ b/lib/util/gettime.c
@@ -0,0 +1,224 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/time.h>
+#include <time.h>
+
+#if defined(__MACH__) && !defined(HAVE_CLOCK_GETTIME)
+# include <mach/mach.h>
+# include <mach/mach_time.h>
+# include <mach/clock.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * On Linux and FreeBSD, CLOCK_MONOTONIC does not run while sleeping.
+ * Linux provides CLOCK_BOOTTIME which runs while sleeping (FreeBSD does not).
+ * Some systems provide CLOCK_UPTIME which only runs while awake.
+ */
+#if defined(CLOCK_BOOTTIME)
+# define SUDO_CLOCK_BOOTTIME CLOCK_BOOTTIME
+#elif defined(CLOCK_MONOTONIC_RAW)
+# define SUDO_CLOCK_BOOTTIME CLOCK_MONOTONIC_RAW
+#elif defined(CLOCK_MONOTONIC)
+# define SUDO_CLOCK_BOOTTIME CLOCK_MONOTONIC
+#endif
+#if defined(CLOCK_UPTIME_RAW)
+# define SUDO_CLOCK_UPTIME CLOCK_UPTIME_RAW
+#elif defined(CLOCK_UPTIME)
+# define SUDO_CLOCK_UPTIME CLOCK_UPTIME
+#elif defined(CLOCK_MONOTONIC)
+# define SUDO_CLOCK_UPTIME CLOCK_MONOTONIC
+#endif
+
+/*
+ * Wall clock time, may run backward.
+ */
+#if defined(HAVE_CLOCK_GETTIME)
+int
+sudo_gettime_real_v1(struct timespec *ts)
+{
+ debug_decl(sudo_gettime_real, SUDO_DEBUG_UTIL);
+
+ if (clock_gettime(CLOCK_REALTIME, ts) == -1) {
+ struct timeval tv;
+
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "clock_gettime(CLOCK_REALTIME) failed, trying gettimeofday()");
+ if (gettimeofday(&tv, NULL) == -1)
+ debug_return_int(-1);
+ TIMEVAL_TO_TIMESPEC(&tv, ts);
+ }
+ debug_return_int(0);
+}
+#else
+int
+sudo_gettime_real_v1(struct timespec *ts)
+{
+ struct timeval tv;
+ debug_decl(sudo_gettime_real, SUDO_DEBUG_UTIL);
+
+ if (gettimeofday(&tv, NULL) == -1)
+ debug_return_int(-1);
+ TIMEVAL_TO_TIMESPEC(&tv, ts);
+ debug_return_int(0);
+}
+#endif
+
+/*
+ * Monotonic time, only runs forward.
+ * We use a timer that only increments while sleeping, if possible.
+ */
+#if defined(HAVE_CLOCK_GETTIME) && defined(SUDO_CLOCK_BOOTTIME)
+int
+sudo_gettime_mono_v1(struct timespec *ts)
+{
+ static int has_monoclock = -1;
+ debug_decl(sudo_gettime_mono, SUDO_DEBUG_UTIL);
+
+ /* Check whether the kernel/libc actually supports a monotonic clock. */
+# ifdef _SC_MONOTONIC_CLOCK
+ if (has_monoclock == -1)
+ has_monoclock = sysconf(_SC_MONOTONIC_CLOCK) != -1;
+# endif
+ if (!has_monoclock)
+ debug_return_int(sudo_gettime_real(ts));
+ if (clock_gettime(SUDO_CLOCK_BOOTTIME, ts) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "clock_gettime(%d) failed, using wall clock",
+ (int)SUDO_CLOCK_BOOTTIME);
+ has_monoclock = 0;
+ debug_return_int(sudo_gettime_real(ts));
+ }
+ debug_return_int(0);
+}
+#elif defined(HAVE_GETHRTIME)
+int
+sudo_gettime_mono_v1(struct timespec *ts)
+{
+ hrtime_t nsec;
+ debug_decl(sudo_gettime_mono, SUDO_DEBUG_UTIL);
+
+ nsec = gethrtime();
+ ts->tv_sec = nsec / 1000000000;
+ ts->tv_nsec = nsec % 1000000000;
+ debug_return_int(0);
+}
+#elif defined(__MACH__)
+int
+sudo_gettime_mono_v1(struct timespec *ts)
+{
+ uint64_t abstime, nsec;
+ static mach_timebase_info_data_t timebase_info;
+ debug_decl(sudo_gettime_mono, SUDO_DEBUG_UTIL);
+
+ if (timebase_info.denom == 0)
+ (void) mach_timebase_info(&timebase_info);
+#ifdef HAVE_MACH_CONTINUOUS_TIME
+ abstime = mach_continuous_time(); /* runs while asleep */
+#else
+ abstime = mach_absolute_time(); /* doesn't run while asleep */
+#endif
+ nsec = abstime * timebase_info.numer / timebase_info.denom;
+ ts->tv_sec = nsec / 1000000000;
+ ts->tv_nsec = nsec % 1000000000;
+ debug_return_int(0);
+}
+#else
+int
+sudo_gettime_mono_v1(struct timespec *ts)
+{
+ /* No monotonic clock available, use wall clock. */
+ return sudo_gettime_real(ts);
+}
+#endif
+
+/*
+ * Monotonic time, only runs forward.
+ * We use a timer that only increments while awake, if possible.
+ */
+#if defined(HAVE_CLOCK_GETTIME) && defined(SUDO_CLOCK_UPTIME)
+int
+sudo_gettime_awake_v1(struct timespec *ts)
+{
+ static int has_monoclock = -1;
+ debug_decl(sudo_gettime_awake, SUDO_DEBUG_UTIL);
+
+ /* Check whether the kernel/libc actually supports a monotonic clock. */
+# ifdef _SC_MONOTONIC_CLOCK
+ if (has_monoclock == -1)
+ has_monoclock = sysconf(_SC_MONOTONIC_CLOCK) != -1;
+# endif
+ if (!has_monoclock)
+ debug_return_int(sudo_gettime_real(ts));
+ if (clock_gettime(SUDO_CLOCK_UPTIME, ts) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "clock_gettime(%d) failed, using wall clock",
+ (int)SUDO_CLOCK_UPTIME);
+ has_monoclock = 0;
+ debug_return_int(sudo_gettime_real(ts));
+ }
+ debug_return_int(0);
+}
+#elif defined(HAVE_GETHRTIME)
+int
+sudo_gettime_awake_v1(struct timespec *ts)
+{
+ hrtime_t nsec;
+ debug_decl(sudo_gettime_awake, SUDO_DEBUG_UTIL);
+
+ /* Currently the same as sudo_gettime_mono() */
+ nsec = gethrtime();
+ ts->tv_sec = nsec / 1000000000;
+ ts->tv_nsec = nsec % 1000000000;
+ debug_return_int(0);
+}
+#elif defined(__MACH__)
+int
+sudo_gettime_awake_v1(struct timespec *ts)
+{
+ uint64_t abstime, nsec;
+ static mach_timebase_info_data_t timebase_info;
+ debug_decl(sudo_gettime_awake, SUDO_DEBUG_UTIL);
+
+ if (timebase_info.denom == 0)
+ (void) mach_timebase_info(&timebase_info);
+ abstime = mach_absolute_time();
+ nsec = abstime * timebase_info.numer / timebase_info.denom;
+ ts->tv_sec = nsec / 1000000000;
+ ts->tv_nsec = nsec % 1000000000;
+ debug_return_int(0);
+}
+#else
+int
+sudo_gettime_awake_v1(struct timespec *ts)
+{
+ /* No monotonic uptime clock available, use wall clock. */
+ return sudo_gettime_real(ts);
+}
+#endif
diff --git a/lib/util/getusershell.c b/lib/util/getusershell.c
new file mode 100644
index 0000000..7912816
--- /dev/null
+++ b/lib/util/getusershell.c
@@ -0,0 +1,132 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+static char **allowed_shells, **current_shell;
+static char *default_shells[] = {
+ "/bin/sh",
+ "/bin/ksh",
+ "/bin/ksh93",
+ "/bin/bash",
+ "/bin/dash",
+ "/bin/zsh",
+ "/bin/csh",
+ "/bin/tcsh",
+ NULL
+};
+
+static char **
+read_shells(void)
+{
+ size_t maxshells = 16, nshells = 0;
+ size_t linesize = 0;
+ char *line = NULL;
+ FILE *fp;
+ debug_decl(read_shells, SUDO_DEBUG_UTIL);
+
+ if ((fp = fopen("/etc/shells", "r")) == NULL)
+ goto bad;
+
+ free(allowed_shells);
+ allowed_shells = reallocarray(NULL, maxshells, sizeof(char *));
+ if (allowed_shells == NULL)
+ goto bad;
+
+ while (sudo_parseln(&line, &linesize, NULL, fp, PARSELN_CONT_IGN) != -1) {
+ if (nshells + 1 >= maxshells) {
+ char **new_shells;
+
+ new_shells = reallocarray(NULL, maxshells + 16, sizeof(char *));
+ if (new_shells == NULL)
+ goto bad;
+ allowed_shells = new_shells;
+ maxshells += 16;
+ }
+ if ((allowed_shells[nshells] = strdup(line)) == NULL)
+ goto bad;
+ nshells++;
+ }
+ allowed_shells[nshells] = NULL;
+
+ free(line);
+ fclose(fp);
+ debug_return_ptr(allowed_shells);
+bad:
+ free(line);
+ if (fp != NULL)
+ fclose(fp);
+ while (nshells != 0)
+ free(allowed_shells[--nshells]);
+ free(allowed_shells);
+ allowed_shells = NULL;
+ debug_return_ptr(default_shells);
+}
+
+void
+sudo_setusershell(void)
+{
+ debug_decl(setusershell, SUDO_DEBUG_UTIL);
+
+ current_shell = read_shells();
+
+ debug_return;
+}
+
+void
+sudo_endusershell(void)
+{
+ debug_decl(endusershell, SUDO_DEBUG_UTIL);
+
+ if (allowed_shells != NULL) {
+ char **shell;
+
+ for (shell = allowed_shells; *shell != NULL; shell++)
+ free(*shell);
+ free(allowed_shells);
+ allowed_shells = NULL;
+ }
+ current_shell = NULL;
+
+ debug_return;
+}
+
+char *
+sudo_getusershell(void)
+{
+ debug_decl(getusershell, SUDO_DEBUG_UTIL);
+
+ if (current_shell == NULL)
+ current_shell = read_shells();
+
+ debug_return_str(*current_shell++);
+}
diff --git a/lib/util/gidlist.c b/lib/util/gidlist.c
new file mode 100644
index 0000000..d9107cd
--- /dev/null
+++ b/lib/util/gidlist.c
@@ -0,0 +1,87 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <grp.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+/*
+ * Parse a comma-separated list of gids into an allocated array of GETGROUPS_T.
+ * If a pointer to the base gid is specified, it is stored as the first element
+ * in the array.
+ * Returns the number of gids in the allocated array.
+ */
+int
+sudo_parse_gids_v1(const char *gidstr, const gid_t *basegid, GETGROUPS_T **gidsp)
+{
+ int ngids = 0;
+ GETGROUPS_T *gids;
+ const char *cp = gidstr;
+ const char *errstr;
+ char *ep;
+ debug_decl(sudo_parse_gids, SUDO_DEBUG_UTIL);
+
+ /* Count groups. */
+ if (*cp != '\0') {
+ ngids++;
+ do {
+ if (*cp++ == ',')
+ ngids++;
+ } while (*cp != '\0');
+ }
+ /* Base gid is optional. */
+ if (basegid != NULL)
+ ngids++;
+ /* Allocate and fill in array. */
+ if (ngids != 0) {
+ gids = reallocarray(NULL, ngids, sizeof(GETGROUPS_T));
+ if (gids == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_int(-1);
+ }
+ ngids = 0;
+ if (basegid != NULL)
+ gids[ngids++] = *basegid;
+ cp = gidstr;
+ do {
+ gids[ngids] = (GETGROUPS_T) sudo_strtoidx(cp, ",", &ep, &errstr);
+ if (errstr != NULL) {
+ sudo_warnx(U_("%s: %s"), cp, U_(errstr));
+ free(gids);
+ debug_return_int(-1);
+ }
+ if (basegid == NULL || gids[ngids] != *basegid)
+ ngids++;
+ cp = ep + 1;
+ } while (*ep != '\0');
+ *gidsp = gids;
+ }
+ debug_return_int(ngids);
+}
diff --git a/lib/util/glob.c b/lib/util/glob.c
new file mode 100644
index 0000000..762e107
--- /dev/null
+++ b/lib/util/glob.c
@@ -0,0 +1,953 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2008-2014 Todd C. Miller <Todd.Miller@sudo.ws>
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)glob.c 8.3 (Berkeley) 10/13/93
+ */
+
+/*
+ * glob(3) -- a superset of the one defined in POSIX 1003.2.
+ *
+ * The [!...] convention to negate a range is supported (SysV, Posix, ksh).
+ *
+ * Optional extra services, controlled by flags not defined by POSIX:
+ *
+ * GLOB_MAGCHAR:
+ * Set in gl_flags if pattern contained a globbing character.
+ * GLOB_TILDE:
+ * expand ~user/foo to the /home/dir/of/user/foo
+ * GLOB_BRACE:
+ * expand {1,2}{a,b} to 1a 1b 2a 2b
+ * gl_matchc:
+ * Number of matches in the current invocation of glob.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_GLOB
+
+#include <sys/stat.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
+
+#include "sudo_compat.h"
+#include "compat/glob.h"
+#include "compat/charclass.h"
+
+#define DOLLAR '$'
+#define DOT '.'
+#define EOS '\0'
+#define LBRACKET '['
+#define NOT '!'
+#define QUESTION '?'
+#define QUOTE '\\'
+#define RANGE '-'
+#define RBRACKET ']'
+#define SEP '/'
+#define STAR '*'
+#define TILDE '~'
+#define UNDERSCORE '_'
+#define LBRACE '{'
+#define RBRACE '}'
+#define SLASH '/'
+#define COMMA ','
+
+#ifndef DEBUG
+
+#define M_QUOTE 0x8000
+#define M_PROTECT 0x4000
+#define M_MASK 0xffff
+#define M_ASCII 0x00ff
+
+typedef unsigned short Char;
+
+#else
+
+#define M_QUOTE 0x80
+#define M_PROTECT 0x40
+#define M_MASK 0xff
+#define M_ASCII 0x7f
+
+typedef char Char;
+
+#endif
+
+
+#define CHAR(c) ((Char)((c)&M_ASCII))
+#define META(c) ((Char)((c)|M_QUOTE))
+#define M_ALL META('*')
+#define M_END META(']')
+#define M_NOT META('!')
+#define M_ONE META('?')
+#define M_RNG META('-')
+#define M_SET META('[')
+#define M_CLASS META(':')
+#define ismeta(c) (((c)&M_QUOTE) != 0)
+
+#define GLOB_LIMIT_MALLOC 65536
+#define GLOB_LIMIT_STAT 2048
+#define GLOB_LIMIT_READDIR 16384
+
+struct glob_lim {
+ size_t glim_malloc;
+ size_t glim_stat;
+ size_t glim_readdir;
+};
+
+static int compare(const void *, const void *);
+static int g_Ctoc(const Char *, char *, size_t);
+static int g_lstat(Char *, struct stat *, glob_t *);
+static DIR *g_opendir(Char *, glob_t *);
+static Char *g_strchr(const Char *, int);
+static int g_strncmp(const Char *, const char *, size_t);
+static int g_stat(Char *, struct stat *, glob_t *);
+static int glob0(const Char *, glob_t *, struct glob_lim *);
+static int glob1(Char *, Char *, glob_t *, struct glob_lim *);
+static int glob2(Char *, Char *, Char *, Char *, Char *, Char *,
+ glob_t *, struct glob_lim *);
+static int glob3(Char *, Char *, Char *, Char *, Char *,
+ Char *, Char *, glob_t *, struct glob_lim *);
+static int globextend(const Char *, glob_t *, struct glob_lim *,
+ struct stat *);
+static const Char *
+ globtilde(const Char *, Char *, size_t, glob_t *);
+static int globexp1(const Char *, glob_t *, struct glob_lim *);
+static int globexp2(const Char *, const Char *, glob_t *,
+ struct glob_lim *);
+static int match(Char *, Char *, Char *);
+#ifdef DEBUG
+static void qprintf(const char *, Char *);
+#endif
+
+int
+sudo_glob(const char *pattern, int flags, int (*errfunc)(const char *, int),
+ glob_t *pglob)
+{
+ const unsigned char *patnext;
+ int c;
+ Char *bufnext, *bufend, patbuf[PATH_MAX];
+ struct glob_lim limit = { 0, 0, 0 };
+
+ patnext = (unsigned char *) pattern;
+ if (!(flags & GLOB_APPEND)) {
+ pglob->gl_pathc = 0;
+ pglob->gl_pathv = NULL;
+ if (!(flags & GLOB_DOOFFS))
+ pglob->gl_offs = 0;
+ }
+ pglob->gl_flags = flags & ~GLOB_MAGCHAR;
+ pglob->gl_errfunc = errfunc;
+ pglob->gl_matchc = 0;
+
+ if (pglob->gl_offs >= SSIZE_MAX || pglob->gl_pathc >= SSIZE_MAX ||
+ pglob->gl_pathc >= SSIZE_MAX - pglob->gl_offs - 1)
+ return GLOB_NOSPACE;
+
+ if (strnlen(pattern, PATH_MAX) == PATH_MAX)
+ return GLOB_NOMATCH;
+
+ bufnext = patbuf;
+ bufend = bufnext + PATH_MAX - 1;
+ if (flags & GLOB_NOESCAPE)
+ while (bufnext < bufend && (c = *patnext++) != EOS)
+ *bufnext++ = c;
+ else {
+ /* Protect the quoted characters. */
+ while (bufnext < bufend && (c = *patnext++) != EOS)
+ if (c == QUOTE) {
+ if ((c = *patnext++) == EOS) {
+ c = QUOTE;
+ --patnext;
+ }
+ *bufnext++ = c | M_PROTECT;
+ } else
+ *bufnext++ = c;
+ }
+ *bufnext = EOS;
+
+ if (flags & GLOB_BRACE)
+ return globexp1(patbuf, pglob, &limit);
+ else
+ return glob0(patbuf, pglob, &limit);
+}
+
+/*
+ * Expand recursively a glob {} pattern. When there is no more expansion
+ * invoke the standard globbing routine to glob the rest of the magic
+ * characters
+ */
+static int
+globexp1(const Char *pattern, glob_t *pglob, struct glob_lim *limitp)
+{
+ const Char* ptr = pattern;
+
+ /* Protect a single {}, for find(1), like csh */
+ if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)
+ return glob0(pattern, pglob, limitp);
+
+ if ((ptr = (const Char *) g_strchr(ptr, LBRACE)) != NULL)
+ return globexp2(ptr, pattern, pglob, limitp);
+
+ return glob0(pattern, pglob, limitp);
+}
+
+
+/*
+ * Recursive brace globbing helper. Tries to expand a single brace.
+ * If it succeeds then it invokes globexp1 with the new pattern.
+ * If it fails then it tries to glob the rest of the pattern and returns.
+ */
+static int
+globexp2(const Char *ptr, const Char *pattern, glob_t *pglob,
+ struct glob_lim *limitp)
+{
+ int i, rv;
+ Char *lm, *ls;
+ const Char *pe, *pm, *pl;
+ Char patbuf[PATH_MAX];
+
+ /* copy part up to the brace */
+ for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)
+ continue;
+ *lm = EOS;
+ ls = lm;
+
+ /* Find the balanced brace */
+ for (i = 0, pe = ++ptr; *pe; pe++)
+ if (*pe == LBRACKET) {
+ /* Ignore everything between [] */
+ for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)
+ continue;
+ if (*pe == EOS) {
+ /*
+ * We could not find a matching RBRACKET.
+ * Ignore and just look for RBRACE
+ */
+ pe = pm;
+ }
+ } else if (*pe == LBRACE)
+ i++;
+ else if (*pe == RBRACE) {
+ if (i == 0)
+ break;
+ i--;
+ }
+
+ /* Non matching braces; just glob the pattern */
+ if (i != 0 || *pe == EOS)
+ return glob0(patbuf, pglob, limitp);
+
+ for (i = 0, pl = pm = ptr; pm <= pe; pm++) {
+ switch (*pm) {
+ case LBRACKET:
+ /* Ignore everything between [] */
+ for (pl = pm++; *pm != RBRACKET && *pm != EOS; pm++)
+ continue;
+ if (*pm == EOS) {
+ /*
+ * We could not find a matching RBRACKET.
+ * Ignore and just look for RBRACE
+ */
+ pm = pl;
+ }
+ break;
+
+ case LBRACE:
+ i++;
+ break;
+
+ case RBRACE:
+ if (i) {
+ i--;
+ break;
+ }
+ FALLTHROUGH;
+ case COMMA:
+ if (i && *pm == COMMA)
+ break;
+ else {
+ /* Append the current string */
+ for (lm = ls; (pl < pm); *lm++ = *pl++)
+ continue;
+
+ /*
+ * Append the rest of the pattern after the
+ * closing brace
+ */
+ for (pl = pe + 1; (*lm++ = *pl++) != EOS; )
+ continue;
+
+ /* Expand the current pattern */
+#ifdef DEBUG
+ qprintf("globexp2:", patbuf);
+#endif
+ rv = globexp1(patbuf, pglob, limitp);
+ if (rv && rv != GLOB_NOMATCH)
+ return rv;
+
+ /* move after the comma, to the next string */
+ pl = pm + 1;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+
+
+/*
+ * expand tilde from the passwd file.
+ */
+static const Char *
+globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob)
+{
+ struct passwd *pwd;
+ char *h;
+ const Char *p;
+ Char *b, *eb;
+
+ if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE))
+ return pattern;
+
+ /* Copy up to the end of the string or / */
+ eb = &patbuf[patbuf_len - 1];
+ for (p = pattern + 1, h = (char *) patbuf;
+ h < (char *)eb && *p && *p != SLASH; *h++ = *p++)
+ continue;
+
+ *h = EOS;
+
+ if (((char *) patbuf)[0] == EOS) {
+ /*
+ * handle a plain ~ or ~/ by expanding $HOME
+ * first and then trying the password file
+ */
+ if ((h = getenv("HOME")) == NULL) {
+ if ((pwd = getpwuid(getuid())) == NULL)
+ return pattern;
+ else
+ h = pwd->pw_dir;
+ }
+ } else {
+ /*
+ * Expand a ~user
+ */
+ if ((pwd = getpwnam((char*) patbuf)) == NULL)
+ return pattern;
+ else
+ h = pwd->pw_dir;
+ }
+
+ /* Copy the home directory */
+ for (b = patbuf; b < eb && *h; *b++ = *h++)
+ continue;
+
+ /* Append the rest of the pattern */
+ while (b < eb && (*b++ = *p++) != EOS)
+ continue;
+ *b = EOS;
+
+ return patbuf;
+}
+
+static int
+g_strncmp(const Char *s1, const char *s2, size_t n)
+{
+ int rv = 0;
+
+ while (n--) {
+ rv = *(Char *)s1 - *(const unsigned char *)s2++;
+ if (rv)
+ break;
+ if (*s1++ == '\0')
+ break;
+ }
+ return rv;
+}
+
+static int
+g_charclass(const Char **patternp, Char **bufnextp)
+{
+ const Char *pattern = *patternp + 1;
+ Char *bufnext = *bufnextp;
+ const Char *colon;
+ struct cclass *cc;
+ size_t len;
+
+ if ((colon = g_strchr(pattern, ':')) == NULL || colon[1] != ']')
+ return 1; /* not a character class */
+
+ len = (size_t)(colon - pattern);
+ for (cc = cclasses; cc->name != NULL; cc++) {
+ if (!g_strncmp(pattern, cc->name, len) && cc->name[len] == '\0')
+ break;
+ }
+ if (cc->name == NULL)
+ return -1; /* invalid character class */
+ *bufnext++ = M_CLASS;
+ *bufnext++ = (Char)(cc - &cclasses[0]);
+ *bufnextp = bufnext;
+ *patternp += len + 3;
+
+ return 0;
+}
+
+/*
+ * The main glob() routine: compiles the pattern (optionally processing
+ * quotes), calls glob1() to do the real pattern matching, and finally
+ * sorts the list (unless unsorted operation is requested). Returns 0
+ * if things went well, nonzero if errors occurred. It is not an error
+ * to find no matches.
+ */
+static int
+glob0(const Char *pattern, glob_t *pglob, struct glob_lim *limitp)
+{
+ const Char *qpatnext;
+ int c, err;
+ size_t oldpathc;
+ Char *bufnext, patbuf[PATH_MAX];
+
+ qpatnext = globtilde(pattern, patbuf, PATH_MAX, pglob);
+ oldpathc = pglob->gl_pathc;
+ bufnext = patbuf;
+
+ /* We don't need to check for buffer overflow any more. */
+ while ((c = *qpatnext++) != EOS) {
+ switch (c) {
+ case LBRACKET:
+ c = *qpatnext;
+ if (c == NOT)
+ ++qpatnext;
+ if (*qpatnext == EOS ||
+ g_strchr(qpatnext+1, RBRACKET) == NULL) {
+ *bufnext++ = LBRACKET;
+ if (c == NOT)
+ --qpatnext;
+ break;
+ }
+ *bufnext++ = M_SET;
+ if (c == NOT)
+ *bufnext++ = M_NOT;
+ c = *qpatnext++;
+ do {
+ if (c == LBRACKET && *qpatnext == ':') {
+ do {
+ err = g_charclass(&qpatnext,
+ &bufnext);
+ if (err)
+ break;
+ c = *qpatnext++;
+ } while (c == LBRACKET && *qpatnext == ':');
+ if (err == -1 &&
+ !(pglob->gl_flags & GLOB_NOCHECK))
+ return GLOB_NOMATCH;
+ if (c == RBRACKET)
+ break;
+ }
+ *bufnext++ = CHAR(c);
+ if (*qpatnext == RANGE &&
+ (c = qpatnext[1]) != RBRACKET) {
+ *bufnext++ = M_RNG;
+ *bufnext++ = CHAR(c);
+ qpatnext += 2;
+ }
+ } while ((c = *qpatnext++) != RBRACKET);
+ pglob->gl_flags |= GLOB_MAGCHAR;
+ *bufnext++ = M_END;
+ break;
+ case QUESTION:
+ pglob->gl_flags |= GLOB_MAGCHAR;
+ *bufnext++ = M_ONE;
+ break;
+ case STAR:
+ pglob->gl_flags |= GLOB_MAGCHAR;
+ /* collapse adjacent stars to one,
+ * to avoid exponential behavior
+ */
+ if (bufnext == patbuf || bufnext[-1] != M_ALL)
+ *bufnext++ = M_ALL;
+ break;
+ default:
+ *bufnext++ = CHAR(c);
+ break;
+ }
+ }
+ *bufnext = EOS;
+#ifdef DEBUG
+ qprintf("glob0:", patbuf);
+#endif
+
+ if ((err = glob1(patbuf, patbuf + PATH_MAX - 1, pglob, limitp)) != 0)
+ return err;
+
+ /*
+ * If there was no match we are going to append the pattern
+ * if GLOB_NOCHECK was specified.
+ */
+ if (pglob->gl_pathc == oldpathc) {
+ if ((pglob->gl_flags & GLOB_NOCHECK))
+ return globextend(pattern, pglob, limitp, NULL);
+ else
+ return GLOB_NOMATCH;
+ }
+ if (!(pglob->gl_flags & GLOB_NOSORT)) {
+ qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,
+ pglob->gl_pathc - oldpathc, sizeof(char *), compare);
+ }
+ return 0;
+}
+
+static int
+compare(const void *p, const void *q)
+{
+ return strcmp(*(char **)p, *(char **)q);
+}
+
+static int
+glob1(Char *pattern, Char *pattern_last, glob_t *pglob, struct glob_lim *limitp)
+{
+ Char pathbuf[PATH_MAX];
+
+ /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
+ if (*pattern == EOS)
+ return 0;
+ return glob2(pathbuf, pathbuf + PATH_MAX - 1,
+ pathbuf, pathbuf + PATH_MAX - 1,
+ pattern, pattern_last, pglob, limitp);
+}
+
+/*
+ * The functions glob2 and glob3 are mutually recursive; there is one level
+ * of recursion for each segment in the pattern that contains one or more
+ * meta characters.
+ */
+static int
+glob2(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last,
+ Char *pattern, Char *pattern_last, glob_t *pglob, struct glob_lim *limitp)
+{
+ struct stat sb;
+ Char *p, *q;
+ int anymeta;
+
+ /*
+ * Loop over pattern segments until end of pattern or until
+ * segment with meta character found.
+ */
+ for (anymeta = 0;;) {
+ if (*pattern == EOS) { /* End of pattern? */
+ *pathend = EOS;
+
+ if ((pglob->gl_flags & GLOB_LIMIT) &&
+ limitp->glim_stat++ >= GLOB_LIMIT_STAT) {
+ errno = 0;
+ *pathend++ = SEP;
+ *pathend = EOS;
+ return GLOB_NOSPACE;
+ }
+ if (g_lstat(pathbuf, &sb, pglob))
+ return 0;
+
+ if (((pglob->gl_flags & GLOB_MARK) &&
+ pathend[-1] != SEP) && (S_ISDIR(sb.st_mode) ||
+ (S_ISLNK(sb.st_mode) &&
+ (g_stat(pathbuf, &sb, pglob) == 0) &&
+ S_ISDIR(sb.st_mode)))) {
+ if (pathend+1 > pathend_last)
+ return 1;
+ *pathend++ = SEP;
+ *pathend = EOS;
+ }
+ ++pglob->gl_matchc;
+ return globextend(pathbuf, pglob, limitp, &sb);
+ }
+
+ /* Find end of next segment, copy tentatively to pathend. */
+ q = pathend;
+ p = pattern;
+ while (*p != EOS && *p != SEP) {
+ if (ismeta(*p))
+ anymeta = 1;
+ if (q+1 > pathend_last)
+ return 1;
+ *q++ = *p++;
+ }
+
+ if (!anymeta) { /* No expansion, do next segment. */
+ pathend = q;
+ pattern = p;
+ while (*pattern == SEP) {
+ if (pathend+1 > pathend_last)
+ return 1;
+ *pathend++ = *pattern++;
+ }
+ } else
+ /* Need expansion, recurse. */
+ return glob3(pathbuf, pathbuf_last, pathend,
+ pathend_last, pattern, p, pattern_last,
+ pglob, limitp);
+ }
+ /* NOTREACHED */
+}
+
+static int
+glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last,
+ Char *pattern, Char *restpattern, Char *restpattern_last, glob_t *pglob,
+ struct glob_lim *limitp)
+{
+ struct dirent *dp;
+ DIR *dirp;
+ int err;
+ char buf[PATH_MAX];
+
+ if (pathend > pathend_last)
+ return 1;
+ *pathend = EOS;
+ errno = 0;
+
+ if ((dirp = g_opendir(pathbuf, pglob)) == NULL) {
+ /* TODO: don't call for ENOENT or ENOTDIR? */
+ if (pglob->gl_errfunc) {
+ if (g_Ctoc(pathbuf, buf, sizeof(buf)))
+ return GLOB_ABORTED;
+ if (pglob->gl_errfunc(buf, errno) ||
+ pglob->gl_flags & GLOB_ERR)
+ return GLOB_ABORTED;
+ }
+ return 0;
+ }
+
+ err = 0;
+
+ /* Search directory for matching names. */
+ while ((dp = readdir(dirp))) {
+ unsigned char *sc;
+ Char *dc;
+
+ if ((pglob->gl_flags & GLOB_LIMIT) &&
+ limitp->glim_readdir++ >= GLOB_LIMIT_READDIR) {
+ errno = 0;
+ *pathend++ = SEP;
+ *pathend = EOS;
+ err = GLOB_NOSPACE;
+ break;
+ }
+
+ /* Initial DOT must be matched literally. */
+ if (dp->d_name[0] == DOT && *pattern != DOT)
+ continue;
+ dc = pathend;
+ sc = (unsigned char *) dp->d_name;
+ while (dc < pathend_last && (*dc++ = *sc++) != EOS)
+ continue;
+ if (dc >= pathend_last) {
+ *dc = EOS;
+ err = 1;
+ break;
+ }
+
+ if (!match(pathend, pattern, restpattern)) {
+ *pathend = EOS;
+ continue;
+ }
+ err = glob2(pathbuf, pathbuf_last, --dc, pathend_last,
+ restpattern, restpattern_last, pglob, limitp);
+ if (err)
+ break;
+ }
+
+ closedir(dirp);
+ return err;
+}
+
+/*
+ * Extend the gl_pathv member of a glob_t structure to accommodate a new item,
+ * add the new item, and update gl_pathc.
+ *
+ * This assumes the BSD realloc, which only copies the block when its size
+ * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
+ * behavior.
+ *
+ * Return 0 if new item added, error code if memory couldn't be allocated.
+ *
+ * Invariant of the glob_t structure:
+ * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
+ * gl_pathv points to (gl_offs + gl_pathc + 1) items.
+ */
+static int
+globextend(const Char *path, glob_t *pglob, struct glob_lim *limitp,
+ struct stat *sb)
+{
+ char **pathv;
+ size_t i, newn, len;
+ char *copy = NULL;
+ const Char *p;
+
+ newn = 2 + pglob->gl_pathc + pglob->gl_offs;
+ if (pglob->gl_offs >= SSIZE_MAX ||
+ pglob->gl_pathc >= SSIZE_MAX ||
+ newn >= SSIZE_MAX ||
+ SIZE_MAX / sizeof(*pathv) <= newn) {
+ nospace:
+ for (i = pglob->gl_offs; i < newn - 2; i++) {
+ if (pglob->gl_pathv && pglob->gl_pathv[i])
+ free(pglob->gl_pathv[i]);
+ }
+ if (pglob->gl_pathv) {
+ free(pglob->gl_pathv);
+ pglob->gl_pathv = NULL;
+ }
+ return GLOB_NOSPACE;
+ }
+
+ pathv = reallocarray(pglob->gl_pathv, newn, sizeof(*pathv));
+ if (pathv == NULL)
+ goto nospace;
+ if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {
+ /* first time around -- clear initial gl_offs items */
+ pathv += pglob->gl_offs;
+ for (i = pglob->gl_offs; i > 0; i--)
+ *--pathv = NULL;
+ }
+ pglob->gl_pathv = pathv;
+
+ for (p = path; *p++;)
+ continue;
+ len = (size_t)(p - path);
+ limitp->glim_malloc += len;
+ if ((copy = malloc(len)) != NULL) {
+ if (g_Ctoc(path, copy, len)) {
+ free(copy);
+ return GLOB_NOSPACE;
+ }
+ pathv[pglob->gl_offs + pglob->gl_pathc++] = copy;
+ }
+ pathv[pglob->gl_offs + pglob->gl_pathc] = NULL;
+
+ if ((pglob->gl_flags & GLOB_LIMIT) &&
+ (newn * sizeof(*pathv)) + limitp->glim_malloc >
+ GLOB_LIMIT_MALLOC) {
+ errno = 0;
+ return GLOB_NOSPACE;
+ }
+ return copy == NULL ? GLOB_NOSPACE : 0;
+}
+
+
+/*
+ * pattern matching function for filenames. Each occurrence of the *
+ * pattern causes an iteration.
+ *
+ * Note, this function differs from the original as per the discussion
+ * here: https://research.swtch.com/glob
+ *
+ * Basically we removed the recursion and made it use the algorithm
+ * from Russ Cox to not go quadratic on cases like a file called
+ * ("a" x 100) . "x" matched against a pattern like "a*a*a*a*a*a*a*y".
+ */
+static int
+match(Char *name, Char *pat, Char *patend)
+{
+ int ok, negate_range;
+ Char c, k;
+ Char *nextp = NULL;
+ Char *nextn = NULL;
+
+loop:
+ while (pat < patend) {
+ c = *pat++;
+ switch (c & M_MASK) {
+ case M_ALL:
+ while (pat < patend && (*pat & M_MASK) == M_ALL)
+ pat++; /* eat consecutive '*' */
+ if (pat == patend)
+ return 1;
+ if (*name == EOS)
+ return 0;
+ nextn = name + 1;
+ nextp = pat - 1;
+ break;
+ case M_ONE:
+ if (*name++ == EOS)
+ goto fail;
+ break;
+ case M_SET:
+ ok = 0;
+ if ((k = *name++) == EOS)
+ goto fail;
+ if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)
+ ++pat;
+ while (((c = *pat++) & M_MASK) != M_END) {
+ if ((c & M_MASK) == M_CLASS) {
+ Char idx = *pat & M_MASK;
+ if (idx < NCCLASSES &&
+ cclasses[idx].isctype(k))
+ ok = 1;
+ ++pat;
+ }
+ if ((*pat & M_MASK) == M_RNG) {
+ if (c <= k && k <= pat[1])
+ ok = 1;
+ pat += 2;
+ } else if (c == k)
+ ok = 1;
+ }
+ if (ok == negate_range)
+ goto fail;
+ break;
+ default:
+ if (*name++ != c)
+ goto fail;
+ break;
+ }
+ }
+ if (*name == EOS)
+ return 1;
+fail:
+ if (nextn) {
+ pat = nextp;
+ name = nextn;
+ goto loop;
+ }
+ return 0;
+}
+
+/* Free allocated data belonging to a glob_t structure. */
+void
+sudo_globfree(glob_t *pglob)
+{
+ size_t i;
+ char **pp;
+
+ if (pglob->gl_pathv != NULL) {
+ pp = pglob->gl_pathv + pglob->gl_offs;
+ for (i = pglob->gl_pathc; i--; ++pp)
+ if (*pp)
+ free(*pp);
+ free(pglob->gl_pathv);
+ pglob->gl_pathv = NULL;
+ }
+}
+
+static DIR *
+g_opendir(Char *str, glob_t *pglob)
+{
+ char buf[PATH_MAX];
+
+ if (!*str) {
+ buf[0] = '.';
+ buf[1] = '\0';
+ } else {
+ if (g_Ctoc(str, buf, sizeof(buf)))
+ return NULL;
+ }
+
+ return opendir(buf);
+}
+
+static int
+g_lstat(Char *fn, struct stat *sb, glob_t *pglob)
+{
+ char buf[PATH_MAX];
+
+ if (g_Ctoc(fn, buf, sizeof(buf)))
+ return -1;
+ return lstat(buf, sb);
+}
+
+static int
+g_stat(Char *fn, struct stat *sb, glob_t *pglob)
+{
+ char buf[PATH_MAX];
+
+ if (g_Ctoc(fn, buf, sizeof(buf)))
+ return -1;
+ return stat(buf, sb);
+}
+
+static Char *
+g_strchr(const Char *str, int ch)
+{
+ do {
+ if (*str == ch)
+ return (Char *)str;
+ } while (*str++);
+ return NULL;
+}
+
+static int
+g_Ctoc(const Char *str, char *buf, size_t len)
+{
+
+ while (len--) {
+ if ((*buf++ = *str++) == EOS)
+ return 0;
+ }
+ return 1;
+}
+
+#ifdef DEBUG
+static void
+qprintf(const char *str, Char *s)
+{
+ Char *p;
+
+ (void)printf("%s:\n", str);
+ for (p = s; *p; p++)
+ (void)printf("%c", CHAR(*p));
+ (void)printf("\n");
+ for (p = s; *p; p++)
+ (void)printf("%c", *p & M_PROTECT ? '"' : ' ');
+ (void)printf("\n");
+ for (p = s; *p; p++)
+ (void)printf("%c", ismeta(*p) ? '_' : ' ');
+ (void)printf("\n");
+}
+#endif /* DEBUG */
+#endif /* HAVE_GLOB */
diff --git a/lib/util/inet_ntop.c b/lib/util/inet_ntop.c
new file mode 100644
index 0000000..108b887
--- /dev/null
+++ b/lib/util/inet_ntop.c
@@ -0,0 +1,232 @@
+/* $OpenBSD: inet_ntop.c,v 1.9 2014/02/05 14:20:43 millert Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1996 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+#include <config.h>
+
+#if !defined(HAVE_INET_NTOP)
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "sudo_compat.h"
+
+#ifndef EAFNOSUPPORT
+# define EAFNOSUPPORT EINVAL
+#endif
+
+#ifndef NS_IN6ADDRSZ
+# ifdef IN6ADDRSZ
+# define NS_IN6ADDRSZ IN6ADDRSZ
+# else
+# define NS_IN6ADDRSZ 16
+# endif
+#endif
+#ifndef NS_INT16SZ
+# ifdef INT16SZ
+# define NS_INT16SZ INT16SZ
+# else
+# define NS_INT16SZ 2
+# endif
+#endif
+#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..c73c9c3
--- /dev/null
+++ b/lib/util/inet_pton.c
@@ -0,0 +1,252 @@
+/* $OpenBSD: inet_pton.c,v 1.8 2010/05/06 15:47:14 claudio Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1996 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+#include <config.h>
+
+#if !defined(HAVE_INET_PTON)
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <string.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+
+#ifndef EAFNOSUPPORT
+# define EAFNOSUPPORT EINVAL
+#endif
+
+#ifndef NS_INADDRSZ
+# ifdef INADDRSZ
+# define NS_INADDRSZ INADDRSZ
+# else
+# define NS_INADDRSZ 4
+# endif
+#endif
+#ifndef NS_IN6ADDRSZ
+# ifdef IN6ADDRSZ
+# define NS_IN6ADDRSZ IN6ADDRSZ
+# else
+# define NS_IN6ADDRSZ 16
+# endif
+#endif
+#ifndef NS_INT16SZ
+# ifdef INT16SZ
+# define NS_INT16SZ INT16SZ
+# else
+# define NS_INT16SZ 2
+# endif
+#endif
+
+/*
+ * WARNING: Don't even consider trying to compile this on a system where
+ * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX.
+ */
+
+/* int
+ * inet_pton4(src, dst)
+ * like inet_aton() but without all the hexadecimal and shorthand.
+ * return:
+ * 1 if `src' is a valid dotted quad, else 0.
+ * notice:
+ * does not touch `dst' unless it's returning 1.
+ * author:
+ * Paul Vixie, 1996.
+ */
+static int
+inet_pton4(const char *src, u_char *dst)
+{
+ const char digits[] = "0123456789";
+ int saw_digit, octets, ch;
+ u_char tmp[NS_INADDRSZ], *tp;
+
+ saw_digit = 0;
+ octets = 0;
+ /* cppcheck-suppress uninitvar */
+ *(tp = tmp) = '\0';
+ while ((ch = (unsigned char)*src++) != '\0') {
+ const char *pch;
+
+ if ((pch = strchr(digits, ch)) != NULL) {
+ u_int new = *tp * 10 + (pch - digits);
+
+ if (new > 255)
+ return (0);
+ if (!saw_digit) {
+ if (++octets > 4)
+ return (0);
+ saw_digit = 1;
+ }
+ *tp = new;
+ } else if (ch == '.' && saw_digit) {
+ if (octets == 4)
+ return (0);
+ *++tp = 0;
+ saw_digit = 0;
+ } else
+ return (0);
+ }
+ if (octets < 4)
+ return (0);
+
+ memcpy(dst, tmp, NS_INADDRSZ);
+ return (1);
+}
+
+#ifdef HAVE_STRUCT_IN6_ADDR
+/* int
+ * inet_pton6(src, dst)
+ * convert presentation level address to network order binary form.
+ * return:
+ * 1 if `src' is a valid [RFC1884 2.2] address, else 0.
+ * notice:
+ * does not touch `dst' unless it's returning 1.
+ * credit:
+ * inspired by Mark Andrews.
+ * author:
+ * Paul Vixie, 1996.
+ */
+static int
+inet_pton6(const char *src, u_char *dst)
+{
+ const char xdigits_l[] = "0123456789abcdef",
+ xdigits_u[] = "0123456789ABCDEF";
+ u_char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp;
+ const char *xdigits, *curtok;
+ int ch, saw_xdigit, count_xdigit;
+ u_int val;
+
+ /* cppcheck-suppress uninitvar */
+ memset((tp = tmp), 0, NS_IN6ADDRSZ);
+ endp = tp + NS_IN6ADDRSZ;
+ colonp = NULL;
+ /* Leading :: requires some special handling. */
+ if (*src == ':')
+ if (*++src != ':')
+ return (0);
+ curtok = src;
+ saw_xdigit = count_xdigit = 0;
+ val = 0;
+ while ((ch = (unsigned char)*src++) != '\0') {
+ const char *pch;
+
+ if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL)
+ pch = strchr((xdigits = xdigits_u), ch);
+ if (pch != NULL) {
+ if (count_xdigit >= 4)
+ return (0);
+ val <<= 4;
+ val |= (pch - xdigits);
+ if (val > 0xffff)
+ return (0);
+ saw_xdigit = 1;
+ count_xdigit++;
+ continue;
+ }
+ if (ch == ':') {
+ curtok = src;
+ if (!saw_xdigit) {
+ if (colonp)
+ return (0);
+ colonp = tp;
+ continue;
+ } else if (*src == '\0') {
+ return (0);
+ }
+ if (tp + NS_INT16SZ > endp)
+ return (0);
+ *tp++ = (u_char) (val >> 8) & 0xff;
+ *tp++ = (u_char) val & 0xff;
+ saw_xdigit = 0;
+ count_xdigit = 0;
+ val = 0;
+ continue;
+ }
+ if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
+ inet_pton4(curtok, tp) > 0) {
+ tp += NS_INADDRSZ;
+ saw_xdigit = 0;
+ count_xdigit = 0;
+ break; /* '\0' was seen by inet_pton4(). */
+ }
+ return (0);
+ }
+ if (saw_xdigit) {
+ if (tp + NS_INT16SZ > endp)
+ return (0);
+ *tp++ = (u_char) (val >> 8) & 0xff;
+ *tp++ = (u_char) val & 0xff;
+ }
+ if (colonp != NULL) {
+ /*
+ * Since some memmove()'s erroneously fail to handle
+ * overlapping regions, we'll do the shift by hand.
+ */
+ const long n = tp - colonp;
+ long i;
+
+ if (tp == endp)
+ return (0);
+ for (i = 1; i <= n; i++) {
+ endp[- i] = colonp[n - i];
+ colonp[n - i] = 0;
+ }
+ tp = endp;
+ }
+ if (tp != endp)
+ return (0);
+ memcpy(dst, tmp, NS_IN6ADDRSZ);
+ return (1);
+}
+#endif /* HAVE_STRUCT_IN6_ADDR */
+
+/* int
+ * inet_pton(af, src, dst)
+ * convert from presentation format (which usually means ASCII printable)
+ * to network format (which is usually some kind of binary format).
+ * return:
+ * 1 if the address was valid for the specified address family
+ * 0 if the address wasn't valid (`dst' is untouched in this case)
+ * -1 if some other error occurred (`dst' is untouched in this case, too)
+ * author:
+ * Paul Vixie, 1996.
+ */
+int
+sudo_inet_pton(int af, const char *src, void *dst)
+{
+ switch (af) {
+ case AF_INET:
+ return (inet_pton4(src, dst));
+#ifdef HAVE_STRUCT_IN6_ADDR
+ case AF_INET6:
+ return (inet_pton6(src, dst));
+#endif /* HAVE_STRUCT_IN6_ADDR */
+ default:
+ errno = EAFNOSUPPORT;
+ return (-1);
+ }
+ /* NOTREACHED */
+}
+
+#endif /* HAVE_INET_PTON */
diff --git a/lib/util/isblank.c b/lib/util/isblank.c
new file mode 100644
index 0000000..753e030
--- /dev/null
+++ b/lib/util/isblank.c
@@ -0,0 +1,37 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008, 2010-2011, 2013
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_ISBLANK
+
+#include "sudo_compat.h"
+
+#undef isblank
+int
+isblank(int ch)
+{
+ return ch == ' ' || ch == '\t';
+}
+#endif /* HAVE_ISBLANK */
diff --git a/lib/util/json.c b/lib/util/json.c
new file mode 100644
index 0000000..6fac63f
--- /dev/null
+++ b/lib/util/json.c
@@ -0,0 +1,388 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_json.h"
+#include "sudo_util.h"
+
+/*
+ * Double the size of the json buffer.
+ * Returns true on success, false if out of memory.
+ */
+static bool
+json_expand_buf(struct json_container *json)
+{
+ char *newbuf;
+ debug_decl(json_expand_buf, SUDO_DEBUG_UTIL);
+
+ if ((newbuf = reallocarray(json->buf, 2, json->bufsize)) == NULL) {
+ if (json->memfatal) {
+ sudo_fatalx(U_("%s: %s"),
+ __func__, U_("unable to allocate memory"));
+ }
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "%s: %s", __func__, "unable to allocate memory");
+ debug_return_bool(false);
+ }
+ json->buf = newbuf;
+ json->bufsize *= 2;
+
+ debug_return_bool(true);
+}
+
+/*
+ * Start a new line and indent unless formatting as minimal JSON.
+ * Append "indent" number of blank characters.
+ */
+static bool
+json_new_line(struct json_container *json)
+{
+ int indent = json->indent_level;
+ debug_decl(json_new_line, SUDO_DEBUG_UTIL);
+
+ /* No non-essential white space in minimal mode. */
+ if (json->minimal)
+ debug_return_bool(true);
+
+ while (json->buflen + 1 + indent >= json->bufsize) {
+ if (!json_expand_buf(json))
+ debug_return_bool(false);
+ }
+ json->buf[json->buflen++] = '\n';
+ while (indent--) {
+ json->buf[json->buflen++] = ' ';
+ }
+ json->buf[json->buflen] = '\0';
+
+ debug_return_bool(true);
+}
+
+/*
+ * Append a string to the JSON buffer, expanding as needed.
+ * Does not perform any quoting.
+ */
+static bool
+json_append_buf(struct json_container *json, const char *str)
+{
+ size_t len;
+ debug_decl(json_append_buf, SUDO_DEBUG_UTIL);
+
+ len = strlen(str);
+ while (json->buflen + len >= json->bufsize) {
+ if (!json_expand_buf(json))
+ debug_return_bool(false);
+ }
+
+ memcpy(json->buf + json->buflen, str, len);
+ json->buflen += len;
+ json->buf[json->buflen] = '\0';
+
+ debug_return_bool(true);
+}
+
+/*
+ * Append a quoted JSON string, escaping special chars and expanding as needed.
+ * Does not support unicode escapes.
+ */
+static bool
+json_append_string(struct json_container *json, const char *str)
+{
+ char ch;
+ debug_decl(json_append_string, SUDO_DEBUG_UTIL);
+
+ if (!json_append_buf(json, "\""))
+ debug_return_bool(false);
+ while ((ch = *str++) != '\0') {
+ char buf[3], *cp = buf;
+
+ switch (ch) {
+ case '"':
+ case '\\':
+ *cp++ = '\\';
+ break;
+ case '\b':
+ *cp++ = '\\';
+ ch = 'b';
+ break;
+ case '\f':
+ *cp++ = '\\';
+ ch = 'f';
+ break;
+ case '\n':
+ *cp++ = '\\';
+ ch = 'n';
+ break;
+ case '\r':
+ *cp++ = '\\';
+ ch = 'r';
+ break;
+ case '\t':
+ *cp++ = '\\';
+ ch = 't';
+ break;
+ }
+ *cp++ = ch;
+ *cp++ = '\0';
+ if (!json_append_buf(json, buf))
+ debug_return_bool(false);
+ }
+ if (!json_append_buf(json, "\""))
+ debug_return_bool(false);
+
+ debug_return_bool(true);
+}
+
+bool
+sudo_json_init_v1(struct json_container *json, int indent, bool minimal,
+ bool memfatal)
+{
+ debug_decl(sudo_json_init, SUDO_DEBUG_UTIL);
+
+ memset(json, 0, sizeof(*json));
+ json->indent_level = indent;
+ json->indent_increment = indent;
+ json->minimal = minimal;
+ json->memfatal = memfatal;
+ json->buf = malloc(64 * 1024);
+ if (json->buf == NULL) {
+ if (json->memfatal) {
+ sudo_fatalx(U_("%s: %s"),
+ __func__, U_("unable to allocate memory"));
+ }
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
+ "%s: %s", __func__, "unable to allocate memory");
+ debug_return_bool(false);
+ }
+ *json->buf = '\0';
+ json->bufsize = 64 * 1024;
+
+ debug_return_bool(true);
+}
+
+void
+sudo_json_free_v1(struct json_container *json)
+{
+ debug_decl(sudo_json_free, SUDO_DEBUG_UTIL);
+
+ free(json->buf);
+ memset(json, 0, sizeof(*json));
+
+ debug_return;
+}
+
+bool
+sudo_json_open_object_v1(struct json_container *json, const char *name)
+{
+ debug_decl(sudo_json_open_object, SUDO_DEBUG_UTIL);
+
+ /* Add comma if we are continuing an object/array. */
+ if (json->need_comma) {
+ if (!json_append_buf(json, ","))
+ debug_return_bool(false);
+ }
+ if (!json_new_line(json))
+ debug_return_bool(false);
+
+ if (name != NULL) {
+ json_append_string(json, name);
+ if (!json_append_buf(json, json->minimal ? ":{" : ": {"))
+ debug_return_bool(false);
+ } else {
+ if (!json_append_buf(json, "{"))
+ debug_return_bool(false);
+ }
+
+ json->indent_level += json->indent_increment;
+ json->need_comma = false;
+
+ debug_return_bool(true);
+}
+
+bool
+sudo_json_close_object_v1(struct json_container *json)
+{
+ debug_decl(sudo_json_close_object, SUDO_DEBUG_UTIL);
+
+ if (!json->minimal) {
+ json->indent_level -= json->indent_increment;
+ if (!json_new_line(json))
+ debug_return_bool(false);
+ }
+ if (!json_append_buf(json, "}"))
+ debug_return_bool(false);
+
+ debug_return_bool(true);
+}
+
+bool
+sudo_json_open_array_v1(struct json_container *json, const char *name)
+{
+ debug_decl(sudo_json_open_array, SUDO_DEBUG_UTIL);
+
+ /* Add comma if we are continuing an object/array. */
+ if (json->need_comma) {
+ if (!json_append_buf(json, ","))
+ debug_return_bool(false);
+ }
+ if (!json_new_line(json))
+ debug_return_bool(false);
+
+ if (name != NULL) {
+ json_append_string(json, name);
+ if (!json_append_buf(json, json->minimal ? ":[" : ": ["))
+ debug_return_bool(false);
+ } else {
+ if (!json_append_buf(json, "["))
+ debug_return_bool(false);
+ }
+
+ json->indent_level += json->indent_increment;
+ json->need_comma = false;
+
+ debug_return_bool(true);
+}
+
+bool
+sudo_json_close_array_v1(struct json_container *json)
+{
+ debug_decl(sudo_json_close_array, SUDO_DEBUG_UTIL);
+
+ if (!json->minimal) {
+ json->indent_level -= json->indent_increment;
+ if (!json_new_line(json))
+ debug_return_bool(false);
+ }
+ if (!json_append_buf(json, "]"))
+ debug_return_bool(false);
+
+ debug_return_bool(true);
+}
+
+static bool
+sudo_json_add_value_int(struct json_container *json, const char *name,
+ struct json_value *value, bool as_object)
+{
+ char numbuf[(((sizeof(long long) * 8) + 2) / 3) + 2];
+ debug_decl(sudo_json_add_value, SUDO_DEBUG_UTIL);
+
+ /* Add comma if we are continuing an object/array. */
+ if (json->need_comma) {
+ if (!json_append_buf(json, ","))
+ debug_return_bool(false);
+ }
+ if (!json_new_line(json))
+ debug_return_bool(false);
+ json->need_comma = true;
+
+ if (as_object) {
+ if (!json_append_buf(json, json->minimal ? "{" : "{ "))
+ debug_return_bool(false);
+ }
+
+ /* name */
+ if (name != NULL) {
+ if (!json_append_string(json, name))
+ debug_return_bool(false);
+ if (!json_append_buf(json, json->minimal ? ":" : ": "))
+ debug_return_bool(false);
+ }
+
+ /* value */
+ switch (value->type) {
+ case JSON_STRING:
+ if (!json_append_string(json, value->u.string))
+ debug_return_bool(false);
+ break;
+ case JSON_ID:
+ snprintf(numbuf, sizeof(numbuf), "%u", (unsigned int)value->u.id);
+ if (!json_append_buf(json, numbuf))
+ debug_return_bool(false);
+ break;
+ case JSON_NUMBER:
+ snprintf(numbuf, sizeof(numbuf), "%lld", value->u.number);
+ if (!json_append_buf(json, numbuf))
+ debug_return_bool(false);
+ break;
+ case JSON_NULL:
+ if (!json_append_buf(json, "null"))
+ debug_return_bool(false);
+ break;
+ case JSON_BOOL:
+ if (!json_append_buf(json, value->u.boolean ? "true" : "false"))
+ debug_return_bool(false);
+ break;
+ case JSON_ARRAY:
+ sudo_fatalx("internal error: can't print JSON_ARRAY");
+ break;
+ case JSON_OBJECT:
+ sudo_fatalx("internal error: can't print JSON_OBJECT");
+ break;
+ }
+
+ if (as_object) {
+ if (!json_append_buf(json, json->minimal ? "}" : " }"))
+ debug_return_bool(false);
+ }
+
+ debug_return_bool(true);
+}
+
+bool
+sudo_json_add_value_v1(struct json_container *json, const char *name,
+ struct json_value *value)
+{
+ return sudo_json_add_value_int(json, name, value, false);
+}
+
+bool
+sudo_json_add_value_as_object_v1(struct json_container *json, const char *name,
+ struct json_value *value)
+{
+ return sudo_json_add_value_int(json, name, value, true);
+}
+
+char *
+sudo_json_get_buf_v1(struct json_container *json)
+{
+ return json->buf;
+}
+
+unsigned int
+sudo_json_get_len_v1(struct json_container *json)
+{
+ return json->buflen;
+}
diff --git a/lib/util/key_val.c b/lib/util/key_val.c
new file mode 100644
index 0000000..91caad1
--- /dev/null
+++ b/lib/util/key_val.c
@@ -0,0 +1,56 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010-2012, 2014-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * Create a new key=value pair and return it.
+ * The caller is responsible for freeing the string.
+ */
+char *
+sudo_new_key_val_v1(const char *key, const char *val)
+{
+ size_t key_len = strlen(key);
+ size_t val_len = strlen(val);
+ char *cp, *str;
+ debug_decl(sudo_new_key_val, SUDO_DEBUG_UTIL);
+
+ cp = str = malloc(key_len + 1 + val_len + 1);
+ if (cp != NULL) {
+ memcpy(cp, key, key_len);
+ cp += key_len;
+ *cp++ = '=';
+ memcpy(cp, val, val_len);
+ cp += val_len;
+ *cp = '\0';
+ }
+
+ debug_return_str(str);
+}
diff --git a/lib/util/lbuf.c b/lib/util/lbuf.c
new file mode 100644
index 0000000..5ff069e
--- /dev/null
+++ b/lib/util/lbuf.c
@@ -0,0 +1,323 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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 <stdlib.h>
+#include <string.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..638b082
--- /dev/null
+++ b/lib/util/locking.c
@@ -0,0 +1,143 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1999-2005, 2007, 2009-2015
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_debug.h"
+
+bool
+sudo_lock_file_v1(int fd, int type)
+{
+ return sudo_lock_region_v1(fd, type, 0);
+}
+
+/*
+ * Lock/unlock all or part of a file.
+ */
+#ifdef HAVE_LOCKF
+bool
+sudo_lock_region_v1(int fd, int type, off_t len)
+{
+ int op, rc;
+ off_t oldpos = -1;
+ debug_decl(sudo_lock_region, SUDO_DEBUG_UTIL);
+
+ switch (type) {
+ case SUDO_LOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: lock %d:%lld",
+ __func__, fd, (long long)len);
+ op = F_LOCK;
+ break;
+ case SUDO_TLOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: tlock %d:%lld",
+ __func__, fd, (long long)len);
+ op = F_TLOCK;
+ break;
+ case SUDO_UNLOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: unlock %d:%lld",
+ __func__, fd, (long long)len);
+ op = F_ULOCK;
+ /* Must seek to start of file to unlock the entire thing. */
+ if (len == 0 && (oldpos = lseek(fd, 0, SEEK_CUR)) != -1) {
+ if (lseek(fd, 0, SEEK_SET) == -1) {
+ sudo_debug_printf(
+ SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to seek to beginning");
+ }
+ }
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: bad lock type %d",
+ __func__, type);
+ errno = EINVAL;
+ debug_return_bool(false);
+ }
+ rc = lockf(fd, op, len);
+ if (oldpos != -1) {
+ if (lseek(fd, oldpos, SEEK_SET) == -1) {
+ sudo_debug_printf(
+ SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to restore offset");
+ }
+ }
+ debug_return_bool(rc == 0);
+}
+#else
+bool
+sudo_lock_region_v1(int fd, int type, off_t len)
+{
+ struct flock lock;
+ int func;
+ debug_decl(sudo_lock_file, SUDO_DEBUG_UTIL);
+
+ switch (type) {
+ case SUDO_LOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: lock %d:%lld",
+ __func__, fd, (long long)len);
+ lock.l_type = F_WRLCK;
+ func = F_SETLKW;
+ break;
+ case SUDO_TLOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: tlock %d:%lld",
+ __func__, fd, (long long)len);
+ lock.l_type = F_WRLCK;
+ func = F_SETLK;
+ break;
+ case SUDO_UNLOCK:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: unlock %d:%lld",
+ __func__, fd, (long long)len);
+ lock.l_type = F_UNLCK;
+ func = F_SETLK;
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: bad lock type %d",
+ __func__, type);
+ errno = EINVAL;
+ debug_return_bool(false);
+ }
+ lock.l_start = 0;
+ lock.l_len = len;
+ lock.l_pid = 0;
+ lock.l_whence = len ? SEEK_CUR : SEEK_SET;
+
+ debug_return_bool(fcntl(fd, func, &lock) == 0);
+}
+#endif
diff --git a/lib/util/logfac.c b/lib/util/logfac.c
new file mode 100644
index 0000000..97a80d9
--- /dev/null
+++ b/lib/util/logfac.c
@@ -0,0 +1,90 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1999-2005, 2007-2019
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <syslog.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * For converting between syslog numbers and strings.
+ */
+struct strmap {
+ char *name;
+ int num;
+};
+
+static const struct strmap facilities[] = {
+#ifdef LOG_AUTHPRIV
+ { "authpriv", LOG_AUTHPRIV },
+#endif
+ { "auth", LOG_AUTH },
+ { "daemon", LOG_DAEMON },
+ { "user", LOG_USER },
+ { "local0", LOG_LOCAL0 },
+ { "local1", LOG_LOCAL1 },
+ { "local2", LOG_LOCAL2 },
+ { "local3", LOG_LOCAL3 },
+ { "local4", LOG_LOCAL4 },
+ { "local5", LOG_LOCAL5 },
+ { "local6", LOG_LOCAL6 },
+ { "local7", LOG_LOCAL7 },
+ { NULL, -1 }
+};
+
+bool
+sudo_str2logfac_v1(const char *str, int *logfac)
+{
+ const struct strmap *fac;
+ debug_decl(sudo_str2logfac, SUDO_DEBUG_UTIL);
+
+ for (fac = facilities; fac->name != NULL; fac++) {
+ if (strcmp(str, fac->name) == 0) {
+ *logfac = fac->num;
+ debug_return_bool(true);
+ }
+ }
+ debug_return_bool(false);
+}
+
+const char *
+sudo_logfac2str_v1(int num)
+{
+ const struct strmap *fac;
+ debug_decl(sudo_logfac2str, SUDO_DEBUG_UTIL);
+
+ for (fac = facilities; fac->name != NULL; fac++) {
+ if (fac->num == num)
+ break;
+ }
+ debug_return_const_str(fac->name);
+}
diff --git a/lib/util/logpri.c b/lib/util/logpri.c
new file mode 100644
index 0000000..34d0602
--- /dev/null
+++ b/lib/util/logpri.c
@@ -0,0 +1,85 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1999-2005, 2007-2019
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <syslog.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * For converting between syslog numbers and strings.
+ */
+struct strmap {
+ char *name;
+ int num;
+};
+
+static const struct strmap priorities[] = {
+ { "alert", LOG_ALERT },
+ { "crit", LOG_CRIT },
+ { "debug", LOG_DEBUG },
+ { "emerg", LOG_EMERG },
+ { "err", LOG_ERR },
+ { "info", LOG_INFO },
+ { "notice", LOG_NOTICE },
+ { "warning", LOG_WARNING },
+ { "none", -1 },
+ { NULL, -1 }
+};
+
+bool
+sudo_str2logpri_v1(const char *str, int *logpri)
+{
+ const struct strmap *pri;
+ debug_decl(sudo_str2logpri, SUDO_DEBUG_UTIL);
+
+ for (pri = priorities; pri->name != NULL; pri++) {
+ if (strcmp(str, pri->name) == 0) {
+ *logpri = pri->num;
+ debug_return_bool(true);
+ }
+ }
+ debug_return_bool(false);
+}
+
+const char *
+sudo_logpri2str_v1(int num)
+{
+ const struct strmap *pri;
+ debug_decl(sudo_logpri2str, SUDO_DEBUG_UTIL);
+
+ for (pri = priorities; pri->name != NULL; pri++) {
+ if (pri->num == num)
+ break;
+ }
+ debug_return_const_str(pri->name);
+}
diff --git a/lib/util/memrchr.c b/lib/util/memrchr.c
new file mode 100644
index 0000000..a5a1177
--- /dev/null
+++ b/lib/util/memrchr.c
@@ -0,0 +1,51 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2007, 2010-2014
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_MEMRCHR
+
+#include <sys/types.h>
+
+#include "sudo_compat.h"
+
+/*
+ * Reverse memchr()
+ * Find the last occurrence of 'c' in the buffer 's' of size 'n'.
+ */
+void *
+sudo_memrchr(const void *s, int c, size_t n)
+{
+ const unsigned char *cp;
+
+ if (n != 0) {
+ cp = (unsigned char *)s + n;
+ do {
+ if (*(--cp) == (unsigned char)c)
+ return (void *)cp;
+ } while (--n != 0);
+ }
+ return (void *)0;
+}
+#endif /* HAVE_MEMRCHR */
diff --git a/lib/util/mkdir_parents.c b/lib/util/mkdir_parents.c
new file mode 100644
index 0000000..2c9f446
--- /dev/null
+++ b/lib/util/mkdir_parents.c
@@ -0,0 +1,111 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * Create any parent directories needed by path (but not path itself).
+ * Note that path is modified but is restored before it returns.
+ */
+bool
+sudo_mkdir_parents_v1(char *path, uid_t uid, gid_t gid, mode_t mode, bool quiet)
+{
+ char *slash = path;
+ debug_decl(sudo_mkdir_parents, SUDO_DEBUG_UTIL);
+
+ /* cppcheck-suppress nullPointerRedundantCheck */
+ while ((slash = strchr(slash + 1, '/')) != NULL) {
+ struct stat sb;
+ int dfd;
+
+ *slash = '\0';
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "mkdir %s, mode 0%o, uid %d, gid %d", path, (unsigned int)mode,
+ (int)uid, (int)gid);
+reopen:
+ dfd = open(path, O_RDONLY|O_NONBLOCK);
+ if (dfd == -1) {
+ if (errno != ENOENT) {
+ if (!quiet)
+ sudo_warn(U_("unable to open %s"), path);
+ goto bad;
+ }
+ if (mkdir(path, mode) == 0) {
+ if (uid != (uid_t)-1 && gid != (gid_t)-1) {
+ if (chown(path, uid, gid) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to chown %d:%d %s", __func__,
+ (int)uid, (int)gid, path);
+ }
+ }
+ } else {
+ if (errno == EEXIST)
+ goto reopen;
+ if (!quiet)
+ sudo_warn(U_("unable to mkdir %s"), path);
+ goto bad;
+ }
+ } else {
+ /* Already exists, make sure it is a directory. */
+ int rc = fstat(dfd, &sb);
+ close(dfd);
+ if (rc != 0) {
+ if (!quiet)
+ sudo_warn(U_("unable to stat %s"), path);
+ goto bad;
+ }
+ if (!S_ISDIR(sb.st_mode)) {
+ if (!quiet)
+ sudo_warnx(U_("%s exists but is not a directory (0%o)"),
+ path, (unsigned int) sb.st_mode);
+ goto bad;
+ }
+ }
+ *slash = '/';
+ }
+
+ debug_return_bool(true);
+bad:
+ /* We must restore the path before we return. */
+ *slash = '/';
+ debug_return_bool(false);
+}
diff --git a/lib/util/mksiglist.c b/lib/util/mksiglist.c
new file mode 100644
index 0000000..d730a27
--- /dev/null
+++ b/lib/util/mksiglist.c
@@ -0,0 +1,57 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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 <stdlib.h>
+#include <signal.h>
+
+#include "sudo_compat.h"
+
+sudo_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(EXIT_SUCCESS);
+}
diff --git a/lib/util/mksiglist.h b/lib/util/mksiglist.h
new file mode 100644
index 0000000..3fa25e8
--- /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 SIGBUS
+ if (sudo_sys_siglist[SIGBUS] == NULL)
+ sudo_sys_siglist[SIGBUS] = "Bus error";
+#endif
+#ifdef SIGSEGV
+ if (sudo_sys_siglist[SIGSEGV] == NULL)
+ sudo_sys_siglist[SIGSEGV] = "Memory fault";
+#endif
+#ifdef SIGSYS
+ if (sudo_sys_siglist[SIGSYS] == NULL)
+ sudo_sys_siglist[SIGSYS] = "Bad system call";
+#endif
+#ifdef SIGUNUSED
+ if (sudo_sys_siglist[SIGUNUSED] == NULL)
+ sudo_sys_siglist[SIGUNUSED] = "Unused";
+#endif
+#ifdef SIGPIPE
+ if (sudo_sys_siglist[SIGPIPE] == NULL)
+ sudo_sys_siglist[SIGPIPE] = "Broken pipe";
+#endif
+#ifdef SIGALRM
+ if (sudo_sys_siglist[SIGALRM] == NULL)
+ sudo_sys_siglist[SIGALRM] = "Alarm clock";
+#endif
+#ifdef SIGTERM
+ if (sudo_sys_siglist[SIGTERM] == NULL)
+ sudo_sys_siglist[SIGTERM] = "Terminated";
+#endif
+#ifdef SIGSTKFLT
+ if (sudo_sys_siglist[SIGSTKFLT] == NULL)
+ sudo_sys_siglist[SIGSTKFLT] = "Stack fault";
+#endif
+#ifdef SIGIO
+ if (sudo_sys_siglist[SIGIO] == NULL)
+ sudo_sys_siglist[SIGIO] = "I/O possible";
+#endif
+#ifdef SIGXCPU
+ if (sudo_sys_siglist[SIGXCPU] == NULL)
+ sudo_sys_siglist[SIGXCPU] = "CPU time limit exceeded";
+#endif
+#ifdef SIGXFSZ
+ if (sudo_sys_siglist[SIGXFSZ] == NULL)
+ sudo_sys_siglist[SIGXFSZ] = "File size limit exceeded";
+#endif
+#ifdef SIGVTALRM
+ if (sudo_sys_siglist[SIGVTALRM] == NULL)
+ sudo_sys_siglist[SIGVTALRM] = "Virtual timer expired";
+#endif
+#ifdef SIGPROF
+ if (sudo_sys_siglist[SIGPROF] == NULL)
+ sudo_sys_siglist[SIGPROF] = "Profiling timer expired";
+#endif
+#ifdef SIGWINCH
+ if (sudo_sys_siglist[SIGWINCH] == NULL)
+ sudo_sys_siglist[SIGWINCH] = "Window size change";
+#endif
+#ifdef SIGLOST
+ if (sudo_sys_siglist[SIGLOST] == NULL)
+ sudo_sys_siglist[SIGLOST] = "File lock lost";
+#endif
+#ifdef SIGUSR1
+ if (sudo_sys_siglist[SIGUSR1] == NULL)
+ sudo_sys_siglist[SIGUSR1] = "User defined signal 1";
+#endif
+#ifdef SIGUSR2
+ if (sudo_sys_siglist[SIGUSR2] == NULL)
+ sudo_sys_siglist[SIGUSR2] = "User defined signal 2";
+#endif
+#ifdef SIGPWR
+ if (sudo_sys_siglist[SIGPWR] == NULL)
+ sudo_sys_siglist[SIGPWR] = "Power-fail/Restart";
+#endif
+#ifdef SIGPOLL
+ if (sudo_sys_siglist[SIGPOLL] == NULL)
+ sudo_sys_siglist[SIGPOLL] = "Pollable event occurred";
+#endif
+#ifdef SIGSTOP
+ if (sudo_sys_siglist[SIGSTOP] == NULL)
+ sudo_sys_siglist[SIGSTOP] = "Stopped (signal)";
+#endif
+#ifdef SIGTSTP
+ if (sudo_sys_siglist[SIGTSTP] == NULL)
+ sudo_sys_siglist[SIGTSTP] = "Stopped";
+#endif
+#ifdef SIGCONT
+ if (sudo_sys_siglist[SIGCONT] == NULL)
+ sudo_sys_siglist[SIGCONT] = "Continued";
+#endif
+#ifdef SIGCHLD
+ if (sudo_sys_siglist[SIGCHLD] == NULL)
+ sudo_sys_siglist[SIGCHLD] = "Child exited";
+#endif
+#ifdef SIGCLD
+ if (sudo_sys_siglist[SIGCLD] == NULL)
+ sudo_sys_siglist[SIGCLD] = "Child exited";
+#endif
+#ifdef SIGTTIN
+ if (sudo_sys_siglist[SIGTTIN] == NULL)
+ sudo_sys_siglist[SIGTTIN] = "Stopped (tty input)";
+#endif
+#ifdef SIGTTOU
+ if (sudo_sys_siglist[SIGTTOU] == NULL)
+ sudo_sys_siglist[SIGTTOU] = "Stopped (tty output)";
+#endif
+#ifdef SIGINFO
+ if (sudo_sys_siglist[SIGINFO] == NULL)
+ sudo_sys_siglist[SIGINFO] = "Information request";
+#endif
+#ifdef SIGURG
+ if (sudo_sys_siglist[SIGURG] == NULL)
+ sudo_sys_siglist[SIGURG] = "Urgent I/O condition";
+#endif
+#ifdef SIGWAITING
+ if (sudo_sys_siglist[SIGWAITING] == NULL)
+ sudo_sys_siglist[SIGWAITING] = "No runnable LWPs";
+#endif
+#ifdef SIGLWP
+ if (sudo_sys_siglist[SIGLWP] == NULL)
+ sudo_sys_siglist[SIGLWP] = "Inter-LWP signal";
+#endif
+#ifdef SIGFREEZE
+ if (sudo_sys_siglist[SIGFREEZE] == NULL)
+ sudo_sys_siglist[SIGFREEZE] = "Checkpoint freeze";
+#endif
+#ifdef SIGTHAW
+ if (sudo_sys_siglist[SIGTHAW] == NULL)
+ sudo_sys_siglist[SIGTHAW] = "Checkpoint thaw";
+#endif
+#ifdef SIGCANCEL
+ if (sudo_sys_siglist[SIGCANCEL] == NULL)
+ sudo_sys_siglist[SIGCANCEL] = "Thread cancellation";
+#endif
diff --git a/lib/util/mksigname.c b/lib/util/mksigname.c
new file mode 100644
index 0000000..08dc112
--- /dev/null
+++ b/lib/util/mksigname.c
@@ -0,0 +1,57 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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 <stdlib.h>
+#include <signal.h>
+
+#include "sudo_compat.h"
+
+sudo_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(EXIT_SUCCESS);
+}
diff --git a/lib/util/mksigname.h b/lib/util/mksigname.h
new file mode 100644
index 0000000..ed1b19d
--- /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 SIGBUS
+ if (sudo_sys_signame[SIGBUS] == NULL)
+ sudo_sys_signame[SIGBUS] = "BUS";
+#endif
+#ifdef SIGSEGV
+ if (sudo_sys_signame[SIGSEGV] == NULL)
+ sudo_sys_signame[SIGSEGV] = "SEGV";
+#endif
+#ifdef SIGSYS
+ if (sudo_sys_signame[SIGSYS] == NULL)
+ sudo_sys_signame[SIGSYS] = "SYS";
+#endif
+#ifdef SIGUNUSED
+ if (sudo_sys_signame[SIGUNUSED] == NULL)
+ sudo_sys_signame[SIGUNUSED] = "UNUSED";
+#endif
+#ifdef SIGPIPE
+ if (sudo_sys_signame[SIGPIPE] == NULL)
+ sudo_sys_signame[SIGPIPE] = "PIPE";
+#endif
+#ifdef SIGALRM
+ if (sudo_sys_signame[SIGALRM] == NULL)
+ sudo_sys_signame[SIGALRM] = "ALRM";
+#endif
+#ifdef SIGTERM
+ if (sudo_sys_signame[SIGTERM] == NULL)
+ sudo_sys_signame[SIGTERM] = "TERM";
+#endif
+#ifdef SIGSTKFLT
+ if (sudo_sys_signame[SIGSTKFLT] == NULL)
+ sudo_sys_signame[SIGSTKFLT] = "STKFLT";
+#endif
+#ifdef SIGIO
+ if (sudo_sys_signame[SIGIO] == NULL)
+ sudo_sys_signame[SIGIO] = "IO";
+#endif
+#ifdef SIGXCPU
+ if (sudo_sys_signame[SIGXCPU] == NULL)
+ sudo_sys_signame[SIGXCPU] = "XCPU";
+#endif
+#ifdef SIGXFSZ
+ if (sudo_sys_signame[SIGXFSZ] == NULL)
+ sudo_sys_signame[SIGXFSZ] = "XFSZ";
+#endif
+#ifdef SIGVTALRM
+ if (sudo_sys_signame[SIGVTALRM] == NULL)
+ sudo_sys_signame[SIGVTALRM] = "VTALRM";
+#endif
+#ifdef SIGPROF
+ if (sudo_sys_signame[SIGPROF] == NULL)
+ sudo_sys_signame[SIGPROF] = "PROF";
+#endif
+#ifdef SIGWINCH
+ if (sudo_sys_signame[SIGWINCH] == NULL)
+ sudo_sys_signame[SIGWINCH] = "WINCH";
+#endif
+#ifdef SIGLOST
+ if (sudo_sys_signame[SIGLOST] == NULL)
+ sudo_sys_signame[SIGLOST] = "LOST";
+#endif
+#ifdef SIGUSR1
+ if (sudo_sys_signame[SIGUSR1] == NULL)
+ sudo_sys_signame[SIGUSR1] = "USR1";
+#endif
+#ifdef SIGUSR2
+ if (sudo_sys_signame[SIGUSR2] == NULL)
+ sudo_sys_signame[SIGUSR2] = "USR2";
+#endif
+#ifdef SIGPWR
+ if (sudo_sys_signame[SIGPWR] == NULL)
+ sudo_sys_signame[SIGPWR] = "PWR";
+#endif
+#ifdef SIGPOLL
+ if (sudo_sys_signame[SIGPOLL] == NULL)
+ sudo_sys_signame[SIGPOLL] = "POLL";
+#endif
+#ifdef SIGSTOP
+ if (sudo_sys_signame[SIGSTOP] == NULL)
+ sudo_sys_signame[SIGSTOP] = "STOP";
+#endif
+#ifdef SIGTSTP
+ if (sudo_sys_signame[SIGTSTP] == NULL)
+ sudo_sys_signame[SIGTSTP] = "TSTP";
+#endif
+#ifdef SIGCONT
+ if (sudo_sys_signame[SIGCONT] == NULL)
+ sudo_sys_signame[SIGCONT] = "CONT";
+#endif
+#ifdef SIGCHLD
+ if (sudo_sys_signame[SIGCHLD] == NULL)
+ sudo_sys_signame[SIGCHLD] = "CHLD";
+#endif
+#ifdef SIGCLD
+ if (sudo_sys_signame[SIGCLD] == NULL)
+ sudo_sys_signame[SIGCLD] = "CLD";
+#endif
+#ifdef SIGTTIN
+ if (sudo_sys_signame[SIGTTIN] == NULL)
+ sudo_sys_signame[SIGTTIN] = "TTIN";
+#endif
+#ifdef SIGTTOU
+ if (sudo_sys_signame[SIGTTOU] == NULL)
+ sudo_sys_signame[SIGTTOU] = "TTOU";
+#endif
+#ifdef SIGINFO
+ if (sudo_sys_signame[SIGINFO] == NULL)
+ sudo_sys_signame[SIGINFO] = "INFO";
+#endif
+#ifdef SIGURG
+ if (sudo_sys_signame[SIGURG] == NULL)
+ sudo_sys_signame[SIGURG] = "URG";
+#endif
+#ifdef SIGWAITING
+ if (sudo_sys_signame[SIGWAITING] == NULL)
+ sudo_sys_signame[SIGWAITING] = "WAITING";
+#endif
+#ifdef SIGLWP
+ if (sudo_sys_signame[SIGLWP] == NULL)
+ sudo_sys_signame[SIGLWP] = "LWP";
+#endif
+#ifdef SIGFREEZE
+ if (sudo_sys_signame[SIGFREEZE] == NULL)
+ sudo_sys_signame[SIGFREEZE] = "FREEZE";
+#endif
+#ifdef SIGTHAW
+ if (sudo_sys_signame[SIGTHAW] == NULL)
+ sudo_sys_signame[SIGTHAW] = "THAW";
+#endif
+#ifdef SIGCANCEL
+ if (sudo_sys_signame[SIGCANCEL] == NULL)
+ sudo_sys_signame[SIGCANCEL] = "CANCEL";
+#endif
diff --git a/lib/util/mktemp.c b/lib/util/mktemp.c
new file mode 100644
index 0000000..c4ac2b3
--- /dev/null
+++ b/lib/util/mktemp.c
@@ -0,0 +1,123 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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/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
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_rand.h"
+#include "pathnames.h"
+
+#define MKTEMP_FILE 1
+#define MKTEMP_DIR 2
+
+#define TEMPCHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+#define NUM_CHARS (sizeof(TEMPCHARS) - 1)
+#define MIN_X 6
+
+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..96082f5
--- /dev/null
+++ b/lib/util/nanosleep.c
@@ -0,0 +1,65 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2011, 2013, 2017-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_NANOSLEEP
+
+#include <sys/types.h>
+#include <sys/time.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif /* HAVE_SYS_SELECT_H */
+#include <time.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+int
+sudo_nanosleep(const struct timespec *ts, struct timespec *rts)
+{
+ struct timeval timeout, endtime, now;
+ int rval;
+
+ if (ts->tv_sec == 0 && ts->tv_nsec < 1000) {
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 1;
+ } else {
+ TIMESPEC_TO_TIMEVAL(&timeout, ts);
+ }
+ if (rts != NULL) {
+ if (gettimeofday(&endtime, NULL) == -1)
+ return -1;
+ sudo_timevaladd(&endtime, &timeout, &endtime);
+ }
+ rval = select(0, NULL, NULL, NULL, &timeout);
+ if (rts != NULL && rval == -1 && errno == EINTR) {
+ if (gettimeofday(&now, NULL) == -1)
+ return -1;
+ sudo_timevalsub(&endtime, &now, &endtime);
+ TIMEVAL_TO_TIMESPEC(&endtime, rts);
+ }
+ return rval;
+}
+#endif /* HAVE_NANOSLEEP */
diff --git a/lib/util/openat.c b/lib/util/openat.c
new file mode 100644
index 0000000..5431525
--- /dev/null
+++ b/lib/util/openat.c
@@ -0,0 +1,63 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2015, 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_OPENAT
+int
+sudo_openat(int dfd, const char *path, int flags, mode_t mode)
+{
+ int fd, odfd;
+
+ if (dfd == AT_FDCWD)
+ return open(path, flags, mode);
+
+ /* Save cwd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ return -1;
+
+ if (fchdir(dfd) == -1) {
+ close(odfd);
+ return -1;
+ }
+
+ fd = open(path, flags, mode);
+
+ /* Restore cwd */
+ if (fchdir(odfd) == -1) {
+ /* Should not happen */
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+ }
+ close(odfd);
+
+ return fd;
+}
+#endif /* HAVE_OPENAT */
diff --git a/lib/util/parseln.c b/lib/util/parseln.c
new file mode 100644
index 0000000..2d86bcc
--- /dev/null
+++ b/lib/util/parseln.c
@@ -0,0 +1,131 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2007, 2013-2016 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_debug.h"
+
+/*
+ * Read a line of input, honoring line continuation chars.
+ * Remove comments and strip off leading and trailing spaces.
+ * Returns the line length and updates the buf and bufsize pointers.
+ * XXX - just use a struct w/ state, including getdelim buffer?
+ * could also make comment char and line continuation configurable
+ */
+ssize_t
+sudo_parseln_v2(char **bufp, size_t *bufsizep, unsigned int *lineno, FILE *fp, int flags)
+{
+ size_t linesize = 0, total = 0;
+ ssize_t len;
+ char *cp, *line = NULL;
+ bool continued, comment;
+ debug_decl(sudo_parseln, SUDO_DEBUG_UTIL);
+
+ do {
+ comment = false;
+ continued = false;
+ len = getdelim(&line, &linesize, '\n', fp);
+ if (len == -1)
+ break;
+ if (lineno != NULL)
+ (*lineno)++;
+
+ /* Remove trailing newline(s) if present. */
+ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+ line[--len] = '\0';
+
+ /* Remove comments or check for line continuation (but not both) */
+ if ((cp = strchr(line, '#')) != NULL) {
+ if (cp == line || !ISSET(flags, PARSELN_COMM_BOL)) {
+ *cp = '\0';
+ len = (ssize_t)(cp - line);
+ comment = true;
+ }
+ }
+ if (!comment && !ISSET(flags, PARSELN_CONT_IGN)) {
+ if (len > 0 && line[len - 1] == '\\' && (len == 1 || line[len - 2] != '\\')) {
+ line[--len] = '\0';
+ continued = true;
+ }
+ }
+
+ /* Trim leading and trailing whitespace */
+ if (!continued) {
+ while (len > 0 && isblank((unsigned char)line[len - 1]))
+ line[--len] = '\0';
+ }
+ for (cp = line; isblank((unsigned char)*cp); cp++)
+ len--;
+
+ if (*bufp == NULL || total + len >= *bufsizep) {
+ void *tmp;
+ size_t size = total + len + 1;
+
+ if (size < 64) {
+ size = 64;
+ } else if (size <= 0x80000000) {
+ /* Round up to next highest power of two. */
+ size--;
+ size |= size >> 1;
+ size |= size >> 2;
+ size |= size >> 4;
+ size |= size >> 8;
+ size |= size >> 16;
+ size++;
+ }
+ if ((tmp = realloc(*bufp, size)) == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to allocate memory");
+ len = -1;
+ total = 0;
+ break;
+ }
+ *bufp = tmp;
+ *bufsizep = size;
+ }
+ memcpy(*bufp + total, cp, len + 1);
+ total += len;
+ } while (continued);
+ free(line);
+ if (len == -1 && total == 0)
+ debug_return_ssize_t(-1);
+ debug_return_ssize_t(total);
+}
+
+ssize_t
+sudo_parseln_v1(char **bufp, size_t *bufsizep, unsigned int *lineno, FILE *fp)
+{
+ return sudo_parseln_v2(bufp, bufsizep, lineno, fp, 0);
+}
diff --git a/lib/util/pipe2.c b/lib/util/pipe2.c
new file mode 100644
index 0000000..43527a0
--- /dev/null
+++ b/lib/util/pipe2.c
@@ -0,0 +1,64 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2017 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_PIPE2
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+int
+sudo_pipe2(int fildes[2], int flags)
+{
+ if (pipe(fildes) != 0)
+ return -1;
+
+ if (ISSET(flags, O_CLOEXEC)) {
+ if (fcntl(fildes[0], F_SETFD, FD_CLOEXEC) == -1)
+ goto bad;
+ if (fcntl(fildes[1], F_SETFD, FD_CLOEXEC) == -1)
+ goto bad;
+ }
+ if (ISSET(flags, O_NONBLOCK)) {
+ int oflags = fcntl(fildes[0], F_GETFL, 0);
+ if (oflags == -1)
+ goto bad;
+ if (fcntl(fildes[0], F_SETFL, oflags | O_NONBLOCK) == -1)
+ goto bad;
+ oflags = fcntl(fildes[1], F_GETFL, 0);
+ if (oflags == -1)
+ goto bad;
+ if (fcntl(fildes[1], F_SETFL, oflags | O_NONBLOCK) == -1)
+ goto bad;
+ }
+ return 0;
+bad:
+ close(fildes[0]);
+ close(fildes[1]);
+ return -1;
+}
+
+#endif /* HAVE_PIPE2 */
diff --git a/lib/util/pread.c b/lib/util/pread.c
new file mode 100644
index 0000000..9328c54
--- /dev/null
+++ b/lib/util/pread.c
@@ -0,0 +1,48 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#if !defined(HAVE_PREAD) && !defined(HAVE_PREAD64)
+ssize_t
+sudo_pread(int fd, void *buf, size_t nbytes, off_t offset)
+{
+ ssize_t nread;
+ off_t old_offset;
+
+ old_offset = lseek(fd, (off_t)0, SEEK_CUR);
+ if (old_offset == -1 || lseek(fd, offset, SEEK_SET) == -1)
+ return -1;
+
+ nread = read(fd, buf, nbytes);
+ if (lseek(fd, old_offset, SEEK_SET) == -1)
+ return -1;
+
+ return nread;
+}
+#endif /* !HAVE_PREAD && !HAVE_PREAD64 */
diff --git a/lib/util/progname.c b/lib/util/progname.c
new file mode 100644
index 0000000..b23a766
--- /dev/null
+++ b/lib/util/progname.c
@@ -0,0 +1,135 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#ifdef HAVE_GETPROGNAME
+
+# ifndef HAVE_SETPROGNAME
+/* Assume __progname if have getprogname(3) but not setprogname(3). */
+extern const char *__progname;
+
+void
+sudo_setprogname(const char *name)
+{
+ const char *slash = strrchr(name, '/');
+ __progname = slash ? slash + 1 : name;
+}
+# endif
+
+void
+initprogname2(const char *name, const char * const * allowed)
+{
+ const char *progname;
+ int i;
+
+ /* Fall back on "name" if getprogname() returns an empty string. */
+ if ((progname = getprogname()) != NULL && *progname != '\0')
+ name = progname;
+
+ /* Check for libtool prefix and strip it if present. */
+ if (name[0] == 'l' && name[1] == 't' && name[2] == '-' && name[3] != '\0')
+ name += 3;
+
+ /* Check allow list if present (first element is the default). */
+ if (allowed != NULL) {
+ for (i = 0; ; i++) {
+ if (allowed[i] == NULL) {
+ name = allowed[0];
+ break;
+ }
+ if (strcmp(allowed[i], name) == 0)
+ break;
+ }
+ }
+
+ /* Update internal progname if needed. */
+ if (name != progname)
+ setprogname(name);
+ return;
+}
+
+#else /* !HAVE_GETPROGNAME */
+
+static const char *progname = "";
+
+void
+initprogname2(const char *name, const char * const * allowed)
+{
+ int i;
+# 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;
+
+ /* Check allow list if present (first element is the default). */
+ if (allowed != NULL) {
+ for (i = 0; ; i++) {
+ if (allowed[i] == NULL) {
+ progname = allowed[0];
+ break;
+ }
+ if (strcmp(allowed[i], progname) == 0)
+ break;
+ }
+ }
+}
+
+const char *
+sudo_getprogname(void)
+{
+ return progname;
+}
+
+void
+sudo_setprogname(const char *name)
+{
+ const char *slash = strrchr(name, '/');
+ progname = slash ? slash + 1 : name;
+}
+#endif /* !HAVE_GETPROGNAME */
+
+void
+initprogname(const char *name)
+{
+ initprogname2(name, NULL);
+}
diff --git a/lib/util/pw_dup.c b/lib/util/pw_dup.c
new file mode 100644
index 0000000..ccd2fbb
--- /dev/null
+++ b/lib/util/pw_dup.c
@@ -0,0 +1,99 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2000, 2002, 2012-2014
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_PW_DUP
+
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+
+#include "sudo_compat.h"
+
+#define PW_SIZE(name, size) \
+do { \
+ if (pw->name) { \
+ size = strlen(pw->name) + 1; \
+ total += size; \
+ } \
+} while (0)
+
+#define PW_COPY(name, size) \
+do { \
+ if (pw->name) { \
+ (void)memcpy(cp, pw->name, size); \
+ newpw->name = cp; \
+ cp += size; \
+ } \
+} while (0)
+
+struct passwd *
+sudo_pw_dup(const struct passwd *pw)
+{
+ size_t nsize = 0, psize = 0, gsize = 0, dsize = 0, ssize = 0, total;
+#ifdef HAVE_LOGIN_CAP_H
+ size_t csize = 0;
+#endif
+ struct passwd *newpw;
+ char *cp;
+
+ /* Allocate in one big chunk for easy freeing */
+ total = sizeof(struct passwd);
+ PW_SIZE(pw_name, nsize);
+ PW_SIZE(pw_passwd, psize);
+#ifdef HAVE_LOGIN_CAP_H
+ PW_SIZE(pw_class, csize);
+#endif
+ PW_SIZE(pw_gecos, gsize);
+ PW_SIZE(pw_dir, dsize);
+ PW_SIZE(pw_shell, ssize);
+
+ if ((cp = malloc(total)) == NULL)
+ return NULL;
+ newpw = (struct passwd *)cp;
+
+ /*
+ * Copy in passwd contents and make strings relative to space
+ * at the end of the buffer.
+ */
+ (void)memcpy(newpw, pw, sizeof(struct passwd));
+ cp += sizeof(struct passwd);
+
+ PW_COPY(pw_name, nsize);
+ PW_COPY(pw_passwd, psize);
+#ifdef HAVE_LOGIN_CAP_H
+ PW_COPY(pw_class, csize);
+#endif
+ PW_COPY(pw_gecos, gsize);
+ PW_COPY(pw_dir, dsize);
+ PW_COPY(pw_shell, ssize);
+
+ return newpw;
+}
+#endif /* HAVE_PW_DUP */
diff --git a/lib/util/pwrite.c b/lib/util/pwrite.c
new file mode 100644
index 0000000..48f8cbf
--- /dev/null
+++ b/lib/util/pwrite.c
@@ -0,0 +1,48 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#if !defined(HAVE_PWRITE) && !defined(HAVE_PWRITE64)
+ssize_t
+sudo_pwrite(int fd, const void *buf, size_t nbytes, off_t offset)
+{
+ ssize_t nwritten;
+ off_t old_offset;
+
+ old_offset = lseek(fd, (off_t)0, SEEK_CUR);
+ if (old_offset == -1 || lseek(fd, offset, SEEK_SET) == -1)
+ return -1;
+
+ nwritten = write(fd, buf, nbytes);
+ if (lseek(fd, old_offset, SEEK_SET) == -1)
+ return -1;
+
+ return nwritten;
+}
+#endif /* HAVE_PWRITE && !HAVE_PWRITE64 */
diff --git a/lib/util/reallocarray.c b/lib/util/reallocarray.c
new file mode 100644
index 0000000..56283e7
--- /dev/null
+++ b/lib/util/reallocarray.c
@@ -0,0 +1,57 @@
+/* $OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $ */
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_REALLOCARRAY
+
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <errno.h>
+#include <limits.h>
+
+#include "sudo_compat.h"
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+
+void *
+sudo_reallocarray(void *optr, size_t nmemb, size_t size)
+{
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return realloc(optr, size * nmemb);
+}
+
+#endif /* HAVE_REALLOCARRAY */
diff --git a/lib/util/regress/fnmatch/fnm_test.c b/lib/util/regress/fnmatch/fnm_test.c
new file mode 100644
index 0000000..9f2f01c
--- /dev/null
+++ b/lib/util/regress/fnmatch/fnm_test.c
@@ -0,0 +1,78 @@
+/* $OpenBSD: fnm_test.c,v 1.1 2008/10/01 23:04:58 millert Exp $ */
+
+/*
+ * Public domain, 2008, Todd C. Miller <Todd.Miller@sudo.ws>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_FNMATCH
+# include <fnmatch.h>
+#else
+# include "compat/fnmatch.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp = stdin;
+ char pattern[1024], string[1024], flagstr[1024];
+ int 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(EXIT_FAILURE);
+ }
+ }
+
+ /*
+ * Read in test file, which is formatted thusly:
+ *
+ * pattern string flags expected_result
+ *
+ */
+ for (;;) {
+ got = fscanf(fp, "%s %s %s %d\n", pattern, string, flagstr,
+ &want);
+ if (got == EOF)
+ break;
+ if (got == 4) {
+ flags = 0;
+ if (strcmp(flagstr, "FNM_NOESCAPE") == 0)
+ flags |= FNM_NOESCAPE;
+ else if (strcmp(flagstr, "FNM_PATHNAME") == 0)
+ flags |= FNM_PATHNAME;
+ else if (strcmp(flagstr, "FNM_PERIOD") == 0)
+ flags |= FNM_PERIOD;
+ else if (strcmp(flagstr, "FNM_LEADING_DIR") == 0)
+ flags |= FNM_LEADING_DIR;
+ else if (strcmp(flagstr, "FNM_CASEFOLD") == 0)
+ flags |= FNM_CASEFOLD;
+ got = fnmatch(pattern, string, flags);
+ if (got != want) {
+ fprintf(stderr,
+ "fnmatch: %s %s %d: want %d, got %d\n",
+ pattern, string, flags, want, got);
+ errors++;
+ }
+ 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/getdelim/getdelim_test.c b/lib/util/regress/getdelim/getdelim_test.c
new file mode 100644
index 0000000..6a550bc
--- /dev/null
+++ b/lib/util/regress/getdelim/getdelim_test.c
@@ -0,0 +1,144 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_getdelim() works as expected.
+ */
+
+struct getdelim_test {
+ const char *input;
+ const char *output[4];
+ int delim;
+};
+
+/*
+ * TODO: test error case.
+ * test realloc case (buf > LINE_MAX)
+ */
+static struct getdelim_test test_data[] = {
+ { "a\nb\nc\n", { "a\n", "b\n", "c\n", NULL }, '\n' },
+ { "a\nb\nc", { "a\n", "b\n", "c", NULL }, '\n' },
+ { "a\tb\tc\t", { "a\t", "b\t", "c\t", NULL }, '\t' },
+ { "a\tb\tc", { "a\t", "b\t", "c", NULL }, '\t' },
+ { NULL, { NULL }, '\0' }
+};
+
+static int errors = 0, ntests = 0;
+
+static void
+runtests(char **buf, size_t *buflen)
+{
+ int i, j, sv[2];
+ pid_t pid;
+ FILE *fp;
+
+ for (i = 0; test_data[i].input != NULL; i++) {
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1)
+ sudo_fatal_nodebug("socketpair");
+
+ switch ((pid = fork())) {
+ case -1:
+ sudo_fatal_nodebug("fork");
+ case 0:
+ /* child */
+ close(sv[0]);
+ if (send(sv[1], test_data[i].input, strlen(test_data[i].input), 0) == -1) {
+ sudo_warn_nodebug("send");
+ _exit(127);
+ }
+ _exit(EXIT_SUCCESS);
+ break;
+ default:
+ /* parent */
+ break;
+ }
+
+ close(sv[1]);
+ if ((fp = fdopen(sv[0], "r")) == NULL)
+ sudo_fatal_nodebug("fdopen");
+
+ for (j = 0; test_data[i].output[j] != NULL; j++) {
+ ntests++;
+ alarm(10);
+ if (getdelim(buf, buflen, test_data[i].delim, fp) == -1)
+ sudo_fatal_nodebug("getdelim");
+ alarm(0);
+ if (strcmp(*buf, test_data[i].output[j]) != 0) {
+ sudo_warnx_nodebug("failed test #%d: expected %s, got %s",
+ ntests, test_data[i].output[j], *buf);
+ errors++;
+ }
+ }
+ /* test EOF */
+ ntests++;
+ alarm(30);
+ if (getdelim(buf, buflen, test_data[i].delim, fp) != -1) {
+ sudo_warnx_nodebug("failed test #%d: expected EOF, got %s",
+ ntests, *buf);
+ errors++;
+ } else {
+ if (!feof(fp)) {
+ sudo_warn_nodebug("failed test #%d: expected EOF, got error",
+ ntests);
+ errors++;
+ }
+ }
+ fclose(fp);
+ waitpid(pid, NULL, 0);
+ alarm(0);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ size_t buflen = 0;
+ char *buf = NULL;
+
+ initprogname(argc > 0 ? argv[0] : "getdelim_test");
+
+ runtests(&buf, &buflen);
+
+ /* XXX - redo tests with preallocated buffer filled with junk */
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ exit(errors);
+}
diff --git a/lib/util/regress/getgrouplist/getgrouplist_test.c b/lib/util/regress/getgrouplist/getgrouplist_test.c
new file mode 100644
index 0000000..fd9fefc
--- /dev/null
+++ b/lib/util/regress/getgrouplist/getgrouplist_test.c
@@ -0,0 +1,100 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <pwd.h>
+#include <grp.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_getgrouplist2() works as expected.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ int errors = 0;
+#ifndef HAVE_GETGROUPLIST_2
+ GETGROUPS_T *groups = NULL;
+ struct passwd *pw;
+ struct group *grp;
+ char *username;
+ int 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..a98d03c
--- /dev/null
+++ b/lib/util/regress/glob/globtest.c
@@ -0,0 +1,210 @@
+/* $OpenBSD: globtest.c,v 1.1 2008/10/01 23:04:36 millert Exp $ */
+
+/*
+ * Public domain, 2008, Todd C. Miller <Todd.Miller@sudo.ws>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_GLOB
+# include <glob.h>
+#else
+# include "compat/glob.h"
+#endif
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#define MAX_RESULTS 256
+
+struct gl_entry {
+ int flags;
+ int nresults;
+ char pattern[1024];
+ char *results[MAX_RESULTS];
+};
+
+int test_glob(struct gl_entry *);
+sudo_dso_public int main(int argc, char *argv[]);
+
+int
+main(int argc, char **argv)
+{
+ FILE *fp = stdin;
+ char buf[2048], *cp, *ep;
+ int 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(EXIT_FAILURE);
+ }
+ }
+
+ /*
+ * Read in test file, which is formatted thusly:
+ *
+ * [pattern] <flags>
+ * result1
+ * result2
+ * result3
+ * ...
+ *
+ */
+ lineno = 0;
+ memset(&entry, 0, sizeof(entry));
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ lineno++;
+ len = strlen(buf);
+ if (len > 0) {
+ if (buf[len - 1] != '\n') {
+ fprintf(stderr,
+ "globtest: missing newline at EOF\n");
+ exit(EXIT_FAILURE);
+ }
+ buf[--len] = '\0';
+ }
+ if (len == 0)
+ continue; /* blank line */
+
+ if (buf[0] == '[') {
+ /* check previous pattern */
+ if (entry.pattern[0]) {
+ errors += test_glob(&entry);
+ tests++;
+ }
+
+ /* start new entry */
+ if ((cp = strrchr(buf + 1, ']')) == NULL) {
+ fprintf(stderr,
+ "globtest: invalid entry on line %d\n",
+ lineno);
+ exit(EXIT_FAILURE);
+ }
+ len = cp - buf - 1;
+ if (len >= sizeof(entry.pattern)) {
+ fprintf(stderr,
+ "globtest: pattern too big on line %d\n",
+ lineno);
+ exit(EXIT_FAILURE);
+ }
+ memcpy(entry.pattern, buf + 1, len);
+ entry.pattern[len] = '\0';
+
+ cp += 2;
+ if (*cp++ != '<') {
+ fprintf(stderr,
+ "globtest: invalid entry on line %d\n",
+ lineno);
+ exit(EXIT_FAILURE);
+ }
+ ep = strchr(cp, '>');
+ if (ep == NULL) {
+ fprintf(stderr,
+ "globtest: invalid entry on line %d\n",
+ lineno);
+ exit(EXIT_FAILURE);
+ }
+ *ep = '\0';
+ entry.flags = 0;
+ for ((cp = strtok_r(cp, "|", &ep)); cp != NULL; (cp = strtok_r(NULL, "|", &ep))) {
+ if (strcmp(cp, "GLOB_APPEND") == 0)
+ entry.flags |= GLOB_APPEND;
+ else if (strcmp(cp, "GLOB_DOOFFS") == 0)
+ entry.flags |= GLOB_DOOFFS;
+ else if (strcmp(cp, "GLOB_ERR") == 0)
+ entry.flags |= GLOB_ERR;
+ else if (strcmp(cp, "GLOB_MARK") == 0)
+ entry.flags |= GLOB_MARK;
+ else if (strcmp(cp, "GLOB_NOCHECK") == 0)
+ entry.flags |= GLOB_NOCHECK;
+ else if (strcmp(cp, "GLOB_NOSORT") == 0)
+ entry.flags |= GLOB_NOSORT;
+ else if (strcmp(cp, "GLOB_NOESCAPE") == 0)
+ entry.flags |= GLOB_NOESCAPE;
+ else if (strcmp(cp, "GLOB_BRACE") == 0)
+ entry.flags |= GLOB_BRACE;
+ else if (strcmp(cp, "GLOB_TILDE") == 0)
+ entry.flags |= GLOB_TILDE;
+ else if (strcmp(cp, "NONE") != 0) {
+ fprintf(stderr,
+ "globtest: invalid flags on line %d\n",
+ lineno);
+ exit(EXIT_FAILURE);
+ }
+ }
+ entry.nresults = 0;
+ continue;
+ }
+ if (!entry.pattern[0]) {
+ fprintf(stderr, "globtest: missing entry on line %d\n",
+ lineno);
+ exit(EXIT_FAILURE);
+ }
+
+ if (entry.nresults + 1 > MAX_RESULTS) {
+ fprintf(stderr,
+ "globtest: too many results for %s, max %d\n",
+ entry.pattern, MAX_RESULTS);
+ exit(EXIT_FAILURE);
+ }
+ 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(EXIT_FAILURE);
+ }
+
+ 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..cd5aa97
--- /dev/null
+++ b/lib/util/regress/mktemp/mktemp_test.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2010 Philip Guenther <guenther@openbsd.org>
+ *
+ * Public domain.
+ *
+ * Verify that mkdtemp() and mkstemps() doesn't overrun or underrun
+ * the template buffer and that it can generate names that don't
+ * contain any X's
+ */
+
+#include <config.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+#ifndef MAP_ANON
+# if defined(MAP_ANONYMOUS)
+# define MAP_ANON MAP_ANONYMOUS
+# endif
+#endif
+
+#define MAX_TEMPLATE_LEN 10
+#define MAX_TRIES 100
+#define MIN_Xs 6
+
+#define SUFFIX ".suff"
+#define SLEN (sizeof SUFFIX - 1)
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * verify that a path generated by mkdtemp() or mkstemp() looks like a
+ * reasonable expansion of the template and matches the fd. Returns true
+ * if all the X's were replaced with non-X's
+ */
+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..d96a8a0
--- /dev/null
+++ b/lib/util/regress/parse_gids/parse_gids_test.c
@@ -0,0 +1,105 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_parse_gids() works as expected.
+ */
+
+struct parse_gids_test {
+ const char *gids;
+ gid_t *baseptr;
+ gid_t basegid;
+ int ngids;
+ const GETGROUPS_T *gidlist;
+};
+
+static const GETGROUPS_T test1_out[] = { 0, 1, 2, 3, 4 };
+static const GETGROUPS_T test2_out[] = { 1, 2, 3, 4 };
+static const GETGROUPS_T test3_out[] = { 0, 1, (gid_t)-2, 3, 4 };
+
+/* XXX - test syntax errors too */
+static struct parse_gids_test test_data[] = {
+ { "1,2,3,4", &test_data[0].basegid, 0, 5, test1_out },
+ { "1,2,3,4", NULL, 0, 4, test2_out },
+ { "1,-2,3,4", &test_data[2].basegid, 0, 5, test3_out },
+ { NULL, false, 0, 0, NULL }
+};
+
+static void
+dump_gids(const char *prefix, int ngids, const GETGROUPS_T *gidlist)
+{
+ int i;
+
+ fprintf(stderr, "%s: %s: ", getprogname(), prefix);
+ for (i = 0; i < ngids; i++) {
+ fprintf(stderr, "%s%d", i ? ", " : "", (int)gidlist[i]);
+ }
+ fputc('\n', stderr);
+}
+
+int
+main(int argc, char *argv[])
+{
+ GETGROUPS_T *gidlist = NULL;
+ int i, j, errors = 0, ntests = 0;
+ int 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(EXIT_FAILURE); /* 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..0ac5398
--- /dev/null
+++ b/lib/util/regress/progname/progname_test.c
@@ -0,0 +1,56 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that getprogname() returns the expected result.
+ * On some systems (AIX), we may have issues with symbolic links.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ 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(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/lib/util/regress/strsig/strsig_test.c b/lib/util/regress/strsig/strsig_test.c
new file mode 100644
index 0000000..573dc1d
--- /dev/null
+++ b/lib/util/regress/strsig/strsig_test.c
@@ -0,0 +1,306 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Note: we do not test SIGUNUSED as it may not appear in sys_sigabbrev[]
+ * on Linux. FreeBSD is missing SIGLWP (aka SIGTHR) in sys_signame[].
+ */
+static struct signal_data {
+ int rval;
+ int signo;
+ const char *sigstr;
+ const char *altstr;
+} signal_data[] = {
+#ifdef SIGHUP
+ { 0, SIGHUP, "HUP", NULL },
+#endif
+#ifdef SIGINT
+ { 0, SIGINT, "INT", NULL },
+#endif
+#ifdef SIGQUIT
+ { 0, SIGQUIT, "QUIT", NULL },
+#endif
+#ifdef SIGILL
+ { 0, SIGILL, "ILL", NULL },
+#endif
+#ifdef SIGTRAP
+ { 0, SIGTRAP, "TRAP", NULL },
+#endif
+#ifdef SIGABRT
+ { 0, SIGABRT, "ABRT", "IOT" },
+#endif
+#ifdef SIGIOT
+ { 0, SIGIOT, "IOT", "ABRT" },
+#endif
+#ifdef SIGEMT
+ { 0, SIGEMT, "EMT", NULL },
+#endif
+#ifdef SIGFPE
+ { 0, SIGFPE, "FPE", NULL },
+#endif
+#ifdef SIGKILL
+ { 0, SIGKILL, "KILL", NULL },
+#endif
+#ifdef SIGBUS
+ { 0, SIGBUS, "BUS", NULL },
+#endif
+#ifdef SIGSEGV
+ { 0, SIGSEGV, "SEGV", NULL },
+#endif
+#ifdef SIGSYS
+ { 0, SIGSYS, "SYS", NULL },
+#endif
+#ifdef SIGPIPE
+ { 0, SIGPIPE, "PIPE", NULL },
+#endif
+#ifdef SIGALRM
+ { 0, SIGALRM, "ALRM", NULL },
+#endif
+#ifdef SIGTERM
+ { 0, SIGTERM, "TERM", NULL },
+#endif
+#ifdef SIGSTKFLT
+ { 0, SIGSTKFLT, "STKFLT", NULL },
+#endif
+#ifdef SIGIO
+ { 0, SIGIO, "IO", "POLL"},
+#endif
+#ifdef SIGXCPU
+ { 0, SIGXCPU, "XCPU", NULL },
+#endif
+#ifdef SIGXFSZ
+ { 0, SIGXFSZ, "XFSZ", NULL },
+#endif
+#ifdef SIGVTALRM
+ { 0, SIGVTALRM, "VTALRM", NULL },
+#endif
+#ifdef SIGPROF
+ { 0, SIGPROF, "PROF", NULL },
+#endif
+#ifdef SIGWINCH
+ { 0, SIGWINCH, "WINCH", NULL },
+#endif
+#ifdef SIGLOST
+ { 0, SIGLOST, "LOST", NULL },
+#endif
+#ifdef SIGUSR1
+ { 0, SIGUSR1, "USR1", NULL },
+#endif
+#ifdef SIGUSR2
+ { 0, SIGUSR2, "USR2", NULL },
+#endif
+#ifdef SIGPWR
+ { 0, SIGPWR, "PWR", NULL },
+#endif
+#ifdef SIGPOLL
+ { 0, SIGPOLL, "POLL", "IO" },
+#endif
+#ifdef SIGSTOP
+ { 0, SIGSTOP, "STOP", NULL },
+#endif
+#ifdef SIGTSTP
+ { 0, SIGTSTP, "TSTP", NULL },
+#endif
+#ifdef SIGCONT
+ { 0, SIGCONT, "CONT", NULL },
+#endif
+#ifdef SIGCHLD
+ { 0, SIGCHLD, "CHLD", "CLD" },
+#endif
+#ifdef SIGCLD
+ { 0, SIGCLD, "CLD", "CHLD" },
+#endif
+#ifdef SIGTTIN
+ { 0, SIGTTIN, "TTIN", NULL },
+#endif
+#ifdef SIGTTOU
+ { 0, SIGTTOU, "TTOU", NULL },
+#endif
+#ifdef SIGINFO
+ { 0, SIGINFO, "INFO", NULL },
+#endif
+#ifdef SIGURG
+ { 0, SIGURG, "URG", NULL },
+#endif
+#ifdef SIGWAITING
+ { 0, SIGWAITING, "WAITING", NULL },
+#endif
+#if defined(SIGLWP) && !defined(__FreeBSD__)
+ { 0, SIGLWP, "LWP", NULL },
+#endif
+#ifdef SIGFREEZE
+ { 0, SIGFREEZE, "FREEZE", NULL },
+#endif
+#ifdef SIGTHAW
+ { 0, SIGTHAW, "THAW", NULL },
+#endif
+#ifdef SIGCANCEL
+ { 0, SIGCANCEL, "CANCEL", NULL },
+#endif
+#if defined(SIGRTMIN) && defined(SIGRTMAX)
+ { 0, -1, "RTMIN", NULL },
+ { 0, -1, "RTMIN+1", NULL },
+ { 0, -1, "RTMIN+2", NULL },
+ { 0, -1, "RTMIN+3", NULL },
+ { 0, -1, "RTMAX-3", NULL },
+ { 0, -1, "RTMAX-2", NULL },
+ { 0, -1, "RTMAX-1", NULL },
+ { 0, -1, "RTMAX", NULL },
+#endif
+ { -1, 1024, "QWERT", NULL }, /* invalid */
+ { -1, 0, NULL, NULL }
+};
+
+#ifndef HAVE_SIG2STR
+static int
+test_sig2str(int *ntests)
+{
+ struct signal_data *d;
+ int rval, errors = 0;
+ char sigstr[SIG2STR_MAX];
+
+ for (d = signal_data; d->signo != 0; d++) {
+ (*ntests)++;
+ rval = sudo_sig2str(d->signo, sigstr);
+ if (rval != d->rval) {
+ sudo_warnx_nodebug("FAIL: sig2str(SIG%s): %d != %d",
+ d->sigstr, rval, d->rval);
+ errors++;
+ continue;
+ }
+ if (rval != 0)
+ continue;
+ if (strcmp(sigstr, d->sigstr) != 0 &&
+ (d->altstr != NULL && strcmp(sigstr, d->altstr) != 0)) {
+ sudo_warnx_nodebug("FAIL: signal %d: %s != %s", d->signo,
+ sigstr, d->sigstr);
+ errors++;
+ continue;
+ }
+ }
+
+ return errors;
+}
+#else
+static int
+test_sig2str(int *ntests)
+{
+ return 0;
+}
+#endif /* HAVE_SIG2STR */
+
+#ifndef HAVE_STR2SIG
+static int
+test_str2sig(int *ntests)
+{
+ struct signal_data *d;
+ int rval, errors = 0;
+ int signo;
+
+ for (d = signal_data; d->sigstr != NULL; d++) {
+ (*ntests)++;
+ rval = sudo_str2sig(d->sigstr, &signo);
+ if (rval != d->rval) {
+ sudo_warnx_nodebug("FAIL: str2sig(SIG%s): %d != %d",
+ d->sigstr, rval, d->rval);
+ errors++;
+ continue;
+ }
+ if (rval != 0)
+ continue;
+ if (signo != d->signo) {
+ sudo_warnx_nodebug("FAIL: signal SIG%s: %d != %d", d->sigstr,
+ signo, d->signo);
+ errors++;
+ continue;
+ }
+ }
+
+ return errors;
+}
+#else
+static int
+test_str2sig(int *ntests)
+{
+ return 0;
+}
+#endif /* HAVE_STR2SIG */
+
+#if defined(SIGRTMIN) && defined(SIGRTMAX)
+static
+void init_sigrt(void)
+{
+ int i;
+
+ /* Initialize real-time signal values. */
+ for (i = 0; signal_data[i].signo != -1; i++)
+ continue;
+ signal_data[i++].signo = SIGRTMIN;
+ signal_data[i++].signo = SIGRTMIN + 1;
+ signal_data[i++].signo = SIGRTMIN + 2;
+ signal_data[i++].signo = SIGRTMIN + 3;
+ signal_data[i++].signo = SIGRTMAX - 3;
+ signal_data[i++].signo = SIGRTMAX - 2;
+ signal_data[i++].signo = SIGRTMAX - 1;
+ signal_data[i++].signo = SIGRTMAX;
+
+}
+#else
+static
+void init_sigrt(void)
+{
+ /* No real-time signals. */
+ return;
+}
+#endif
+
+/*
+ * Simple tests for sig2str() and str2sig().
+ */
+int
+main(int argc, char *argv[])
+{
+ int errors = 0;
+ int ntests = 0;
+
+ initprogname(argc > 0 ? argv[0] : "strsig_test");
+
+ init_sigrt();
+ errors += test_sig2str(&ntests);
+ errors += test_str2sig(&ntests);
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ exit(errors);
+}
diff --git a/lib/util/regress/strsplit/strsplit_test.c b/lib/util/regress/strsplit/strsplit_test.c
new file mode 100644
index 0000000..d44f19e
--- /dev/null
+++ b/lib/util/regress/strsplit/strsplit_test.c
@@ -0,0 +1,102 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_strsplit() works as expected.
+ */
+
+struct strsplit_test {
+ const char *input;
+ size_t input_len;
+ const char **output;
+};
+
+static const char test1_in[] = " vi ";
+static const char *test1_out[] = { "vi", NULL };
+static const char test2_in[] = "vi -r ";
+static const char *test2_out[] = { "vi", "-r", NULL };
+static const char test3_in[] = "vi -r -R abc\tdef ";
+static const char *test3_out[] = { "vi", "-r", "-R", "abc", "def", NULL };
+static const char test4_in[] = "vi -r -R abc\tdef ";
+static const char *test4_out[] = { "vi", "-r", "-R", "abc", NULL };
+static const char test5_in[] = "";
+static const char *test5_out[] = { NULL };
+
+static struct strsplit_test test_data[] = {
+ { test1_in, sizeof(test1_in) - 1, test1_out },
+ { test2_in, sizeof(test2_in) - 1, test2_out },
+ { test3_in, sizeof(test3_in) - 1, test3_out },
+ { test4_in, sizeof(test4_in) - 5, test4_out },
+ { test5_in, sizeof(test5_in) - 1, test5_out },
+ { NULL, 0, NULL }
+};
+
+int
+main(int argc, char *argv[])
+{
+ const char *cp, *ep, *input_end;
+ int 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/strtofoo/strtobool_test.c b/lib/util/regress/strtofoo/strtobool_test.c
new file mode 100644
index 0000000..10cd82f
--- /dev/null
+++ b/lib/util/regress/strtofoo/strtobool_test.c
@@ -0,0 +1,85 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/* sudo_strtobool() tests */
+static struct strtobool_data {
+ const char *bool_str;
+ int value;
+} strtobool_data[] = {
+ { "true", true },
+ { "false", false },
+ { "TrUe", true },
+ { "fAlSe", false },
+ { "1", true },
+ { "0", false },
+ { "on", true },
+ { "off", false },
+ { "yes", true },
+ { "no", false },
+ { "nope", -1 },
+ { "10", -1 },
+ { "one", -1 },
+ { "zero", -1 },
+ { NULL, 0 }
+};
+
+/*
+ * Simple tests for sudo_strtobool()
+ */
+int
+main(int argc, char *argv[])
+{
+ struct strtobool_data *d;
+ int errors = 0;
+ int ntests = 0;
+ int value;
+
+ initprogname(argc > 0 ? argv[0] : "strtobool_test");
+
+ for (d = strtobool_data; d->bool_str != NULL; d++) {
+ ntests++;
+ value = sudo_strtobool(d->bool_str);
+ if (value != d->value) {
+ sudo_warnx_nodebug("FAIL: %s != %d", d->bool_str, d->value);
+ errors++;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/util/regress/strtofoo/strtoid_test.c b/lib/util/regress/strtofoo/strtoid_test.c
new file mode 100644
index 0000000..306eccb
--- /dev/null
+++ b/lib/util/regress/strtofoo/strtoid_test.c
@@ -0,0 +1,105 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/* sudo_strtoidx() tests */
+static struct strtoidx_data {
+ const char *idstr;
+ id_t id;
+ const char *sep;
+ const char *ep;
+ int errnum;
+} strtoidx_data[] = {
+ { "0,1", 0, ",", ",", 0 },
+ { "10", 10, NULL, NULL, 0 },
+ { "-1", 0, NULL, NULL, EINVAL },
+ { "4294967295", 0, NULL, NULL, EINVAL },
+ { "4294967296", 0, NULL, NULL, ERANGE },
+ { "-2147483649", 0, NULL, NULL, ERANGE },
+ { "-2", -2, NULL, NULL, 0 },
+#if SIZEOF_ID_T != SIZEOF_LONG_LONG
+ { "-2", (id_t)4294967294U, NULL, NULL, 0 },
+#endif
+ { "4294967294", (id_t)4294967294U, NULL, NULL, 0 },
+ { NULL, 0, NULL, NULL, 0 }
+};
+
+/*
+ * Simple tests for sudo_strtoidx()
+ */
+int
+main(int argc, char *argv[])
+{
+ struct strtoidx_data *d;
+ const char *errstr;
+ char *ep;
+ int errors = 0;
+ int ntests = 0;
+ id_t value;
+
+ initprogname(argc > 0 ? argv[0] : "strtoid_test");
+
+ for (d = strtoidx_data; d->idstr != NULL; d++) {
+ ntests++;
+ errstr = "some error";
+ value = sudo_strtoidx(d->idstr, d->sep, &ep, &errstr);
+ if (d->errnum != 0) {
+ if (errstr == NULL) {
+ sudo_warnx_nodebug("FAIL: %s: missing errstr for errno %d",
+ d->idstr, d->errnum);
+ errors++;
+ } else if (value != 0) {
+ sudo_warnx_nodebug("FAIL: %s should return 0 on error",
+ d->idstr);
+ errors++;
+ } else if (errno != d->errnum) {
+ sudo_warnx_nodebug("FAIL: %s: errno mismatch, %d != %d",
+ d->idstr, errno, d->errnum);
+ errors++;
+ }
+ } else if (errstr != NULL) {
+ sudo_warnx_nodebug("FAIL: %s: %s", d->idstr, errstr);
+ errors++;
+ } else if (value != d->id) {
+ sudo_warnx_nodebug("FAIL: %s != %u", d->idstr, (unsigned int)d->id);
+ errors++;
+ } else if (d->ep != NULL && ep[0] != d->ep[0]) {
+ sudo_warnx_nodebug("FAIL: ep[0] %d != %d", (int)(unsigned char)ep[0],
+ (int)(unsigned char)d->ep[0]);
+ errors++;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/util/regress/strtofoo/strtomode_test.c b/lib/util/regress/strtofoo/strtomode_test.c
new file mode 100644
index 0000000..3855f2a
--- /dev/null
+++ b/lib/util/regress/strtofoo/strtomode_test.c
@@ -0,0 +1,78 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2014-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/* sudo_strtomode() tests */
+static struct strtomode_data {
+ const char *mode_str;
+ mode_t mode;
+} strtomode_data[] = {
+ { "755", 0755 },
+ { "007", 007 },
+ { "7", 7 },
+ { "8", (mode_t)-1 },
+ { NULL, 0 }
+};
+
+/*
+ * Simple tests for sudo_strtomode().
+ */
+int
+main(int argc, char *argv[])
+{
+ struct strtomode_data *d;
+ const char *errstr;
+ int errors = 0;
+ int ntests = 0;
+ mode_t mode;
+
+ initprogname(argc > 0 ? argv[0] : "strtomode_test");
+
+ for (d = strtomode_data; d->mode_str != NULL; d++) {
+ ntests++;
+ errstr = "some error";
+ mode = sudo_strtomode(d->mode_str, &errstr);
+ if (errstr != NULL) {
+ if (d->mode != (mode_t)-1) {
+ sudo_warnx_nodebug("FAIL: %s: %s", d->mode_str, errstr);
+ errors++;
+ }
+ } else if (mode != d->mode) {
+ sudo_warnx_nodebug("FAIL: %s != 0%o", d->mode_str,
+ (unsigned int) d->mode);
+ errors++;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/util/regress/strtofoo/strtonum_test.c b/lib/util/regress/strtofoo/strtonum_test.c
new file mode 100644
index 0000000..cefb88d
--- /dev/null
+++ b/lib/util/regress/strtofoo/strtonum_test.c
@@ -0,0 +1,122 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/* sudo_strtonum() tests */
+static struct strtonum_data {
+ const char *str;
+ long long minval;
+ long long maxval;
+ long long retval;
+ int errnum;
+} strtonum_data[] = {
+ { "0,1", LLONG_MIN, LLONG_MAX, 0, EINVAL },
+ { "0", INT_MAX, INT_MIN, 0, EINVAL },
+ { "", 0, UINT_MAX, 0, EINVAL },
+ { " ", 0, UINT_MAX, 0, EINVAL },
+ { "-1 ", 0, UINT_MAX, 0, EINVAL },
+ { "9223372036854775808X", LLONG_MIN, LLONG_MAX, 0, EINVAL },
+ { "-9223372036854775809X", LLONG_MIN, LLONG_MAX, 0, EINVAL },
+
+ { "10", 0, 255, 10, 0 },
+ { "-1", 0, UINT_MAX, 0, ERANGE },
+
+ { "-40", -100, -50, 0, ERANGE },
+ { "-60", -100, -50, -60, 0 },
+ { "-200", -100, -50, 0, ERANGE },
+
+ { "42", 42, 42, 42, 0 },
+ { "-42", -42, -42, -42, 0 },
+
+ { "4294967295", 0, UINT_MAX, UINT_MAX, 0 },
+ { "4294967295", INT_MIN, INT_MAX, 0, ERANGE },
+ { "4294967296", 0, UINT_MAX, 0, ERANGE },
+
+ { "2147483647", INT_MIN, INT_MAX, INT_MAX, 0 },
+ { "-2147483648", INT_MIN, INT_MAX, INT_MIN, 0 },
+ { "2147483648", INT_MIN, INT_MAX, 0, ERANGE },
+ { "-2147483649", INT_MIN, INT_MAX, 0, ERANGE },
+
+ { "9223372036854775807", LLONG_MIN, LLONG_MAX, LLONG_MAX, 0 },
+ { "-9223372036854775808", LLONG_MIN, LLONG_MAX, LLONG_MIN, 0 },
+ { "9223372036854775808", LLONG_MIN, LLONG_MAX, 0, ERANGE },
+ { "-9223372036854775809", LLONG_MIN, LLONG_MAX, 0, ERANGE },
+
+ { NULL, 0, 0, 0, 0 }
+};
+
+/*
+ * Simple tests for sudo_strtonum()
+ */
+int
+main(int argc, char *argv[])
+{
+ struct strtonum_data *d;
+ const char *errstr;
+ int errors = 0;
+ int ntests = 0;
+ long long value;
+
+ initprogname(argc > 0 ? argv[0] : "strtonum_test");
+
+ for (d = strtonum_data; d->str != NULL; d++) {
+ ntests++;
+ errstr = "some error";
+ value = sudo_strtonum(d->str, d->minval, d->maxval, &errstr);
+ if (d->errnum != 0) {
+ if (errstr == NULL) {
+ sudo_warnx_nodebug("FAIL: \"%s\": missing errstr for errno %d",
+ d->str, d->errnum);
+ errors++;
+ } else if (value != 0) {
+ sudo_warnx_nodebug("FAIL: %s should return 0 on error",
+ d->str);
+ errors++;
+ } else if (errno != d->errnum) {
+ sudo_warnx_nodebug("FAIL: \"%s\": errno mismatch, %d != %d",
+ d->str, errno, d->errnum);
+ errors++;
+ }
+ } else if (errstr != NULL) {
+ sudo_warnx_nodebug("FAIL: \"%s\": %s", d->str, errstr);
+ errors++;
+ } else if (value != d->retval) {
+ sudo_warnx_nodebug("FAIL: %s != %lld", d->str, d->retval);
+ errors++;
+ }
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/util/regress/sudo_conf/conf_test.c b/lib/util/regress/sudo_conf/conf_test.c
new file mode 100644
index 0000000..5c5ffec
--- /dev/null
+++ b/lib/util/regress/sudo_conf/conf_test.c
@@ -0,0 +1,95 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+static void sudo_conf_dump(void);
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * 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 developer_mode %s\n",
+ sudo_conf_developer_mode() ? "true" : "false");
+ 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..4727153
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test1.in
@@ -0,0 +1,82 @@
+#
+# Sample /etc/sudo.conf file
+#
+# Format:
+# Plugin plugin_name plugin_path plugin_options ...
+# Path askpass /path/to/askpass
+# Path noexec /path/to/sudo_noexec.so
+# Debug sudo /var/log/sudo_debug all@warn
+# Set disable_coredump true
+#
+# Sudo plugins:
+#
+# The plugin_path is relative to ${prefix}/libexec unless fully qualified.
+# The plugin_name corresponds to a global symbol in the plugin
+# that contains the plugin interface structure.
+# The plugin_options are optional.
+#
+# The sudoers plugin is used by default if no Plugin lines are present.
+Plugin sudoers_policy sudoers.so
+Plugin sudoers_io sudoers.so
+
+#
+# Sudo askpass:
+#
+# An askpass helper program may be specified to provide a graphical
+# password prompt for "sudo -A" support. Sudo does not ship with its
+# own askpass program but can use the OpenSSH askpass.
+#
+# Use the OpenSSH askpass
+Path askpass /usr/X11R6/bin/ssh-askpass
+#
+# Use the Gnome OpenSSH askpass
+#Path askpass /usr/libexec/openssh/gnome-ssh-askpass
+
+#
+# Sudo noexec:
+#
+# Path to a shared library containing replacements for the execv(),
+# execve() and fexecve() library functions that just return an error.
+# This is used to implement the "noexec" functionality on systems that
+# support LD_PRELOAD or its equivalent.
+# The compiled-in value is usually sufficient and should only be changed
+# if you rename or move the sudo_noexec.so file.
+#
+Path noexec /usr/local/libexec/sudo_noexec.so
+Path noexec /usr/libexec/sudo_noexec.so
+
+#
+# Core dumps:
+#
+# By default, sudo disables core dumps while it is executing (they
+# are re-enabled for the command that is run).
+# To aid in debugging sudo problems, you may wish to enable core
+# dumps by setting "disable_coredump" to false.
+#
+Set disable_coredump false
+
+#
+# Developer mode:
+#
+# By default, sudo enforces that each plugin it loads is only modifiable as
+# non root user. This might not be very convenient for plugin development,
+# so this can be disabled by setting "developer_mode" to true.
+#
+Set developer_mode true
+
+#
+# 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..47584e9
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test1.out.ok
@@ -0,0 +1,8 @@
+Set developer_mode true
+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..d7c595e
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test2.out.ok
@@ -0,0 +1,4 @@
+Set developer_mode false
+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..8281666
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test3.out.ok
@@ -0,0 +1,6 @@
+Set developer_mode false
+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..d7c595e
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test4.out.ok
@@ -0,0 +1,4 @@
+Set developer_mode false
+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..d7c595e
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test5.out.ok
@@ -0,0 +1,4 @@
+Set developer_mode false
+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..d6e938a
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test6.out.ok
@@ -0,0 +1,4 @@
+Set developer_mode false
+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..fedef8b
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test7.out.ok
@@ -0,0 +1,8 @@
+Set developer_mode false
+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_conf/test8.err.ok b/lib/util/regress/sudo_conf/test8.err.ok
new file mode 100644
index 0000000..2ff6773
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test8.err.ok
@@ -0,0 +1 @@
+conf_test: invalid value for developer_mode "foo" in regress/sudo_conf/test8.in, line 1
diff --git a/lib/util/regress/sudo_conf/test8.in b/lib/util/regress/sudo_conf/test8.in
new file mode 100644
index 0000000..e9a6773
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test8.in
@@ -0,0 +1 @@
+Set developer_mode foo
diff --git a/lib/util/regress/sudo_conf/test8.out.ok b/lib/util/regress/sudo_conf/test8.out.ok
new file mode 100644
index 0000000..d7c595e
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test8.out.ok
@@ -0,0 +1,4 @@
+Set developer_mode false
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups -1
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..9176ebd
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/parseln_test.c
@@ -0,0 +1,49 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Simple test driver for sudo_parseln().
+ * Behaves similarly to "cat -n" but with comment removal
+ * and line continuation.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ unsigned int lineno = 0;
+ size_t linesize = 0;
+ char *line = NULL;
+
+ 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(EXIT_SUCCESS);
+}
diff --git a/lib/util/regress/sudo_parseln/test1.in b/lib/util/regress/sudo_parseln/test1.in
new file mode 100644
index 0000000..8f417dd
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test1.in
@@ -0,0 +1,72 @@
+#
+# Sample /etc/sudo.conf file
+#
+# Format:
+# Plugin plugin_name plugin_path plugin_options ...
+# Path askpass /path/to/askpass
+# Path noexec /path/to/sudo_noexec.so
+# Debug sudo /var/log/sudo_debug all@warn
+# Set disable_coredump true
+#
+# Sudo plugins:
+#
+# The plugin_path is relative to ${prefix}/libexec unless fully qualified.
+# The plugin_name corresponds to a global symbol in the plugin
+# that contains the plugin interface structure.
+# The plugin_options are optional.
+#
+# The sudoers plugin is used by default if no Plugin lines are present.
+Plugin sudoers_policy sudoers.so
+Plugin sudoers_io sudoers.so
+
+#
+# Sudo askpass:
+#
+# An askpass helper program may be specified to provide a graphical
+# password prompt for "sudo -A" support. Sudo does not ship with its
+# own askpass program but can use the OpenSSH askpass.
+#
+# Use the OpenSSH askpass
+#Path askpass /usr/X11R6/bin/ssh-askpass
+#
+# Use the Gnome OpenSSH askpass
+#Path askpass /usr/libexec/openssh/gnome-ssh-askpass
+
+#
+# Sudo noexec:
+#
+# Path to a shared library containing replacements for the execv(),
+# execve() and fexecve() library functions that just return an error.
+# This is used to implement the "noexec" functionality on systems that
+# support LD_PRELOAD or its equivalent.
+# The compiled-in value is usually sufficient and should only be changed
+# if you rename or move the sudo_noexec.so file.
+#
+#Path noexec /usr/libexec/sudo_noexec.so
+
+#
+# Core dumps:
+#
+# By default, sudo disables core dumps while it is executing (they
+# are re-enabled for the command that is run).
+# To aid in debugging sudo problems, you may wish to enable core
+# dumps by setting "disable_coredump" to false.
+#
+#Set disable_coredump false
+
+#
+# User groups:
+#
+# Sudo passes the user's group list to the policy plugin.
+# If the user is a member of the maximum number of groups (usually 16),
+# sudo will query the group database directly to be sure to include
+# the full list of groups.
+#
+# On some systems, this can be expensive so the behavior is configurable.
+# The "group_source" setting has three possible values:
+# static - use the user's list of groups returned by the kernel.
+# dynamic - query the group database to find the list of groups.
+# adaptive - if user is in less than the maximum number of groups.
+# use the kernel list, else query the group database.
+#
+#Set group_source static
diff --git a/lib/util/regress/sudo_parseln/test1.out.ok b/lib/util/regress/sudo_parseln/test1.out.ok
new file mode 100644
index 0000000..c98ca77
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test1.out.ok
@@ -0,0 +1,72 @@
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19 Plugin sudoers_policy sudoers.so
+ 20 Plugin sudoers_io sudoers.so
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
diff --git a/lib/util/regress/sudo_parseln/test2.in b/lib/util/regress/sudo_parseln/test2.in
new file mode 100644
index 0000000..49166ee
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test2.in
@@ -0,0 +1,8 @@
+this \
+is all \
+one line
+# this is a comment, and does not get continued\
+trim the \
+ leading \
+ white \
+space
diff --git a/lib/util/regress/sudo_parseln/test2.out.ok b/lib/util/regress/sudo_parseln/test2.out.ok
new file mode 100644
index 0000000..d921968
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test2.out.ok
@@ -0,0 +1,3 @@
+ 3 this is all one line
+ 4
+ 8 trim the leading white space
diff --git a/lib/util/regress/sudo_parseln/test3.in b/lib/util/regress/sudo_parseln/test3.in
new file mode 100644
index 0000000..e372c07
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test3.in
@@ -0,0 +1 @@
+line continuation at EOF \
diff --git a/lib/util/regress/sudo_parseln/test3.out.ok b/lib/util/regress/sudo_parseln/test3.out.ok
new file mode 100644
index 0000000..2e8d16d
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test3.out.ok
@@ -0,0 +1 @@
+ 1 line continuation at EOF
diff --git a/lib/util/regress/sudo_parseln/test4.in b/lib/util/regress/sudo_parseln/test4.in
new file mode 100644
index 0000000..3583f3b
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test4.in
@@ -0,0 +1,4 @@
+line contin\
+uation raw
+line contin\
+ uation indented
diff --git a/lib/util/regress/sudo_parseln/test4.out.ok b/lib/util/regress/sudo_parseln/test4.out.ok
new file mode 100644
index 0000000..38afbeb
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test4.out.ok
@@ -0,0 +1,2 @@
+ 2 line continuation raw
+ 4 line continuation indented
diff --git a/lib/util/regress/sudo_parseln/test5.in b/lib/util/regress/sudo_parseln/test5.in
new file mode 100644
index 0000000..57ddad2
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test5.in
@@ -0,0 +1 @@
+\
diff --git a/lib/util/regress/sudo_parseln/test5.out.ok b/lib/util/regress/sudo_parseln/test5.out.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test5.out.ok
diff --git a/lib/util/regress/sudo_parseln/test6.in b/lib/util/regress/sudo_parseln/test6.in
new file mode 100644
index 0000000..95cac84
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test6.in
@@ -0,0 +1,3 @@
+ leading and trailing white space
+ # a comment
+\
diff --git a/lib/util/regress/sudo_parseln/test6.out.ok b/lib/util/regress/sudo_parseln/test6.out.ok
new file mode 100644
index 0000000..340765e
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test6.out.ok
@@ -0,0 +1,2 @@
+ 1 leading and trailing white space
+ 2
diff --git a/lib/util/regress/tailq/hltq_test.c b/lib/util/regress/tailq/hltq_test.c
new file mode 100644
index 0000000..834f511
--- /dev/null
+++ b/lib/util/regress/tailq/hltq_test.c
@@ -0,0 +1,190 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_queue.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Note: HLTQ_ENTRY is intentionally in the middle of the struct
+ * to catch bad assumptions in the PREV/NEXT macros.
+ */
+struct test_data {
+ int a;
+ HLTQ_ENTRY(test_data) entries;
+ char b;
+};
+
+TAILQ_HEAD(test_data_list, test_data);
+
+/*
+ * Simple tests for headless tail queue macros.
+ */
+int
+main(int argc, char *argv[])
+{
+ struct test_data d1, d2, d3;
+ struct test_data *hltq;
+ struct test_data_list tq;
+ int 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..27b9f14
--- /dev/null
+++ b/lib/util/regress/vsyslog/vsyslog_test.c
@@ -0,0 +1,130 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2017-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_vsyslog() works as expected.
+ */
+static char *expected_result;
+static int errors;
+static int ntests;
+
+/*
+ * Replacement for syslog(3) that just verifies 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[])
+{
+ int len;
+ 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. */
+ len = snprintf(buf1, sizeof(buf1),
+ "unable to open %s: %s", "/var/log/sudo-io/seq", strerror(ENOENT));
+ if (len < 0 || len >= ssizeof(buf1))
+ sudo_warnx_nodebug("buf1 truncated at %s:%d", __FILE__, __LINE__);
+ 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';
+ len = snprintf(buf2, sizeof(buf2), "%s: %s", buf1, strerror(EINVAL));
+ if (len < 0 || len >= ssizeof(buf2))
+ sudo_warnx_nodebug("buf2 truncated at %s:%d", __FILE__, __LINE__);
+ 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';
+ len = snprintf(buf2, sizeof(buf2), "%.*s", 2047, buf1);
+ if (len < 0 || len >= ssizeof(buf2))
+ sudo_warnx_nodebug("buf2 truncated at %s:%d", __FILE__, __LINE__);
+ 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/roundup.c b/lib/util/roundup.c
new file mode 100644
index 0000000..38f1571
--- /dev/null
+++ b/lib/util/roundup.c
@@ -0,0 +1,43 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "config.h"
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * Round 32-bit unsigned length to the next highest power of two.
+ * Always returns at least 64.
+ * Algorithm from bit twiddling hacks.
+ */
+unsigned int
+sudo_pow2_roundup_v1(unsigned int len)
+{
+ if (len < 64)
+ return 64;
+ len--;
+ len |= len >> 1;
+ len |= len >> 2;
+ len |= len >> 4;
+ len |= len >> 8;
+ len |= len >> 16;
+ len++;
+ return len;
+}
diff --git a/lib/util/secure_path.c b/lib/util/secure_path.c
new file mode 100644
index 0000000..1d2c97d
--- /dev/null
+++ b/lib/util/secure_path.c
@@ -0,0 +1,79 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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/stat.h>
+#include <string.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..f424ddb
--- /dev/null
+++ b/lib/util/setgroups.c
@@ -0,0 +1,52 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011-2012, 2014-2016 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <grp.h>
+#include <limits.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+int
+sudo_setgroups_v1(int ngids, const GETGROUPS_T *gids)
+{
+ int maxgids, ret;
+ debug_decl(sudo_setgroups, SUDO_DEBUG_UTIL);
+
+ ret = setgroups(ngids, (GETGROUPS_T *)gids);
+ if (ret == -1 && errno == EINVAL) {
+ /* Too many groups, try again with fewer. */
+ maxgids = (int)sysconf(_SC_NGROUPS_MAX);
+ if (maxgids == -1)
+ maxgids = NGROUPS_MAX;
+ if (ngids > maxgids)
+ ret = setgroups(maxgids, (GETGROUPS_T *)gids);
+ }
+ debug_return_int(ret);
+}
diff --git a/lib/util/sha2.c b/lib/util/sha2.c
new file mode 100644
index 0000000..b7a28cc
--- /dev/null
+++ b/lib/util/sha2.c
@@ -0,0 +1,515 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+/*
+ * Implementation of SHA-224, SHA-256, SHA-384 and SHA-512
+ * as per FIPS 180-4: Secure Hash Standard (SHS)
+ * http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf
+ *
+ * Derived from the public domain SHA-1 and SHA-2 implementations
+ * by Steve Reid and Wei Dai respectively.
+ */
+
+#include <config.h>
+#include <string.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#if defined(HAVE_ENDIAN_H)
+# include <endian.h>
+#elif defined(HAVE_SYS_ENDIAN_H)
+# include <sys/endian.h>
+#elif defined(HAVE_MACHINE_ENDIAN_H)
+# include <machine/endian.h>
+#else
+# include "compat/endian.h"
+#endif
+
+#include "sudo_compat.h"
+#include "compat/sha2.h"
+
+/*
+ * SHA-2 operates on 32-bit and 64-bit words in big endian byte order.
+ * The following macros convert between character arrays and big endian words.
+ */
+#define BE8TO32(x, y) do { \
+ (x) = (((uint32_t)((y)[0] & 255) << 24) | \
+ ((uint32_t)((y)[1] & 255) << 16) | \
+ ((uint32_t)((y)[2] & 255) << 8) | \
+ ((uint32_t)((y)[3] & 255))); \
+} while (0)
+
+#define BE8TO64(x, y) do { \
+ (x) = (((uint64_t)((y)[0] & 255) << 56) | \
+ ((uint64_t)((y)[1] & 255) << 48) | \
+ ((uint64_t)((y)[2] & 255) << 40) | \
+ ((uint64_t)((y)[3] & 255) << 32) | \
+ ((uint64_t)((y)[4] & 255) << 24) | \
+ ((uint64_t)((y)[5] & 255) << 16) | \
+ ((uint64_t)((y)[6] & 255) << 8) | \
+ ((uint64_t)((y)[7] & 255))); \
+} while (0)
+
+#define BE32TO8(x, y) do { \
+ (x)[0] = (uint8_t)(((y) >> 24) & 255); \
+ (x)[1] = (uint8_t)(((y) >> 16) & 255); \
+ (x)[2] = (uint8_t)(((y) >> 8) & 255); \
+ (x)[3] = (uint8_t)((y) & 255); \
+} while (0)
+
+#define BE64TO8(x, y) do { \
+ (x)[0] = (uint8_t)(((y) >> 56) & 255); \
+ (x)[1] = (uint8_t)(((y) >> 48) & 255); \
+ (x)[2] = (uint8_t)(((y) >> 40) & 255); \
+ (x)[3] = (uint8_t)(((y) >> 32) & 255); \
+ (x)[4] = (uint8_t)(((y) >> 24) & 255); \
+ (x)[5] = (uint8_t)(((y) >> 16) & 255); \
+ (x)[6] = (uint8_t)(((y) >> 8) & 255); \
+ (x)[7] = (uint8_t)((y) & 255); \
+} while (0)
+
+#define rotrFixed(x,y) (y ? ((x>>y) | (x<<(sizeof(x)*8-y))) : x)
+
+#define blk0(i) (W[i])
+#define blk2(i) (W[i&15]+=s1(W[(i-2)&15])+W[(i-7)&15]+s0(W[(i-15)&15]))
+
+#define Ch(x,y,z) (z^(x&(y^z)))
+#define Maj(x,y,z) (y^((x^y)&(y^z)))
+
+#define a(i) T[(0-i)&7]
+#define b(i) T[(1-i)&7]
+#define c(i) T[(2-i)&7]
+#define d(i) T[(3-i)&7]
+#define e(i) T[(4-i)&7]
+#define f(i) T[(5-i)&7]
+#define g(i) T[(6-i)&7]
+#define h(i) T[(7-i)&7]
+
+void
+SHA224Init(SHA2_CTX *ctx)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->state.st32[0] = 0xc1059ed8UL;
+ ctx->state.st32[1] = 0x367cd507UL;
+ ctx->state.st32[2] = 0x3070dd17UL;
+ ctx->state.st32[3] = 0xf70e5939UL;
+ ctx->state.st32[4] = 0xffc00b31UL;
+ ctx->state.st32[5] = 0x68581511UL;
+ ctx->state.st32[6] = 0x64f98fa7UL;
+ ctx->state.st32[7] = 0xbefa4fa4UL;
+}
+
+void
+SHA224Transform(uint32_t state[8], const uint8_t buffer[SHA224_BLOCK_LENGTH])
+{
+ SHA256Transform(state, buffer);
+}
+
+void
+SHA224Update(SHA2_CTX *ctx, const uint8_t *data, size_t len)
+{
+ SHA256Update(ctx, data, len);
+}
+
+void
+SHA224Pad(SHA2_CTX *ctx)
+{
+ SHA256Pad(ctx);
+}
+
+void
+SHA224Final(uint8_t digest[SHA224_DIGEST_LENGTH], SHA2_CTX *ctx)
+{
+ SHA256Pad(ctx);
+ if (digest != NULL) {
+#if BYTE_ORDER == BIG_ENDIAN
+ memcpy(digest, ctx->state.st32, SHA224_DIGEST_LENGTH);
+#else
+ unsigned int i;
+
+ for (i = 0; i < 7; i++)
+ BE32TO8(digest + (i * 4), ctx->state.st32[i]);
+#endif
+ memset(ctx, 0, sizeof(*ctx));
+ }
+}
+
+static const uint32_t SHA256_K[64] = {
+ 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL,
+ 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL,
+ 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL,
+ 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL,
+ 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL,
+ 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL,
+ 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL,
+ 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL,
+ 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL,
+ 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
+ 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL,
+ 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL,
+ 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL,
+ 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL,
+ 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL,
+ 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL
+};
+
+void
+SHA256Init(SHA2_CTX *ctx)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->state.st32[0] = 0x6a09e667UL;
+ ctx->state.st32[1] = 0xbb67ae85UL;
+ ctx->state.st32[2] = 0x3c6ef372UL;
+ ctx->state.st32[3] = 0xa54ff53aUL;
+ ctx->state.st32[4] = 0x510e527fUL;
+ ctx->state.st32[5] = 0x9b05688cUL;
+ ctx->state.st32[6] = 0x1f83d9abUL;
+ ctx->state.st32[7] = 0x5be0cd19UL;
+}
+
+/* Round macros for SHA256 */
+#define R(i) do { \
+ h(i)+=S1(e(i))+Ch(e(i),f(i),g(i))+SHA256_K[i+j]+(j?blk2(i):blk0(i)); \
+ d(i)+=h(i); \
+ h(i)+=S0(a(i))+Maj(a(i),b(i),c(i)); \
+} while (0)
+
+#define S0(x) (rotrFixed(x,2)^rotrFixed(x,13)^rotrFixed(x,22))
+#define S1(x) (rotrFixed(x,6)^rotrFixed(x,11)^rotrFixed(x,25))
+#define s0(x) (rotrFixed(x,7)^rotrFixed(x,18)^(x>>3))
+#define s1(x) (rotrFixed(x,17)^rotrFixed(x,19)^(x>>10))
+
+void
+SHA256Transform(uint32_t state[8], const uint8_t data[SHA256_BLOCK_LENGTH])
+{
+ uint32_t W[16];
+ uint32_t T[8];
+ unsigned int j;
+
+ /* Copy context state to working vars. */
+ memcpy(T, state, sizeof(T));
+ /* Copy data to W in big endian format. */
+#if BYTE_ORDER == BIG_ENDIAN
+ memcpy(W, data, sizeof(W));
+#else
+ for (j = 0; j < 16; j++) {
+ BE8TO32(W[j], data);
+ data += 4;
+ }
+#endif
+ /* 64 operations, partially loop unrolled. */
+ for (j = 0; j < 64; j += 16)
+ {
+ R( 0); R( 1); R( 2); R( 3);
+ R( 4); R( 5); R( 6); R( 7);
+ R( 8); R( 9); R(10); R(11);
+ R(12); R(13); R(14); R(15);
+ }
+ /* Add the working vars back into context state. */
+ state[0] += a(0);
+ state[1] += b(0);
+ state[2] += c(0);
+ state[3] += d(0);
+ state[4] += e(0);
+ state[5] += f(0);
+ state[6] += g(0);
+ state[7] += h(0);
+ /* Cleanup */
+ explicit_bzero(T, sizeof(T));
+ explicit_bzero(W, sizeof(W));
+}
+
+#undef S0
+#undef S1
+#undef s0
+#undef s1
+#undef R
+
+void
+SHA256Update(SHA2_CTX *ctx, const uint8_t *data, size_t len)
+{
+ size_t i = 0, j;
+
+ j = (size_t)((ctx->count[0] >> 3) & (SHA256_BLOCK_LENGTH - 1));
+ ctx->count[0] += ((uint64_t)len << 3);
+ if ((j + len) > SHA256_BLOCK_LENGTH - 1) {
+ memcpy(&ctx->buffer[j], data, (i = SHA256_BLOCK_LENGTH - j));
+ SHA256Transform(ctx->state.st32, ctx->buffer);
+ for ( ; i + SHA256_BLOCK_LENGTH - 1 < len; i += SHA256_BLOCK_LENGTH)
+ SHA256Transform(ctx->state.st32, (uint8_t *)&data[i]);
+ j = 0;
+ }
+ memcpy(&ctx->buffer[j], &data[i], len - i);
+}
+
+void
+SHA256Pad(SHA2_CTX *ctx)
+{
+ uint8_t finalcount[8];
+
+ /* Store unpadded message length in bits in big endian format. */
+ BE64TO8(finalcount, ctx->count[0]);
+
+ /* Append a '1' bit (0x80) to the message. */
+ SHA256Update(ctx, (uint8_t *)"\200", 1);
+
+ /* Pad message such that the resulting length modulo 512 is 448. */
+ while ((ctx->count[0] & 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. */
+ explicit_bzero(T, sizeof(T));
+ explicit_bzero(W, sizeof(W));
+}
+
+void
+SHA512Update(SHA2_CTX *ctx, const uint8_t *data, size_t len)
+{
+ size_t i = 0, j;
+
+ j = (size_t)((ctx->count[0] >> 3) & (SHA512_BLOCK_LENGTH - 1));
+ ctx->count[0] += ((uint64_t)len << 3);
+ if (ctx->count[0] < ((uint64_t)len << 3))
+ ctx->count[1]++;
+ if ((j + len) > SHA512_BLOCK_LENGTH - 1) {
+ memcpy(&ctx->buffer[j], data, (i = SHA512_BLOCK_LENGTH - j));
+ SHA512Transform(ctx->state.st64, ctx->buffer);
+ for ( ; i + SHA512_BLOCK_LENGTH - 1 < len; i += SHA512_BLOCK_LENGTH)
+ SHA512Transform(ctx->state.st64, (uint8_t *)&data[i]);
+ j = 0;
+ }
+ memcpy(&ctx->buffer[j], &data[i], len - i);
+}
+
+void
+SHA512Pad(SHA2_CTX *ctx)
+{
+ uint8_t finalcount[16];
+
+ /* Store unpadded message length in bits in big endian format. */
+ BE64TO8(finalcount, ctx->count[1]);
+ BE64TO8(finalcount + 8, ctx->count[0]);
+
+ /* Append a '1' bit (0x80) to the message. */
+ SHA512Update(ctx, (uint8_t *)"\200", 1);
+
+ /* Pad message such that the resulting length modulo 1024 is 896. */
+ while ((ctx->count[0] & 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..1d86021
--- /dev/null
+++ b/lib/util/sig2str.c
@@ -0,0 +1,100 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2012-2015, 2017-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_SIG2STR
+
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#if !defined(HAVE_SIGABBREV_NP)
+# if defined(HAVE_DECL_SYS_SIGNAME) && HAVE_DECL_SYS_SIGNAME == 1
+# define sigabbrev_np(_x) sys_signame[(_x)]
+# elif defined(HAVE_DECL__SYS_SIGNAME) && HAVE_DECL__SYS_SIGNAME == 1
+# define sigabbrev_np(_x) _sys_signame[(_x)]
+# elif defined(HAVE_SYS_SIGABBREV)
+# define sigabbrev_np(_x) sys_sigabbrev[(_x)]
+# if defined(HAVE_DECL_SYS_SIGABBREV) && HAVE_DECL_SYS_SIGABBREV == 0
+ /* sys_sigabbrev is not declared by glibc */
+ extern const char *const sys_sigabbrev[NSIG];
+# endif
+# else
+# define sigabbrev_np(_x) sudo_sys_signame[(_x)]
+ extern const char *const sudo_sys_signame[NSIG];
+# endif
+#endif /* !HAVE_SIGABBREV_NP */
+
+/*
+ * Translate signal number to name.
+ */
+int
+sudo_sig2str(int signo, char *signame)
+{
+#if defined(SIGRTMIN) && defined(SIGRTMAX)
+ /* Realtime signal support. */
+ if (signo >= SIGRTMIN && signo <= SIGRTMAX) {
+# ifdef _SC_RTSIG_MAX
+ const long rtmax = sysconf(_SC_RTSIG_MAX);
+# else
+ const long rtmax = SIGRTMAX - SIGRTMIN;
+# endif
+ if (rtmax > 0) {
+ if (signo == SIGRTMIN) {
+ strlcpy(signame, "RTMIN", SIG2STR_MAX);
+ } else if (signo == SIGRTMAX) {
+ strlcpy(signame, "RTMAX", SIG2STR_MAX);
+ } else if (signo <= SIGRTMIN + (rtmax / 2) - 1) {
+ (void)snprintf(signame, SIG2STR_MAX, "RTMIN+%d",
+ (signo - SIGRTMIN));
+ } else {
+ (void)snprintf(signame, SIG2STR_MAX, "RTMAX-%d",
+ (SIGRTMAX - signo));
+ }
+ }
+ return 0;
+ }
+#endif
+ if (signo > 0 && signo < NSIG) {
+ const char *cp = sigabbrev_np(signo);
+ if (cp != NULL) {
+ strlcpy(signame, cp, SIG2STR_MAX);
+ /* Make sure we always return an upper case signame. */
+ if (islower((unsigned char)signame[0])) {
+ int i;
+ for (i = 0; signame[i] != '\0'; i++)
+ signame[i] = toupper((unsigned char)signame[i]);
+ }
+ return 0;
+ }
+ }
+ errno = EINVAL;
+ return -1;
+}
+#endif /* HAVE_SIG2STR */
diff --git a/lib/util/siglist.in b/lib/util/siglist.in
new file mode 100644
index 0000000..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..6d027ad
--- /dev/null
+++ b/lib/util/snprintf.c
@@ -0,0 +1,1588 @@
+/* $OpenBSD: vfprintf.c,v 1.67 2014/12/21 00:23:30 daniel Exp $ */
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1999-2005, 2008, 2010-2016
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * From: @(#)vfprintf.c 8.1 (Berkeley) 6/4/93
+ */
+
+/*
+ * v?snprintf/v?asprintf based on OpenBSD vfprintf.c.
+ */
+
+#include <config.h>
+
+#if !defined(HAVE_VSNPRINTF) || !defined(HAVE_SNPRINTF) || \
+ !defined(HAVE_VASPRINTF) || !defined(HAVE_ASPRINTF) || \
+ defined(PREFER_PORTABLE_SNPRINTF)
+
+#include <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>
+#include <string.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/str2sig.c b/lib/util/str2sig.c
new file mode 100644
index 0000000..e265ec4
--- /dev/null
+++ b/lib/util/str2sig.c
@@ -0,0 +1,174 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_STR2SIG
+
+#include <errno.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <ctype.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#if !defined(HAVE_SIGABBREV_NP)
+# if defined(HAVE_DECL_SYS_SIGNAME) && HAVE_DECL_SYS_SIGNAME == 1
+# define sigabbrev_np(_x) sys_signame[(_x)]
+# elif defined(HAVE_DECL__SYS_SIGNAME) && HAVE_DECL__SYS_SIGNAME == 1
+# define sigabbrev_np(_x) _sys_signame[(_x)]
+# elif defined(HAVE_SYS_SIGABBREV)
+# define sigabbrev_np(_x) sys_sigabbrev[(_x)]
+# if defined(HAVE_DECL_SYS_SIGABBREV) && HAVE_DECL_SYS_SIGABBREV == 0
+ /* sys_sigabbrev is not declared by glibc */
+ extern const char *const sys_sigabbrev[NSIG];
+# endif
+# else
+# define sigabbrev_np(_x) sudo_sys_signame[(_x)]
+ extern const char *const sudo_sys_signame[NSIG];
+# endif
+#endif /* !HAVE_SIGABBREV_NP */
+
+/*
+ * Many systems use aliases for source backward compatibility.
+ */
+static struct sigalias {
+ const char *name;
+ int number;
+} sigaliases[] = {
+#ifdef SIGABRT
+ { "ABRT", SIGABRT },
+#endif
+#ifdef SIGCLD
+ { "CLD", SIGCLD },
+#endif
+#ifdef SIGIO
+ { "IO", SIGIO },
+#endif
+#ifdef SIGIOT
+ { "IOT", SIGIOT },
+#endif
+#ifdef SIGLOST
+ { "LOST", SIGLOST },
+#endif
+#ifdef SIGPOLL
+ { "POLL", SIGPOLL },
+#endif
+ { NULL, -1 }
+};
+
+/*
+ * Translate signal name to number.
+ */
+int
+sudo_str2sig(const char *signame, int *result)
+{
+ struct sigalias *alias;
+ const char *errstr;
+ int signo;
+
+ /* Could be a signal number encoded as a string. */
+ if (isdigit((unsigned char)signame[0])) {
+ signo = sudo_strtonum(signame, 0, NSIG - 1, &errstr);
+ if (errstr != NULL)
+ return -1;
+ *result = signo;
+ return 0;
+ }
+
+ /* Check real-time signals. */
+#if defined(SIGRTMIN)
+ if (strncmp(signame, "RTMIN", 5) == 0) {
+ if (signame[5] == '\0') {
+ *result = SIGRTMIN;
+ return 0;
+ }
+ if (signame[5] == '+') {
+ if (isdigit((unsigned char)signame[6])) {
+# ifdef _SC_RTSIG_MAX
+ const long rtmax = sysconf(_SC_RTSIG_MAX);
+# else
+ const long rtmax = SIGRTMAX - SIGRTMIN;
+# endif
+ const int off = signame[6] - '0';
+
+ if (rtmax > 0 && off < rtmax / 2) {
+ *result = SIGRTMIN + off;
+ return 0;
+ }
+ }
+ }
+ }
+#endif
+#if defined(SIGRTMAX)
+ if (strncmp(signame, "RTMAX", 5) == 0) {
+ if (signame[5] == '\0') {
+ *result = SIGRTMAX;
+ return 0;
+ }
+ if (signame[5] == '-') {
+ if (isdigit((unsigned char)signame[6])) {
+# ifdef _SC_RTSIG_MAX
+ const long rtmax = sysconf(_SC_RTSIG_MAX);
+# else
+ const long rtmax = SIGRTMAX - SIGRTMIN;
+# endif
+ const int off = signame[6] - '0';
+
+ if (rtmax > 0 && off < rtmax / 2) {
+ *result = SIGRTMAX - off;
+ return 0;
+ }
+ }
+ }
+ }
+#endif
+
+ /* Check aliases. */
+ for (alias = sigaliases; alias->name != NULL; alias++) {
+ if (strcmp(signame, alias->name) == 0) {
+ *result = alias->number;
+ return 0;
+ }
+ }
+
+ for (signo = 1; signo < NSIG; signo++) {
+ const char *cp = sigabbrev_np(signo);
+ if (cp != NULL) {
+ /* On macOS sys_signame[] may contain lower-case names. */
+ if (strcasecmp(signame, cp) == 0) {
+ *result = signo;
+ return 0;
+ }
+ }
+ }
+
+ errno = EINVAL;
+ return -1;
+}
+#endif /* HAVE_STR2SIG */
diff --git a/lib/util/strlcat.c b/lib/util/strlcat.c
new file mode 100644
index 0000000..fa1934a
--- /dev/null
+++ b/lib/util/strlcat.c
@@ -0,0 +1,69 @@
+/* $OpenBSD: strlcat.c,v 1.15 2015/03/02 21:41:08 millert Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1998, 2003-2005, 2010-2011, 2013-2015
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_STRLCAT
+
+#include <string.h>
+
+#include "sudo_compat.h"
+
+/*
+ * Appends src to string dst of size dsize (unlike strncat, dsize is the
+ * full size of dst, not space left). At most dsize-1 characters
+ * will be copied. Always NUL terminates (unless dsize <= strlen(dst)).
+ * Returns strlen(src) + MIN(dsize, strlen(initial dst)).
+ * If retval >= dsize, truncation occurred.
+ */
+size_t
+sudo_strlcat(char *dst, const char *src, size_t dsize)
+{
+ const char *odst = dst;
+ const char *osrc = src;
+ size_t n = dsize;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end. */
+ while (n-- != 0 && *dst != '\0')
+ dst++;
+ dlen = dst - odst;
+ n = dsize - dlen;
+
+ if (n-- == 0)
+ return(dlen + strlen(src));
+ while (*src != '\0') {
+ if (n != 0) {
+ *dst++ = *src;
+ n--;
+ }
+ src++;
+ }
+ *dst = '\0';
+
+ return(dlen + (src - osrc)); /* count does not include NUL */
+}
+#endif /* HAVE_STRLCAT */
diff --git a/lib/util/strlcpy.c b/lib/util/strlcpy.c
new file mode 100644
index 0000000..f9e5244
--- /dev/null
+++ b/lib/util/strlcpy.c
@@ -0,0 +1,64 @@
+/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 1998, 2003-2005, 2010-2011, 2013-2015
+ * Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_STRLCPY
+
+#include <string.h>
+
+#include "sudo_compat.h"
+
+/*
+ * Copy string src to buffer dst of size dsize. At most dsize-1
+ * chars will be copied. Always NUL terminates (unless dsize == 0).
+ * Returns strlen(src); if retval >= dsize, truncation occurred.
+ */
+size_t
+sudo_strlcpy(char *dst, const char *src, size_t dsize)
+{
+ const char *osrc = src;
+ size_t nleft = dsize;
+
+ /* Copy as many bytes as will fit. */
+ if (nleft != 0) {
+ while (--nleft != 0) {
+ if ((*dst++ = *src++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src. */
+ if (nleft == 0) {
+ if (dsize != 0)
+ *dst = '\0'; /* NUL-terminate dst */
+ while (*src++)
+ continue;
+ }
+
+ return(src - osrc - 1); /* count does not include NUL */
+}
+#endif /* HAVE_STRLCPY */
diff --git a/lib/util/strndup.c b/lib/util/strndup.c
new file mode 100644
index 0000000..75b92eb
--- /dev/null
+++ b/lib/util/strndup.c
@@ -0,0 +1,51 @@
+/* $OpenBSD: strndup.c,v 1.1 2010/05/18 22:24:55 tedu Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_STRNDUP
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "sudo_compat.h"
+
+char *
+sudo_strndup(const char *str, size_t maxlen)
+{
+ char *copy;
+ size_t len;
+
+ len = strnlen(str, maxlen);
+ copy = malloc(len + 1);
+ if (copy != NULL) {
+ (void)memcpy(copy, str, len);
+ copy[len] = '\0';
+ }
+
+ return copy;
+}
+
+#endif /* HAVE_STRNDUP */
diff --git a/lib/util/strnlen.c b/lib/util/strnlen.c
new file mode 100644
index 0000000..5e0977a
--- /dev/null
+++ b/lib/util/strnlen.c
@@ -0,0 +1,45 @@
+/* $OpenBSD: strnlen.c,v 1.5 2014/06/10 04:17:37 deraadt Exp $ */
+
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_STRNLEN
+
+#include <sys/types.h>
+
+#include "sudo_compat.h"
+
+size_t
+sudo_strnlen(const char *str, size_t maxlen)
+{
+ const char *cp;
+
+ for (cp = str; maxlen != 0 && *cp != '\0'; cp++, maxlen--)
+ continue;
+
+ return (size_t)(cp - str);
+}
+
+#endif /* HAVE_STRNLEN */
diff --git a/lib/util/strsignal.c b/lib/util/strsignal.c
new file mode 100644
index 0000000..ac95229
--- /dev/null
+++ b/lib/util/strsignal.c
@@ -0,0 +1,52 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2009-2014 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifndef HAVE_STRSIGNAL
+
+#include <signal.h>
+
+#include "sudo_compat.h"
+#include "sudo_gettext.h"
+
+#if defined(HAVE_DECL_SYS_SIGLIST) && HAVE_DECL_SYS_SIGLIST == 1
+# define sudo_sys_siglist sys_siglist
+#elif defined(HAVE_DECL__SYS_SIGLIST) && HAVE_DECL__SYS_SIGLIST == 1
+# define sudo_sys_siglist _sys_siglist
+#else
+extern const char *const sudo_sys_siglist[NSIG];
+#endif
+
+/*
+ * Get signal description string
+ */
+char *
+sudo_strsignal(int signo)
+{
+ if (signo > 0 && signo < NSIG && sudo_sys_siglist[signo] != NULL)
+ return (char *)sudo_sys_siglist[signo];
+ /* XXX - should be "Unknown signal: %d" */
+ return _("Unknown signal");
+}
+#endif /* HAVE_STRSIGNAL */
diff --git a/lib/util/strsplit.c b/lib/util/strsplit.c
new file mode 100644
index 0000000..7663460
--- /dev/null
+++ b/lib/util/strsplit.c
@@ -0,0 +1,73 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/*
+ * Like strtok_r but non-destructive and works w/o a NUL terminator.
+ * TODO: Optimize by storing current char in a variable ch
+ */
+const char *
+sudo_strsplit_v1(const char *str, const char *endstr, const char *sep, const char **last)
+{
+ const char *cp, *s;
+ debug_decl(sudo_strsplit, SUDO_DEBUG_UTIL);
+
+ /* If no str specified, use last ptr (if any). */
+ if (str == NULL)
+ str = *last;
+
+ /* Skip leading separator characters. */
+ while (str < endstr) {
+ for (s = sep; *s != '\0'; s++) {
+ if (*str == *s) {
+ str++;
+ break;
+ }
+ }
+ if (*s == '\0')
+ break;
+ }
+
+ /* Empty string? */
+ if (str >= endstr) {
+ *last = endstr;
+ debug_return_ptr(NULL);
+ }
+
+ /* Scan str until we hit a char from sep. */
+ for (cp = str; cp < endstr; cp++) {
+ for (s = sep; *s != '\0'; s++) {
+ if (*cp == *s)
+ break;
+ }
+ if (*s != '\0')
+ break;
+ }
+ *last = cp;
+ debug_return_const_ptr(str);
+}
diff --git a/lib/util/strtobool.c b/lib/util/strtobool.c
new file mode 100644
index 0000000..3cd1986
--- /dev/null
+++ b/lib/util/strtobool.c
@@ -0,0 +1,77 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010-2016 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+int
+sudo_strtobool_v1(const char *str)
+{
+ debug_decl(sudo_strtobool, SUDO_DEBUG_UTIL);
+
+ switch (*str) {
+ case '0':
+ case '1':
+ if (str[1] == '\0')
+ debug_return_int(*str - '0');
+ break;
+ case 'y':
+ case 'Y':
+ if (strcasecmp(str, "yes") == 0)
+ debug_return_int(1);
+ break;
+ case 't':
+ case 'T':
+ if (strcasecmp(str, "true") == 0)
+ debug_return_int(1);
+ break;
+ case 'o':
+ case 'O':
+ if (strcasecmp(str, "on") == 0)
+ debug_return_int(1);
+ if (strcasecmp(str, "off") == 0)
+ debug_return_int(0);
+ break;
+ case 'n':
+ case 'N':
+ if (strcasecmp(str, "no") == 0)
+ debug_return_int(0);
+ break;
+ case 'f':
+ case 'F':
+ if (strcasecmp(str, "false") == 0)
+ debug_return_int(0);
+ break;
+ }
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "invalid boolean value \"%s\"", str);
+
+ debug_return_int(-1);
+}
diff --git a/lib/util/strtoid.c b/lib/util/strtoid.c
new file mode 100644
index 0000000..8da857b
--- /dev/null
+++ b/lib/util/strtoid.c
@@ -0,0 +1,111 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/types.h> /* for id_t */
+
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+/* strtoid.c (not exported) */
+long long sudo_strtonumx(const char *str, long long minval, long long maxval, char **ep, const char **errstrp);
+
+/*
+ * Make sure that the ID ends with a valid separator char.
+ */
+static bool
+valid_separator(const char *p, const char *ep, const char *sep)
+{
+ bool valid = false;
+
+ if (ep != p) {
+ /* check for valid separator (including '\0') */
+ if (sep == NULL)
+ sep = "";
+ do {
+ if (*ep == *sep)
+ valid = true;
+ } while (*sep++ != '\0');
+ }
+ return valid;
+}
+
+/*
+ * Parse a uid/gid in string form.
+ * If sep is non-NULL, it contains valid separator characters (e.g. comma, space)
+ * If endp is non-NULL it is set to the next char after the ID.
+ * On success, returns the parsed ID and clears errstr.
+ * On error, returns 0 and sets errstr.
+ */
+id_t
+sudo_strtoidx_v1(const char *p, const char *sep, char **endp, const char **errstrp)
+{
+ const char *errstr;
+ char *ep;
+ id_t ret;
+ debug_decl(sudo_strtoid, SUDO_DEBUG_UTIL);
+
+ ret = sudo_strtonumx(p, INT_MIN, UINT_MAX, &ep, &errstr);
+ if (errstr == NULL) {
+ /*
+ * Disallow id -1 (UINT_MAX), which means "no change"
+ * and check for a valid separator (if specified).
+ */
+ if (ret == (id_t)-1 || ret == (id_t)UINT_MAX || !valid_separator(p, ep, sep)) {
+ errstr = N_("invalid value");
+ errno = EINVAL;
+ ret = 0;
+ }
+ }
+ if (errstrp != NULL)
+ *errstrp = errstr;
+ if (endp != NULL)
+ *endp = ep;
+ debug_return_id_t(ret);
+}
+
+/* Backward compatibility */
+id_t
+sudo_strtoid_v1(const char *p, const char *sep, char **endp, const char **errstrp)
+{
+ return sudo_strtoidx_v1(p, sep, endp, errstrp);
+}
+
+/* Simplified interface */
+id_t
+sudo_strtoid_v2(const char *p, const char **errstrp)
+{
+ return sudo_strtoidx_v1(p, NULL, NULL, errstrp);
+}
diff --git a/lib/util/strtomode.c b/lib/util/strtomode.c
new file mode 100644
index 0000000..1e35d34
--- /dev/null
+++ b/lib/util/strtomode.c
@@ -0,0 +1,65 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+/*
+ * Parse an octal file mode in the range [0, 0777].
+ * On success, returns the parsed mode and clears errstr.
+ * On error, returns 0 and sets errstr.
+ */
+int
+sudo_strtomode_v1(const char *cp, const char **errstr)
+{
+ char *ep;
+ long lval;
+ debug_decl(sudo_strtomode, SUDO_DEBUG_UTIL);
+
+ errno = 0;
+ lval = strtol(cp, &ep, 8);
+ if (ep == cp || *ep != '\0') {
+ if (errstr != NULL)
+ *errstr = N_("invalid value");
+ errno = EINVAL;
+ debug_return_int(0);
+ }
+ if (lval < 0 || lval > ACCESSPERMS) {
+ if (errstr != NULL)
+ *errstr = lval < 0 ? N_("value too small") : N_("value too large");
+ errno = ERANGE;
+ debug_return_int(0);
+ }
+ if (errstr != NULL)
+ *errstr = NULL;
+ debug_return_int((int)lval);
+}
diff --git a/lib/util/strtonum.c b/lib/util/strtonum.c
new file mode 100644
index 0000000..12edbbe
--- /dev/null
+++ b/lib/util/strtonum.c
@@ -0,0 +1,193 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015, 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+enum strtonum_err {
+ STN_INITIAL,
+ STN_VALID,
+ STN_INVALID,
+ STN_TOOSMALL,
+ STN_TOOBIG
+};
+
+/*
+ * Convert a string to a number in the range [minval, maxval]
+ * Unlike strtonum(), this returns the first non-digit in endp (if not NULL).
+ */
+long long
+sudo_strtonumx(const char *str, long long minval, long long maxval, char **endp,
+ const char **errstrp)
+{
+ enum strtonum_err errval = STN_INITIAL;
+ long long lastval, result = 0;
+ const char *cp = str;
+ unsigned char ch;
+ int remainder;
+ char sign;
+
+ if (minval > maxval) {
+ errval = STN_INVALID;
+ goto done;
+ }
+
+ /* Trim leading space and check sign, if any. */
+ do {
+ ch = *cp++;
+ } while (isspace(ch));
+ switch (ch) {
+ case '-':
+ sign = '-';
+ ch = *cp++;
+ break;
+ case '+':
+ ch = *cp++;
+ FALLTHROUGH;
+ default:
+ sign = '+';
+ break;
+ }
+
+ /*
+ * To prevent overflow we determine the highest (or lowest in
+ * the case of negative numbers) value result can have *before*
+ * if its multiplied (divided) by 10 as well as the remainder.
+ * If result matches this value and the next digit is larger than
+ * the remainder, we know the result is out of range.
+ * The remainder is always positive since it is compared against
+ * an unsigned digit.
+ */
+ if (sign == '-') {
+ lastval = minval / 10;
+ remainder = -(minval % 10);
+ if (remainder < 0) {
+ lastval += 1;
+ remainder += 10;
+ }
+ for (;; ch = *cp++) {
+ if (!isdigit(ch))
+ break;
+ ch -= '0';
+ if (result < lastval || (result == lastval && ch > remainder)) {
+ /* Skip remaining digits. */
+ do {
+ ch = *cp++;
+ } while (isdigit(ch));
+ errval = STN_TOOSMALL;
+ break;
+ } else {
+ result *= 10;
+ result -= ch;
+ errval = STN_VALID;
+ }
+ }
+ if (result > maxval)
+ errval = STN_TOOBIG;
+ } else {
+ lastval = maxval / 10;
+ remainder = maxval % 10;
+ for (;; ch = *cp++) {
+ if (!isdigit(ch))
+ break;
+ ch -= '0';
+ if (result > lastval || (result == lastval && ch > remainder)) {
+ /* Skip remaining digits. */
+ do {
+ ch = *cp++;
+ } while (isdigit(ch));
+ errval = STN_TOOBIG;
+ break;
+ } else {
+ result *= 10;
+ result += ch;
+ errval = STN_VALID;
+ }
+ }
+ if (result < minval)
+ errval = STN_TOOSMALL;
+ }
+
+done:
+ switch (errval) {
+ case STN_INITIAL:
+ case STN_VALID:
+ if (errstrp != NULL)
+ *errstrp = NULL;
+ break;
+ case STN_INVALID:
+ result = 0;
+ errno = EINVAL;
+ if (errstrp != NULL)
+ *errstrp = N_("invalid value");
+ break;
+ case STN_TOOSMALL:
+ result = 0;
+ errno = ERANGE;
+ if (errstrp != NULL)
+ *errstrp = N_("value too small");
+ break;
+ case STN_TOOBIG:
+ result = 0;
+ errno = ERANGE;
+ if (errstrp != NULL)
+ *errstrp = N_("value too large");
+ break;
+ }
+ if (endp != NULL) {
+ if (errval == STN_INITIAL || errval == STN_INVALID)
+ *endp = (char *)str;
+ else
+ *endp = (char *)(cp - 1);
+ }
+ return result;
+}
+
+/*
+ * Convert a string to a number in the range [minval, maxval]
+ */
+long long
+sudo_strtonum(const char *str, long long minval, long long maxval,
+ const char **errstrp)
+{
+ const char *errstr;
+ char *ep;
+ long long ret;
+
+ ret = sudo_strtonumx(str, minval, maxval, &ep, &errstr);
+ /* Check for empty string and terminating NUL. */
+ if (str == ep || *ep != '\0') {
+ errno = EINVAL;
+ errstr = N_("invalid value");
+ ret = 0;
+ }
+ if (errstrp != NULL)
+ *errstrp = errstr;
+ return ret;
+}
diff --git a/lib/util/sudo_conf.c b/lib/util/sudo_conf.c
new file mode 100644
index 0000000..3880423
--- /dev/null
+++ b/lib/util/sudo_conf.c
@@ -0,0 +1,758 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_plugin.h"
+#include "sudo_util.h"
+#include "pathnames.h"
+
+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;
+};
+
+struct sudo_conf_settings {
+ bool updated;
+ bool developer_mode;
+ bool disable_coredump;
+ bool probe_interfaces;
+ int group_source;
+ int max_groups;
+};
+
+static int parse_debug(const char *entry, const char *conf_file, unsigned int lineno);
+static int parse_path(const char *entry, const char *conf_file, unsigned int lineno);
+static int parse_plugin(const char *entry, const char *conf_file, unsigned int lineno);
+static int parse_variable(const char *entry, const char *conf_file, unsigned int lineno);
+
+static struct sudo_conf_table sudo_conf_table[] = {
+ { "Debug", sizeof("Debug") - 1, parse_debug },
+ { "Path", sizeof("Path") - 1, parse_path },
+ { "Plugin", sizeof("Plugin") - 1, parse_plugin },
+ { "Set", sizeof("Set") - 1, parse_variable },
+ { NULL }
+};
+
+static int set_var_developer_mode(const char *entry, const char *conf_file, unsigned int);
+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[] = {
+ { "developer_mode", sizeof("developer_mode") - 1, set_var_developer_mode },
+ { "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
+
+#define SUDO_CONF_PATH_INITIALIZER { \
+ { "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 } \
+}
+
+#define SUDO_CONF_SETTINGS_INITIALIZER { \
+ false, /* updated */ \
+ false, /* developer_mode */ \
+ true, /* disable_coredump */ \
+ true, /* probe_interfaces */ \
+ GROUP_SOURCE_ADAPTIVE, /* group_source */ \
+ -1 /* max_groups */ \
+}
+
+static struct sudo_conf_data {
+ struct sudo_conf_settings settings;
+ struct sudo_conf_debug_list debugging;
+ struct plugin_info_list plugins;
+ struct sudo_conf_path_table path_table[6];
+} sudo_conf_data = {
+ SUDO_CONF_SETTINGS_INITIALIZER,
+ TAILQ_HEAD_INITIALIZER(sudo_conf_data.debugging),
+ TAILQ_HEAD_INITIALIZER(sudo_conf_data.plugins),
+ SUDO_CONF_PATH_INITIALIZER
+};
+
+/*
+ * "Set variable_name value"
+ */
+static int
+parse_variable(const char *entry, const char *conf_file, unsigned int lineno)
+{
+ struct sudo_conf_table *var;
+ int ret;
+ debug_decl(parse_variable, SUDO_DEBUG_UTIL);
+
+ for (var = sudo_conf_var_table; var->name != NULL; var++) {
+ if (strncmp(entry, var->name, var->namelen) == 0 &&
+ isblank((unsigned char)entry[var->namelen])) {
+ entry += var->namelen + 1;
+ while (isblank((unsigned char)*entry))
+ entry++;
+ ret = var->parser(entry, conf_file, lineno);
+ sudo_debug_printf(ret ? SUDO_DEBUG_INFO : SUDO_DEBUG_ERROR,
+ "%s: %s:%u: Set %s %s", __func__, conf_file,
+ lineno, var->name, entry);
+ debug_return_int(ret);
+ }
+ }
+ sudo_debug_printf(SUDO_DEBUG_WARN, "%s: %s:%u: unknown setting %s",
+ __func__, conf_file, lineno, entry);
+ debug_return_int(false);
+}
+
+/*
+ * "Path name /path/to/file"
+ * If path is missing it will be set to the NULL pointer.
+ */
+static int
+parse_path(const char *entry, const char *conf_file, unsigned int lineno)
+{
+ const char *entry_end = entry + strlen(entry);
+ const char *ep, *name, *path;
+ struct sudo_conf_path_table *cur;
+ size_t namelen;
+ debug_decl(parse_path, SUDO_DEBUG_UTIL);
+
+ /* Parse name. */
+ name = sudo_strsplit(entry, entry_end, " \t", &ep);
+ if (name == NULL)
+ goto bad;
+ namelen = (size_t)(ep - name);
+
+ /* Parse path (if present). */
+ path = sudo_strsplit(NULL, entry_end, " \t", &ep);
+
+ /* Match supported paths, ignoring unknown paths. */
+ for (cur = sudo_conf_data.path_table; cur->pname != NULL; cur++) {
+ if (namelen == cur->pnamelen &&
+ strncasecmp(name, cur->pname, cur->pnamelen) == 0) {
+ char *pval = NULL;
+ if (path != NULL) {
+ if ((pval = strdup(path)) == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__,
+ U_("unable to allocate memory"));
+ debug_return_int(-1);
+ }
+ }
+ if (cur->dynamic)
+ free(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_developer_mode(const char *strval, const char *conf_file,
+ unsigned int lineno)
+{
+ int val = sudo_strtobool(strval);
+ debug_decl(set_var_developer_mode, SUDO_DEBUG_UTIL);
+
+ if (val == -1) {
+ sudo_warnx(U_("invalid value for %s \"%s\" in %s, line %u"),
+ "developer_mode", strval, conf_file, lineno);
+ debug_return_bool(false);
+ }
+ sudo_conf_data.settings.developer_mode = val;
+ debug_return_bool(true);
+}
+
+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.settings.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.settings.group_source = GROUP_SOURCE_ADAPTIVE;
+ } else if (strcasecmp(strval, "static") == 0) {
+ sudo_conf_data.settings.group_source = GROUP_SOURCE_STATIC;
+ } else if (strcasecmp(strval, "dynamic") == 0) {
+ sudo_conf_data.settings.group_source = GROUP_SOURCE_DYNAMIC;
+ } else {
+ sudo_warnx(U_("unsupported group source \"%s\" in %s, line %u"), strval,
+ conf_file, lineno);
+ debug_return_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 = sudo_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.settings.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.settings.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.settings.group_source;
+}
+
+int
+sudo_conf_max_groups_v1(void)
+{
+ return sudo_conf_data.settings.max_groups;
+}
+
+struct plugin_info_list *
+sudo_conf_plugins_v1(void)
+{
+ return &sudo_conf_data.plugins;
+}
+
+struct sudo_conf_debug_list *
+sudo_conf_debugging_v1(void)
+{
+ return &sudo_conf_data.debugging;
+}
+
+/* Return the debug files list for a program, or NULL if none. */
+struct sudo_conf_debug_file_list *
+sudo_conf_debug_files_v1(const char *progname)
+{
+ struct sudo_conf_debug *debug_spec;
+ 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_developer_mode_v1(void)
+{
+ return sudo_conf_data.settings.developer_mode;
+}
+
+bool
+sudo_conf_disable_coredump_v1(void)
+{
+ return sudo_conf_data.settings.disable_coredump;
+}
+
+bool
+sudo_conf_probe_interfaces_v1(void)
+{
+ return sudo_conf_data.settings.probe_interfaces;
+}
+
+/*
+ * Free dynamically allocated parts of sudo_conf_data and
+ * reset to initial values.
+ */
+static void
+sudo_conf_init(int conf_types)
+{
+ struct sudo_conf_debug *debug_spec;
+ struct sudo_debug_file *debug_file;
+ struct plugin_info *plugin_info;
+ int i;
+ debug_decl(sudo_conf_init, SUDO_DEBUG_UTIL);
+
+ /* Free and reset paths. */
+ if (ISSET(conf_types, SUDO_CONF_PATHS)) {
+ const struct sudo_conf_path_table path_table[] = SUDO_CONF_PATH_INITIALIZER;
+ sudo_conf_clear_paths();
+ memcpy(sudo_conf_data.path_table, path_table,
+ sizeof(sudo_conf_data.path_table));
+ }
+
+ /* Free and reset debug settings. */
+ if (ISSET(conf_types, SUDO_CONF_DEBUG)) {
+ while ((debug_spec = TAILQ_FIRST(&sudo_conf_data.debugging))) {
+ TAILQ_REMOVE(&sudo_conf_data.debugging, debug_spec, entries);
+ free(debug_spec->progname);
+ while ((debug_file = TAILQ_FIRST(&debug_spec->debug_files))) {
+ TAILQ_REMOVE(&debug_spec->debug_files, debug_file, entries);
+ free(debug_file->debug_file);
+ free(debug_file->debug_flags);
+ free(debug_file);
+ }
+ free(debug_spec);
+ }
+ }
+
+ /* Free and reset plugins. */
+ if (ISSET(conf_types, SUDO_CONF_PLUGINS)) {
+ while ((plugin_info = TAILQ_FIRST(&sudo_conf_data.plugins))) {
+ TAILQ_REMOVE(&sudo_conf_data.plugins, plugin_info, entries);
+ free(plugin_info->symbol_name);
+ free(plugin_info->path);
+ if (plugin_info->options != NULL) {
+ for (i = 0; plugin_info->options[i] != NULL; i++)
+ free(plugin_info->options[i]);
+ free(plugin_info->options);
+ }
+ free(plugin_info);
+ }
+ }
+
+ /* Set initial values. */
+ if (ISSET(conf_types, SUDO_CONF_SETTINGS)) {
+ const struct sudo_conf_settings settings = SUDO_CONF_SETTINGS_INITIALIZER;
+ sudo_conf_data.settings = settings;
+ }
+
+ debug_return;
+}
+
+/*
+ * Read in /etc/sudo.conf and populates sudo_conf_data.
+ */
+int
+sudo_conf_read_v1(const char *conf_file, int conf_types)
+{
+ 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;
+ }
+
+ /* Reset to initial values if necessary. */
+ if (sudo_conf_data.settings.updated)
+ sudo_conf_init(conf_types);
+
+ while (sudo_parseln(&line, &linesize, &conf_lineno, fp, 0) != -1) {
+ struct sudo_conf_table *cur;
+ unsigned int i;
+ char *cp;
+
+ if (*(cp = line) == '\0')
+ continue; /* empty line or comment */
+
+ for (i = 0, cur = sudo_conf_table; cur->name != NULL; i++, cur++) {
+ if (strncasecmp(cp, cur->name, cur->namelen) == 0 &&
+ isblank((unsigned char)cp[cur->namelen])) {
+ if (ISSET(conf_types, (1 << i))) {
+ cp += cur->namelen;
+ while (isblank((unsigned char)*cp))
+ cp++;
+ ret = cur->parser(cp, conf_file, conf_lineno);
+ switch (ret) {
+ case true:
+ sudo_conf_data.settings.updated = true;
+ break;
+ case false:
+ break;
+ default:
+ goto done;
+ }
+ }
+ break;
+ }
+ }
+ if (cur->name == NULL) {
+ sudo_debug_printf(SUDO_DEBUG_WARN,
+ "%s: %s:%u: unsupported entry: %s", __func__, conf_file,
+ conf_lineno, line);
+ }
+ }
+ ret = true;
+
+done:
+ if (fp != NULL)
+ fclose(fp);
+ free(line);
+
+ /* Restore locale if needed. */
+ if (prev_locale[0] != 'C' || prev_locale[1] != '\0')
+ setlocale(LC_ALL, prev_locale);
+ free(prev_locale);
+ debug_return_int(ret);
+}
+
+/*
+ * Used by the sudo_conf regress test to clear compile-time path settings.
+ */
+void
+sudo_conf_clear_paths_v1(void)
+{
+ struct sudo_conf_path_table *cur;
+ debug_decl(sudo_conf_clear_paths, SUDO_DEBUG_UTIL);
+
+ for (cur = sudo_conf_data.path_table; cur->pname != NULL; cur++) {
+ if (cur->dynamic)
+ free(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..50c1845
--- /dev/null
+++ b/lib/util/sudo_debug.c
@@ -0,0 +1,962 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011-2017 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_plugin.h"
+#include "sudo_util.h"
+
+/*
+ * The debug priorities and subsystems are currently hard-coded.
+ * In the future we might consider allowing plugins to register their
+ * own subsystems and provide direct access to the debugging API.
+ */
+
+/* Note: this must match the order in sudo_debug.h */
+static const char *const sudo_debug_priorities[] = {
+ "crit",
+ "err",
+ "warn",
+ "notice",
+ "diag",
+ "info",
+ "trace",
+ "debug",
+ NULL
+};
+
+/* Note: this must match the order in sudo_debug.h */
+static const char *const sudo_debug_default_subsystems[] = {
+ "args",
+ "conv",
+ "edit",
+ "event",
+ "exec",
+ "hooks",
+ "main",
+ "netif",
+ "pcomm",
+ "plugin",
+ "pty",
+ "selinux",
+ "util",
+ "utmp",
+ NULL
+};
+
+#define NUM_DEF_SUBSYSTEMS (nitems(sudo_debug_default_subsystems) - 1)
+
+/*
+ * For multiple programs/plugins there is a per-program instance
+ * and one or more outputs (files).
+ */
+struct sudo_debug_output {
+ SLIST_ENTRY(sudo_debug_output) entries;
+ char *filename;
+ int *settings;
+ int fd;
+};
+SLIST_HEAD(sudo_debug_output_list, sudo_debug_output);
+struct sudo_debug_instance {
+ char *program;
+ const char *const *subsystems;
+ const unsigned int *subsystem_ids;
+ unsigned int max_subsystem;
+ unsigned int refcnt;
+ struct sudo_debug_output_list outputs;
+};
+
+/* Support up to 10 instances. */
+#define SUDO_DEBUG_INSTANCE_MAX 10
+static struct sudo_debug_instance *sudo_debug_instances[SUDO_DEBUG_INSTANCE_MAX];
+static int sudo_debug_last_instance = -1;
+
+static char sudo_debug_pidstr[(((sizeof(int) * 8) + 2) / 3) + 3];
+static size_t sudo_debug_pidlen;
+
+#define round_nfds(_n) (((_n) + (4 * NBBY) - 1) & ~((4 * NBBY) - 1))
+static int sudo_debug_fds_size;
+static unsigned char *sudo_debug_fds;
+static int sudo_debug_max_fd = -1;
+
+/* Default instance index to use for common utility functions. */
+static int sudo_debug_active_instance = -1;
+
+/*
+ * Free the specified output structure.
+ */
+static void
+sudo_debug_free_output(struct sudo_debug_output *output)
+{
+ free(output->filename);
+ free(output->settings);
+ if (output->fd != -1)
+ close(output->fd);
+ free(output);
+}
+
+/*
+ * Create a new output file for the specified debug instance.
+ * Returns NULL if the file cannot be opened or memory cannot be allocated.
+ * XXX - check for duplicates
+ */
+static struct sudo_debug_output *
+sudo_debug_new_output(struct sudo_debug_instance *instance,
+ struct sudo_debug_file *debug_file)
+{
+ char *buf, *cp, *last, *subsys, *pri;
+ struct sudo_debug_output *output;
+ unsigned int j;
+ int i;
+
+ /* Create new output for the instance. */
+ /* XXX - reuse fd for existing filename? */
+ output = calloc(1, sizeof(*output));
+ if (output == NULL)
+ goto oom;
+ output->fd = -1;
+ output->settings = reallocarray(NULL, instance->max_subsystem + 1,
+ sizeof(int));
+ if (output->settings == NULL)
+ goto oom;
+ output->filename = strdup(debug_file->debug_file);
+ if (output->filename == NULL)
+ goto oom;
+
+ /* Init per-subsystems settings to -1 since 0 is a valid priority. */
+ for (j = 0; j <= instance->max_subsystem; j++)
+ output->settings[j] = -1;
+
+ /* Open debug file. */
+ output->fd = open(output->filename, O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR);
+ if (output->fd == -1) {
+ /* Create debug file as needed and set group ownership. */
+ if (errno == ENOENT) {
+ output->fd = open(output->filename, O_WRONLY|O_APPEND|O_CREAT,
+ S_IRUSR|S_IWUSR);
+ }
+ if (output->fd == -1) {
+ sudo_warn_nodebug("%s", output->filename);
+ goto bad;
+ }
+ ignore_result(fchown(output->fd, (uid_t)-1, 0));
+ }
+ if (fcntl(output->fd, F_SETFD, FD_CLOEXEC) == -1) {
+ sudo_warn_nodebug("%s", output->filename);
+ goto bad;
+ }
+ if (sudo_debug_fds_size < output->fd) {
+ /* Bump fds size to the next multiple of 4 * NBBY. */
+ const int old_size = sudo_debug_fds_size / NBBY;
+ const int new_size = round_nfds(output->fd + 1) / NBBY;
+ unsigned char *new_fds;
+
+ new_fds = realloc(sudo_debug_fds, new_size);
+ if (new_fds == NULL)
+ goto oom;
+ memset(new_fds + old_size, 0, new_size - old_size);
+ sudo_debug_fds = new_fds;
+ sudo_debug_fds_size = new_size * NBBY;
+ }
+ sudo_setbit(sudo_debug_fds, output->fd);
+ if (output->fd > sudo_debug_max_fd)
+ sudo_debug_max_fd = output->fd;
+
+ /* Parse Debug conf string. */
+ buf = strdup(debug_file->debug_flags);
+ if (buf == NULL)
+ goto oom;
+ for ((cp = strtok_r(buf, ",", &last)); cp != NULL; (cp = strtok_r(NULL, ",", &last))) {
+ /* Should be in the form subsys@pri. */
+ subsys = cp;
+ if ((pri = strchr(cp, '@')) == NULL)
+ continue;
+ *pri++ = '\0';
+
+ /* Look up priority and subsystem, fill in sudo_debug_settings[]. */
+ for (i = 0; sudo_debug_priorities[i] != NULL; i++) {
+ if (strcasecmp(pri, sudo_debug_priorities[i]) == 0) {
+ for (j = 0; instance->subsystems[j] != NULL; j++) {
+ if (strcasecmp(subsys, "all") == 0) {
+ const unsigned int idx = instance->subsystem_ids ?
+ SUDO_DEBUG_SUBSYS(instance->subsystem_ids[j]) : j;
+ if (i > output->settings[idx])
+ output->settings[idx] = i;
+ continue;
+ }
+ if (strcasecmp(subsys, instance->subsystems[j]) == 0) {
+ const unsigned int idx = instance->subsystem_ids ?
+ SUDO_DEBUG_SUBSYS(instance->subsystem_ids[j]) : j;
+ if (i > output->settings[idx])
+ output->settings[idx] = i;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ free(buf);
+
+ return output;
+oom:
+ // -V:sudo_warn_nodebug:575, 618
+ sudo_warn_nodebug(NULL);
+bad:
+ if (output != NULL)
+ sudo_debug_free_output(output);
+ return NULL;
+}
+
+/*
+ * Register a program/plugin with the debug framework,
+ * parses settings string from sudo.conf and opens debug_files.
+ * If subsystem names are specified they override the default values.
+ * NOTE: subsystems must not be freed by caller unless deregistered.
+ * Sets the active instance to the newly registered instance.
+ * Returns instance index on success, SUDO_DEBUG_INSTANCE_INITIALIZER
+ * if no debug files are specified and SUDO_DEBUG_INSTANCE_ERROR
+ * on error.
+ */
+int
+sudo_debug_register_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;
+ instance->refcnt = 1;
+ SLIST_INIT(&instance->outputs);
+ sudo_debug_instances[idx] = instance;
+ if (idx != free_idx)
+ sudo_debug_last_instance++;
+ } else {
+ /* Check for matching instance but different ids[]. */
+ if (ids != NULL && instance->subsystem_ids != ids) {
+ unsigned int i;
+
+ for (i = 0; subsystems[i] != NULL; i++)
+ ids[i] = instance->subsystem_ids[i];
+ }
+ instance->refcnt++;
+ }
+
+ TAILQ_FOREACH(debug_file, debug_files, entries) {
+ output = sudo_debug_new_output(instance, debug_file);
+ 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.
+ * Returns the number of remaining references for the instance or -1 on error.
+ */
+int
+sudo_debug_deregister_v1(int idx)
+{
+ struct sudo_debug_instance *instance;
+ struct sudo_debug_output *output, *next;
+ debug_decl_func(sudo_debug_deregister);
+
+ if (idx < 0 || idx > sudo_debug_last_instance) {
+ sudo_warnx_nodebug("%s: invalid instance ID %d, max %d",
+ __func__, idx, sudo_debug_last_instance);
+ return -1;
+ }
+ /* Reset active instance as needed. */
+ if (sudo_debug_active_instance == idx)
+ sudo_debug_active_instance = -1;
+
+ instance = sudo_debug_instances[idx];
+ if (instance == NULL)
+ return -1; /* already deregistered */
+
+ if (--instance->refcnt != 0)
+ return instance->refcnt; /* ref held by other caller */
+
+ /* Free up instance data, note that subsystems[] is owned by caller. */
+ sudo_debug_instances[idx] = NULL;
+ SLIST_FOREACH_SAFE(output, &instance->outputs, entries, next) {
+ close(output->fd);
+ free(output->filename);
+ free(output->settings);
+ free(output);
+ }
+ free(instance->program);
+ free(instance);
+
+ if (idx == sudo_debug_last_instance)
+ sudo_debug_last_instance--;
+
+ return 0;
+}
+
+/*
+ * Parse the "filename flags,..." debug_flags entry from sudo.conf
+ * and insert a new sudo_debug_file struct into the list.
+ * Returns 0 on success, 1 on parse error or -1 on malloc failure.
+ */
+int
+sudo_debug_parse_flags_v1(struct sudo_conf_debug_file_list *debug_files,
+ const char *entry)
+{
+ struct sudo_debug_file *debug_file;
+ const char *filename, *flags;
+ size_t namelen;
+
+ /* Only process new-style debug flags: filename flags,... */
+ filename = entry;
+ if (*filename != '/' || (flags = strpbrk(filename, " \t")) == NULL)
+ return 1;
+ namelen = (size_t)(flags - filename);
+ while (isblank((unsigned char)*flags))
+ flags++;
+ if (*flags != '\0') {
+ if ((debug_file = calloc(1, sizeof(*debug_file))) == NULL)
+ goto oom;
+ if ((debug_file->debug_file = strndup(filename, namelen)) == NULL)
+ goto oom;
+ if ((debug_file->debug_flags = strdup(flags)) == NULL)
+ goto oom;
+ TAILQ_INSERT_TAIL(debug_files, debug_file, entries);
+ }
+ return 0;
+oom:
+ if (debug_file != NULL) {
+ free(debug_file->debug_file);
+ free(debug_file->debug_flags);
+ free(debug_file);
+ }
+ return -1;
+}
+
+int
+sudo_debug_get_instance_v1(const char *program)
+{
+ int idx;
+
+ for (idx = 0; idx <= sudo_debug_last_instance; idx++) {
+ if (sudo_debug_instances[idx] == NULL)
+ continue;
+ if (strcmp(sudo_debug_instances[idx]->program, program) == 0)
+ return idx;
+ }
+ return SUDO_DEBUG_INSTANCE_INITIALIZER;
+}
+
+pid_t
+sudo_debug_fork_v1(void)
+{
+ pid_t pid;
+
+ if ((pid = fork()) == 0) {
+ (void)snprintf(sudo_debug_pidstr, sizeof(sudo_debug_pidstr), "[%d] ",
+ (int)getpid());
+ sudo_debug_pidlen = strlen(sudo_debug_pidstr);
+ }
+
+ return pid;
+}
+
+void
+sudo_debug_enter_v1(const char *func, const char *file, int line,
+ int subsys)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "-> %s @ %s:%d", func, file, line);
+}
+
+void
+sudo_debug_exit_v1(const char *func, const char *file, int line,
+ int subsys)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d", func, file, line);
+}
+
+void
+sudo_debug_exit_int_v1(const char *func, const char *file, int line,
+ int subsys, int ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %d", func, file, line, ret);
+}
+
+void
+sudo_debug_exit_long_v1(const char *func, const char *file, int line,
+ int subsys, long ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %ld", func, file, line, ret);
+}
+
+void
+sudo_debug_exit_id_t_v1(const char *func, const char *file, int line,
+ int subsys, id_t ret)
+{
+#if SIZEOF_ID_T == 8
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %lld", func, file, line, (long long)ret);
+#else
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %d", func, file, line, (int)ret);
+#endif
+}
+
+void
+sudo_debug_exit_size_t_v1(const char *func, const char *file, int line,
+ int subsys, size_t ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %zu", func, file, line, ret);
+}
+
+void
+sudo_debug_exit_ssize_t_v1(const char *func, const char *file, int line,
+ int subsys, ssize_t ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %zd", func, file, line, ret);
+}
+
+void
+sudo_debug_exit_time_t_v1(const char *func, const char *file, int line,
+ int subsys, time_t ret)
+{
+#if SIZEOF_TIME_T == 8
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %lld", func, file, line, (long long)ret);
+#else
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %d", func, file, line, (int)ret);
+#endif
+}
+
+void
+sudo_debug_exit_bool_v1(const char *func, const char *file, int line,
+ int subsys, bool ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %s", func, file, line, ret ? "true" : "false");
+}
+
+void
+sudo_debug_exit_str_v1(const char *func, const char *file, int line,
+ int subsys, const char *ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %s", func, file, line, ret ? ret : "(null)");
+}
+
+void
+sudo_debug_exit_str_masked_v1(const char *func, const char *file, int line,
+ int subsys, const char *ret)
+{
+ static const char stars[] = "********************************************************************************";
+ int len = ret ? strlen(ret) : sizeof("(null)") - 1;
+
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %.*s", func, file, line, len, ret ? stars : "(null)");
+}
+
+void
+sudo_debug_exit_ptr_v1(const char *func, const char *file, int line,
+ int subsys, const void *ret)
+{
+ sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
+ "<- %s @ %s:%d := %p", func, file, line, ret);
+}
+
+void
+sudo_debug_write2_v1(int fd, const char *func, const char *file, int lineno,
+ const char *str, int len, int errnum)
+{
+ char *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));
+}
+
+bool
+sudo_debug_needed_v1(int level)
+{
+ unsigned int subsys;
+ int pri;
+ struct sudo_debug_instance *instance;
+ struct sudo_debug_output *output;
+ bool result = false;
+
+ if (sudo_debug_active_instance == -1)
+ goto out;
+
+ /* Extract priority and subsystem from level. */
+ pri = SUDO_DEBUG_PRI(level);
+ subsys = (unsigned int)SUDO_DEBUG_SUBSYS(level);
+
+ if (sudo_debug_active_instance > sudo_debug_last_instance)
+ goto out;
+
+ instance = sudo_debug_instances[sudo_debug_active_instance];
+ if (instance == NULL)
+ goto out;
+
+ if (subsys <= instance->max_subsystem) {
+ SLIST_FOREACH(output, &instance->outputs, entries) {
+ if (output->settings[subsys] >= pri) {
+ result = true;
+ break;
+ }
+ }
+ }
+out:
+ return result;
+}
+
+void
+sudo_debug_vprintf2_v1(const char *func, const char *file, int lineno, int level,
+ const char *fmt, va_list ap)
+{
+ int buflen, pri, saved_errno = errno;
+ unsigned int subsys;
+ char static_buf[1024], *buf = static_buf;
+ struct sudo_debug_instance *instance;
+ struct sudo_debug_output *output;
+ debug_decl_func(sudo_debug_vprintf2);
+
+ if (sudo_debug_active_instance == -1)
+ goto out;
+
+ /* Extract priority and subsystem from level. */
+ pri = SUDO_DEBUG_PRI(level);
+ subsys = SUDO_DEBUG_SUBSYS(level);
+
+ /* Find matching instance. */
+ if (sudo_debug_active_instance > sudo_debug_last_instance) {
+ sudo_warnx_nodebug("%s: invalid instance ID %d, max %d",
+ __func__, sudo_debug_active_instance, sudo_debug_last_instance);
+ goto out;
+ }
+ instance = sudo_debug_instances[sudo_debug_active_instance];
+ if (instance == NULL) {
+ sudo_warnx_nodebug("%s: unregistered instance index %d", __func__,
+ sudo_debug_active_instance);
+ goto out;
+ }
+
+ SLIST_FOREACH(output, &instance->outputs, entries) {
+ /* Make sure we want debug info at this level. */
+ if (subsys <= instance->max_subsystem && output->settings[subsys] >= pri) {
+ va_list ap2;
+
+ /* Operate on a copy of ap to support multiple outputs. */
+ va_copy(ap2, ap);
+ buflen = fmt ? vsnprintf(static_buf, sizeof(static_buf), fmt, ap2) : 0;
+ va_end(ap2);
+ if (buflen >= ssizeof(static_buf)) {
+ va_list ap3;
+
+ /* Not enough room in static buf, allocate dynamically. */
+ va_copy(ap3, ap);
+ buflen = vasprintf(&buf, fmt, ap3);
+ va_end(ap3);
+ }
+ if (buflen != -1) {
+ int errcode = ISSET(level, SUDO_DEBUG_ERRNO) ? saved_errno : 0;
+ if (ISSET(level, SUDO_DEBUG_LINENO))
+ sudo_debug_write2(output->fd, func, file, lineno, buf, buflen, errcode);
+ else
+ sudo_debug_write2(output->fd, NULL, NULL, 0, buf, buflen, errcode);
+ if (buf != static_buf) {
+ free(buf);
+ buf = static_buf;
+ }
+ }
+ }
+ }
+out:
+ errno = saved_errno;
+}
+
+#ifdef NO_VARIADIC_MACROS
+void
+sudo_debug_printf_nvm_v1(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ sudo_debug_vprintf2(NULL, NULL, 0, pri, fmt, ap);
+ va_end(ap);
+}
+#endif /* NO_VARIADIC_MACROS */
+
+void
+sudo_debug_printf2_v1(const char *func, const char *file, int lineno, int level,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ sudo_debug_vprintf2(func, file, lineno, level, fmt, ap);
+ va_end(ap);
+}
+
+#define EXEC_PREFIX "exec "
+
+void
+sudo_debug_execve2_v1(int level, const char *path, char *const argv[], char *const envp[])
+{
+ int buflen, pri, saved_errno = errno;
+ unsigned int subsys;
+ struct sudo_debug_instance *instance;
+ struct sudo_debug_output *output;
+ char * const *av;
+ char *cp, static_buf[4096], *buf = static_buf;
+ size_t plen;
+ debug_decl_func(sudo_debug_execve2);
+
+ if (sudo_debug_active_instance == -1)
+ 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 >= ssizeof(static_buf)) {
+ buf = malloc(buflen + 1);
+ if (buf == NULL)
+ goto out;
+ }
+
+ /* Copy prefix and command. */
+ memcpy(buf, EXEC_PREFIX, sizeof(EXEC_PREFIX) - 1);
+ cp = buf + sizeof(EXEC_PREFIX) - 1;
+ memcpy(cp, path, plen);
+ cp += plen;
+
+ /* Copy argv. */
+ if (argv[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..6068e36
--- /dev/null
+++ b/lib/util/sudo_dso.c
@@ -0,0 +1,320 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if defined(HAVE_SHL_LOAD)
+# include <dl.h>
+#elif defined(HAVE_DLOPEN)
+# include <dlfcn.h>
+#endif
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_dso.h"
+
+/*
+ * 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..f90cfb3
--- /dev/null
+++ b/lib/util/term.c
@@ -0,0 +1,290 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011-2015, 2017-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+/* TCSASOFT is a BSD extension that ignores control flags and speed. */
+#ifndef TCSASOFT
+# define TCSASOFT 0
+#endif
+
+/* Non-standard termios input flags */
+#ifndef IUCLC
+# define IUCLC 0
+#endif
+#ifndef IMAXBEL
+# define IMAXBEL 0
+#endif
+#ifndef IUTF8
+# define IUTF8 0
+#endif
+
+/* Non-standard termios output flags */
+#ifndef OLCUC
+# define OLCUC 0
+#endif
+#ifndef ONLCR
+# define ONLCR 0
+#endif
+#ifndef OCRNL
+# define OCRNL 0
+#endif
+#ifndef ONOCR
+# define ONOCR 0
+#endif
+#ifndef ONLRET
+# define ONLRET 0
+#endif
+
+/* Non-standard termios local flags */
+#ifndef XCASE
+# define XCASE 0
+#endif
+#ifndef IEXTEN
+# define IEXTEN 0
+#endif
+#ifndef ECHOCTL
+# define ECHOCTL 0
+#endif
+#ifndef ECHOKE
+# define ECHOKE 0
+#endif
+#ifndef PENDIN
+# define PENDIN 0
+#endif
+
+static struct termios oterm;
+static int changed;
+
+/* tgetpass() needs to know the erase and kill chars for cbreak mode. */
+sudo_dso_public int sudo_term_eof;
+sudo_dso_public int sudo_term_erase;
+sudo_dso_public int sudo_term_kill;
+
+static volatile sig_atomic_t got_sigttou;
+
+/*
+ * SIGTTOU signal handler for term_restore that just sets a flag.
+ */
+static void
+sigttou(int signo)
+{
+ got_sigttou = 1;
+}
+
+/*
+ * Like tcsetattr() but restarts on EINTR _except_ for SIGTTOU.
+ * Returns 0 on success or -1 on failure, setting errno.
+ * Sets got_sigttou on failure if interrupted by SIGTTOU.
+ */
+static int
+tcsetattr_nobg(int fd, int flags, struct termios *tp)
+{
+ struct sigaction sa, osa;
+ int rc;
+
+ /*
+ * 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)
+{
+ struct termios term;
+ debug_decl(sudo_term_noecho, SUDO_DEBUG_UTIL);
+
+ if (!changed && tcgetattr(fd, &oterm) != 0)
+ debug_return_bool(false);
+ (void) memcpy(&term, &oterm, sizeof(term));
+ CLR(term.c_lflag, ECHO|ECHONL);
+#ifdef VSTATUS
+ term.c_cc[VSTATUS] = _POSIX_VDISABLE;
+#endif
+ if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
+ changed = 1;
+ debug_return_bool(true);
+ }
+ debug_return_bool(false);
+}
+
+/*
+ * Set terminal to raw mode with optional terminal signals.
+ * Returns true on success or false on failure.
+ */
+bool
+sudo_term_raw_v1(int fd, int isig)
+{
+ struct termios term;
+ debug_decl(sudo_term_raw, SUDO_DEBUG_UTIL);
+
+ if (!changed && tcgetattr(fd, &oterm) != 0)
+ debug_return_bool(false);
+ (void) memcpy(&term, &oterm, sizeof(term));
+ /* Set terminal to raw mode but optionally enable terminal signals. */
+ cfmakeraw(&term);
+ if (isig)
+ SET(term.c_lflag, ISIG);
+ if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
+ changed = 1;
+ debug_return_bool(true);
+ }
+ debug_return_bool(false);
+}
+
+/*
+ * Set terminal to cbreak mode.
+ * Returns true on success or false on failure.
+ */
+bool
+sudo_term_cbreak_v1(int fd)
+{
+ struct termios term;
+ debug_decl(sudo_term_cbreak, SUDO_DEBUG_UTIL);
+
+ if (!changed && tcgetattr(fd, &oterm) != 0)
+ debug_return_bool(false);
+ (void) memcpy(&term, &oterm, sizeof(term));
+ /* Set terminal to half-cooked mode */
+ term.c_cc[VMIN] = 1;
+ term.c_cc[VTIME] = 0;
+ /* cppcheck-suppress redundantAssignment */
+ CLR(term.c_lflag, ECHO | ECHONL | ICANON | IEXTEN);
+ /* cppcheck-suppress redundantAssignment */
+ SET(term.c_lflag, ISIG);
+#ifdef VSTATUS
+ term.c_cc[VSTATUS] = _POSIX_VDISABLE;
+#endif
+ if (tcsetattr_nobg(fd, TCSASOFT|TCSADRAIN, &term) == 0) {
+ sudo_term_eof = term.c_cc[VEOF];
+ sudo_term_erase = term.c_cc[VERASE];
+ sudo_term_kill = term.c_cc[VKILL];
+ changed = 1;
+ debug_return_bool(true);
+ }
+ debug_return_bool(false);
+}
+
+/* Termios flags to copy between terminals. */
+#define INPUT_FLAGS (IGNPAR|PARMRK|INPCK|ISTRIP|INLCR|IGNCR|ICRNL|IUCLC|IXON|IXANY|IXOFF|IMAXBEL|IUTF8)
+#define OUTPUT_FLAGS (OPOST|OLCUC|ONLCR|OCRNL|ONOCR|ONLRET)
+#define CONTROL_FLAGS (CS7|CS8|PARENB|PARODD)
+#define LOCAL_FLAGS (ISIG|ICANON|XCASE|ECHO|ECHOE|ECHOK|ECHONL|NOFLSH|TOSTOP|IEXTEN|ECHOCTL|ECHOKE|PENDIN)
+
+/*
+ * Copy terminal settings from one descriptor to another.
+ * We cannot simply copy the struct termios as src and dst may be
+ * different terminal types (pseudo-tty vs. console or glass tty).
+ * Returns true on success or false on failure.
+ */
+bool
+sudo_term_copy_v1(int src, int dst)
+{
+ struct termios tt_src, tt_dst;
+ struct winsize wsize;
+ speed_t speed;
+ int i;
+ debug_decl(sudo_term_copy, SUDO_DEBUG_UTIL);
+
+ if (tcgetattr(src, &tt_src) != 0 || tcgetattr(dst, &tt_dst) != 0)
+ debug_return_bool(false);
+
+ /* Clear select input, output, control and local flags. */
+ CLR(tt_dst.c_iflag, INPUT_FLAGS);
+ CLR(tt_dst.c_oflag, OUTPUT_FLAGS);
+ CLR(tt_dst.c_cflag, CONTROL_FLAGS);
+ CLR(tt_dst.c_lflag, LOCAL_FLAGS);
+
+ /* Copy select input, output, control and local flags. */
+ SET(tt_dst.c_iflag, (tt_src.c_iflag & INPUT_FLAGS));
+ SET(tt_dst.c_oflag, (tt_src.c_oflag & OUTPUT_FLAGS));
+ SET(tt_dst.c_cflag, (tt_src.c_cflag & CONTROL_FLAGS));
+ SET(tt_dst.c_lflag, (tt_src.c_lflag & LOCAL_FLAGS));
+
+ /* Copy special chars from src verbatim. */
+ for (i = 0; i < NCCS; i++)
+ tt_dst.c_cc[i] = tt_src.c_cc[i];
+
+ /* Copy speed from src (zero output speed closes the connection). */
+ if ((speed = cfgetospeed(&tt_src)) == B0)
+ speed = B38400;
+ cfsetospeed(&tt_dst, speed);
+ speed = cfgetispeed(&tt_src);
+ cfsetispeed(&tt_dst, speed);
+
+ if (tcsetattr_nobg(dst, TCSASOFT|TCSAFLUSH, &tt_dst) == -1)
+ debug_return_bool(false);
+
+ if (ioctl(src, TIOCGWINSZ, &wsize) == 0)
+ (void)ioctl(dst, TIOCSWINSZ, &wsize);
+
+ debug_return_bool(true);
+}
diff --git a/lib/util/ttyname_dev.c b/lib/util/ttyname_dev.c
new file mode 100644
index 0000000..08b9777
--- /dev/null
+++ b/lib/util/ttyname_dev.c
@@ -0,0 +1,311 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2012-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#if defined(MAJOR_IN_MKDEV)
+# include <sys/mkdev.h>
+#elif defined(MAJOR_IN_SYSMACROS)
+# include <sys/sysmacros.h>
+#else
+# include <sys/param.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <dirent.h>
+
+#include "pathnames.h"
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_conf.h"
+#include "sudo_util.h"
+
+#if defined(HAVE_DEVNAME)
+/*
+ * Like ttyname() but uses a dev_t instead of an open fd.
+ * Returns name on success and NULL on failure, setting errno.
+ * The BSD version uses devname().
+ */
+char *
+sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen)
+{
+ char *dev;
+ debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
+
+ /* Some versions of devname() return NULL on failure, others do not. */
+ dev = devname(tdev, S_IFCHR);
+ if (dev != NULL && *dev != '?' && *dev != '#') {
+ if (strlcpy(name, _PATH_DEV, namelen) < namelen &&
+ strlcat(name, dev, namelen) < namelen)
+ debug_return_str(name);
+ errno = ERANGE;
+ } else {
+ /* Not all versions of devname() set errno. */
+ errno = ENOENT;
+ }
+ debug_return_str(NULL);
+}
+#elif defined(HAVE__TTYNAME_DEV)
+extern char *_ttyname_dev(dev_t rdev, char *buffer, size_t buflen);
+
+/*
+ * Like ttyname() but uses a dev_t instead of an open fd.
+ * Returns name on success and NULL on failure, setting errno.
+ * This version is just a wrapper around _ttyname_dev().
+ */
+char *
+sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen)
+{
+ int serrno = errno;
+ debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
+
+ /*
+ * _ttyname_dev() sets errno to ERANGE if namelen is too small
+ * but does not modify it if tdev is not found.
+ */
+ errno = ENOENT;
+ if (_ttyname_dev(tdev, name, namelen) == NULL)
+ debug_return_str(NULL);
+ errno = serrno;
+
+ debug_return_str(name);
+}
+#else
+/*
+ * Device nodes to ignore.
+ */
+static const char *ignore_devs[] = {
+ _PATH_DEV "stdin",
+ _PATH_DEV "stdout",
+ _PATH_DEV "stderr",
+ NULL
+};
+
+/*
+ * Do a scan of a directory looking for the specified device.
+ * Does not descend into subdirectories.
+ * Returns name on success and NULL on failure, setting errno.
+ */
+static char *
+sudo_ttyname_scan(const char *dir, dev_t rdev, char *name, size_t namelen)
+{
+ size_t sdlen;
+ char pathbuf[PATH_MAX];
+ char *ret = NULL;
+ struct dirent *dp;
+ struct stat sb;
+ unsigned int i;
+ DIR *d = NULL;
+ debug_decl(sudo_ttyname_scan, SUDO_DEBUG_UTIL);
+
+ if (dir[0] == '\0') {
+ errno = ENOENT;
+ goto done;
+ }
+ if ((d = opendir(dir)) == NULL)
+ goto done;
+
+ if (fstat(dirfd(d), &sb) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to fstat %s", dir);
+ goto done;
+ }
+ if ((sb.st_mode & S_IWOTH) != 0) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "ignoring world-writable directory %s", dir);
+ errno = ENOENT;
+ goto done;
+ }
+
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "scanning for dev %u in %s", (unsigned int)rdev, dir);
+
+ sdlen = strlen(dir);
+ while (sdlen > 0 && dir[sdlen - 1] == '/')
+ sdlen--;
+ if (sdlen + 1 >= sizeof(pathbuf)) {
+ errno = ERANGE;
+ goto done;
+ }
+ memcpy(pathbuf, dir, sdlen);
+ pathbuf[sdlen++] = '/';
+
+ while ((dp = readdir(d)) != NULL) {
+ /* Skip anything starting with "." */
+ if (dp->d_name[0] == '.')
+ continue;
+
+ pathbuf[sdlen] = '\0';
+ if (strlcat(pathbuf, dp->d_name, sizeof(pathbuf)) >= sizeof(pathbuf)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "%s%s is too big to fit in pathbuf", pathbuf, dp->d_name);
+ continue;
+ }
+
+ /* Ignore device nodes listed in ignore_devs[]. */
+ for (i = 0; ignore_devs[i] != NULL; i++) {
+ if (strcmp(pathbuf, ignore_devs[i]) == 0)
+ break;
+ }
+ if (ignore_devs[i] != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "ignoring %s", pathbuf);
+ continue;
+ }
+
+# if defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ /*
+ * Avoid excessive stat() calls by checking dp->d_type.
+ */
+ switch (dp->d_type) {
+ case DT_CHR:
+ case DT_LNK:
+ case DT_UNKNOWN:
+ break;
+ default:
+ /* Not a character device or link, skip it. */
+ sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+ "skipping non-device %s", pathbuf);
+ continue;
+ }
+# endif
+ if (stat(pathbuf, &sb) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+ "unable to stat %s", pathbuf);
+ continue;
+ }
+ if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "resolved dev %u as %s", (unsigned int)rdev, pathbuf);
+ if (strlcpy(name, pathbuf, namelen) < namelen) {
+ ret = name;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to store %s, have %zu, need %zu",
+ pathbuf, namelen, strlen(pathbuf) + 1);
+ errno = ERANGE;
+ }
+ goto done;
+ }
+ }
+
+done:
+ if (d != NULL)
+ closedir(d);
+ debug_return_str(ret);
+}
+
+static char *
+sudo_dev_check(dev_t rdev, const char *devname, char *buf, size_t buflen)
+{
+ struct stat sb;
+ debug_decl(sudo_dev_check, SUDO_DEBUG_UTIL);
+
+ if (stat(devname, &sb) == 0) {
+ if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "comparing dev %u to %s: match!",
+ (unsigned int)rdev, devname);
+ if (strlcpy(buf, devname, buflen) < buflen)
+ debug_return_str(buf);
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "unable to store %s, have %zu, need %zu",
+ devname, buflen, strlen(devname) + 1);
+ errno = ERANGE;
+ }
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+ "comparing dev %u to %s: no", (unsigned int)rdev, devname);
+ debug_return_str(NULL);
+}
+
+/*
+ * Like ttyname() but uses a dev_t instead of an open fd.
+ * Returns name on success and NULL on failure, setting errno.
+ * Generic version.
+ */
+char *
+sudo_ttyname_dev_v1(dev_t rdev, char *buf, size_t buflen)
+{
+ const char *devsearch, *devsearch_end;
+ char path[PATH_MAX], *ret;
+ const char *cp, *ep;
+ size_t len;
+ debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
+
+ /*
+ * First, check /dev/console.
+ */
+ ret = sudo_dev_check(rdev, _PATH_DEV "console", buf, buflen);
+ if (ret != NULL)
+ goto done;
+
+ /*
+ * Then check the device search path.
+ */
+ devsearch = sudo_conf_devsearch_path();
+ devsearch_end = devsearch + strlen(devsearch);
+ for (cp = sudo_strsplit(devsearch, devsearch_end, ":", &ep);
+ cp != NULL; cp = sudo_strsplit(NULL, devsearch_end, ":", &ep)) {
+
+ len = (size_t)(ep - cp);
+ if (len >= sizeof(path)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "devsearch entry %.*s too long", (int)len, cp);
+ continue;
+ }
+ memcpy(path, cp, len);
+ path[len] = '\0';
+
+ if (strcmp(path, _PATH_DEV "pts") == 0) {
+ /* Special case /dev/pts */
+ len = (size_t)snprintf(path, sizeof(path), "%spts/%u",
+ _PATH_DEV, (unsigned int)minor(rdev));
+ if (len >= sizeof(path)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+ "devsearch entry %spts/%u too long",
+ _PATH_DEV, (unsigned int)minor(rdev));
+ continue;
+ }
+ ret = sudo_dev_check(rdev, path, buf, buflen);
+ if (ret != NULL)
+ goto done;
+ } else {
+ /* Scan path, looking for rdev. */
+ ret = sudo_ttyname_scan(path, rdev, buf, buflen);
+ if (ret != NULL || errno == ENOMEM)
+ goto done;
+ }
+ }
+
+done:
+ debug_return_str(ret);
+}
+#endif
diff --git a/lib/util/ttysize.c b/lib/util/ttysize.c
new file mode 100644
index 0000000..221ea92
--- /dev/null
+++ b/lib/util/ttysize.c
@@ -0,0 +1,71 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010-2012, 2014-2015 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/ioctl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <termios.h> /* for struct winsize on HP-UX */
+#include <limits.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+static int
+get_ttysize_ioctl(int *rowp, int *colp)
+{
+ struct winsize wsize;
+ debug_decl(get_ttysize_ioctl, SUDO_DEBUG_UTIL);
+
+ if (ioctl(STDERR_FILENO, TIOCGWINSZ, &wsize) == 0 &&
+ wsize.ws_row != 0 && wsize.ws_col != 0) {
+ *rowp = wsize.ws_row;
+ *colp = wsize.ws_col;
+ debug_return_int(0);
+ }
+ debug_return_int(-1);
+}
+
+void
+sudo_get_ttysize_v1(int *rowp, int *colp)
+{
+ debug_decl(sudo_get_ttysize, SUDO_DEBUG_UTIL);
+
+ if (get_ttysize_ioctl(rowp, colp) == -1) {
+ char *p;
+
+ /* Fall back on $LINES and $COLUMNS. */
+ if ((p = getenv("LINES")) == NULL ||
+ (*rowp = sudo_strtonum(p, 1, INT_MAX, NULL)) <= 0) {
+ *rowp = 24;
+ }
+ if ((p = getenv("COLUMNS")) == NULL ||
+ (*colp = sudo_strtonum(p, 1, INT_MAX, NULL)) <= 0) {
+ *colp = 80;
+ }
+ }
+
+ debug_return;
+}
diff --git a/lib/util/unlinkat.c b/lib/util/unlinkat.c
new file mode 100644
index 0000000..f1d590e
--- /dev/null
+++ b/lib/util/unlinkat.c
@@ -0,0 +1,60 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+
+#ifndef HAVE_UNLINKAT
+int
+sudo_unlinkat(int dfd, const char *path, int flag)
+{
+ int odfd, ret;
+
+ if (dfd == AT_FDCWD)
+ return unlink(path);
+
+ /* Save cwd */
+ if ((odfd = open(".", O_RDONLY)) == -1)
+ return -1;
+
+ if (fchdir(dfd) == -1) {
+ close(odfd);
+ return -1;
+ }
+
+ ret = unlink(path);
+
+ /* Restore cwd */
+ if (fchdir(odfd) == -1) {
+ /* Should not happen */
+ ret = -1;
+ }
+ close(odfd);
+
+ return ret;
+}
+#endif /* HAVE_UNLINKAT */
diff --git a/lib/util/util.exp.in b/lib/util/util.exp.in
new file mode 100644
index 0000000..b0b56f0
--- /dev/null
+++ b/lib/util/util.exp.in
@@ -0,0 +1,145 @@
+@COMPAT_EXP@initprogname
+initprogname2
+sudo_conf_askpass_path_v1
+sudo_conf_clear_paths_v1
+sudo_conf_debug_files_v1
+sudo_conf_debugging_v1
+sudo_conf_developer_mode_v1
+sudo_conf_devsearch_path_v1
+sudo_conf_disable_coredump_v1
+sudo_conf_group_source_v1
+sudo_conf_max_groups_v1
+sudo_conf_noexec_path_v1
+sudo_conf_plugin_dir_path_v1
+sudo_conf_plugins_v1
+sudo_conf_probe_interfaces_v1
+sudo_conf_read_v1
+sudo_conf_sesh_path_v1
+sudo_debug_deregister_v1
+sudo_debug_enter_v1
+sudo_debug_execve2_v1
+sudo_debug_exit_bool_v1
+sudo_debug_exit_id_t_v1
+sudo_debug_exit_int_v1
+sudo_debug_exit_long_v1
+sudo_debug_exit_ptr_v1
+sudo_debug_exit_size_t_v1
+sudo_debug_exit_ssize_t_v1
+sudo_debug_exit_str_masked_v1
+sudo_debug_exit_str_v1
+sudo_debug_exit_time_t_v1
+sudo_debug_exit_v1
+sudo_debug_fork_v1
+sudo_debug_get_active_instance_v1
+sudo_debug_get_fds_v1
+sudo_debug_get_instance_v1
+sudo_debug_needed_v1
+sudo_debug_parse_flags_v1
+sudo_debug_printf2_v1
+sudo_debug_register_v1
+sudo_debug_set_active_instance_v1
+sudo_debug_update_fd_v1
+sudo_debug_vprintf2_v1
+sudo_debug_write2_v1
+sudo_digest_alloc_v1
+sudo_digest_final_v1
+sudo_digest_free_v1
+sudo_digest_getlen_v1
+sudo_digest_reset_v1
+sudo_digest_update_v1
+sudo_dso_findsym_v1
+sudo_dso_load_v1
+sudo_dso_preload_table_v1
+sudo_dso_strerror_v1
+sudo_dso_unload_v1
+sudo_ev_add_v1
+sudo_ev_add_v2
+sudo_ev_alloc_v1
+sudo_ev_base_alloc_v1
+sudo_ev_base_free_v1
+sudo_ev_base_setdef_v1
+sudo_ev_del_v1
+sudo_ev_dispatch_v1
+sudo_ev_free_v1
+sudo_ev_get_timeleft_v1
+sudo_ev_get_timeleft_v2
+sudo_ev_got_break_v1
+sudo_ev_got_exit_v1
+sudo_ev_loop_v1
+sudo_ev_loopbreak_v1
+sudo_ev_loopcontinue_v1
+sudo_ev_loopexit_v1
+sudo_ev_pending_v1
+sudo_ev_set_v1
+sudo_fatal_callback_deregister_v1
+sudo_fatal_callback_register_v1
+sudo_fatal_nodebug_v1
+sudo_fatalx_nodebug_v1
+sudo_gai_fatal_nodebug_v1
+sudo_gai_vfatal_nodebug_v1
+sudo_gai_vwarn_nodebug_v1
+sudo_gai_warn_nodebug_v1
+sudo_get_ttysize_v1
+sudo_getgrouplist2_v1
+sudo_gethostname_v1
+sudo_gettime_awake_v1
+sudo_gettime_mono_v1
+sudo_gettime_real_v1
+sudo_json_add_value_as_object_v1
+sudo_json_add_value_v1
+sudo_json_close_array_v1
+sudo_json_close_object_v1
+sudo_json_free_v1
+sudo_json_get_buf_v1
+sudo_json_get_len_v1
+sudo_json_init_v1
+sudo_json_open_array_v1
+sudo_json_open_object_v1
+sudo_lbuf_append_quoted_v1
+sudo_lbuf_append_v1
+sudo_lbuf_clearerr_v1
+sudo_lbuf_destroy_v1
+sudo_lbuf_error_v1
+sudo_lbuf_init_v1
+sudo_lbuf_print_v1
+sudo_lock_file_v1
+sudo_lock_region_v1
+sudo_logfac2str_v1
+sudo_logpri2str_v1
+sudo_mkdir_parents_v1
+sudo_new_key_val_v1
+sudo_parse_gids_v1
+sudo_parseln_v1
+sudo_parseln_v2
+sudo_pow2_roundup_v1
+sudo_secure_dir_v1
+sudo_secure_file_v1
+sudo_setgroups_v1
+sudo_str2logfac_v1
+sudo_str2logpri_v1
+sudo_strsplit_v1
+sudo_strtobool_v1
+sudo_strtoid_v1
+sudo_strtoid_v2
+sudo_strtoidx_v1
+sudo_strtomode_v1
+sudo_strtonum
+sudo_term_cbreak_v1
+sudo_term_copy_v1
+sudo_term_eof
+sudo_term_erase
+sudo_term_kill
+sudo_term_noecho_v1
+sudo_term_raw_v1
+sudo_term_restore_v1
+sudo_ttyname_dev_v1
+sudo_uuid_create_v1
+sudo_uuid_to_string_v1
+sudo_vfatal_nodebug_v1
+sudo_vfatalx_nodebug_v1
+sudo_vwarn_nodebug_v1
+sudo_vwarnx_nodebug_v1
+sudo_warn_nodebug_v1
+sudo_warn_set_conversation_v1
+sudo_warn_set_locale_func_v1
+sudo_warnx_nodebug_v1
diff --git a/lib/util/utimens.c b/lib/util/utimens.c
new file mode 100644
index 0000000..c6b8ee2
--- /dev/null
+++ b/lib/util/utimens.c
@@ -0,0 +1,200 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2015, 2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#if !defined(HAVE_FUTIMENS) || !defined(HAVE_UTIMENSAT)
+
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <time.h>
+#if !defined(HAVE_UTIMES) || defined(HAVE_FUTIME)
+# include <utime.h>
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#if !defined(HAVE_FUTIMES) && defined(HAVE_FUTIMESAT)
+# define futimes(_f, _tv) futimesat(_f, NULL, _tv)
+# define HAVE_FUTIMES
+#endif
+
+#if defined(HAVE_ST_MTIM)
+# ifdef HAVE_ST__TIM
+# define ATIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_atim.st__tim)
+# define MTIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_mtim.st__tim)
+# else
+# define ATIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_atim)
+# define MTIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_mtim)
+# endif
+#elif defined(HAVE_ST_MTIMESPEC)
+# define ATIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_atimespec)
+# define MTIME_TO_TIMEVAL(_x, _y) TIMESPEC_TO_TIMEVAL((_x), &(_y)->st_mtimespec)
+#elif defined(HAVE_ST_NMTIME)
+# define ATIME_TO_TIMEVAL(_x, _y) do { (_x)->tv_sec = (_y)->st_atime; (_x)->tv_usec = (_y)->st_natime; } while (0)
+# define MTIME_TO_TIMEVAL(_x, _y) do { (_x)->tv_sec = (_y)->st_mtime; (_x)->tv_usec = (_y)->st_nmtime; } while (0)
+#else
+# define ATIME_TO_TIMEVAL(_x, _y) do { (_x)->tv_sec = (_y)->st_atime; (_x)->tv_usec = 0; } while (0)
+# define MTIME_TO_TIMEVAL(_x, _y) do { (_x)->tv_sec = (_y)->st_mtime; (_x)->tv_usec = 0; } while (0)
+#endif /* HAVE_ST_MTIM */
+
+/*
+ * Convert the pair of timespec structs passed to futimens() / utimensat()
+ * to a pair of timeval structs, handling UTIME_OMIT and UTIME_NOW.
+ * Returns 0 on success and -1 on failure (setting errno).
+ */
+static int
+utimens_ts_to_tv(int fd, const char *file, const struct timespec *ts,
+ struct timeval *tv)
+{
+ TIMESPEC_TO_TIMEVAL(&tv[0], &ts[0]);
+ TIMESPEC_TO_TIMEVAL(&tv[1], &ts[1]);
+ if (ts[0].tv_nsec == UTIME_OMIT || ts[1].tv_nsec == UTIME_OMIT) {
+ struct stat sb;
+
+ if (fd != -1) {
+ /* For futimens() */
+ if (fstat(fd, &sb) == -1)
+ return -1;
+ } else {
+ /* For utimensat() */
+ if (stat(file, &sb) == -1)
+ return -1;
+ }
+ if (ts[0].tv_nsec == UTIME_OMIT)
+ ATIME_TO_TIMEVAL(&tv[0], &sb);
+ if (ts[1].tv_nsec == UTIME_OMIT)
+ MTIME_TO_TIMEVAL(&tv[1], &sb);
+ }
+ if (ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) {
+ struct timeval now;
+
+ if (gettimeofday(&now, NULL) == -1)
+ return -1;
+ if (ts[0].tv_nsec == UTIME_NOW)
+ tv[0] = now;
+ if (ts[1].tv_nsec == UTIME_NOW)
+ tv[1] = now;
+ }
+ return 0;
+}
+
+#if defined(HAVE_FUTIMES)
+/*
+ * Emulate futimens() via futimes()
+ */
+int
+sudo_futimens(int fd, const struct timespec *ts)
+{
+ struct timeval tv[2], *times = NULL;
+
+ if (ts != NULL) {
+ if (utimens_ts_to_tv(fd, NULL, ts, tv) == -1)
+ return -1;
+ times = tv;
+ }
+ return futimes(fd, times);
+}
+#elif defined(HAVE_FUTIME)
+/*
+ * Emulate futimens() via futime()
+ */
+int
+sudo_futimens(int fd, const struct timespec *ts)
+{
+ struct utimbuf utb, *times = NULL;
+
+ if (ts != NULL) {
+ struct timeval tv[2];
+
+ if (utimens_ts_to_tv(fd, NULL, ts, tv) == -1)
+ return -1;
+ utb.actime = (time_t)(tv[0].tv_sec + tv[0].tv_usec / 1000000);
+ utb.modtime = (time_t)(tv[1].tv_sec + tv[1].tv_usec / 1000000);
+ times = &utb;
+ }
+ return futime(fd, times);
+}
+#else
+/*
+ * Nothing to do but fail.
+ */
+int
+sudo_futimens(int fd, const struct timespec *ts)
+{
+ errno = ENOSYS;
+ return -1;
+}
+#endif /* HAVE_FUTIMES */
+
+#if defined(HAVE_UTIMES)
+/*
+ * Emulate utimensat() via utimes()
+ */
+int
+sudo_utimensat(int fd, const char *file, const struct timespec *ts, int flag)
+{
+ struct timeval tv[2], *times = NULL;
+
+ if (fd != AT_FDCWD || flag != 0) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (ts != NULL) {
+ if (utimens_ts_to_tv(-1, file, ts, tv) == -1)
+ return -1;
+ times = tv;
+ }
+ return utimes(file, times);
+}
+#else
+/*
+ * Emulate utimensat() via utime()
+ */
+int
+sudo_utimensat(int fd, const char *file, const struct timespec *ts, int flag)
+{
+ struct utimbuf utb, *times = NULL;
+
+ if (fd != AT_FDCWD || flag != 0) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (ts != NULL) {
+ struct timeval tv[2];
+
+ if (utimens_ts_to_tv(-1, file, ts, tv) == -1)
+ return -1;
+ utb.actime = (time_t)(tv[0].tv_sec + tv[0].tv_usec / 1000000);
+ utb.modtime = (time_t)(tv[1].tv_sec + tv[1].tv_usec / 1000000);
+ times = &utb;
+ }
+ return utime(file, times);
+}
+#endif /* !HAVE_UTIMES */
+
+#endif /* !HAVE_FUTIMENS && !HAVE_UTIMENSAT */
diff --git a/lib/util/uuid.c b/lib/util/uuid.c
new file mode 100644
index 0000000..cb6726e
--- /dev/null
+++ b/lib/util/uuid.c
@@ -0,0 +1,111 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_rand.h"
+
+struct uuid {
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_high_and_version;
+ uint16_t clock_seq_and_variant;
+ unsigned char node[6];
+};
+
+/*
+ * Create a type 4 (random), variant 1 universally unique identifier (UUID).
+ */
+void
+sudo_uuid_create_v1(unsigned char uuid_out[16])
+{
+ union {
+ struct uuid id;
+ unsigned char u8[16];
+ } uuid;
+
+ arc4random_buf(&uuid, sizeof(uuid));
+
+ /* Convert fields to host by order. */
+ uuid.id.time_low = ntohl(uuid.id.time_low);
+ uuid.id.time_mid = ntohs(uuid.id.time_mid);
+ uuid.id.time_high_and_version = ntohs(uuid.id.time_high_and_version);
+ uuid.id.clock_seq_and_variant = ntohs(uuid.id.clock_seq_and_variant);
+
+ /* Set version to 4 (random) in the high nibble. */
+ uuid.id.time_high_and_version &= 0x0fff;
+ uuid.id.time_high_and_version |= 0x4000;
+
+ /* Set variant to 1 (first two bits are 10) */
+ uuid.id.clock_seq_and_variant &= 0x3fff;
+ uuid.id.clock_seq_and_variant |= 0x8000;
+
+ /* Store fields in network byte order (big endian). */
+ uuid.id.time_low = htonl(uuid.id.time_low);
+ uuid.id.time_mid = htons(uuid.id.time_mid);
+ uuid.id.time_high_and_version = htons(uuid.id.time_high_and_version);
+ uuid.id.clock_seq_and_variant = htons(uuid.id.clock_seq_and_variant);
+ memcpy(uuid_out, &uuid, 16);
+}
+
+/*
+ * Format a uuid as a 36-byte string (plus one for the NUL).
+ */
+char *
+sudo_uuid_to_string_v1(unsigned char uuid[16], char *dst, size_t dstsiz)
+{
+ const char hex[] = "0123456789abcdef";
+ char *cp = dst;
+ int i;
+
+ if (dstsiz < sizeof("123e4567-e89b-12d3-a456-426655440000"))
+ return NULL;
+
+ for (i = 0; i < 16; i++) {
+ *cp++ = hex[uuid[i] >> 4];
+ *cp++ = hex[uuid[i] & 0x0f];
+
+ switch (i) {
+ case 4:
+ case 6:
+ case 8:
+ case 10:
+ *cp++ = '-';
+ break;
+ }
+ }
+ *cp = '\0';
+
+ return dst;
+}
diff --git a/lib/util/vsyslog.c b/lib/util/vsyslog.c
new file mode 100644
index 0000000..1eaaf96
--- /dev/null
+++ b/lib/util/vsyslog.c
@@ -0,0 +1,79 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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>
+#include <string.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 */
diff --git a/lib/zlib/Makefile.in b/lib/zlib/Makefile.in
new file mode 100644
index 0000000..558ba94
--- /dev/null
+++ b/lib/zlib/Makefile.in
@@ -0,0 +1,220 @@
+#
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2011-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+# @configure_input@
+#
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+abs_srcdir = @abs_srcdir@
+top_srcdir = @top_srcdir@
+abs_top_srcdir = @abs_top_srcdir@
+top_builddir = @top_builddir@
+abs_top_builddir = @abs_top_builddir@
+scriptdir = $(top_srcdir)/scripts
+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 = $(srcdir)/zlib.exp
+shlib_map = zlib.map
+shlib_opt = zlib.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 flags
+CPPFLAGS = -I. -I$(srcdir)
+
+# Usually -O and/or -g
+CFLAGS = @CFLAGS@
+
+# Flags to pass to the link stage
+LDFLAGS =
+LT_LDFLAGS = @ZLIB_LDFLAGS@ @LT_LDFLAGS@ @LT_LDEXPORTS@
+
+# Flags to pass to libtool
+LTFLAGS =
+
+# 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
+
+# User and group ids the installed files should be "owned" by
+install_uid = 0
+install_gid = 0
+
+#### End of system configuration section. ####
+
+SHELL = @SHELL@
+
+LTOBJS = adler32.lo compress.lo crc32.lo deflate.lo gzclose.lo gzlib.lo \
+ gzread.lo gzwrite.lo infback.lo inffast.lo inflate.lo inftrees.lo \
+ trees.lo uncompr.lo zutil.lo
+
+all: libsudo_z.la
+
+depend:
+ $(scriptdir)/mkdep.pl --srcdir=$(abs_top_srcdir) \
+ --builddir=$(abs_top_builddir) lib/zlib/Makefile.in
+ cd $(top_builddir) && ./config.status --file lib/zlib/Makefile
+
+Makefile: $(srcdir)/Makefile.in
+ cd $(top_builddir) && ./config.status --file lib/zlib/Makefile
+
+.SUFFIXES: .c .h .lo
+
+.c.lo:
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $<
+
+$(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_z.la: $(LTOBJS) @LT_LDDEP@
+ case "$(LT_LDFLAGS)" in \
+ *-no-install*) \
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(LDFLAGS) $(LT_LDFLAGS) $(LTOBJS);; \
+ *) \
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(LDFLAGS) $(ASAN_LDFLAGS) $(SSP_LDFLAGS) $(LT_LDFLAGS) $(LTOBJS) -version-info $(SHLIB_VERSION) -rpath $(libexecdir)/sudo;; \
+ esac
+
+pre-install:
+
+install:
+ 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_z.la $(DESTDIR)$(libexecdir)/sudo; \
+ fi;; \
+ esac
+
+install-dirs:
+
+install-binaries:
+
+install-includes:
+
+install-doc:
+
+install-plugin:
+
+uninstall:
+ $(LIBTOOL) $(LTFLAGS) --mode=uninstall rm -f $(DESTDIR)$(libexecdir)/sudo/libsudo_z.la
+ -test -z "$(INSTALL_BACKUP)" || \
+ rf -f $(DESTDIR)$(libexecdir)/sudo/libsudo_z.*~
+
+splint:
+
+cppcheck:
+
+pvs-log-files:
+
+pvs-studio:
+
+check:
+
+clean:
+ -$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f *.lo *.o *.la
+ -rm -f stamp-* core *.core core.*
+
+mostlyclean: clean
+
+distclean: clean
+ -rm -rf Makefile .libs zconf.h
+
+clobber: distclean
+
+realclean: distclean
+ rm -f TAGS tags
+
+cleandir: realclean
+
+# Autogenerated dependencies, do not modify
+adler32.lo: $(srcdir)/adler32.c $(srcdir)/zlib.h $(srcdir)/zutil.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/adler32.c
+compress.lo: $(srcdir)/compress.c $(srcdir)/zlib.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/compress.c
+crc32.lo: $(srcdir)/crc32.c $(srcdir)/crc32.h $(srcdir)/zlib.h \
+ $(srcdir)/zutil.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/crc32.c
+deflate.lo: $(srcdir)/deflate.c $(srcdir)/deflate.h $(srcdir)/zlib.h \
+ $(srcdir)/zutil.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/deflate.c
+gzclose.lo: $(srcdir)/gzclose.c $(srcdir)/gzguts.h $(srcdir)/zlib.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/gzclose.c
+gzlib.lo: $(srcdir)/gzlib.c $(srcdir)/gzguts.h $(srcdir)/zlib.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/gzlib.c
+gzread.lo: $(srcdir)/gzread.c $(srcdir)/gzguts.h $(srcdir)/zlib.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/gzread.c
+gzwrite.lo: $(srcdir)/gzwrite.c $(srcdir)/gzguts.h $(srcdir)/zlib.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/gzwrite.c
+infback.lo: $(srcdir)/infback.c $(srcdir)/inffast.h $(srcdir)/inffixed.h \
+ $(srcdir)/inflate.h $(srcdir)/inftrees.h $(srcdir)/zlib.h \
+ $(srcdir)/zutil.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/infback.c
+inffast.lo: $(srcdir)/inffast.c $(srcdir)/inffast.h $(srcdir)/inflate.h \
+ $(srcdir)/inftrees.h $(srcdir)/zlib.h $(srcdir)/zutil.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/inffast.c
+inflate.lo: $(srcdir)/inflate.c $(srcdir)/inffast.h $(srcdir)/inffixed.h \
+ $(srcdir)/inflate.h $(srcdir)/inftrees.h $(srcdir)/zlib.h \
+ $(srcdir)/zutil.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/inflate.c
+inftrees.lo: $(srcdir)/inftrees.c $(srcdir)/inftrees.h $(srcdir)/zlib.h \
+ $(srcdir)/zutil.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/inftrees.c
+trees.lo: $(srcdir)/trees.c $(srcdir)/deflate.h $(srcdir)/trees.h \
+ $(srcdir)/zlib.h $(srcdir)/zutil.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/trees.c
+uncompr.lo: $(srcdir)/uncompr.c $(srcdir)/zlib.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/uncompr.c
+zutil.lo: $(srcdir)/zutil.c $(srcdir)/gzguts.h $(srcdir)/zlib.h \
+ $(srcdir)/zutil.h ./zconf.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/zutil.c
diff --git a/lib/zlib/adler32.c b/lib/zlib/adler32.c
new file mode 100644
index 0000000..d0be438
--- /dev/null
+++ b/lib/zlib/adler32.c
@@ -0,0 +1,186 @@
+/* adler32.c -- compute the Adler-32 checksum of a data stream
+ * Copyright (C) 1995-2011, 2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#include "zutil.h"
+
+local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2));
+
+#define BASE 65521U /* largest prime smaller than 65536 */
+#define NMAX 5552
+/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
+
+#define DO1(buf,i) {adler += (buf)[i]; sum2 += adler;}
+#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1);
+#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2);
+#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4);
+#define DO16(buf) DO8(buf,0); DO8(buf,8);
+
+/* use NO_DIVIDE if your processor does not do division in hardware --
+ try it both ways to see which is faster */
+#ifdef NO_DIVIDE
+/* note that this assumes BASE is 65521, where 65536 % 65521 == 15
+ (thank you to John Reiser for pointing this out) */
+# define CHOP(a) \
+ do { \
+ unsigned long tmp = a >> 16; \
+ a &= 0xffffUL; \
+ a += (tmp << 4) - tmp; \
+ } while (0)
+# define MOD28(a) \
+ do { \
+ CHOP(a); \
+ if (a >= BASE) a -= BASE; \
+ } while (0)
+# define MOD(a) \
+ do { \
+ CHOP(a); \
+ MOD28(a); \
+ } while (0)
+# define MOD63(a) \
+ do { /* this assumes a is not negative */ \
+ z_off64_t tmp = a >> 32; \
+ a &= 0xffffffffL; \
+ a += (tmp << 8) - (tmp << 5) + tmp; \
+ tmp = a >> 16; \
+ a &= 0xffffL; \
+ a += (tmp << 4) - tmp; \
+ tmp = a >> 16; \
+ a &= 0xffffL; \
+ a += (tmp << 4) - tmp; \
+ if (a >= BASE) a -= BASE; \
+ } while (0)
+#else
+# define MOD(a) a %= BASE
+# define MOD28(a) a %= BASE
+# define MOD63(a) a %= BASE
+#endif
+
+/* ========================================================================= */
+uLong ZEXPORT adler32_z(adler, buf, len)
+ uLong adler;
+ const Bytef *buf;
+ z_size_t len;
+{
+ unsigned long sum2;
+ unsigned n;
+
+ /* split Adler-32 into component sums */
+ sum2 = (adler >> 16) & 0xffff;
+ adler &= 0xffff;
+
+ /* in case user likes doing a byte at a time, keep it fast */
+ if (len == 1) {
+ adler += buf[0];
+ if (adler >= BASE)
+ adler -= BASE;
+ sum2 += adler;
+ if (sum2 >= BASE)
+ sum2 -= BASE;
+ return adler | (sum2 << 16);
+ }
+
+ /* initial Adler-32 value (deferred check for len == 1 speed) */
+ if (buf == Z_NULL)
+ return 1L;
+
+ /* in case short lengths are provided, keep it somewhat fast */
+ if (len < 16) {
+ while (len--) {
+ adler += *buf++;
+ sum2 += adler;
+ }
+ if (adler >= BASE)
+ adler -= BASE;
+ MOD28(sum2); /* only added so many BASE's */
+ return adler | (sum2 << 16);
+ }
+
+ /* do length NMAX blocks -- requires just one modulo operation */
+ while (len >= NMAX) {
+ len -= NMAX;
+ n = NMAX / 16; /* NMAX is divisible by 16 */
+ do {
+ DO16(buf); /* 16 sums unrolled */
+ buf += 16;
+ } while (--n);
+ MOD(adler);
+ MOD(sum2);
+ }
+
+ /* do remaining bytes (less than NMAX, still just one modulo) */
+ if (len) { /* avoid modulos if none remaining */
+ while (len >= 16) {
+ len -= 16;
+ DO16(buf);
+ buf += 16;
+ }
+ while (len--) {
+ adler += *buf++;
+ sum2 += adler;
+ }
+ MOD(adler);
+ MOD(sum2);
+ }
+
+ /* return recombined sums */
+ return adler | (sum2 << 16);
+}
+
+/* ========================================================================= */
+uLong ZEXPORT adler32(adler, buf, len)
+ uLong adler;
+ const Bytef *buf;
+ uInt len;
+{
+ return adler32_z(adler, buf, len);
+}
+
+/* ========================================================================= */
+local uLong adler32_combine_(adler1, adler2, len2)
+ uLong adler1;
+ uLong adler2;
+ z_off64_t len2;
+{
+ unsigned long sum1;
+ unsigned long sum2;
+ unsigned rem;
+
+ /* for negative len, return invalid adler32 as a clue for debugging */
+ if (len2 < 0)
+ return 0xffffffffUL;
+
+ /* the derivation of this formula is left as an exercise for the reader */
+ MOD63(len2); /* assumes len2 >= 0 */
+ rem = (unsigned)len2;
+ sum1 = adler1 & 0xffff;
+ sum2 = rem * sum1;
+ MOD(sum2);
+ sum1 += (adler2 & 0xffff) + BASE - 1;
+ sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem;
+ if (sum1 >= BASE) sum1 -= BASE;
+ if (sum1 >= BASE) sum1 -= BASE;
+ if (sum2 >= ((unsigned long)BASE << 1)) sum2 -= ((unsigned long)BASE << 1);
+ if (sum2 >= BASE) sum2 -= BASE;
+ return sum1 | (sum2 << 16);
+}
+
+/* ========================================================================= */
+uLong ZEXPORT adler32_combine(adler1, adler2, len2)
+ uLong adler1;
+ uLong adler2;
+ z_off_t len2;
+{
+ return adler32_combine_(adler1, adler2, len2);
+}
+
+uLong ZEXPORT adler32_combine64(adler1, adler2, len2)
+ uLong adler1;
+ uLong adler2;
+ z_off64_t len2;
+{
+ return adler32_combine_(adler1, adler2, len2);
+}
diff --git a/lib/zlib/compress.c b/lib/zlib/compress.c
new file mode 100644
index 0000000..e2db404
--- /dev/null
+++ b/lib/zlib/compress.c
@@ -0,0 +1,86 @@
+/* compress.c -- compress a memory buffer
+ * Copyright (C) 1995-2005, 2014, 2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#define ZLIB_INTERNAL
+#include "zlib.h"
+
+/* ===========================================================================
+ Compresses the source buffer into the destination buffer. The level
+ parameter has the same meaning as in deflateInit. sourceLen is the byte
+ length of the source buffer. Upon entry, destLen is the total size of the
+ destination buffer, which must be at least 0.1% larger than sourceLen plus
+ 12 bytes. Upon exit, destLen is the actual size of the compressed buffer.
+
+ compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_BUF_ERROR if there was not enough room in the output buffer,
+ Z_STREAM_ERROR if the level parameter is invalid.
+*/
+int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
+ Bytef *dest;
+ uLongf *destLen;
+ const Bytef *source;
+ uLong sourceLen;
+ int level;
+{
+ z_stream stream;
+ int err;
+ const uInt max = (uInt)-1;
+ uLong left;
+
+ left = *destLen;
+ *destLen = 0;
+
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+ stream.opaque = (voidpf)0;
+
+ err = deflateInit(&stream, level);
+ if (err != Z_OK) return err;
+
+ stream.next_out = dest;
+ stream.avail_out = 0;
+ stream.next_in = (z_const Bytef *)source;
+ stream.avail_in = 0;
+
+ do {
+ if (stream.avail_out == 0) {
+ stream.avail_out = left > (uLong)max ? max : (uInt)left;
+ left -= stream.avail_out;
+ }
+ if (stream.avail_in == 0) {
+ stream.avail_in = sourceLen > (uLong)max ? max : (uInt)sourceLen;
+ sourceLen -= stream.avail_in;
+ }
+ err = deflate(&stream, sourceLen ? Z_NO_FLUSH : Z_FINISH);
+ } while (err == Z_OK);
+
+ *destLen = stream.total_out;
+ deflateEnd(&stream);
+ return err == Z_STREAM_END ? Z_OK : err;
+}
+
+/* ===========================================================================
+ */
+int ZEXPORT compress (dest, destLen, source, sourceLen)
+ Bytef *dest;
+ uLongf *destLen;
+ const Bytef *source;
+ uLong sourceLen;
+{
+ return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION);
+}
+
+/* ===========================================================================
+ If the default memLevel or windowBits for deflateInit() is changed, then
+ this function needs to be updated.
+ */
+uLong ZEXPORT compressBound (sourceLen)
+ uLong sourceLen;
+{
+ return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
+ (sourceLen >> 25) + 13;
+}
diff --git a/lib/zlib/crc32.c b/lib/zlib/crc32.c
new file mode 100644
index 0000000..9580440
--- /dev/null
+++ b/lib/zlib/crc32.c
@@ -0,0 +1,442 @@
+/* crc32.c -- compute the CRC-32 of a data stream
+ * Copyright (C) 1995-2006, 2010, 2011, 2012, 2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ *
+ * Thanks to Rodney Brown <rbrown64@csc.com.au> for his contribution of faster
+ * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing
+ * tables for updating the shift register in one step with three exclusive-ors
+ * instead of four steps with four exclusive-ors. This results in about a
+ * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3.
+ */
+
+/* @(#) $Id$ */
+
+/*
+ Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore
+ protection on the static variables used to control the first-use generation
+ of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should
+ first call get_crc_table() to initialize the tables before allowing more than
+ one thread to use crc32().
+
+ DYNAMIC_CRC_TABLE and MAKECRCH can be #defined to write out crc32.h.
+ */
+
+#ifdef MAKECRCH
+# include <stdio.h>
+# ifndef DYNAMIC_CRC_TABLE
+# define DYNAMIC_CRC_TABLE
+# endif /* !DYNAMIC_CRC_TABLE */
+#endif /* MAKECRCH */
+
+#include "zutil.h" /* for STDC and FAR definitions */
+
+/* Definitions for doing the crc four data bytes at a time. */
+#if !defined(NOBYFOUR) && defined(Z_U4)
+# define BYFOUR
+#endif
+#ifdef BYFOUR
+ local unsigned long crc32_little OF((unsigned long,
+ const unsigned char FAR *, z_size_t));
+ local unsigned long crc32_big OF((unsigned long,
+ const unsigned char FAR *, z_size_t));
+# define TBLS 8
+#else
+# define TBLS 1
+#endif /* BYFOUR */
+
+/* Local functions for crc concatenation */
+local unsigned long gf2_matrix_times OF((unsigned long *mat,
+ unsigned long vec));
+local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat));
+local uLong crc32_combine_ OF((uLong crc1, uLong crc2, z_off64_t len2));
+
+
+#ifdef DYNAMIC_CRC_TABLE
+
+local volatile int crc_table_empty = 1;
+local z_crc_t FAR crc_table[TBLS][256];
+local void make_crc_table OF((void));
+#ifdef MAKECRCH
+ local void write_table OF((FILE *, const z_crc_t FAR *));
+#endif /* MAKECRCH */
+/*
+ Generate tables for a byte-wise 32-bit CRC calculation on the polynomial:
+ x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1.
+
+ Polynomials over GF(2) are represented in binary, one bit per coefficient,
+ with the lowest powers in the most significant bit. Then adding polynomials
+ is just exclusive-or, and multiplying a polynomial by x is a right shift by
+ one. If we call the above polynomial p, and represent a byte as the
+ polynomial q, also with the lowest power in the most significant bit (so the
+ byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p,
+ where a mod b means the remainder after dividing a by b.
+
+ This calculation is done using the shift-register method of multiplying and
+ taking the remainder. The register is initialized to zero, and for each
+ incoming bit, x^32 is added mod p to the register if the bit is a one (where
+ x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by
+ x (which is shifting right by one and adding x^32 mod p if the bit shifted
+ out is a one). We start with the highest power (least significant bit) of
+ q and repeat for all eight bits of q.
+
+ The first table is simply the CRC of all possible eight bit values. This is
+ all the information needed to generate CRCs on data a byte at a time for all
+ combinations of CRC register values and incoming bytes. The remaining tables
+ allow for word-at-a-time CRC calculation for both big-endian and little-
+ endian machines, where a word is four bytes.
+*/
+local void make_crc_table()
+{
+ z_crc_t c;
+ int n, k;
+ z_crc_t poly; /* polynomial exclusive-or pattern */
+ /* terms of polynomial defining this crc (except x^32): */
+ static volatile int first = 1; /* flag to limit concurrent making */
+ static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26};
+
+ /* See if another task is already doing this (not thread-safe, but better
+ than nothing -- significantly reduces duration of vulnerability in
+ case the advice about DYNAMIC_CRC_TABLE is ignored) */
+ if (first) {
+ first = 0;
+
+ /* make exclusive-or pattern from polynomial (0xedb88320UL) */
+ poly = 0;
+ for (n = 0; n < (int)(sizeof(p)/sizeof(unsigned char)); n++)
+ poly |= (z_crc_t)1 << (31 - p[n]);
+
+ /* generate a crc for every 8-bit value */
+ for (n = 0; n < 256; n++) {
+ c = (z_crc_t)n;
+ for (k = 0; k < 8; k++)
+ c = c & 1 ? poly ^ (c >> 1) : c >> 1;
+ crc_table[0][n] = c;
+ }
+
+#ifdef BYFOUR
+ /* generate crc for each value followed by one, two, and three zeros,
+ and then the byte reversal of those as well as the first table */
+ for (n = 0; n < 256; n++) {
+ c = crc_table[0][n];
+ crc_table[4][n] = ZSWAP32(c);
+ for (k = 1; k < 4; k++) {
+ c = crc_table[0][c & 0xff] ^ (c >> 8);
+ crc_table[k][n] = c;
+ crc_table[k + 4][n] = ZSWAP32(c);
+ }
+ }
+#endif /* BYFOUR */
+
+ crc_table_empty = 0;
+ }
+ else { /* not first */
+ /* wait for the other guy to finish (not efficient, but rare) */
+ while (crc_table_empty)
+ ;
+ }
+
+#ifdef MAKECRCH
+ /* write out CRC tables to crc32.h */
+ {
+ FILE *out;
+
+ out = fopen("crc32.h", "w");
+ if (out == NULL) return;
+ fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n");
+ fprintf(out, " * Generated automatically by crc32.c\n */\n\n");
+ fprintf(out, "local const z_crc_t FAR ");
+ fprintf(out, "crc_table[TBLS][256] =\n{\n {\n");
+ write_table(out, crc_table[0]);
+# ifdef BYFOUR
+ fprintf(out, "#ifdef BYFOUR\n");
+ for (k = 1; k < 8; k++) {
+ fprintf(out, " },\n {\n");
+ write_table(out, crc_table[k]);
+ }
+ fprintf(out, "#endif\n");
+# endif /* BYFOUR */
+ fprintf(out, " }\n};\n");
+ fclose(out);
+ }
+#endif /* MAKECRCH */
+}
+
+#ifdef MAKECRCH
+local void write_table(out, table)
+ FILE *out;
+ const z_crc_t FAR *table;
+{
+ int n;
+
+ for (n = 0; n < 256; n++)
+ fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ",
+ (unsigned long)(table[n]),
+ n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", "));
+}
+#endif /* MAKECRCH */
+
+#else /* !DYNAMIC_CRC_TABLE */
+/* ========================================================================
+ * Tables of CRC-32s of all single-byte values, made by make_crc_table().
+ */
+#include "crc32.h"
+#endif /* DYNAMIC_CRC_TABLE */
+
+/* =========================================================================
+ * This function can be used by asm versions of crc32()
+ */
+const z_crc_t FAR * ZEXPORT get_crc_table()
+{
+#ifdef DYNAMIC_CRC_TABLE
+ if (crc_table_empty)
+ make_crc_table();
+#endif /* DYNAMIC_CRC_TABLE */
+ return (const z_crc_t FAR *)crc_table;
+}
+
+/* ========================================================================= */
+#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8)
+#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1
+
+/* ========================================================================= */
+unsigned long ZEXPORT crc32_z(crc, buf, len)
+ unsigned long crc;
+ const unsigned char FAR *buf;
+ z_size_t len;
+{
+ if (buf == Z_NULL) return 0UL;
+
+#ifdef DYNAMIC_CRC_TABLE
+ if (crc_table_empty)
+ make_crc_table();
+#endif /* DYNAMIC_CRC_TABLE */
+
+#ifdef BYFOUR
+ if (sizeof(void *) == sizeof(ptrdiff_t)) {
+ z_crc_t endian;
+
+ endian = 1;
+ if (*((unsigned char *)(&endian)))
+ return crc32_little(crc, buf, len);
+ else
+ return crc32_big(crc, buf, len);
+ }
+#endif /* BYFOUR */
+ crc = crc ^ 0xffffffffUL;
+ while (len >= 8) {
+ DO8;
+ len -= 8;
+ }
+ if (len) do {
+ DO1;
+ } while (--len);
+ return crc ^ 0xffffffffUL;
+}
+
+/* ========================================================================= */
+unsigned long ZEXPORT crc32(crc, buf, len)
+ unsigned long crc;
+ const unsigned char FAR *buf;
+ uInt len;
+{
+ return crc32_z(crc, buf, len);
+}
+
+#ifdef BYFOUR
+
+/*
+ This BYFOUR code accesses the passed unsigned char * buffer with a 32-bit
+ integer pointer type. This violates the strict aliasing rule, where a
+ compiler can assume, for optimization purposes, that two pointers to
+ fundamentally different types won't ever point to the same memory. This can
+ manifest as a problem only if one of the pointers is written to. This code
+ only reads from those pointers. So long as this code remains isolated in
+ this compilation unit, there won't be a problem. For this reason, this code
+ should not be copied and pasted into a compilation unit in which other code
+ writes to the buffer that is passed to these routines.
+ */
+
+/* ========================================================================= */
+#define DOLIT4 c ^= *buf4++; \
+ c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \
+ crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24]
+#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4
+
+/* ========================================================================= */
+local unsigned long crc32_little(crc, buf, len)
+ unsigned long crc;
+ const unsigned char FAR *buf;
+ z_size_t len;
+{
+ register z_crc_t c;
+ register const z_crc_t FAR *buf4;
+
+ c = (z_crc_t)crc;
+ c = ~c;
+ while (len && ((ptrdiff_t)buf & 3)) {
+ c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8);
+ len--;
+ }
+
+ buf4 = (const z_crc_t FAR *)(const void FAR *)buf;
+ while (len >= 32) {
+ DOLIT32;
+ len -= 32;
+ }
+ while (len >= 4) {
+ DOLIT4;
+ len -= 4;
+ }
+ buf = (const unsigned char FAR *)buf4;
+
+ if (len) do {
+ c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8);
+ } while (--len);
+ c = ~c;
+ return (unsigned long)c;
+}
+
+/* ========================================================================= */
+#define DOBIG4 c ^= *buf4++; \
+ c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \
+ crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24]
+#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4
+
+/* ========================================================================= */
+local unsigned long crc32_big(crc, buf, len)
+ unsigned long crc;
+ const unsigned char FAR *buf;
+ z_size_t len;
+{
+ register z_crc_t c;
+ register const z_crc_t FAR *buf4;
+
+ c = ZSWAP32((z_crc_t)crc);
+ c = ~c;
+ while (len && ((ptrdiff_t)buf & 3)) {
+ c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8);
+ len--;
+ }
+
+ buf4 = (const z_crc_t FAR *)(const void FAR *)buf;
+ while (len >= 32) {
+ DOBIG32;
+ len -= 32;
+ }
+ while (len >= 4) {
+ DOBIG4;
+ len -= 4;
+ }
+ buf = (const unsigned char FAR *)buf4;
+
+ if (len) do {
+ c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8);
+ } while (--len);
+ c = ~c;
+ return (unsigned long)(ZSWAP32(c));
+}
+
+#endif /* BYFOUR */
+
+#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */
+
+/* ========================================================================= */
+local unsigned long gf2_matrix_times(mat, vec)
+ unsigned long *mat;
+ unsigned long vec;
+{
+ unsigned long sum;
+
+ sum = 0;
+ while (vec) {
+ if (vec & 1)
+ sum ^= *mat;
+ vec >>= 1;
+ mat++;
+ }
+ return sum;
+}
+
+/* ========================================================================= */
+local void gf2_matrix_square(square, mat)
+ unsigned long *square;
+ unsigned long *mat;
+{
+ int n;
+
+ for (n = 0; n < GF2_DIM; n++)
+ square[n] = gf2_matrix_times(mat, mat[n]);
+}
+
+/* ========================================================================= */
+local uLong crc32_combine_(crc1, crc2, len2)
+ uLong crc1;
+ uLong crc2;
+ z_off64_t len2;
+{
+ int n;
+ unsigned long row;
+ unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */
+ unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */
+
+ /* degenerate case (also disallow negative lengths) */
+ if (len2 <= 0)
+ return crc1;
+
+ /* put operator for one zero bit in odd */
+ odd[0] = 0xedb88320UL; /* CRC-32 polynomial */
+ row = 1;
+ for (n = 1; n < GF2_DIM; n++) {
+ odd[n] = row;
+ row <<= 1;
+ }
+
+ /* put operator for two zero bits in even */
+ gf2_matrix_square(even, odd);
+
+ /* put operator for four zero bits in odd */
+ gf2_matrix_square(odd, even);
+
+ /* apply len2 zeros to crc1 (first square will put the operator for one
+ zero byte, eight zero bits, in even) */
+ do {
+ /* apply zeros operator for this bit of len2 */
+ gf2_matrix_square(even, odd);
+ if (len2 & 1)
+ crc1 = gf2_matrix_times(even, crc1);
+ len2 >>= 1;
+
+ /* if no more bits set, then done */
+ if (len2 == 0)
+ break;
+
+ /* another iteration of the loop with odd and even swapped */
+ gf2_matrix_square(odd, even);
+ if (len2 & 1)
+ crc1 = gf2_matrix_times(odd, crc1);
+ len2 >>= 1;
+
+ /* if no more bits set, then done */
+ } while (len2 != 0);
+
+ /* return combined crc */
+ crc1 ^= crc2;
+ return crc1;
+}
+
+/* ========================================================================= */
+uLong ZEXPORT crc32_combine(crc1, crc2, len2)
+ uLong crc1;
+ uLong crc2;
+ z_off_t len2;
+{
+ return crc32_combine_(crc1, crc2, len2);
+}
+
+uLong ZEXPORT crc32_combine64(crc1, crc2, len2)
+ uLong crc1;
+ uLong crc2;
+ z_off64_t len2;
+{
+ return crc32_combine_(crc1, crc2, len2);
+}
diff --git a/lib/zlib/crc32.h b/lib/zlib/crc32.h
new file mode 100644
index 0000000..9e0c778
--- /dev/null
+++ b/lib/zlib/crc32.h
@@ -0,0 +1,441 @@
+/* crc32.h -- tables for rapid CRC calculation
+ * Generated automatically by crc32.c
+ */
+
+local const z_crc_t FAR crc_table[TBLS][256] =
+{
+ {
+ 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL,
+ 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL,
+ 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL,
+ 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL,
+ 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL,
+ 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL,
+ 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL,
+ 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL,
+ 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL,
+ 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL,
+ 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL,
+ 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL,
+ 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL,
+ 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL,
+ 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL,
+ 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL,
+ 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL,
+ 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL,
+ 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL,
+ 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL,
+ 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL,
+ 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL,
+ 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL,
+ 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL,
+ 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL,
+ 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL,
+ 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL,
+ 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL,
+ 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL,
+ 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL,
+ 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL,
+ 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL,
+ 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL,
+ 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL,
+ 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL,
+ 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL,
+ 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL,
+ 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL,
+ 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL,
+ 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL,
+ 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL,
+ 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL,
+ 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL,
+ 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL,
+ 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL,
+ 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL,
+ 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL,
+ 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL,
+ 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL,
+ 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL,
+ 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL,
+ 0x2d02ef8dUL
+#ifdef BYFOUR
+ },
+ {
+ 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL,
+ 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL,
+ 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL,
+ 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL,
+ 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL,
+ 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL,
+ 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL,
+ 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL,
+ 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL,
+ 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL,
+ 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL,
+ 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL,
+ 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL,
+ 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL,
+ 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL,
+ 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL,
+ 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL,
+ 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL,
+ 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL,
+ 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL,
+ 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL,
+ 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL,
+ 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL,
+ 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL,
+ 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL,
+ 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL,
+ 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL,
+ 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL,
+ 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL,
+ 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL,
+ 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL,
+ 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL,
+ 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL,
+ 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL,
+ 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL,
+ 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL,
+ 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL,
+ 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL,
+ 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL,
+ 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL,
+ 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL,
+ 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL,
+ 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL,
+ 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL,
+ 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL,
+ 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL,
+ 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL,
+ 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL,
+ 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL,
+ 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL,
+ 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL,
+ 0x9324fd72UL
+ },
+ {
+ 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL,
+ 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL,
+ 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL,
+ 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL,
+ 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL,
+ 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL,
+ 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL,
+ 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL,
+ 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL,
+ 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL,
+ 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL,
+ 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL,
+ 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL,
+ 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL,
+ 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL,
+ 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL,
+ 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL,
+ 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL,
+ 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL,
+ 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL,
+ 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL,
+ 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL,
+ 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL,
+ 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL,
+ 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL,
+ 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL,
+ 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL,
+ 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL,
+ 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL,
+ 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL,
+ 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL,
+ 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL,
+ 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL,
+ 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL,
+ 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL,
+ 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL,
+ 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL,
+ 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL,
+ 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL,
+ 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL,
+ 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL,
+ 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL,
+ 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL,
+ 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL,
+ 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL,
+ 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL,
+ 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL,
+ 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL,
+ 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL,
+ 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL,
+ 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL,
+ 0xbe9834edUL
+ },
+ {
+ 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL,
+ 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL,
+ 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL,
+ 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL,
+ 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL,
+ 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL,
+ 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL,
+ 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL,
+ 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL,
+ 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL,
+ 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL,
+ 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL,
+ 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL,
+ 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL,
+ 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL,
+ 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL,
+ 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL,
+ 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL,
+ 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL,
+ 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL,
+ 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL,
+ 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL,
+ 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL,
+ 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL,
+ 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL,
+ 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL,
+ 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL,
+ 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL,
+ 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL,
+ 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL,
+ 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL,
+ 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL,
+ 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL,
+ 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL,
+ 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL,
+ 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL,
+ 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL,
+ 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL,
+ 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL,
+ 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL,
+ 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL,
+ 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL,
+ 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL,
+ 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL,
+ 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL,
+ 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL,
+ 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL,
+ 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL,
+ 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL,
+ 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL,
+ 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL,
+ 0xde0506f1UL
+ },
+ {
+ 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL,
+ 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL,
+ 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL,
+ 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL,
+ 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL,
+ 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL,
+ 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL,
+ 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL,
+ 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL,
+ 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL,
+ 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL,
+ 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL,
+ 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL,
+ 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL,
+ 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL,
+ 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL,
+ 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL,
+ 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL,
+ 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL,
+ 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL,
+ 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL,
+ 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL,
+ 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL,
+ 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL,
+ 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL,
+ 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL,
+ 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL,
+ 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL,
+ 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL,
+ 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL,
+ 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL,
+ 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL,
+ 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL,
+ 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL,
+ 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL,
+ 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL,
+ 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL,
+ 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL,
+ 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL,
+ 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL,
+ 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL,
+ 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL,
+ 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL,
+ 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL,
+ 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL,
+ 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL,
+ 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL,
+ 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL,
+ 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL,
+ 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL,
+ 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL,
+ 0x8def022dUL
+ },
+ {
+ 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL,
+ 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL,
+ 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL,
+ 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL,
+ 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL,
+ 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL,
+ 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL,
+ 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL,
+ 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL,
+ 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL,
+ 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL,
+ 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL,
+ 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL,
+ 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL,
+ 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL,
+ 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL,
+ 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL,
+ 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL,
+ 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL,
+ 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL,
+ 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL,
+ 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL,
+ 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL,
+ 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL,
+ 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL,
+ 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL,
+ 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL,
+ 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL,
+ 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL,
+ 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL,
+ 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL,
+ 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL,
+ 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL,
+ 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL,
+ 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL,
+ 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL,
+ 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL,
+ 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL,
+ 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL,
+ 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL,
+ 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL,
+ 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL,
+ 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL,
+ 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL,
+ 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL,
+ 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL,
+ 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL,
+ 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL,
+ 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL,
+ 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL,
+ 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL,
+ 0x72fd2493UL
+ },
+ {
+ 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL,
+ 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL,
+ 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL,
+ 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL,
+ 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL,
+ 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL,
+ 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL,
+ 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL,
+ 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL,
+ 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL,
+ 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL,
+ 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL,
+ 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL,
+ 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL,
+ 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL,
+ 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL,
+ 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL,
+ 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL,
+ 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL,
+ 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL,
+ 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL,
+ 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL,
+ 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL,
+ 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL,
+ 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL,
+ 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL,
+ 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL,
+ 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL,
+ 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL,
+ 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL,
+ 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL,
+ 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL,
+ 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL,
+ 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL,
+ 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL,
+ 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL,
+ 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL,
+ 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL,
+ 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL,
+ 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL,
+ 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL,
+ 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL,
+ 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL,
+ 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL,
+ 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL,
+ 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL,
+ 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL,
+ 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL,
+ 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL,
+ 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL,
+ 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL,
+ 0xed3498beUL
+ },
+ {
+ 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL,
+ 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL,
+ 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL,
+ 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL,
+ 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL,
+ 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL,
+ 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL,
+ 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL,
+ 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL,
+ 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL,
+ 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL,
+ 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL,
+ 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL,
+ 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL,
+ 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL,
+ 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL,
+ 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL,
+ 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL,
+ 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL,
+ 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL,
+ 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL,
+ 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL,
+ 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL,
+ 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL,
+ 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL,
+ 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL,
+ 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL,
+ 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL,
+ 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL,
+ 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL,
+ 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL,
+ 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL,
+ 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL,
+ 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL,
+ 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL,
+ 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL,
+ 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL,
+ 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL,
+ 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL,
+ 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL,
+ 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL,
+ 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL,
+ 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL,
+ 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL,
+ 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL,
+ 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL,
+ 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL,
+ 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL,
+ 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL,
+ 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL,
+ 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL,
+ 0xf10605deUL
+#endif
+ }
+};
diff --git a/lib/zlib/deflate.c b/lib/zlib/deflate.c
new file mode 100644
index 0000000..1e39dcb
--- /dev/null
+++ b/lib/zlib/deflate.c
@@ -0,0 +1,2165 @@
+/* deflate.c -- compress data using the deflation algorithm
+ * Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ * ALGORITHM
+ *
+ * The "deflation" process depends on being able to identify portions
+ * of the input text which are identical to earlier input (within a
+ * sliding window trailing behind the input currently being processed).
+ *
+ * The most straightforward technique turns out to be the fastest for
+ * most input files: try all possible matches and select the longest.
+ * The key feature of this algorithm is that insertions into the string
+ * dictionary are very simple and thus fast, and deletions are avoided
+ * completely. Insertions are performed at each input character, whereas
+ * string matches are performed only when the previous match ends. So it
+ * is preferable to spend more time in matches to allow very fast string
+ * insertions and avoid deletions. The matching algorithm for small
+ * strings is inspired from that of Rabin & Karp. A brute force approach
+ * is used to find longer strings when a small match has been found.
+ * A similar algorithm is used in comic (by Jan-Mark Wams) and freeze
+ * (by Leonid Broukhis).
+ * A previous version of this file used a more sophisticated algorithm
+ * (by Fiala and Greene) which is guaranteed to run in linear amortized
+ * time, but has a larger average cost, uses more memory and is patented.
+ * However the F&G algorithm may be faster for some highly redundant
+ * files if the parameter max_chain_length (described below) is too large.
+ *
+ * ACKNOWLEDGEMENTS
+ *
+ * The idea of lazy evaluation of matches is due to Jan-Mark Wams, and
+ * I found it in 'freeze' written by Leonid Broukhis.
+ * Thanks to many people for bug reports and testing.
+ *
+ * REFERENCES
+ *
+ * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification".
+ * Available in http://tools.ietf.org/html/rfc1951
+ *
+ * A description of the Rabin and Karp algorithm is given in the book
+ * "Algorithms" by R. Sedgewick, Addison-Wesley, p252.
+ *
+ * Fiala,E.R., and Greene,D.H.
+ * Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595
+ *
+ */
+
+/* @(#) $Id$ */
+
+#include "deflate.h"
+
+const char deflate_copyright[] =
+ " deflate 1.2.11 Copyright 1995-2017 Jean-loup Gailly and Mark Adler ";
+/*
+ If you use the zlib library in a product, an acknowledgment is welcome
+ in the documentation of your product. If for some reason you cannot
+ include such an acknowledgment, I would appreciate that you keep this
+ copyright string in the executable of your product.
+ */
+
+/* ===========================================================================
+ * Function prototypes.
+ */
+typedef enum {
+ need_more, /* block not completed, need more input or more output */
+ block_done, /* block flush performed */
+ finish_started, /* finish started, need only more output at next deflate */
+ finish_done /* finish done, accept no more input or output */
+} block_state;
+
+typedef block_state (*compress_func) OF((deflate_state *s, int flush));
+/* Compression function. Returns the block state after the call. */
+
+local int deflateStateCheck OF((z_streamp strm));
+local void slide_hash OF((deflate_state *s));
+local void fill_window OF((deflate_state *s));
+local block_state deflate_stored OF((deflate_state *s, int flush));
+local block_state deflate_fast OF((deflate_state *s, int flush));
+#ifndef FASTEST
+local block_state deflate_slow OF((deflate_state *s, int flush));
+#endif
+local block_state deflate_rle OF((deflate_state *s, int flush));
+local block_state deflate_huff OF((deflate_state *s, int flush));
+local void lm_init OF((deflate_state *s));
+local void putShortMSB OF((deflate_state *s, uInt b));
+local void flush_pending OF((z_streamp strm));
+local unsigned read_buf OF((z_streamp strm, Bytef *buf, unsigned size));
+#ifdef ASMV
+# pragma message("Assembler code may have bugs -- use at your own risk")
+ void match_init OF((void)); /* asm code initialization */
+ uInt longest_match OF((deflate_state *s, IPos cur_match));
+#else
+local uInt longest_match OF((deflate_state *s, IPos cur_match));
+#endif
+
+#ifdef ZLIB_DEBUG
+local void check_match OF((deflate_state *s, IPos start, IPos match,
+ int length));
+#endif
+
+/* ===========================================================================
+ * Local data
+ */
+
+#define NIL 0
+/* Tail of hash chains */
+
+#ifndef TOO_FAR
+# define TOO_FAR 4096
+#endif
+/* Matches of length 3 are discarded if their distance exceeds TOO_FAR */
+
+/* Values for max_lazy_match, good_match and max_chain_length, depending on
+ * the desired pack level (0..9). The values given below have been tuned to
+ * exclude worst case performance for pathological files. Better values may be
+ * found for specific files.
+ */
+typedef struct config_s {
+ ush good_length; /* reduce lazy search above this match length */
+ ush max_lazy; /* do not perform lazy search above this match length */
+ ush nice_length; /* quit search above this match length */
+ ush max_chain;
+ compress_func func;
+} config;
+
+#ifdef FASTEST
+local const config configuration_table[2] = {
+/* good lazy nice chain */
+/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */
+/* 1 */ {4, 4, 8, 4, deflate_fast}}; /* max speed, no lazy matches */
+#else
+local const config configuration_table[10] = {
+/* good lazy nice chain */
+/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */
+/* 1 */ {4, 4, 8, 4, deflate_fast}, /* max speed, no lazy matches */
+/* 2 */ {4, 5, 16, 8, deflate_fast},
+/* 3 */ {4, 6, 32, 32, deflate_fast},
+
+/* 4 */ {4, 4, 16, 16, deflate_slow}, /* lazy matches */
+/* 5 */ {8, 16, 32, 32, deflate_slow},
+/* 6 */ {8, 16, 128, 128, deflate_slow},
+/* 7 */ {8, 32, 128, 256, deflate_slow},
+/* 8 */ {32, 128, 258, 1024, deflate_slow},
+/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */
+#endif
+
+/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4
+ * For deflate_fast() (levels <= 3) good is ignored and lazy has a different
+ * meaning.
+ */
+
+/* rank Z_BLOCK between Z_NO_FLUSH and Z_PARTIAL_FLUSH */
+#define RANK(f) (((f) * 2) - ((f) > 4 ? 9 : 0))
+
+/* ===========================================================================
+ * Update a hash value with the given input byte
+ * IN assertion: all calls to UPDATE_HASH are made with consecutive input
+ * characters, so that a running hash key can be computed from the previous
+ * key instead of complete recalculation each time.
+ */
+#define UPDATE_HASH(s,h,c) (h = (((h)<<s->hash_shift) ^ (c)) & s->hash_mask)
+
+
+/* ===========================================================================
+ * Insert string str in the dictionary and set match_head to the previous head
+ * of the hash chain (the most recent string with same hash key). Return
+ * the previous length of the hash chain.
+ * If this file is compiled with -DFASTEST, the compression level is forced
+ * to 1, and no hash chains are maintained.
+ * IN assertion: all calls to INSERT_STRING are made with consecutive input
+ * characters and the first MIN_MATCH bytes of str are valid (except for
+ * the last MIN_MATCH-1 bytes of the input file).
+ */
+#ifdef FASTEST
+#define INSERT_STRING(s, str, match_head) \
+ (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \
+ match_head = s->head[s->ins_h], \
+ s->head[s->ins_h] = (Pos)(str))
+#else
+#define INSERT_STRING(s, str, match_head) \
+ (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \
+ match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \
+ s->head[s->ins_h] = (Pos)(str))
+#endif
+
+/* ===========================================================================
+ * Initialize the hash table (avoiding 64K overflow for 16 bit systems).
+ * prev[] will be initialized on the fly.
+ */
+#define CLEAR_HASH(s) \
+ s->head[s->hash_size-1] = NIL; \
+ zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head));
+
+/* ===========================================================================
+ * Slide the hash table when sliding the window down (could be avoided with 32
+ * bit values at the expense of memory usage). We slide even when level == 0 to
+ * keep the hash table consistent if we switch back to level > 0 later.
+ */
+local void slide_hash(s)
+ deflate_state *s;
+{
+ unsigned n, m;
+ Posf *p;
+ uInt wsize = s->w_size;
+
+ n = s->hash_size;
+ p = &s->head[n];
+ do {
+ m = *--p;
+ *p = (Pos)(m >= wsize ? m - wsize : NIL);
+ } while (--n);
+ n = wsize;
+#ifndef FASTEST
+ p = &s->prev[n];
+ do {
+ m = *--p;
+ *p = (Pos)(m >= wsize ? m - wsize : NIL);
+ /* If n is not on any hash chain, prev[n] is garbage but
+ * its value will never be used.
+ */
+ } while (--n);
+#endif
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateInit_(strm, level, version, stream_size)
+ z_streamp strm;
+ int level;
+ const char *version;
+ int stream_size;
+{
+ return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL,
+ Z_DEFAULT_STRATEGY, version, stream_size);
+ /* To do: ignore strm->next_in if we use it as window */
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
+ version, stream_size)
+ z_streamp strm;
+ int level;
+ int method;
+ int windowBits;
+ int memLevel;
+ int strategy;
+ const char *version;
+ int stream_size;
+{
+ deflate_state *s;
+ int wrap = 1;
+ static const char my_version[] = ZLIB_VERSION;
+
+ ushf *overlay;
+ /* We overlay pending_buf and d_buf+l_buf. This works since the average
+ * output size for (length,distance) codes is <= 24 bits.
+ */
+
+ if (version == Z_NULL || version[0] != my_version[0] ||
+ stream_size != sizeof(z_stream)) {
+ return Z_VERSION_ERROR;
+ }
+ if (strm == Z_NULL) return Z_STREAM_ERROR;
+
+ strm->msg = Z_NULL;
+ if (strm->zalloc == (alloc_func)0) {
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zalloc = zcalloc;
+ strm->opaque = (voidpf)0;
+#endif
+ }
+ if (strm->zfree == (free_func)0)
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zfree = zcfree;
+#endif
+
+#ifdef FASTEST
+ if (level != 0) level = 1;
+#else
+ if (level == Z_DEFAULT_COMPRESSION) level = 6;
+#endif
+
+ if (windowBits < 0) { /* suppress zlib wrapper */
+ wrap = 0;
+ windowBits = -windowBits;
+ }
+#ifdef GZIP
+ else if (windowBits > 15) {
+ wrap = 2; /* write gzip wrapper instead */
+ windowBits -= 16;
+ }
+#endif
+ if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED ||
+ windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
+ strategy < 0 || strategy > Z_FIXED || (windowBits == 8 && wrap != 1)) {
+ return Z_STREAM_ERROR;
+ }
+ if (windowBits == 8) windowBits = 9; /* until 256-byte window bug fixed */
+ s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state));
+ if (s == Z_NULL) return Z_MEM_ERROR;
+ strm->state = (struct internal_state FAR *)s;
+ s->strm = strm;
+ s->status = INIT_STATE; /* to pass state test in deflateReset() */
+
+ s->wrap = wrap;
+ s->gzhead = Z_NULL;
+ s->w_bits = (uInt)windowBits;
+ s->w_size = 1 << s->w_bits;
+ s->w_mask = s->w_size - 1;
+
+ s->hash_bits = (uInt)memLevel + 7;
+ s->hash_size = 1 << s->hash_bits;
+ s->hash_mask = s->hash_size - 1;
+ s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH);
+
+ s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte));
+ s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos));
+ s->head = (Posf *) ZALLOC(strm, s->hash_size, sizeof(Pos));
+
+ s->high_water = 0; /* nothing written to s->window yet */
+
+ s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */
+
+ overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
+ s->pending_buf = (uchf *) overlay;
+ s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L);
+
+ if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL ||
+ s->pending_buf == Z_NULL) {
+ s->status = FINISH_STATE;
+ strm->msg = ERR_MSG(Z_MEM_ERROR);
+ deflateEnd (strm);
+ return Z_MEM_ERROR;
+ }
+ s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
+ s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+
+ s->level = level;
+ s->strategy = strategy;
+ s->method = (Byte)method;
+
+ return deflateReset(strm);
+}
+
+/* =========================================================================
+ * Check for a valid deflate stream state. Return 0 if ok, 1 if not.
+ */
+local int deflateStateCheck (strm)
+ z_streamp strm;
+{
+ deflate_state *s;
+ if (strm == Z_NULL ||
+ strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0)
+ return 1;
+ s = strm->state;
+ if (s == Z_NULL || s->strm != strm || (s->status != INIT_STATE &&
+#ifdef GZIP
+ s->status != GZIP_STATE &&
+#endif
+ s->status != EXTRA_STATE &&
+ s->status != NAME_STATE &&
+ s->status != COMMENT_STATE &&
+ s->status != HCRC_STATE &&
+ s->status != BUSY_STATE &&
+ s->status != FINISH_STATE))
+ return 1;
+ return 0;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
+ z_streamp strm;
+ const Bytef *dictionary;
+ uInt dictLength;
+{
+ deflate_state *s;
+ uInt str, n;
+ int wrap;
+ unsigned avail;
+ z_const unsigned char *next;
+
+ if (deflateStateCheck(strm) || dictionary == Z_NULL)
+ return Z_STREAM_ERROR;
+ s = strm->state;
+ wrap = s->wrap;
+ if (wrap == 2 || (wrap == 1 && s->status != INIT_STATE) || s->lookahead)
+ return Z_STREAM_ERROR;
+
+ /* when using zlib wrappers, compute Adler-32 for provided dictionary */
+ if (wrap == 1)
+ strm->adler = adler32(strm->adler, dictionary, dictLength);
+ s->wrap = 0; /* avoid computing Adler-32 in read_buf */
+
+ /* if dictionary would fill window, just replace the history */
+ if (dictLength >= s->w_size) {
+ if (wrap == 0) { /* already empty otherwise */
+ CLEAR_HASH(s);
+ s->strstart = 0;
+ s->block_start = 0L;
+ s->insert = 0;
+ }
+ dictionary += dictLength - s->w_size; /* use the tail */
+ dictLength = s->w_size;
+ }
+
+ /* insert dictionary into window and hash */
+ avail = strm->avail_in;
+ next = strm->next_in;
+ strm->avail_in = dictLength;
+ strm->next_in = (z_const Bytef *)dictionary;
+ fill_window(s);
+ while (s->lookahead >= MIN_MATCH) {
+ str = s->strstart;
+ n = s->lookahead - (MIN_MATCH-1);
+ do {
+ UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]);
+#ifndef FASTEST
+ s->prev[str & s->w_mask] = s->head[s->ins_h];
+#endif
+ s->head[s->ins_h] = (Pos)str;
+ str++;
+ } while (--n);
+ s->strstart = str;
+ s->lookahead = MIN_MATCH-1;
+ fill_window(s);
+ }
+ s->strstart += s->lookahead;
+ s->block_start = (long)s->strstart;
+ s->insert = s->lookahead;
+ s->lookahead = 0;
+ s->match_length = s->prev_length = MIN_MATCH-1;
+ s->match_available = 0;
+ strm->next_in = next;
+ strm->avail_in = avail;
+ s->wrap = wrap;
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength)
+ z_streamp strm;
+ Bytef *dictionary;
+ uInt *dictLength;
+{
+ deflate_state *s;
+ uInt len;
+
+ if (deflateStateCheck(strm))
+ return Z_STREAM_ERROR;
+ s = strm->state;
+ len = s->strstart + s->lookahead;
+ if (len > s->w_size)
+ len = s->w_size;
+ if (dictionary != Z_NULL && len)
+ zmemcpy(dictionary, s->window + s->strstart + s->lookahead - len, len);
+ if (dictLength != Z_NULL)
+ *dictLength = len;
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateResetKeep (strm)
+ z_streamp strm;
+{
+ deflate_state *s;
+
+ if (deflateStateCheck(strm)) {
+ return Z_STREAM_ERROR;
+ }
+
+ strm->total_in = strm->total_out = 0;
+ strm->msg = Z_NULL; /* use zfree if we ever allocate msg dynamically */
+ strm->data_type = Z_UNKNOWN;
+
+ s = (deflate_state *)strm->state;
+ s->pending = 0;
+ s->pending_out = s->pending_buf;
+
+ if (s->wrap < 0) {
+ s->wrap = -s->wrap; /* was made negative by deflate(..., Z_FINISH); */
+ }
+ s->status =
+#ifdef GZIP
+ s->wrap == 2 ? GZIP_STATE :
+#endif
+ s->wrap ? INIT_STATE : BUSY_STATE;
+ strm->adler =
+#ifdef GZIP
+ s->wrap == 2 ? crc32(0L, Z_NULL, 0) :
+#endif
+ adler32(0L, Z_NULL, 0);
+ s->last_flush = Z_NO_FLUSH;
+
+ _tr_init(s);
+
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateReset (strm)
+ z_streamp strm;
+{
+ int ret;
+
+ ret = deflateResetKeep(strm);
+ if (ret == Z_OK)
+ lm_init(strm->state);
+ return ret;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateSetHeader (strm, head)
+ z_streamp strm;
+ gz_headerp head;
+{
+ if (deflateStateCheck(strm) || strm->state->wrap != 2)
+ return Z_STREAM_ERROR;
+ strm->state->gzhead = head;
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflatePending (strm, pending, bits)
+ unsigned *pending;
+ int *bits;
+ z_streamp strm;
+{
+ if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
+ if (pending != Z_NULL)
+ *pending = strm->state->pending;
+ if (bits != Z_NULL)
+ *bits = strm->state->bi_valid;
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflatePrime (strm, bits, value)
+ z_streamp strm;
+ int bits;
+ int value;
+{
+ deflate_state *s;
+ int put;
+
+ if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
+ s = strm->state;
+ if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3))
+ return Z_BUF_ERROR;
+ do {
+ put = Buf_size - s->bi_valid;
+ if (put > bits)
+ put = bits;
+ s->bi_buf |= (ush)((value & ((1 << put) - 1)) << s->bi_valid);
+ s->bi_valid += put;
+ _tr_flush_bits(s);
+ value >>= put;
+ bits -= put;
+ } while (bits);
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateParams(strm, level, strategy)
+ z_streamp strm;
+ int level;
+ int strategy;
+{
+ deflate_state *s;
+ compress_func func;
+
+ if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
+ s = strm->state;
+
+#ifdef FASTEST
+ if (level != 0) level = 1;
+#else
+ if (level == Z_DEFAULT_COMPRESSION) level = 6;
+#endif
+ if (level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) {
+ return Z_STREAM_ERROR;
+ }
+ func = configuration_table[s->level].func;
+
+ if ((strategy != s->strategy || func != configuration_table[level].func) &&
+ s->high_water) {
+ /* Flush the last buffer: */
+ int err = deflate(strm, Z_BLOCK);
+ if (err == Z_STREAM_ERROR)
+ return err;
+ if (strm->avail_out == 0)
+ return Z_BUF_ERROR;
+ }
+ if (s->level != level) {
+ if (s->level == 0 && s->matches != 0) {
+ if (s->matches == 1)
+ slide_hash(s);
+ else
+ CLEAR_HASH(s);
+ s->matches = 0;
+ }
+ s->level = level;
+ s->max_lazy_match = configuration_table[level].max_lazy;
+ s->good_match = configuration_table[level].good_length;
+ s->nice_match = configuration_table[level].nice_length;
+ s->max_chain_length = configuration_table[level].max_chain;
+ }
+ s->strategy = strategy;
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain)
+ z_streamp strm;
+ int good_length;
+ int max_lazy;
+ int nice_length;
+ int max_chain;
+{
+ deflate_state *s;
+
+ if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
+ s = strm->state;
+ s->good_match = (uInt)good_length;
+ s->max_lazy_match = (uInt)max_lazy;
+ s->nice_match = nice_length;
+ s->max_chain_length = (uInt)max_chain;
+ return Z_OK;
+}
+
+/* =========================================================================
+ * For the default windowBits of 15 and memLevel of 8, this function returns
+ * a close to exact, as well as small, upper bound on the compressed size.
+ * They are coded as constants here for a reason--if the #define's are
+ * changed, then this function needs to be changed as well. The return
+ * value for 15 and 8 only works for those exact settings.
+ *
+ * For any setting other than those defaults for windowBits and memLevel,
+ * the value returned is a conservative worst case for the maximum expansion
+ * resulting from using fixed blocks instead of stored blocks, which deflate
+ * can emit on compressed data for some combinations of the parameters.
+ *
+ * This function could be more sophisticated to provide closer upper bounds for
+ * every combination of windowBits and memLevel. But even the conservative
+ * upper bound of about 14% expansion does not seem onerous for output buffer
+ * allocation.
+ */
+uLong ZEXPORT deflateBound(strm, sourceLen)
+ z_streamp strm;
+ uLong sourceLen;
+{
+ deflate_state *s;
+ uLong complen, wraplen;
+
+ /* conservative upper bound for compressed data */
+ complen = sourceLen +
+ ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5;
+
+ /* if can't get parameters, return conservative bound plus zlib wrapper */
+ if (deflateStateCheck(strm))
+ return complen + 6;
+
+ /* compute wrapper length */
+ s = strm->state;
+ switch (s->wrap) {
+ case 0: /* raw deflate */
+ wraplen = 0;
+ break;
+ case 1: /* zlib wrapper */
+ wraplen = 6 + (s->strstart ? 4 : 0);
+ break;
+#ifdef GZIP
+ case 2: /* gzip wrapper */
+ wraplen = 18;
+ if (s->gzhead != Z_NULL) { /* user-supplied gzip header */
+ Bytef *str;
+ if (s->gzhead->extra != Z_NULL)
+ wraplen += 2 + s->gzhead->extra_len;
+ str = s->gzhead->name;
+ if (str != Z_NULL)
+ do {
+ wraplen++;
+ } while (*str++);
+ str = s->gzhead->comment;
+ if (str != Z_NULL)
+ do {
+ wraplen++;
+ } while (*str++);
+ if (s->gzhead->hcrc)
+ wraplen += 2;
+ }
+ break;
+#endif
+ default: /* for compiler happiness */
+ wraplen = 6;
+ }
+
+ /* if not default parameters, return conservative bound */
+ if (s->w_bits != 15 || s->hash_bits != 8 + 7)
+ return complen + wraplen;
+
+ /* default settings: return tight bound for that case */
+ return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
+ (sourceLen >> 25) + 13 - 6 + wraplen;
+}
+
+/* =========================================================================
+ * Put a short in the pending buffer. The 16-bit value is put in MSB order.
+ * IN assertion: the stream state is correct and there is enough room in
+ * pending_buf.
+ */
+local void putShortMSB (s, b)
+ deflate_state *s;
+ uInt b;
+{
+ put_byte(s, (Byte)(b >> 8));
+ put_byte(s, (Byte)(b & 0xff));
+}
+
+/* =========================================================================
+ * Flush as much pending output as possible. All deflate() output, except for
+ * some deflate_stored() output, goes through this function so some
+ * applications may wish to modify it to avoid allocating a large
+ * strm->next_out buffer and copying into it. (See also read_buf()).
+ */
+local void flush_pending(strm)
+ z_streamp strm;
+{
+ unsigned len;
+ deflate_state *s = strm->state;
+
+ _tr_flush_bits(s);
+ len = s->pending;
+ if (len > strm->avail_out) len = strm->avail_out;
+ if (len == 0) return;
+
+ zmemcpy(strm->next_out, s->pending_out, len);
+ strm->next_out += len;
+ s->pending_out += len;
+ strm->total_out += len;
+ strm->avail_out -= len;
+ s->pending -= len;
+ if (s->pending == 0) {
+ s->pending_out = s->pending_buf;
+ }
+}
+
+/* ===========================================================================
+ * Update the header CRC with the bytes s->pending_buf[beg..s->pending - 1].
+ */
+#define HCRC_UPDATE(beg) \
+ do { \
+ if (s->gzhead->hcrc && s->pending > (beg)) \
+ strm->adler = crc32(strm->adler, s->pending_buf + (beg), \
+ s->pending - (beg)); \
+ } while (0)
+
+/* ========================================================================= */
+int ZEXPORT deflate (strm, flush)
+ z_streamp strm;
+ int flush;
+{
+ int old_flush; /* value of flush param for previous deflate call */
+ deflate_state *s;
+
+ if (deflateStateCheck(strm) || flush > Z_BLOCK || flush < 0) {
+ return Z_STREAM_ERROR;
+ }
+ s = strm->state;
+
+ if (strm->next_out == Z_NULL ||
+ (strm->avail_in != 0 && strm->next_in == Z_NULL) ||
+ (s->status == FINISH_STATE && flush != Z_FINISH)) {
+ ERR_RETURN(strm, Z_STREAM_ERROR);
+ }
+ if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR);
+
+ old_flush = s->last_flush;
+ s->last_flush = flush;
+
+ /* Flush as much pending output as possible */
+ if (s->pending != 0) {
+ flush_pending(strm);
+ if (strm->avail_out == 0) {
+ /* Since avail_out is 0, deflate will be called again with
+ * more output space, but possibly with both pending and
+ * avail_in equal to zero. There won't be anything to do,
+ * but this is not an error situation so make sure we
+ * return OK instead of BUF_ERROR at next call of deflate:
+ */
+ s->last_flush = -1;
+ return Z_OK;
+ }
+
+ /* Make sure there is something to do and avoid duplicate consecutive
+ * flushes. For repeated and useless calls with Z_FINISH, we keep
+ * returning Z_STREAM_END instead of Z_BUF_ERROR.
+ */
+ } else if (strm->avail_in == 0 && RANK(flush) <= RANK(old_flush) &&
+ flush != Z_FINISH) {
+ ERR_RETURN(strm, Z_BUF_ERROR);
+ }
+
+ /* User must not provide more input after the first FINISH: */
+ if (s->status == FINISH_STATE && strm->avail_in != 0) {
+ ERR_RETURN(strm, Z_BUF_ERROR);
+ }
+
+ /* Write the header */
+ if (s->status == INIT_STATE) {
+ /* zlib header */
+ uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8;
+ uInt level_flags;
+
+ if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2)
+ level_flags = 0;
+ else if (s->level < 6)
+ level_flags = 1;
+ else if (s->level == 6)
+ level_flags = 2;
+ else
+ level_flags = 3;
+ header |= (level_flags << 6);
+ if (s->strstart != 0) header |= PRESET_DICT;
+ header += 31 - (header % 31);
+
+ putShortMSB(s, header);
+
+ /* Save the adler32 of the preset dictionary: */
+ if (s->strstart != 0) {
+ putShortMSB(s, (uInt)(strm->adler >> 16));
+ putShortMSB(s, (uInt)(strm->adler & 0xffff));
+ }
+ strm->adler = adler32(0L, Z_NULL, 0);
+ s->status = BUSY_STATE;
+
+ /* Compression must start with an empty pending buffer */
+ flush_pending(strm);
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
+ }
+ }
+#ifdef GZIP
+ if (s->status == GZIP_STATE) {
+ /* gzip header */
+ strm->adler = crc32(0L, Z_NULL, 0);
+ put_byte(s, 31);
+ put_byte(s, 139);
+ put_byte(s, 8);
+ if (s->gzhead == Z_NULL) {
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, s->level == 9 ? 2 :
+ (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
+ 4 : 0));
+ put_byte(s, OS_CODE);
+ s->status = BUSY_STATE;
+
+ /* Compression must start with an empty pending buffer */
+ flush_pending(strm);
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
+ }
+ }
+ else {
+ put_byte(s, (s->gzhead->text ? 1 : 0) +
+ (s->gzhead->hcrc ? 2 : 0) +
+ (s->gzhead->extra == Z_NULL ? 0 : 4) +
+ (s->gzhead->name == Z_NULL ? 0 : 8) +
+ (s->gzhead->comment == Z_NULL ? 0 : 16)
+ );
+ put_byte(s, (Byte)(s->gzhead->time & 0xff));
+ put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff));
+ put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff));
+ put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff));
+ put_byte(s, s->level == 9 ? 2 :
+ (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
+ 4 : 0));
+ put_byte(s, s->gzhead->os & 0xff);
+ if (s->gzhead->extra != Z_NULL) {
+ put_byte(s, s->gzhead->extra_len & 0xff);
+ put_byte(s, (s->gzhead->extra_len >> 8) & 0xff);
+ }
+ if (s->gzhead->hcrc)
+ strm->adler = crc32(strm->adler, s->pending_buf,
+ s->pending);
+ s->gzindex = 0;
+ s->status = EXTRA_STATE;
+ }
+ }
+ if (s->status == EXTRA_STATE) {
+ if (s->gzhead->extra != Z_NULL) {
+ ulg beg = s->pending; /* start of bytes to update crc */
+ uInt left = (s->gzhead->extra_len & 0xffff) - s->gzindex;
+ while (s->pending + left > s->pending_buf_size) {
+ uInt copy = s->pending_buf_size - s->pending;
+ zmemcpy(s->pending_buf + s->pending,
+ s->gzhead->extra + s->gzindex, copy);
+ s->pending = s->pending_buf_size;
+ HCRC_UPDATE(beg);
+ s->gzindex += copy;
+ flush_pending(strm);
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
+ }
+ beg = 0;
+ left -= copy;
+ }
+ zmemcpy(s->pending_buf + s->pending,
+ s->gzhead->extra + s->gzindex, left);
+ s->pending += left;
+ HCRC_UPDATE(beg);
+ s->gzindex = 0;
+ }
+ s->status = NAME_STATE;
+ }
+ if (s->status == NAME_STATE) {
+ if (s->gzhead->name != Z_NULL) {
+ ulg beg = s->pending; /* start of bytes to update crc */
+ int val;
+ do {
+ if (s->pending == s->pending_buf_size) {
+ HCRC_UPDATE(beg);
+ flush_pending(strm);
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
+ }
+ beg = 0;
+ }
+ val = s->gzhead->name[s->gzindex++];
+ put_byte(s, val);
+ } while (val != 0);
+ HCRC_UPDATE(beg);
+ s->gzindex = 0;
+ }
+ s->status = COMMENT_STATE;
+ }
+ if (s->status == COMMENT_STATE) {
+ if (s->gzhead->comment != Z_NULL) {
+ ulg beg = s->pending; /* start of bytes to update crc */
+ int val;
+ do {
+ if (s->pending == s->pending_buf_size) {
+ HCRC_UPDATE(beg);
+ flush_pending(strm);
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
+ }
+ beg = 0;
+ }
+ val = s->gzhead->comment[s->gzindex++];
+ put_byte(s, val);
+ } while (val != 0);
+ HCRC_UPDATE(beg);
+ }
+ s->status = HCRC_STATE;
+ }
+ if (s->status == HCRC_STATE) {
+ if (s->gzhead->hcrc) {
+ if (s->pending + 2 > s->pending_buf_size) {
+ flush_pending(strm);
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
+ }
+ }
+ put_byte(s, (Byte)(strm->adler & 0xff));
+ put_byte(s, (Byte)((strm->adler >> 8) & 0xff));
+ strm->adler = crc32(0L, Z_NULL, 0);
+ }
+ s->status = BUSY_STATE;
+
+ /* Compression must start with an empty pending buffer */
+ flush_pending(strm);
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
+ }
+ }
+#endif
+
+ /* Start a new block or continue the current one.
+ */
+ if (strm->avail_in != 0 || s->lookahead != 0 ||
+ (flush != Z_NO_FLUSH && s->status != FINISH_STATE)) {
+ block_state bstate;
+
+ bstate = s->level == 0 ? deflate_stored(s, flush) :
+ s->strategy == Z_HUFFMAN_ONLY ? deflate_huff(s, flush) :
+ s->strategy == Z_RLE ? deflate_rle(s, flush) :
+ (*(configuration_table[s->level].func))(s, flush);
+
+ if (bstate == finish_started || bstate == finish_done) {
+ s->status = FINISH_STATE;
+ }
+ if (bstate == need_more || bstate == finish_started) {
+ if (strm->avail_out == 0) {
+ s->last_flush = -1; /* avoid BUF_ERROR next call, see above */
+ }
+ return Z_OK;
+ /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
+ * of deflate should use the same flush parameter to make sure
+ * that the flush is complete. So we don't have to output an
+ * empty block here, this will be done at next call. This also
+ * ensures that for a very small output buffer, we emit at most
+ * one empty block.
+ */
+ }
+ if (bstate == block_done) {
+ if (flush == Z_PARTIAL_FLUSH) {
+ _tr_align(s);
+ } else if (flush != Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */
+ _tr_stored_block(s, (char*)0, 0L, 0);
+ /* For a full flush, this empty block will be recognized
+ * as a special marker by inflate_sync().
+ */
+ if (flush == Z_FULL_FLUSH) {
+ CLEAR_HASH(s); /* forget history */
+ if (s->lookahead == 0) {
+ s->strstart = 0;
+ s->block_start = 0L;
+ s->insert = 0;
+ }
+ }
+ }
+ flush_pending(strm);
+ if (strm->avail_out == 0) {
+ s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */
+ return Z_OK;
+ }
+ }
+ }
+
+ if (flush != Z_FINISH) return Z_OK;
+ if (s->wrap <= 0) return Z_STREAM_END;
+
+ /* Write the trailer */
+#ifdef GZIP
+ if (s->wrap == 2) {
+ put_byte(s, (Byte)(strm->adler & 0xff));
+ put_byte(s, (Byte)((strm->adler >> 8) & 0xff));
+ put_byte(s, (Byte)((strm->adler >> 16) & 0xff));
+ put_byte(s, (Byte)((strm->adler >> 24) & 0xff));
+ put_byte(s, (Byte)(strm->total_in & 0xff));
+ put_byte(s, (Byte)((strm->total_in >> 8) & 0xff));
+ put_byte(s, (Byte)((strm->total_in >> 16) & 0xff));
+ put_byte(s, (Byte)((strm->total_in >> 24) & 0xff));
+ }
+ else
+#endif
+ {
+ putShortMSB(s, (uInt)(strm->adler >> 16));
+ putShortMSB(s, (uInt)(strm->adler & 0xffff));
+ }
+ flush_pending(strm);
+ /* If avail_out is zero, the application will call deflate again
+ * to flush the rest.
+ */
+ if (s->wrap > 0) s->wrap = -s->wrap; /* write the trailer only once! */
+ return s->pending != 0 ? Z_OK : Z_STREAM_END;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateEnd (strm)
+ z_streamp strm;
+{
+ int status;
+
+ if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
+
+ status = strm->state->status;
+
+ /* Deallocate in reverse order of allocations: */
+ TRY_FREE(strm, strm->state->pending_buf);
+ TRY_FREE(strm, strm->state->head);
+ TRY_FREE(strm, strm->state->prev);
+ TRY_FREE(strm, strm->state->window);
+
+ ZFREE(strm, strm->state);
+ strm->state = Z_NULL;
+
+ return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK;
+}
+
+/* =========================================================================
+ * Copy the source state to the destination state.
+ * To simplify the source, this is not supported for 16-bit MSDOS (which
+ * doesn't have enough memory anyway to duplicate compression states).
+ */
+int ZEXPORT deflateCopy (dest, source)
+ z_streamp dest;
+ z_streamp source;
+{
+#ifdef MAXSEG_64K
+ return Z_STREAM_ERROR;
+#else
+ deflate_state *ds;
+ deflate_state *ss;
+ ushf *overlay;
+
+
+ if (deflateStateCheck(source) || dest == Z_NULL) {
+ return Z_STREAM_ERROR;
+ }
+
+ ss = source->state;
+
+ zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream));
+
+ ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state));
+ if (ds == Z_NULL) return Z_MEM_ERROR;
+ dest->state = (struct internal_state FAR *) ds;
+ zmemcpy((voidpf)ds, (voidpf)ss, sizeof(deflate_state));
+ ds->strm = dest;
+
+ ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte));
+ ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos));
+ ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos));
+ overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2);
+ ds->pending_buf = (uchf *) overlay;
+
+ if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL ||
+ ds->pending_buf == Z_NULL) {
+ deflateEnd (dest);
+ return Z_MEM_ERROR;
+ }
+ /* following zmemcpy do not work for 16-bit MSDOS */
+ zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte));
+ zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos));
+ zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos));
+ zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size);
+
+ ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf);
+ ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush);
+ ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize;
+
+ ds->l_desc.dyn_tree = ds->dyn_ltree;
+ ds->d_desc.dyn_tree = ds->dyn_dtree;
+ ds->bl_desc.dyn_tree = ds->bl_tree;
+
+ return Z_OK;
+#endif /* MAXSEG_64K */
+}
+
+/* ===========================================================================
+ * Read a new buffer from the current input stream, update the adler32
+ * and total number of bytes read. All deflate() input goes through
+ * this function so some applications may wish to modify it to avoid
+ * allocating a large strm->next_in buffer and copying from it.
+ * (See also flush_pending()).
+ */
+local unsigned read_buf(strm, buf, size)
+ z_streamp strm;
+ Bytef *buf;
+ unsigned size;
+{
+ unsigned len = strm->avail_in;
+
+ if (len > size) len = size;
+ if (len == 0) return 0;
+
+ strm->avail_in -= len;
+
+ zmemcpy(buf, strm->next_in, len);
+ if (strm->state->wrap == 1) {
+ strm->adler = adler32(strm->adler, buf, len);
+ }
+#ifdef GZIP
+ else if (strm->state->wrap == 2) {
+ strm->adler = crc32(strm->adler, buf, len);
+ }
+#endif
+ strm->next_in += len;
+ strm->total_in += len;
+
+ return len;
+}
+
+/* ===========================================================================
+ * Initialize the "longest match" routines for a new zlib stream
+ */
+local void lm_init (s)
+ deflate_state *s;
+{
+ s->window_size = (ulg)2L*s->w_size;
+
+ CLEAR_HASH(s);
+
+ /* Set the default configuration parameters:
+ */
+ s->max_lazy_match = configuration_table[s->level].max_lazy;
+ s->good_match = configuration_table[s->level].good_length;
+ s->nice_match = configuration_table[s->level].nice_length;
+ s->max_chain_length = configuration_table[s->level].max_chain;
+
+ s->strstart = 0;
+ s->block_start = 0L;
+ s->lookahead = 0;
+ s->insert = 0;
+ s->match_length = s->prev_length = MIN_MATCH-1;
+ s->match_available = 0;
+ s->ins_h = 0;
+#ifndef FASTEST
+#ifdef ASMV
+ match_init(); /* initialize the asm code */
+#endif
+#endif
+}
+
+#ifndef FASTEST
+/* ===========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
+ * OUT assertion: the match length is not greater than s->lookahead.
+ */
+#ifndef ASMV
+/* For 80x86 and 680x0, an optimized version will be provided in match.asm or
+ * match.S. The code will be functionally equivalent.
+ */
+local uInt longest_match(s, cur_match)
+ deflate_state *s;
+ IPos cur_match; /* current match */
+{
+ unsigned chain_length = s->max_chain_length;/* max hash chain length */
+ register Bytef *scan = s->window + s->strstart; /* current string */
+ register Bytef *match; /* matched string */
+ register int len; /* length of current match */
+ int best_len = (int)s->prev_length; /* best match length so far */
+ int nice_match = s->nice_match; /* stop if match long enough */
+ IPos limit = s->strstart > (IPos)MAX_DIST(s) ?
+ s->strstart - (IPos)MAX_DIST(s) : NIL;
+ /* Stop when cur_match becomes <= limit. To simplify the code,
+ * we prevent matches with the string of window index 0.
+ */
+ Posf *prev = s->prev;
+ uInt wmask = s->w_mask;
+
+#ifdef UNALIGNED_OK
+ /* Compare two bytes at a time. Note: this is not always beneficial.
+ * Try with and without -DUNALIGNED_OK to check.
+ */
+ register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1;
+ register ush scan_start = *(ushf*)scan;
+ register ush scan_end = *(ushf*)(scan+best_len-1);
+#else
+ register Bytef *strend = s->window + s->strstart + MAX_MATCH;
+ register Byte scan_end1 = scan[best_len-1];
+ register Byte scan_end = scan[best_len];
+#endif
+
+ /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+ * It is easy to get rid of this optimization if necessary.
+ */
+ Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+ /* Do not waste too much time if we already have a good match: */
+ if (s->prev_length >= s->good_match) {
+ chain_length >>= 2;
+ }
+ /* Do not look for matches beyond the end of the input. This is necessary
+ * to make deflate deterministic.
+ */
+ if ((uInt)nice_match > s->lookahead) nice_match = (int)s->lookahead;
+
+ Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+ do {
+ Assert(cur_match < s->strstart, "no future");
+ match = s->window + cur_match;
+
+ /* Skip to next match if the match length cannot increase
+ * or if the match length is less than 2. Note that the checks below
+ * for insufficient lookahead only occur occasionally for performance
+ * reasons. Therefore uninitialized memory will be accessed, and
+ * conditional jumps will be made that depend on those values.
+ * However the length of the match is limited to the lookahead, so
+ * the output of deflate is not affected by the uninitialized values.
+ */
+#if (defined(UNALIGNED_OK) && MAX_MATCH == 258)
+ /* This code assumes sizeof(unsigned short) == 2. Do not use
+ * UNALIGNED_OK if your compiler uses a different size.
+ */
+ if (*(ushf*)(match+best_len-1) != scan_end ||
+ *(ushf*)match != scan_start) continue;
+
+ /* It is not necessary to compare scan[2] and match[2] since they are
+ * always equal when the other bytes match, given that the hash keys
+ * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at
+ * strstart+3, +5, ... up to strstart+257. We check for insufficient
+ * lookahead only every 4th comparison; the 128th check will be made
+ * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is
+ * necessary to put more guard bytes at the end of the window, or
+ * to check more often for insufficient lookahead.
+ */
+ Assert(scan[2] == match[2], "scan[2]?");
+ scan++, match++;
+ do {
+ } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+ *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+ *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+ *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+ scan < strend);
+ /* The funny "do {}" generates better code on most compilers */
+
+ /* Here, scan <= window+strstart+257 */
+ Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+ if (*scan == *match) scan++;
+
+ len = (MAX_MATCH - 1) - (int)(strend-scan);
+ scan = strend - (MAX_MATCH-1);
+
+#else /* UNALIGNED_OK */
+
+ if (match[best_len] != scan_end ||
+ match[best_len-1] != scan_end1 ||
+ *match != *scan ||
+ *++match != scan[1]) continue;
+
+ /* The check at best_len-1 can be removed because it will be made
+ * again later. (This heuristic is not always a win.)
+ * It is not necessary to compare scan[2] and match[2] since they
+ * are always equal when the other bytes match, given that
+ * the hash keys are equal and that HASH_BITS >= 8.
+ */
+ scan += 2, match++;
+ Assert(*scan == *match, "match[2]?");
+
+ /* We check for insufficient lookahead only every 8th comparison;
+ * the 256th check will be made at strstart+258.
+ */
+ do {
+ } while (*++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ scan < strend);
+
+ Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+ len = MAX_MATCH - (int)(strend - scan);
+ scan = strend - MAX_MATCH;
+
+#endif /* UNALIGNED_OK */
+
+ if (len > best_len) {
+ s->match_start = cur_match;
+ best_len = len;
+ if (len >= nice_match) break;
+#ifdef UNALIGNED_OK
+ scan_end = *(ushf*)(scan+best_len-1);
+#else
+ scan_end1 = scan[best_len-1];
+ scan_end = scan[best_len];
+#endif
+ }
+ } while ((cur_match = prev[cur_match & wmask]) > limit
+ && --chain_length != 0);
+
+ if ((uInt)best_len <= s->lookahead) return (uInt)best_len;
+ return s->lookahead;
+}
+#endif /* ASMV */
+
+#else /* FASTEST */
+
+/* ---------------------------------------------------------------------------
+ * Optimized version for FASTEST only
+ */
+local uInt longest_match(s, cur_match)
+ deflate_state *s;
+ IPos cur_match; /* current match */
+{
+ register Bytef *scan = s->window + s->strstart; /* current string */
+ register Bytef *match; /* matched string */
+ register int len; /* length of current match */
+ register Bytef *strend = s->window + s->strstart + MAX_MATCH;
+
+ /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+ * It is easy to get rid of this optimization if necessary.
+ */
+ Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+ Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+ Assert(cur_match < s->strstart, "no future");
+
+ match = s->window + cur_match;
+
+ /* Return failure if the match length is less than 2:
+ */
+ if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1;
+
+ /* The check at best_len-1 can be removed because it will be made
+ * again later. (This heuristic is not always a win.)
+ * It is not necessary to compare scan[2] and match[2] since they
+ * are always equal when the other bytes match, given that
+ * the hash keys are equal and that HASH_BITS >= 8.
+ */
+ scan += 2, match += 2;
+ Assert(*scan == *match, "match[2]?");
+
+ /* We check for insufficient lookahead only every 8th comparison;
+ * the 256th check will be made at strstart+258.
+ */
+ do {
+ } while (*++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ scan < strend);
+
+ Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+ len = MAX_MATCH - (int)(strend - scan);
+
+ if (len < MIN_MATCH) return MIN_MATCH - 1;
+
+ s->match_start = cur_match;
+ return (uInt)len <= s->lookahead ? (uInt)len : s->lookahead;
+}
+
+#endif /* FASTEST */
+
+#ifdef ZLIB_DEBUG
+
+#define EQUAL 0
+/* result of memcmp for equal strings */
+
+/* ===========================================================================
+ * Check that the match at match_start is indeed a match.
+ */
+local void check_match(s, start, match, length)
+ deflate_state *s;
+ IPos start, match;
+ int length;
+{
+ /* check that the match is indeed a match */
+ if (zmemcmp(s->window + match,
+ s->window + start, length) != EQUAL) {
+ fprintf(stderr, " start %u, match %u, length %d\n",
+ start, match, length);
+ do {
+ fprintf(stderr, "%c%c", s->window[match++], s->window[start++]);
+ } while (--length != 0);
+ z_error("invalid match");
+ }
+ if (z_verbose > 1) {
+ fprintf(stderr,"\\[%d,%d]", start-match, length);
+ do { putc(s->window[start++], stderr); } while (--length != 0);
+ }
+}
+#else
+# define check_match(s, start, match, length)
+#endif /* ZLIB_DEBUG */
+
+/* ===========================================================================
+ * Fill the window when the lookahead becomes insufficient.
+ * Updates strstart and lookahead.
+ *
+ * IN assertion: lookahead < MIN_LOOKAHEAD
+ * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+ * At least one byte has been read, or avail_in == 0; reads are
+ * performed for at least two bytes (required for the zip translate_eol
+ * option -- not supported here).
+ */
+local void fill_window(s)
+ deflate_state *s;
+{
+ unsigned n;
+ unsigned more; /* Amount of free space at the end of the window. */
+ uInt wsize = s->w_size;
+
+ Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");
+
+ do {
+ more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart);
+
+ /* Deal with !@#$% 64K limit: */
+ if (sizeof(int) <= 2) {
+ if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
+ more = wsize;
+
+ } else if (more == (unsigned)(-1)) {
+ /* Very unlikely, but possible on 16 bit machine if
+ * strstart == 0 && lookahead == 1 (input done a byte at time)
+ */
+ more--;
+ }
+ }
+
+ /* If the window is almost full and there is insufficient lookahead,
+ * move the upper half to the lower one to make room in the upper half.
+ */
+ if (s->strstart >= wsize+MAX_DIST(s)) {
+
+ zmemcpy(s->window, s->window+wsize, (unsigned)wsize - more);
+ s->match_start -= wsize;
+ s->strstart -= wsize; /* we now have strstart >= MAX_DIST */
+ s->block_start -= (long) wsize;
+ slide_hash(s);
+ more += wsize;
+ }
+ if (s->strm->avail_in == 0) break;
+
+ /* If there was no sliding:
+ * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+ * more == window_size - lookahead - strstart
+ * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+ * => more >= window_size - 2*WSIZE + 2
+ * In the BIG_MEM or MMAP case (not yet supported),
+ * window_size == input_size + MIN_LOOKAHEAD &&
+ * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+ * Otherwise, window_size == 2*WSIZE so more >= 2.
+ * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+ */
+ Assert(more >= 2, "more < 2");
+
+ n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more);
+ s->lookahead += n;
+
+ /* Initialize the hash value now that we have some input: */
+ if (s->lookahead + s->insert >= MIN_MATCH) {
+ uInt str = s->strstart - s->insert;
+ s->ins_h = s->window[str];
+ UPDATE_HASH(s, s->ins_h, s->window[str + 1]);
+#if MIN_MATCH != 3
+ Call UPDATE_HASH() MIN_MATCH-3 more times
+#endif
+ while (s->insert) {
+ UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]);
+#ifndef FASTEST
+ s->prev[str & s->w_mask] = s->head[s->ins_h];
+#endif
+ s->head[s->ins_h] = (Pos)str;
+ str++;
+ s->insert--;
+ if (s->lookahead + s->insert < MIN_MATCH)
+ break;
+ }
+ }
+ /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
+ * but this is not important since only literal bytes will be emitted.
+ */
+
+ } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0);
+
+ /* If the WIN_INIT bytes after the end of the current data have never been
+ * written, then zero those bytes in order to avoid memory check reports of
+ * the use of uninitialized (or uninitialised as Julian writes) bytes by
+ * the longest match routines. Update the high water mark for the next
+ * time through here. WIN_INIT is set to MAX_MATCH since the longest match
+ * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
+ */
+ if (s->high_water < s->window_size) {
+ ulg curr = s->strstart + (ulg)(s->lookahead);
+ ulg init;
+
+ if (s->high_water < curr) {
+ /* Previous high water mark below current data -- zero WIN_INIT
+ * bytes or up to end of window, whichever is less.
+ */
+ init = s->window_size - curr;
+ if (init > WIN_INIT)
+ init = WIN_INIT;
+ zmemzero(s->window + curr, (unsigned)init);
+ s->high_water = curr + init;
+ }
+ else if (s->high_water < (ulg)curr + WIN_INIT) {
+ /* High water mark at or above current data, but below current data
+ * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
+ * to end of window, whichever is less.
+ */
+ init = (ulg)curr + WIN_INIT - s->high_water;
+ if (init > s->window_size - s->high_water)
+ init = s->window_size - s->high_water;
+ zmemzero(s->window + s->high_water, (unsigned)init);
+ s->high_water += init;
+ }
+ }
+
+ Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
+ "not enough room for search");
+}
+
+/* ===========================================================================
+ * Flush the current block, with given end-of-file flag.
+ * IN assertion: strstart is set to the end of the current match.
+ */
+#define FLUSH_BLOCK_ONLY(s, last) { \
+ _tr_flush_block(s, (s->block_start >= 0L ? \
+ (charf *)&s->window[(unsigned)s->block_start] : \
+ (charf *)Z_NULL), \
+ (ulg)((long)s->strstart - s->block_start), \
+ (last)); \
+ s->block_start = s->strstart; \
+ flush_pending(s->strm); \
+ Tracev((stderr,"[FLUSH]")); \
+}
+
+/* Same but force premature exit if necessary. */
+#define FLUSH_BLOCK(s, last) { \
+ FLUSH_BLOCK_ONLY(s, last); \
+ if (s->strm->avail_out == 0) return (last) ? finish_started : need_more; \
+}
+
+/* Maximum stored block length in deflate format (not including header). */
+#define MAX_STORED 65535
+
+/* Minimum of a and b. */
+#ifndef MIN
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+#endif
+
+/* ===========================================================================
+ * Copy without compression as much as possible from the input stream, return
+ * the current block state.
+ *
+ * In case deflateParams() is used to later switch to a non-zero compression
+ * level, s->matches (otherwise unused when storing) keeps track of the number
+ * of hash table slides to perform. If s->matches is 1, then one hash table
+ * slide will be done when switching. If s->matches is 2, the maximum value
+ * allowed here, then the hash table will be cleared, since two or more slides
+ * is the same as a clear.
+ *
+ * deflate_stored() is written to minimize the number of times an input byte is
+ * copied. It is most efficient with large input and output buffers, which
+ * maximizes the opportunites to have a single copy from next_in to next_out.
+ */
+local block_state deflate_stored(s, flush)
+ deflate_state *s;
+ int flush;
+{
+ /* Smallest worthy block size when not flushing or finishing. By default
+ * this is 32K. This can be as small as 507 bytes for memLevel == 1. For
+ * large input and output buffers, the stored block size will be larger.
+ */
+ unsigned min_block = MIN(s->pending_buf_size - 5, s->w_size);
+
+ /* Copy as many min_block or larger stored blocks directly to next_out as
+ * possible. If flushing, copy the remaining available input to next_out as
+ * stored blocks, if there is enough space.
+ */
+ unsigned len, left, have, last = 0;
+ unsigned used = s->strm->avail_in;
+ do {
+ /* Set len to the maximum size block that we can copy directly with the
+ * available input data and output space. Set left to how much of that
+ * would be copied from what's left in the window.
+ */
+ len = MAX_STORED; /* maximum deflate stored block length */
+ have = (s->bi_valid + 42) >> 3; /* number of header bytes */
+ if (s->strm->avail_out < have) /* need room for header */
+ break;
+ /* maximum stored block length that will fit in avail_out: */
+ have = s->strm->avail_out - have;
+ left = s->strstart - s->block_start; /* bytes left in window */
+ if (len > (ulg)left + s->strm->avail_in)
+ len = left + s->strm->avail_in; /* limit len to the input */
+ if (len > have)
+ len = have; /* limit len to the output */
+
+ /* If the stored block would be less than min_block in length, or if
+ * unable to copy all of the available input when flushing, then try
+ * copying to the window and the pending buffer instead. Also don't
+ * write an empty block when flushing -- deflate() does that.
+ */
+ if (len < min_block && ((len == 0 && flush != Z_FINISH) ||
+ flush == Z_NO_FLUSH ||
+ len != left + s->strm->avail_in))
+ break;
+
+ /* Make a dummy stored block in pending to get the header bytes,
+ * including any pending bits. This also updates the debugging counts.
+ */
+ last = flush == Z_FINISH && len == left + s->strm->avail_in ? 1 : 0;
+ _tr_stored_block(s, (char *)0, 0L, last);
+
+ /* Replace the lengths in the dummy stored block with len. */
+ s->pending_buf[s->pending - 4] = len;
+ s->pending_buf[s->pending - 3] = len >> 8;
+ s->pending_buf[s->pending - 2] = ~len;
+ s->pending_buf[s->pending - 1] = ~len >> 8;
+
+ /* Write the stored block header bytes. */
+ flush_pending(s->strm);
+
+#ifdef ZLIB_DEBUG
+ /* Update debugging counts for the data about to be copied. */
+ s->compressed_len += len << 3;
+ s->bits_sent += len << 3;
+#endif
+
+ /* Copy uncompressed bytes from the window to next_out. */
+ if (left) {
+ if (left > len)
+ left = len;
+ zmemcpy(s->strm->next_out, s->window + s->block_start, left);
+ s->strm->next_out += left;
+ s->strm->avail_out -= left;
+ s->strm->total_out += left;
+ s->block_start += left;
+ len -= left;
+ }
+
+ /* Copy uncompressed bytes directly from next_in to next_out, updating
+ * the check value.
+ */
+ if (len) {
+ read_buf(s->strm, s->strm->next_out, len);
+ s->strm->next_out += len;
+ s->strm->avail_out -= len;
+ s->strm->total_out += len;
+ }
+ } while (last == 0);
+
+ /* Update the sliding window with the last s->w_size bytes of the copied
+ * data, or append all of the copied data to the existing window if less
+ * than s->w_size bytes were copied. Also update the number of bytes to
+ * insert in the hash tables, in the event that deflateParams() switches to
+ * a non-zero compression level.
+ */
+ used -= s->strm->avail_in; /* number of input bytes directly copied */
+ if (used) {
+ /* If any input was used, then no unused input remains in the window,
+ * therefore s->block_start == s->strstart.
+ */
+ if (used >= s->w_size) { /* supplant the previous history */
+ s->matches = 2; /* clear hash */
+ zmemcpy(s->window, s->strm->next_in - s->w_size, s->w_size);
+ s->strstart = s->w_size;
+ }
+ else {
+ if (s->window_size - s->strstart <= used) {
+ /* Slide the window down. */
+ s->strstart -= s->w_size;
+ zmemcpy(s->window, s->window + s->w_size, s->strstart);
+ if (s->matches < 2)
+ s->matches++; /* add a pending slide_hash() */
+ }
+ zmemcpy(s->window + s->strstart, s->strm->next_in - used, used);
+ s->strstart += used;
+ }
+ s->block_start = s->strstart;
+ s->insert += MIN(used, s->w_size - s->insert);
+ }
+ if (s->high_water < s->strstart)
+ s->high_water = s->strstart;
+
+ /* If the last block was written to next_out, then done. */
+ if (last)
+ return finish_done;
+
+ /* If flushing and all input has been consumed, then done. */
+ if (flush != Z_NO_FLUSH && flush != Z_FINISH &&
+ s->strm->avail_in == 0 && (long)s->strstart == s->block_start)
+ return block_done;
+
+ /* Fill the window with any remaining input. */
+ have = s->window_size - s->strstart - 1;
+ if (s->strm->avail_in > have && s->block_start >= (long)s->w_size) {
+ /* Slide the window down. */
+ s->block_start -= s->w_size;
+ s->strstart -= s->w_size;
+ zmemcpy(s->window, s->window + s->w_size, s->strstart);
+ if (s->matches < 2)
+ s->matches++; /* add a pending slide_hash() */
+ have += s->w_size; /* more space now */
+ }
+ if (have > s->strm->avail_in)
+ have = s->strm->avail_in;
+ if (have) {
+ read_buf(s->strm, s->window + s->strstart, have);
+ s->strstart += have;
+ }
+ if (s->high_water < s->strstart)
+ s->high_water = s->strstart;
+
+ /* There was not enough avail_out to write a complete worthy or flushed
+ * stored block to next_out. Write a stored block to pending instead, if we
+ * have enough input for a worthy block, or if flushing and there is enough
+ * room for the remaining input as a stored block in the pending buffer.
+ */
+ have = (s->bi_valid + 42) >> 3; /* number of header bytes */
+ /* maximum stored block length that will fit in pending: */
+ have = MIN(s->pending_buf_size - have, MAX_STORED);
+ min_block = MIN(have, s->w_size);
+ left = s->strstart - s->block_start;
+ if (left >= min_block ||
+ ((left || flush == Z_FINISH) && flush != Z_NO_FLUSH &&
+ s->strm->avail_in == 0 && left <= have)) {
+ len = MIN(left, have);
+ last = flush == Z_FINISH && s->strm->avail_in == 0 &&
+ len == left ? 1 : 0;
+ _tr_stored_block(s, (charf *)s->window + s->block_start, len, last);
+ s->block_start += len;
+ flush_pending(s->strm);
+ }
+
+ /* We've done all we can with the available input and output. */
+ return last ? finish_started : need_more;
+}
+
+/* ===========================================================================
+ * Compress as much as possible from the input stream, return the current
+ * block state.
+ * This function does not perform lazy evaluation of matches and inserts
+ * new strings in the dictionary only for unmatched strings or for short
+ * matches. It is used only for the fast compression options.
+ */
+local block_state deflate_fast(s, flush)
+ deflate_state *s;
+ int flush;
+{
+ IPos hash_head; /* head of the hash chain */
+ int bflush; /* set if current block must be flushed */
+
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the next match, plus MIN_MATCH bytes to insert the
+ * string following the next match.
+ */
+ if (s->lookahead < MIN_LOOKAHEAD) {
+ fill_window(s);
+ if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+ return need_more;
+ }
+ if (s->lookahead == 0) break; /* flush the current block */
+ }
+
+ /* Insert the string window[strstart .. strstart+2] in the
+ * dictionary, and set hash_head to the head of the hash chain:
+ */
+ hash_head = NIL;
+ if (s->lookahead >= MIN_MATCH) {
+ INSERT_STRING(s, s->strstart, hash_head);
+ }
+
+ /* Find the longest match, discarding those <= prev_length.
+ * At this point we have always match_length < MIN_MATCH
+ */
+ if (hash_head != NIL && s->strstart - hash_head <= MAX_DIST(s)) {
+ /* To simplify the code, we prevent matches with the string
+ * of window index 0 (in particular we have to avoid a match
+ * of the string with itself at the start of the input file).
+ */
+ s->match_length = longest_match (s, hash_head);
+ /* longest_match() sets match_start */
+ }
+ if (s->match_length >= MIN_MATCH) {
+ check_match(s, s->strstart, s->match_start, s->match_length);
+
+ _tr_tally_dist(s, s->strstart - s->match_start,
+ s->match_length - MIN_MATCH, bflush);
+
+ s->lookahead -= s->match_length;
+
+ /* Insert new strings in the hash table only if the match length
+ * is not too large. This saves time but degrades compression.
+ */
+#ifndef FASTEST
+ if (s->match_length <= s->max_insert_length &&
+ s->lookahead >= MIN_MATCH) {
+ s->match_length--; /* string at strstart already in table */
+ do {
+ s->strstart++;
+ INSERT_STRING(s, s->strstart, hash_head);
+ /* strstart never exceeds WSIZE-MAX_MATCH, so there are
+ * always MIN_MATCH bytes ahead.
+ */
+ } while (--s->match_length != 0);
+ s->strstart++;
+ } else
+#endif
+ {
+ s->strstart += s->match_length;
+ s->match_length = 0;
+ s->ins_h = s->window[s->strstart];
+ UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]);
+#if MIN_MATCH != 3
+ Call UPDATE_HASH() MIN_MATCH-3 more times
+#endif
+ /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
+ * matter since it will be recomputed at next deflate call.
+ */
+ }
+ } else {
+ /* No match, output a literal byte */
+ Tracevv((stderr,"%c", s->window[s->strstart]));
+ _tr_tally_lit (s, s->window[s->strstart], bflush);
+ s->lookahead--;
+ s->strstart++;
+ }
+ if (bflush) FLUSH_BLOCK(s, 0);
+ }
+ s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1;
+ if (flush == Z_FINISH) {
+ FLUSH_BLOCK(s, 1);
+ return finish_done;
+ }
+ if (s->last_lit)
+ FLUSH_BLOCK(s, 0);
+ return block_done;
+}
+
+#ifndef FASTEST
+/* ===========================================================================
+ * Same as above, but achieves better compression. We use a lazy
+ * evaluation for matches: a match is finally adopted only if there is
+ * no better match at the next window position.
+ */
+local block_state deflate_slow(s, flush)
+ deflate_state *s;
+ int flush;
+{
+ IPos hash_head; /* head of hash chain */
+ int bflush; /* set if current block must be flushed */
+
+ /* Process the input block. */
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the next match, plus MIN_MATCH bytes to insert the
+ * string following the next match.
+ */
+ if (s->lookahead < MIN_LOOKAHEAD) {
+ fill_window(s);
+ if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+ return need_more;
+ }
+ if (s->lookahead == 0) break; /* flush the current block */
+ }
+
+ /* Insert the string window[strstart .. strstart+2] in the
+ * dictionary, and set hash_head to the head of the hash chain:
+ */
+ hash_head = NIL;
+ if (s->lookahead >= MIN_MATCH) {
+ INSERT_STRING(s, s->strstart, hash_head);
+ }
+
+ /* Find the longest match, discarding those <= prev_length.
+ */
+ s->prev_length = s->match_length, s->prev_match = s->match_start;
+ s->match_length = MIN_MATCH-1;
+
+ if (hash_head != NIL && s->prev_length < s->max_lazy_match &&
+ s->strstart - hash_head <= MAX_DIST(s)) {
+ /* To simplify the code, we prevent matches with the string
+ * of window index 0 (in particular we have to avoid a match
+ * of the string with itself at the start of the input file).
+ */
+ s->match_length = longest_match (s, hash_head);
+ /* longest_match() sets match_start */
+
+ if (s->match_length <= 5 && (s->strategy == Z_FILTERED
+#if TOO_FAR <= 32767
+ || (s->match_length == MIN_MATCH &&
+ s->strstart - s->match_start > TOO_FAR)
+#endif
+ )) {
+
+ /* If prev_match is also MIN_MATCH, match_start is garbage
+ * but we will ignore the current match anyway.
+ */
+ s->match_length = MIN_MATCH-1;
+ }
+ }
+ /* If there was a match at the previous step and the current
+ * match is not better, output the previous match:
+ */
+ if (s->prev_length >= MIN_MATCH && s->match_length <= s->prev_length) {
+ uInt max_insert = s->strstart + s->lookahead - MIN_MATCH;
+ /* Do not insert strings in hash table beyond this. */
+
+ check_match(s, s->strstart-1, s->prev_match, s->prev_length);
+
+ _tr_tally_dist(s, s->strstart -1 - s->prev_match,
+ s->prev_length - MIN_MATCH, bflush);
+
+ /* Insert in hash table all strings up to the end of the match.
+ * strstart-1 and strstart are already inserted. If there is not
+ * enough lookahead, the last two strings are not inserted in
+ * the hash table.
+ */
+ s->lookahead -= s->prev_length-1;
+ s->prev_length -= 2;
+ do {
+ if (++s->strstart <= max_insert) {
+ INSERT_STRING(s, s->strstart, hash_head);
+ }
+ } while (--s->prev_length != 0);
+ s->match_available = 0;
+ s->match_length = MIN_MATCH-1;
+ s->strstart++;
+
+ if (bflush) FLUSH_BLOCK(s, 0);
+
+ } else if (s->match_available) {
+ /* If there was no match at the previous position, output a
+ * single literal. If there was a match but the current match
+ * is longer, truncate the previous match to a single literal.
+ */
+ Tracevv((stderr,"%c", s->window[s->strstart-1]));
+ _tr_tally_lit(s, s->window[s->strstart-1], bflush);
+ if (bflush) {
+ FLUSH_BLOCK_ONLY(s, 0);
+ }
+ s->strstart++;
+ s->lookahead--;
+ if (s->strm->avail_out == 0) return need_more;
+ } else {
+ /* There is no previous match to compare with, wait for
+ * the next step to decide.
+ */
+ s->match_available = 1;
+ s->strstart++;
+ s->lookahead--;
+ }
+ }
+ Assert (flush != Z_NO_FLUSH, "no flush?");
+ if (s->match_available) {
+ Tracevv((stderr,"%c", s->window[s->strstart-1]));
+ _tr_tally_lit(s, s->window[s->strstart-1], bflush);
+ s->match_available = 0;
+ }
+ s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1;
+ if (flush == Z_FINISH) {
+ FLUSH_BLOCK(s, 1);
+ return finish_done;
+ }
+ if (s->last_lit)
+ FLUSH_BLOCK(s, 0);
+ return block_done;
+}
+#endif /* FASTEST */
+
+/* ===========================================================================
+ * For Z_RLE, simply look for runs of bytes, generate matches only of distance
+ * one. Do not maintain a hash table. (It will be regenerated if this run of
+ * deflate switches away from Z_RLE.)
+ */
+local block_state deflate_rle(s, flush)
+ deflate_state *s;
+ int flush;
+{
+ int bflush; /* set if current block must be flushed */
+ uInt prev; /* byte at distance one to match */
+ Bytef *scan, *strend; /* scan goes up to strend for length of run */
+
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the longest run, plus one for the unrolled loop.
+ */
+ if (s->lookahead <= MAX_MATCH) {
+ fill_window(s);
+ if (s->lookahead <= MAX_MATCH && flush == Z_NO_FLUSH) {
+ return need_more;
+ }
+ if (s->lookahead == 0) break; /* flush the current block */
+ }
+
+ /* See how many times the previous byte repeats */
+ s->match_length = 0;
+ if (s->lookahead >= MIN_MATCH && s->strstart > 0) {
+ scan = s->window + s->strstart - 1;
+ prev = *scan;
+ if (prev == *++scan && prev == *++scan && prev == *++scan) {
+ strend = s->window + s->strstart + MAX_MATCH;
+ do {
+ } while (prev == *++scan && prev == *++scan &&
+ prev == *++scan && prev == *++scan &&
+ prev == *++scan && prev == *++scan &&
+ prev == *++scan && prev == *++scan &&
+ scan < strend);
+ s->match_length = MAX_MATCH - (uInt)(strend - scan);
+ if (s->match_length > s->lookahead)
+ s->match_length = s->lookahead;
+ }
+ Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
+ }
+
+ /* Emit match if have run of MIN_MATCH or longer, else emit literal */
+ if (s->match_length >= MIN_MATCH) {
+ check_match(s, s->strstart, s->strstart - 1, s->match_length);
+
+ _tr_tally_dist(s, 1, s->match_length - MIN_MATCH, bflush);
+
+ s->lookahead -= s->match_length;
+ s->strstart += s->match_length;
+ s->match_length = 0;
+ } else {
+ /* No match, output a literal byte */
+ Tracevv((stderr,"%c", s->window[s->strstart]));
+ _tr_tally_lit (s, s->window[s->strstart], bflush);
+ s->lookahead--;
+ s->strstart++;
+ }
+ if (bflush) FLUSH_BLOCK(s, 0);
+ }
+ s->insert = 0;
+ if (flush == Z_FINISH) {
+ FLUSH_BLOCK(s, 1);
+ return finish_done;
+ }
+ if (s->last_lit)
+ FLUSH_BLOCK(s, 0);
+ return block_done;
+}
+
+/* ===========================================================================
+ * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table.
+ * (It will be regenerated if this run of deflate switches away from Huffman.)
+ */
+local block_state deflate_huff(s, flush)
+ deflate_state *s;
+ int flush;
+{
+ int bflush; /* set if current block must be flushed */
+
+ for (;;) {
+ /* Make sure that we have a literal to write. */
+ if (s->lookahead == 0) {
+ fill_window(s);
+ if (s->lookahead == 0) {
+ if (flush == Z_NO_FLUSH)
+ return need_more;
+ break; /* flush the current block */
+ }
+ }
+
+ /* Output a literal byte */
+ s->match_length = 0;
+ Tracevv((stderr,"%c", s->window[s->strstart]));
+ _tr_tally_lit (s, s->window[s->strstart], bflush);
+ s->lookahead--;
+ s->strstart++;
+ if (bflush) FLUSH_BLOCK(s, 0);
+ }
+ s->insert = 0;
+ if (flush == Z_FINISH) {
+ FLUSH_BLOCK(s, 1);
+ return finish_done;
+ }
+ if (s->last_lit)
+ FLUSH_BLOCK(s, 0);
+ return block_done;
+}
diff --git a/lib/zlib/deflate.h b/lib/zlib/deflate.h
new file mode 100644
index 0000000..23ecdd3
--- /dev/null
+++ b/lib/zlib/deflate.h
@@ -0,0 +1,349 @@
+/* deflate.h -- internal compression state
+ * Copyright (C) 1995-2016 Jean-loup Gailly
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+/* @(#) $Id$ */
+
+#ifndef DEFLATE_H
+#define DEFLATE_H
+
+#include "zutil.h"
+
+/* define NO_GZIP when compiling if you want to disable gzip header and
+ trailer creation by deflate(). NO_GZIP would be used to avoid linking in
+ the crc code when it is not needed. For shared libraries, gzip encoding
+ should be left enabled. */
+#ifndef NO_GZIP
+# define GZIP
+#endif
+
+/* ===========================================================================
+ * Internal compression state.
+ */
+
+#define LENGTH_CODES 29
+/* number of length codes, not counting the special END_BLOCK code */
+
+#define LITERALS 256
+/* number of literal bytes 0..255 */
+
+#define L_CODES (LITERALS+1+LENGTH_CODES)
+/* number of Literal or Length codes, including the END_BLOCK code */
+
+#define D_CODES 30
+/* number of distance codes */
+
+#define BL_CODES 19
+/* number of codes used to transfer the bit lengths */
+
+#define HEAP_SIZE (2*L_CODES+1)
+/* maximum heap size */
+
+#define MAX_BITS 15
+/* All codes must not exceed MAX_BITS bits */
+
+#define Buf_size 16
+/* size of bit buffer in bi_buf */
+
+#define INIT_STATE 42 /* zlib header -> BUSY_STATE */
+#ifdef GZIP
+# define GZIP_STATE 57 /* gzip header -> BUSY_STATE | EXTRA_STATE */
+#endif
+#define EXTRA_STATE 69 /* gzip extra block -> NAME_STATE */
+#define NAME_STATE 73 /* gzip file name -> COMMENT_STATE */
+#define COMMENT_STATE 91 /* gzip comment -> HCRC_STATE */
+#define HCRC_STATE 103 /* gzip header CRC -> BUSY_STATE */
+#define BUSY_STATE 113 /* deflate -> FINISH_STATE */
+#define FINISH_STATE 666 /* stream complete */
+/* Stream status */
+
+
+/* Data structure describing a single value and its code string. */
+typedef struct ct_data_s {
+ union {
+ ush freq; /* frequency count */
+ ush code; /* bit string */
+ } fc;
+ union {
+ ush dad; /* father node in Huffman tree */
+ ush len; /* length of bit string */
+ } dl;
+} FAR ct_data;
+
+#define Freq fc.freq
+#define Code fc.code
+#define Dad dl.dad
+#define Len dl.len
+
+typedef struct static_tree_desc_s static_tree_desc;
+
+typedef struct tree_desc_s {
+ ct_data *dyn_tree; /* the dynamic tree */
+ int max_code; /* largest code with non zero frequency */
+ const static_tree_desc *stat_desc; /* the corresponding static tree */
+} FAR tree_desc;
+
+typedef ush Pos;
+typedef Pos FAR Posf;
+typedef unsigned IPos;
+
+/* A Pos is an index in the character window. We use short instead of int to
+ * save space in the various tables. IPos is used only for parameter passing.
+ */
+
+typedef struct internal_state {
+ z_streamp strm; /* pointer back to this zlib stream */
+ int status; /* as the name implies */
+ Bytef *pending_buf; /* output still pending */
+ ulg pending_buf_size; /* size of pending_buf */
+ Bytef *pending_out; /* next pending byte to output to the stream */
+ ulg pending; /* nb of bytes in the pending buffer */
+ int wrap; /* bit 0 true for zlib, bit 1 true for gzip */
+ gz_headerp gzhead; /* gzip header information to write */
+ ulg gzindex; /* where in extra, name, or comment */
+ Byte method; /* can only be DEFLATED */
+ int last_flush; /* value of flush param for previous deflate call */
+
+ /* used by deflate.c: */
+
+ uInt w_size; /* LZ77 window size (32K by default) */
+ uInt w_bits; /* log2(w_size) (8..16) */
+ uInt w_mask; /* w_size - 1 */
+
+ Bytef *window;
+ /* Sliding window. Input bytes are read into the second half of the window,
+ * and move to the first half later to keep a dictionary of at least wSize
+ * bytes. With this organization, matches are limited to a distance of
+ * wSize-MAX_MATCH bytes, but this ensures that IO is always
+ * performed with a length multiple of the block size. Also, it limits
+ * the window size to 64K, which is quite useful on MSDOS.
+ * To do: use the user input buffer as sliding window.
+ */
+
+ ulg window_size;
+ /* Actual size of window: 2*wSize, except when the user input buffer
+ * is directly used as sliding window.
+ */
+
+ Posf *prev;
+ /* Link to older string with same hash index. To limit the size of this
+ * array to 64K, this link is maintained only for the last 32K strings.
+ * An index in this array is thus a window index modulo 32K.
+ */
+
+ Posf *head; /* Heads of the hash chains or NIL. */
+
+ uInt ins_h; /* hash index of string to be inserted */
+ uInt hash_size; /* number of elements in hash table */
+ uInt hash_bits; /* log2(hash_size) */
+ uInt hash_mask; /* hash_size-1 */
+
+ uInt hash_shift;
+ /* Number of bits by which ins_h must be shifted at each input
+ * step. It must be such that after MIN_MATCH steps, the oldest
+ * byte no longer takes part in the hash key, that is:
+ * hash_shift * MIN_MATCH >= hash_bits
+ */
+
+ long block_start;
+ /* Window position at the beginning of the current output block. Gets
+ * negative when the window is moved backwards.
+ */
+
+ uInt match_length; /* length of best match */
+ IPos prev_match; /* previous match */
+ int match_available; /* set if previous match exists */
+ uInt strstart; /* start of string to insert */
+ uInt match_start; /* start of matching string */
+ uInt lookahead; /* number of valid bytes ahead in window */
+
+ uInt prev_length;
+ /* Length of the best match at previous step. Matches not greater than this
+ * are discarded. This is used in the lazy match evaluation.
+ */
+
+ uInt max_chain_length;
+ /* To speed up deflation, hash chains are never searched beyond this
+ * length. A higher limit improves compression ratio but degrades the
+ * speed.
+ */
+
+ uInt max_lazy_match;
+ /* Attempt to find a better match only when the current match is strictly
+ * smaller than this value. This mechanism is used only for compression
+ * levels >= 4.
+ */
+# define max_insert_length max_lazy_match
+ /* Insert new strings in the hash table only if the match length is not
+ * greater than this length. This saves time but degrades compression.
+ * max_insert_length is used only for compression levels <= 3.
+ */
+
+ int level; /* compression level (1..9) */
+ int strategy; /* favor or force Huffman coding*/
+
+ uInt good_match;
+ /* Use a faster search when the previous match is longer than this */
+
+ int nice_match; /* Stop searching when current match exceeds this */
+
+ /* used by trees.c: */
+ /* Didn't use ct_data typedef below to suppress compiler warning */
+ struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */
+ struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
+ struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */
+
+ struct tree_desc_s l_desc; /* desc. for literal tree */
+ struct tree_desc_s d_desc; /* desc. for distance tree */
+ struct tree_desc_s bl_desc; /* desc. for bit length tree */
+
+ ush bl_count[MAX_BITS+1];
+ /* number of codes at each bit length for an optimal tree */
+
+ int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */
+ int heap_len; /* number of elements in the heap */
+ int heap_max; /* element of largest frequency */
+ /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+ * The same heap array is used to build all trees.
+ */
+
+ uch depth[2*L_CODES+1];
+ /* Depth of each subtree used as tie breaker for trees of equal frequency
+ */
+
+ uchf *l_buf; /* buffer for literals or lengths */
+
+ uInt lit_bufsize;
+ /* Size of match buffer for literals/lengths. There are 4 reasons for
+ * limiting lit_bufsize to 64K:
+ * - frequencies can be kept in 16 bit counters
+ * - if compression is not successful for the first block, all input
+ * data is still in the window so we can still emit a stored block even
+ * when input comes from standard input. (This can also be done for
+ * all blocks if lit_bufsize is not greater than 32K.)
+ * - if compression is not successful for a file smaller than 64K, we can
+ * even emit a stored file instead of a stored block (saving 5 bytes).
+ * This is applicable only for zip (not gzip or zlib).
+ * - creating new Huffman trees less frequently may not provide fast
+ * adaptation to changes in the input data statistics. (Take for
+ * example a binary file with poorly compressible code followed by
+ * a highly compressible string table.) Smaller buffer sizes give
+ * fast adaptation but have of course the overhead of transmitting
+ * trees more frequently.
+ * - I can't count above 4
+ */
+
+ uInt last_lit; /* running index in l_buf */
+
+ ushf *d_buf;
+ /* Buffer for distances. To simplify the code, d_buf and l_buf have
+ * the same number of elements. To use different lengths, an extra flag
+ * array would be necessary.
+ */
+
+ ulg opt_len; /* bit length of current block with optimal trees */
+ ulg static_len; /* bit length of current block with static trees */
+ uInt matches; /* number of string matches in current block */
+ uInt insert; /* bytes at end of window left to insert */
+
+#ifdef ZLIB_DEBUG
+ ulg compressed_len; /* total bit length of compressed file mod 2^32 */
+ ulg bits_sent; /* bit length of compressed data sent mod 2^32 */
+#endif
+
+ ush bi_buf;
+ /* Output buffer. bits are inserted starting at the bottom (least
+ * significant bits).
+ */
+ int bi_valid;
+ /* Number of valid bits in bi_buf. All bits above the last valid bit
+ * are always zero.
+ */
+
+ ulg high_water;
+ /* High water mark offset in window for initialized bytes -- bytes above
+ * this are set to zero in order to avoid memory check warnings when
+ * longest match routines access bytes past the input. This is then
+ * updated to the new high water mark.
+ */
+
+} FAR deflate_state;
+
+/* Output a byte on the stream.
+ * IN assertion: there is enough room in pending_buf.
+ */
+#define put_byte(s, c) {s->pending_buf[s->pending++] = (Bytef)(c);}
+
+
+#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1)
+/* Minimum amount of lookahead, except at the end of the input file.
+ * See deflate.c for comments about the MIN_MATCH+1.
+ */
+
+#define MAX_DIST(s) ((s)->w_size-MIN_LOOKAHEAD)
+/* In order to simplify the code, particularly on 16 bit machines, match
+ * distances are limited to MAX_DIST instead of WSIZE.
+ */
+
+#define WIN_INIT MAX_MATCH
+/* Number of bytes after end of data in window to initialize in order to avoid
+ memory checker errors from longest match routines */
+
+ /* in trees.c */
+void ZLIB_INTERNAL _tr_init OF((deflate_state *s));
+int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc));
+void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf,
+ ulg stored_len, int last));
+void ZLIB_INTERNAL _tr_flush_bits OF((deflate_state *s));
+void ZLIB_INTERNAL _tr_align OF((deflate_state *s));
+void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf,
+ ulg stored_len, int last));
+
+#define d_code(dist) \
+ ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)])
+/* Mapping from a distance to a distance code. dist is the distance - 1 and
+ * must not have side effects. _dist_code[256] and _dist_code[257] are never
+ * used.
+ */
+
+#ifndef ZLIB_DEBUG
+/* Inline versions of _tr_tally for speed: */
+
+#if defined(GEN_TREES_H) || !defined(STDC)
+ extern uch ZLIB_INTERNAL _length_code[];
+ extern uch ZLIB_INTERNAL _dist_code[];
+#else
+ extern const uch ZLIB_INTERNAL _length_code[];
+ extern const uch ZLIB_INTERNAL _dist_code[];
+#endif
+
+# define _tr_tally_lit(s, c, flush) \
+ { uch cc = (c); \
+ s->d_buf[s->last_lit] = 0; \
+ s->l_buf[s->last_lit++] = cc; \
+ s->dyn_ltree[cc].Freq++; \
+ flush = (s->last_lit == s->lit_bufsize-1); \
+ }
+# define _tr_tally_dist(s, distance, length, flush) \
+ { uch len = (uch)(length); \
+ ush dist = (ush)(distance); \
+ s->d_buf[s->last_lit] = dist; \
+ s->l_buf[s->last_lit++] = len; \
+ dist--; \
+ s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \
+ s->dyn_dtree[d_code(dist)].Freq++; \
+ flush = (s->last_lit == s->lit_bufsize-1); \
+ }
+#else
+# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c)
+# define _tr_tally_dist(s, distance, length, flush) \
+ flush = _tr_tally(s, distance, length)
+#endif
+
+#endif /* DEFLATE_H */
diff --git a/lib/zlib/gzclose.c b/lib/zlib/gzclose.c
new file mode 100644
index 0000000..caeb99a
--- /dev/null
+++ b/lib/zlib/gzclose.c
@@ -0,0 +1,25 @@
+/* gzclose.c -- zlib gzclose() function
+ * Copyright (C) 2004, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+/* gzclose() is in a separate file so that it is linked in only if it is used.
+ That way the other gzclose functions can be used instead to avoid linking in
+ unneeded compression or decompression routines. */
+int ZEXPORT gzclose(file)
+ gzFile file;
+{
+#ifndef NO_GZCOMPRESS
+ gz_statep state;
+
+ if (file == NULL)
+ return Z_STREAM_ERROR;
+ state = (gz_statep)file;
+
+ return state->mode == GZ_READ ? gzclose_r(file) : gzclose_w(file);
+#else
+ return gzclose_r(file);
+#endif
+}
diff --git a/lib/zlib/gzguts.h b/lib/zlib/gzguts.h
new file mode 100644
index 0000000..85de933
--- /dev/null
+++ b/lib/zlib/gzguts.h
@@ -0,0 +1,218 @@
+/* gzguts.h -- zlib internal header definitions for gz* operations
+ * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013, 2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#ifdef _LARGEFILE64_SOURCE
+# ifndef _LARGEFILE_SOURCE
+# define _LARGEFILE_SOURCE 1
+# endif
+# ifdef _FILE_OFFSET_BITS
+# undef _FILE_OFFSET_BITS
+# endif
+#endif
+
+#ifdef HAVE_HIDDEN
+# define ZLIB_INTERNAL __attribute__((visibility ("hidden")))
+#else
+# define ZLIB_INTERNAL
+#endif
+
+#include "zlib.h"
+#include <stdio.h>
+#ifdef STDC
+# include <string.h>
+# include <stdlib.h>
+# include <limits.h>
+#endif
+
+#ifndef _POSIX_SOURCE
+# define _POSIX_SOURCE
+#endif
+#include <fcntl.h>
+
+#ifdef _WIN32
+# include <stddef.h>
+#endif
+
+#if defined(__TURBOC__) || defined(_MSC_VER) || defined(_WIN32)
+# include <io.h>
+#endif
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+# define WIDECHAR
+#endif
+
+#ifdef WINAPI_FAMILY
+# define open _open
+# define read _read
+# define write _write
+# define close _close
+#endif
+
+#ifdef NO_DEFLATE /* for compatibility with old definition */
+# define NO_GZCOMPRESS
+#endif
+
+#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550)
+# ifndef HAVE_VSNPRINTF
+# define HAVE_VSNPRINTF
+# endif
+#endif
+
+#if defined(__CYGWIN__)
+# ifndef HAVE_VSNPRINTF
+# define HAVE_VSNPRINTF
+# endif
+#endif
+
+#if defined(MSDOS) && defined(__BORLANDC__) && (BORLANDC > 0x410)
+# ifndef HAVE_VSNPRINTF
+# define HAVE_VSNPRINTF
+# endif
+#endif
+
+#ifndef HAVE_VSNPRINTF
+# ifdef MSDOS
+/* vsnprintf may exist on some MS-DOS compilers (DJGPP?),
+ but for now we just assume it doesn't. */
+# define NO_vsnprintf
+# endif
+# ifdef __TURBOC__
+# define NO_vsnprintf
+# endif
+# ifdef WIN32
+/* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */
+# if !defined(vsnprintf) && !defined(NO_vsnprintf)
+# if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 )
+# define vsnprintf _vsnprintf
+# endif
+# endif
+# endif
+# ifdef __SASC
+# define NO_vsnprintf
+# endif
+# ifdef VMS
+# define NO_vsnprintf
+# endif
+# ifdef __OS400__
+# define NO_vsnprintf
+# endif
+# ifdef __MVS__
+# define NO_vsnprintf
+# endif
+#endif
+
+/* unlike snprintf (which is required in C99), _snprintf does not guarantee
+ null termination of the result -- however this is only used in gzlib.c where
+ the result is assured to fit in the space provided */
+#if defined(_MSC_VER) && _MSC_VER < 1900
+# define snprintf _snprintf
+#endif
+
+#ifndef local
+# define local static
+#endif
+/* since "static" is used to mean two completely different things in C, we
+ define "local" for the non-static meaning of "static", for readability
+ (compile with -Dlocal if your debugger can't find static symbols) */
+
+/* gz* functions always use library allocation functions */
+#ifndef STDC
+ extern voidp malloc OF((uInt size));
+ extern void free OF((voidpf ptr));
+#endif
+
+/* get errno and strerror definition */
+#if defined UNDER_CE
+# include <windows.h>
+# define zstrerror() gz_strwinerror((DWORD)GetLastError())
+#else
+# ifndef NO_STRERROR
+# include <errno.h>
+# define zstrerror() strerror(errno)
+# else
+# define zstrerror() "stdio error (consult errno)"
+# endif
+#endif
+
+/* provide prototypes for these when building zlib without LFS */
+#if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0
+ ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
+ ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int));
+ ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile));
+ ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile));
+#endif
+
+/* default memLevel */
+#if MAX_MEM_LEVEL >= 8
+# define DEF_MEM_LEVEL 8
+#else
+# define DEF_MEM_LEVEL MAX_MEM_LEVEL
+#endif
+
+/* default i/o buffer size -- double this for output when reading (this and
+ twice this must be able to fit in an unsigned type) */
+#define GZBUFSIZE 8192
+
+/* gzip modes, also provide a little integrity check on the passed structure */
+#define GZ_NONE 0
+#define GZ_READ 7247
+#define GZ_WRITE 31153
+#define GZ_APPEND 1 /* mode set to GZ_WRITE after the file is opened */
+
+/* values for gz_state how */
+#define LOOK 0 /* look for a gzip header */
+#define COPY 1 /* copy input directly */
+#define GZIP 2 /* decompress a gzip stream */
+
+/* internal gzip file state data structure */
+typedef struct {
+ /* exposed contents for gzgetc() macro */
+ struct gzFile_s x; /* "x" for exposed */
+ /* x.have: number of bytes available at x.next */
+ /* x.next: next output data to deliver or write */
+ /* x.pos: current position in uncompressed data */
+ /* used for both reading and writing */
+ int mode; /* see gzip modes above */
+ int fd; /* file descriptor */
+ char *path; /* path or fd for error messages */
+ unsigned size; /* buffer size, zero if not allocated yet */
+ unsigned want; /* requested buffer size, default is GZBUFSIZE */
+ unsigned char *in; /* input buffer (double-sized when writing) */
+ unsigned char *out; /* output buffer (double-sized when reading) */
+ int direct; /* 0 if processing gzip, 1 if transparent */
+ /* just for reading */
+ int how; /* 0: get header, 1: copy, 2: decompress */
+ z_off64_t start; /* where the gzip data started, for rewinding */
+ int eof; /* true if end of input file reached */
+ int past; /* true if read requested past end */
+ /* just for writing */
+ int level; /* compression level */
+ int strategy; /* compression strategy */
+ /* seek request */
+ z_off64_t skip; /* amount to skip (already rewound if backwards) */
+ int seek; /* true if seek request pending */
+ /* error information */
+ int err; /* error code */
+ char *msg; /* error message */
+ /* zlib inflate or deflate stream */
+ z_stream strm; /* stream structure in-place (not a pointer) */
+} gz_state;
+typedef gz_state FAR *gz_statep;
+
+/* shared functions */
+void ZLIB_INTERNAL gz_error OF((gz_statep, int, const char *));
+#if defined UNDER_CE
+char ZLIB_INTERNAL *gz_strwinerror OF((DWORD error));
+#endif
+
+/* GT_OFF(x), where x is an unsigned value, is true if x > maximum z_off64_t
+ value -- needed when comparing unsigned to z_off64_t, which is signed
+ (possible z_off64_t types off_t, off64_t, and long are all signed) */
+#ifdef INT_MAX
+# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > INT_MAX)
+#else
+unsigned ZLIB_INTERNAL gz_intmax OF((void));
+# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax())
+#endif
diff --git a/lib/zlib/gzlib.c b/lib/zlib/gzlib.c
new file mode 100644
index 0000000..4105e6a
--- /dev/null
+++ b/lib/zlib/gzlib.c
@@ -0,0 +1,637 @@
+/* gzlib.c -- zlib functions common to reading and writing gzip files
+ * Copyright (C) 2004-2017 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+#if defined(_WIN32) && !defined(__BORLANDC__) && !defined(__MINGW32__)
+# define LSEEK _lseeki64
+#else
+#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0
+# define LSEEK lseek64
+#else
+# define LSEEK lseek
+#endif
+#endif
+
+/* Local functions */
+local void gz_reset OF((gz_statep));
+local gzFile gz_open OF((const void *, int, const char *));
+
+#if defined UNDER_CE
+
+/* Map the Windows error number in ERROR to a locale-dependent error message
+ string and return a pointer to it. Typically, the values for ERROR come
+ from GetLastError.
+
+ The string pointed to shall not be modified by the application, but may be
+ overwritten by a subsequent call to gz_strwinerror
+
+ The gz_strwinerror function does not change the current setting of
+ GetLastError. */
+char ZLIB_INTERNAL *gz_strwinerror (error)
+ DWORD error;
+{
+ static char buf[1024];
+
+ wchar_t *msgbuf;
+ DWORD lasterr = GetLastError();
+ DWORD chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
+ | FORMAT_MESSAGE_ALLOCATE_BUFFER,
+ NULL,
+ error,
+ 0, /* Default language */
+ (LPVOID)&msgbuf,
+ 0,
+ NULL);
+ if (chars != 0) {
+ /* If there is an \r\n appended, zap it. */
+ if (chars >= 2
+ && msgbuf[chars - 2] == '\r' && msgbuf[chars - 1] == '\n') {
+ chars -= 2;
+ msgbuf[chars] = 0;
+ }
+
+ if (chars > sizeof (buf) - 1) {
+ chars = sizeof (buf) - 1;
+ msgbuf[chars] = 0;
+ }
+
+ wcstombs(buf, msgbuf, chars + 1);
+ LocalFree(msgbuf);
+ }
+ else {
+ sprintf(buf, "unknown win32 error (%ld)", error);
+ }
+
+ SetLastError(lasterr);
+ return buf;
+}
+
+#endif /* UNDER_CE */
+
+/* Reset gzip file state */
+local void gz_reset(state)
+ gz_statep state;
+{
+ state->x.have = 0; /* no output data available */
+ if (state->mode == GZ_READ) { /* for reading ... */
+ state->eof = 0; /* not at end of file */
+ state->past = 0; /* have not read past end yet */
+ state->how = LOOK; /* look for gzip header */
+ }
+ state->seek = 0; /* no seek request pending */
+ gz_error(state, Z_OK, NULL); /* clear error */
+ state->x.pos = 0; /* no uncompressed data yet */
+ state->strm.avail_in = 0; /* no input data yet */
+}
+
+/* Open a gzip file either by name or file descriptor. */
+local gzFile gz_open(path, fd, mode)
+ const void *path;
+ int fd;
+ const char *mode;
+{
+ gz_statep state;
+ z_size_t len;
+ int oflag;
+#ifdef O_CLOEXEC
+ int cloexec = 0;
+#endif
+#ifdef O_EXCL
+ int exclusive = 0;
+#endif
+
+ /* check input */
+ if (path == NULL)
+ return NULL;
+
+ /* allocate gzFile structure to return */
+ state = (gz_statep)malloc(sizeof(gz_state));
+ if (state == NULL)
+ return NULL;
+ state->size = 0; /* no buffers allocated yet */
+ state->want = GZBUFSIZE; /* requested buffer size */
+ state->msg = NULL; /* no error message yet */
+
+ /* interpret mode */
+ state->mode = GZ_NONE;
+ state->level = Z_DEFAULT_COMPRESSION;
+ state->strategy = Z_DEFAULT_STRATEGY;
+ state->direct = 0;
+ while (*mode) {
+ if (*mode >= '0' && *mode <= '9')
+ state->level = *mode - '0';
+ else
+ switch (*mode) {
+ case 'r':
+ state->mode = GZ_READ;
+ break;
+#ifndef NO_GZCOMPRESS
+ case 'w':
+ state->mode = GZ_WRITE;
+ break;
+ case 'a':
+ state->mode = GZ_APPEND;
+ break;
+#endif
+ case '+': /* can't read and write at the same time */
+ free(state);
+ return NULL;
+ case 'b': /* ignore -- will request binary anyway */
+ break;
+#ifdef O_CLOEXEC
+ case 'e':
+ cloexec = 1;
+ break;
+#endif
+#ifdef O_EXCL
+ case 'x':
+ exclusive = 1;
+ break;
+#endif
+ case 'f':
+ state->strategy = Z_FILTERED;
+ break;
+ case 'h':
+ state->strategy = Z_HUFFMAN_ONLY;
+ break;
+ case 'R':
+ state->strategy = Z_RLE;
+ break;
+ case 'F':
+ state->strategy = Z_FIXED;
+ break;
+ case 'T':
+ state->direct = 1;
+ break;
+ default: /* could consider as an error, but just ignore */
+ ;
+ }
+ mode++;
+ }
+
+ /* must provide an "r", "w", or "a" */
+ if (state->mode == GZ_NONE) {
+ free(state);
+ return NULL;
+ }
+
+ /* can't force transparent read */
+ if (state->mode == GZ_READ) {
+ if (state->direct) {
+ free(state);
+ return NULL;
+ }
+ state->direct = 1; /* for empty file */
+ }
+
+ /* save the path name for error messages */
+#ifdef WIDECHAR
+ if (fd == -2) {
+ len = wcstombs(NULL, path, 0);
+ if (len == (z_size_t)-1)
+ len = 0;
+ }
+ else
+#endif
+ len = strlen((const char *)path);
+ state->path = (char *)malloc(len + 1);
+ if (state->path == NULL) {
+ free(state);
+ return NULL;
+ }
+#ifdef WIDECHAR
+ if (fd == -2)
+ if (len)
+ wcstombs(state->path, path, len + 1);
+ else
+ *(state->path) = 0;
+ else
+#endif
+#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
+ (void)snprintf(state->path, len + 1, "%s", (const char *)path);
+#else
+ strcpy(state->path, path);
+#endif
+
+ /* compute the flags for open() */
+ oflag =
+#ifdef O_LARGEFILE
+ O_LARGEFILE |
+#endif
+#ifdef O_BINARY
+ O_BINARY |
+#endif
+#ifdef O_CLOEXEC
+ (cloexec ? O_CLOEXEC : 0) |
+#endif
+ (state->mode == GZ_READ ?
+ O_RDONLY :
+ (O_WRONLY | O_CREAT |
+#ifdef O_EXCL
+ (exclusive ? O_EXCL : 0) |
+#endif
+ (state->mode == GZ_WRITE ?
+ O_TRUNC :
+ O_APPEND)));
+
+ /* open the file with the appropriate flags (or just use fd) */
+ state->fd = fd > -1 ? fd : (
+#ifdef WIDECHAR
+ fd == -2 ? _wopen(path, oflag, 0666) :
+#endif
+ open((const char *)path, oflag, 0666));
+ if (state->fd == -1) {
+ free(state->path);
+ free(state);
+ return NULL;
+ }
+ if (state->mode == GZ_APPEND) {
+ LSEEK(state->fd, 0, SEEK_END); /* so gzoffset() is correct */
+ state->mode = GZ_WRITE; /* simplify later checks */
+ }
+
+ /* save the current position for rewinding (only if reading) */
+ if (state->mode == GZ_READ) {
+ state->start = LSEEK(state->fd, 0, SEEK_CUR);
+ if (state->start == -1) state->start = 0;
+ }
+
+ /* initialize stream */
+ gz_reset(state);
+
+ /* return stream */
+ return (gzFile)state;
+}
+
+/* -- see zlib.h -- */
+gzFile ZEXPORT gzopen(path, mode)
+ const char *path;
+ const char *mode;
+{
+ return gz_open(path, -1, mode);
+}
+
+/* -- see zlib.h -- */
+gzFile ZEXPORT gzopen64(path, mode)
+ const char *path;
+ const char *mode;
+{
+ return gz_open(path, -1, mode);
+}
+
+/* -- see zlib.h -- */
+gzFile ZEXPORT gzdopen(fd, mode)
+ int fd;
+ const char *mode;
+{
+ char *path; /* identifier for error messages */
+ gzFile gz;
+
+ if (fd == -1 || (path = (char *)malloc(7 + 3 * sizeof(int))) == NULL)
+ return NULL;
+#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
+ (void)snprintf(path, 7 + 3 * sizeof(int), "<fd:%d>", fd);
+#else
+ sprintf(path, "<fd:%d>", fd); /* for debugging */
+#endif
+ gz = gz_open(path, fd, mode);
+ free(path);
+ return gz;
+}
+
+/* -- see zlib.h -- */
+#ifdef WIDECHAR
+gzFile ZEXPORT gzopen_w(path, mode)
+ const wchar_t *path;
+ const char *mode;
+{
+ return gz_open(path, -2, mode);
+}
+#endif
+
+/* -- see zlib.h -- */
+int ZEXPORT gzbuffer(file, size)
+ gzFile file;
+ unsigned size;
+{
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return -1;
+
+ /* make sure we haven't already allocated memory */
+ if (state->size != 0)
+ return -1;
+
+ /* check and set requested size */
+ if ((size << 1) < size)
+ return -1; /* need to be able to double it */
+ if (size < 2)
+ size = 2; /* need two bytes to check magic header */
+ state->want = size;
+ return 0;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzrewind(file)
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+
+ /* check that we're reading and that there's no error */
+ if (state->mode != GZ_READ ||
+ (state->err != Z_OK && state->err != Z_BUF_ERROR))
+ return -1;
+
+ /* back up and start over */
+ if (LSEEK(state->fd, state->start, SEEK_SET) == -1)
+ return -1;
+ gz_reset(state);
+ return 0;
+}
+
+/* -- see zlib.h -- */
+z_off64_t ZEXPORT gzseek64(file, offset, whence)
+ gzFile file;
+ z_off64_t offset;
+ int whence;
+{
+ unsigned n;
+ z_off64_t ret;
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return -1;
+
+ /* check that there's no error */
+ if (state->err != Z_OK && state->err != Z_BUF_ERROR)
+ return -1;
+
+ /* can only seek from start or relative to current position */
+ if (whence != SEEK_SET && whence != SEEK_CUR)
+ return -1;
+
+ /* normalize offset to a SEEK_CUR specification */
+ if (whence == SEEK_SET)
+ offset -= state->x.pos;
+ else if (state->seek)
+ offset += state->skip;
+ state->seek = 0;
+
+ /* if within raw area while reading, just go there */
+ if (state->mode == GZ_READ && state->how == COPY &&
+ state->x.pos + offset >= 0) {
+ ret = LSEEK(state->fd, offset - state->x.have, SEEK_CUR);
+ if (ret == -1)
+ return -1;
+ state->x.have = 0;
+ state->eof = 0;
+ state->past = 0;
+ state->seek = 0;
+ gz_error(state, Z_OK, NULL);
+ state->strm.avail_in = 0;
+ state->x.pos += offset;
+ return state->x.pos;
+ }
+
+ /* calculate skip amount, rewinding if needed for back seek when reading */
+ if (offset < 0) {
+ if (state->mode != GZ_READ) /* writing -- can't go backwards */
+ return -1;
+ offset += state->x.pos;
+ if (offset < 0) /* before start of file! */
+ return -1;
+ if (gzrewind(file) == -1) /* rewind, then skip to offset */
+ return -1;
+ }
+
+ /* if reading, skip what's in output buffer (one less gzgetc() check) */
+ if (state->mode == GZ_READ) {
+ n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > offset ?
+ (unsigned)offset : state->x.have;
+ state->x.have -= n;
+ state->x.next += n;
+ state->x.pos += n;
+ offset -= n;
+ }
+
+ /* request skip (if not zero) */
+ if (offset) {
+ state->seek = 1;
+ state->skip = offset;
+ }
+ return state->x.pos + offset;
+}
+
+/* -- see zlib.h -- */
+z_off_t ZEXPORT gzseek(file, offset, whence)
+ gzFile file;
+ z_off_t offset;
+ int whence;
+{
+ z_off64_t ret;
+
+ ret = gzseek64(file, (z_off64_t)offset, whence);
+ return ret == (z_off_t)ret ? (z_off_t)ret : -1;
+}
+
+/* -- see zlib.h -- */
+z_off64_t ZEXPORT gztell64(file)
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return -1;
+
+ /* return position */
+ return state->x.pos + (state->seek ? state->skip : 0);
+}
+
+/* -- see zlib.h -- */
+z_off_t ZEXPORT gztell(file)
+ gzFile file;
+{
+ z_off64_t ret;
+
+ ret = gztell64(file);
+ return ret == (z_off_t)ret ? (z_off_t)ret : -1;
+}
+
+/* -- see zlib.h -- */
+z_off64_t ZEXPORT gzoffset64(file)
+ gzFile file;
+{
+ z_off64_t offset;
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return -1;
+
+ /* compute and return effective offset in file */
+ offset = LSEEK(state->fd, 0, SEEK_CUR);
+ if (offset == -1)
+ return -1;
+ if (state->mode == GZ_READ) /* reading */
+ offset -= state->strm.avail_in; /* don't count buffered input */
+ return offset;
+}
+
+/* -- see zlib.h -- */
+z_off_t ZEXPORT gzoffset(file)
+ gzFile file;
+{
+ z_off64_t ret;
+
+ ret = gzoffset64(file);
+ return ret == (z_off_t)ret ? (z_off_t)ret : -1;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzeof(file)
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return 0;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return 0;
+
+ /* return end-of-file state */
+ return state->mode == GZ_READ ? state->past : 0;
+}
+
+/* -- see zlib.h -- */
+const char * ZEXPORT gzerror(file, errnum)
+ gzFile file;
+ int *errnum;
+{
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return NULL;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return NULL;
+
+ /* return error information */
+ if (errnum != NULL)
+ *errnum = state->err;
+ return state->err == Z_MEM_ERROR ? "out of memory" :
+ (state->msg == NULL ? "" : state->msg);
+}
+
+/* -- see zlib.h -- */
+void ZEXPORT gzclearerr(file)
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return;
+
+ /* clear error and end-of-file */
+ if (state->mode == GZ_READ) {
+ state->eof = 0;
+ state->past = 0;
+ }
+ gz_error(state, Z_OK, NULL);
+}
+
+/* Create an error message in allocated memory and set state->err and
+ state->msg accordingly. Free any previous error message already there. Do
+ not try to free or allocate space if the error is Z_MEM_ERROR (out of
+ memory). Simply save the error message as a static string. If there is an
+ allocation failure constructing the error message, then convert the error to
+ out of memory. */
+void ZLIB_INTERNAL gz_error(state, err, msg)
+ gz_statep state;
+ int err;
+ const char *msg;
+{
+ /* free previously allocated message and clear */
+ if (state->msg != NULL) {
+ if (state->err != Z_MEM_ERROR)
+ free(state->msg);
+ state->msg = NULL;
+ }
+
+ /* if fatal, set state->x.have to 0 so that the gzgetc() macro fails */
+ if (err != Z_OK && err != Z_BUF_ERROR)
+ state->x.have = 0;
+
+ /* set error code, and if no message, then done */
+ state->err = err;
+ if (msg == NULL)
+ return;
+
+ /* for an out of memory error, return literal string when requested */
+ if (err == Z_MEM_ERROR)
+ return;
+
+ /* construct error message with path */
+ if ((state->msg = (char *)malloc(strlen(state->path) + strlen(msg) + 3)) ==
+ NULL) {
+ state->err = Z_MEM_ERROR;
+ return;
+ }
+#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
+ (void)snprintf(state->msg, strlen(state->path) + strlen(msg) + 3,
+ "%s%s%s", state->path, ": ", msg);
+#else
+ strcpy(state->msg, state->path);
+ strcat(state->msg, ": ");
+ strcat(state->msg, msg);
+#endif
+}
+
+#ifndef INT_MAX
+/* portably return maximum value for an int (when limits.h presumed not
+ available) -- we need to do this to cover cases where 2's complement not
+ used, since C standard permits 1's complement and sign-bit representations,
+ otherwise we could just use ((unsigned)-1) >> 1 */
+unsigned ZLIB_INTERNAL gz_intmax()
+{
+ unsigned p, q;
+
+ p = 1;
+ do {
+ q = p;
+ p <<= 1;
+ p++;
+ } while (p > q);
+ return q >> 1;
+}
+#endif
diff --git a/lib/zlib/gzread.c b/lib/zlib/gzread.c
new file mode 100644
index 0000000..956b91e
--- /dev/null
+++ b/lib/zlib/gzread.c
@@ -0,0 +1,654 @@
+/* gzread.c -- zlib functions for reading gzip files
+ * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013, 2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+/* Local functions */
+local int gz_load OF((gz_statep, unsigned char *, unsigned, unsigned *));
+local int gz_avail OF((gz_statep));
+local int gz_look OF((gz_statep));
+local int gz_decomp OF((gz_statep));
+local int gz_fetch OF((gz_statep));
+local int gz_skip OF((gz_statep, z_off64_t));
+local z_size_t gz_read OF((gz_statep, voidp, z_size_t));
+
+/* Use read() to load a buffer -- return -1 on error, otherwise 0. Read from
+ state->fd, and update state->eof, state->err, and state->msg as appropriate.
+ This function needs to loop on read(), since read() is not guaranteed to
+ read the number of bytes requested, depending on the type of descriptor. */
+local int gz_load(state, buf, len, have)
+ gz_statep state;
+ unsigned char *buf;
+ unsigned len;
+ unsigned *have;
+{
+ int ret;
+ unsigned get, max = ((unsigned)-1 >> 2) + 1;
+
+ *have = 0;
+ do {
+ get = len - *have;
+ if (get > max)
+ get = max;
+ ret = read(state->fd, buf + *have, get);
+ if (ret <= 0)
+ break;
+ *have += (unsigned)ret;
+ } while (*have < len);
+ if (ret < 0) {
+ gz_error(state, Z_ERRNO, zstrerror());
+ return -1;
+ }
+ if (ret == 0)
+ state->eof = 1;
+ return 0;
+}
+
+/* Load up input buffer and set eof flag if last data loaded -- return -1 on
+ error, 0 otherwise. Note that the eof flag is set when the end of the input
+ file is reached, even though there may be unused data in the buffer. Once
+ that data has been used, no more attempts will be made to read the file.
+ If strm->avail_in != 0, then the current data is moved to the beginning of
+ the input buffer, and then the remainder of the buffer is loaded with the
+ available data from the input file. */
+local int gz_avail(state)
+ gz_statep state;
+{
+ unsigned got;
+ z_streamp strm = &(state->strm);
+
+ if (state->err != Z_OK && state->err != Z_BUF_ERROR)
+ return -1;
+ if (state->eof == 0) {
+ if (strm->avail_in) { /* copy what's there to the start */
+ unsigned char *p = state->in;
+ unsigned const char *q = strm->next_in;
+ unsigned n = strm->avail_in;
+ do {
+ *p++ = *q++;
+ } while (--n);
+ }
+ if (gz_load(state, state->in + strm->avail_in,
+ state->size - strm->avail_in, &got) == -1)
+ return -1;
+ strm->avail_in += got;
+ strm->next_in = state->in;
+ }
+ return 0;
+}
+
+/* Look for gzip header, set up for inflate or copy. state->x.have must be 0.
+ If this is the first time in, allocate required memory. state->how will be
+ left unchanged if there is no more input data available, will be set to COPY
+ if there is no gzip header and direct copying will be performed, or it will
+ be set to GZIP for decompression. If direct copying, then leftover input
+ data from the input buffer will be copied to the output buffer. In that
+ case, all further file reads will be directly to either the output buffer or
+ a user buffer. If decompressing, the inflate state will be initialized.
+ gz_look() will return 0 on success or -1 on failure. */
+local int gz_look(state)
+ gz_statep state;
+{
+ z_streamp strm = &(state->strm);
+
+ /* allocate read buffers and inflate memory */
+ if (state->size == 0) {
+ /* allocate buffers */
+ state->in = (unsigned char *)malloc(state->want);
+ state->out = (unsigned char *)malloc(state->want << 1);
+ if (state->in == NULL || state->out == NULL) {
+ free(state->out);
+ free(state->in);
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+ state->size = state->want;
+
+ /* allocate inflate memory */
+ state->strm.zalloc = Z_NULL;
+ state->strm.zfree = Z_NULL;
+ state->strm.opaque = Z_NULL;
+ state->strm.avail_in = 0;
+ state->strm.next_in = Z_NULL;
+ if (inflateInit2(&(state->strm), 15 + 16) != Z_OK) { /* gunzip */
+ free(state->out);
+ free(state->in);
+ state->size = 0;
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+ }
+
+ /* get at least the magic bytes in the input buffer */
+ if (strm->avail_in < 2) {
+ if (gz_avail(state) == -1)
+ return -1;
+ if (strm->avail_in == 0)
+ return 0;
+ }
+
+ /* look for gzip magic bytes -- if there, do gzip decoding (note: there is
+ a logical dilemma here when considering the case of a partially written
+ gzip file, to wit, if a single 31 byte is written, then we cannot tell
+ whether this is a single-byte file, or just a partially written gzip
+ file -- for here we assume that if a gzip file is being written, then
+ the header will be written in a single operation, so that reading a
+ single byte is sufficient indication that it is not a gzip file) */
+ if (strm->avail_in > 1 &&
+ strm->next_in[0] == 31 && strm->next_in[1] == 139) {
+ inflateReset(strm);
+ state->how = GZIP;
+ state->direct = 0;
+ return 0;
+ }
+
+ /* no gzip header -- if we were decoding gzip before, then this is trailing
+ garbage. Ignore the trailing garbage and finish. */
+ if (state->direct == 0) {
+ strm->avail_in = 0;
+ state->eof = 1;
+ state->x.have = 0;
+ return 0;
+ }
+
+ /* doing raw i/o, copy any leftover input to output -- this assumes that
+ the output buffer is larger than the input buffer, which also assures
+ space for gzungetc() */
+ state->x.next = state->out;
+ if (strm->avail_in) {
+ memcpy(state->x.next, strm->next_in, strm->avail_in);
+ state->x.have = strm->avail_in;
+ strm->avail_in = 0;
+ }
+ state->how = COPY;
+ state->direct = 1;
+ return 0;
+}
+
+/* Decompress from input to the provided next_out and avail_out in the state.
+ On return, state->x.have and state->x.next point to the just decompressed
+ data. If the gzip stream completes, state->how is reset to LOOK to look for
+ the next gzip stream or raw data, once state->x.have is depleted. Returns 0
+ on success, -1 on failure. */
+local int gz_decomp(state)
+ gz_statep state;
+{
+ int ret = Z_OK;
+ unsigned had;
+ z_streamp strm = &(state->strm);
+
+ /* fill output buffer up to end of deflate stream */
+ had = strm->avail_out;
+ do {
+ /* get more input for inflate() */
+ if (strm->avail_in == 0 && gz_avail(state) == -1)
+ return -1;
+ if (strm->avail_in == 0) {
+ gz_error(state, Z_BUF_ERROR, "unexpected end of file");
+ break;
+ }
+
+ /* decompress and handle errors */
+ ret = inflate(strm, Z_NO_FLUSH);
+ if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT) {
+ gz_error(state, Z_STREAM_ERROR,
+ "internal error: inflate stream corrupt");
+ return -1;
+ }
+ if (ret == Z_MEM_ERROR) {
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+ if (ret == Z_DATA_ERROR) { /* deflate stream invalid */
+ gz_error(state, Z_DATA_ERROR,
+ strm->msg == NULL ? "compressed data error" : strm->msg);
+ return -1;
+ }
+ } while (strm->avail_out && ret != Z_STREAM_END);
+
+ /* update available output */
+ state->x.have = had - strm->avail_out;
+ state->x.next = strm->next_out - state->x.have;
+
+ /* if the gzip stream completed successfully, look for another */
+ if (ret == Z_STREAM_END)
+ state->how = LOOK;
+
+ /* good decompression */
+ return 0;
+}
+
+/* Fetch data and put it in the output buffer. Assumes state->x.have is 0.
+ Data is either copied from the input file or decompressed from the input
+ file depending on state->how. If state->how is LOOK, then a gzip header is
+ looked for to determine whether to copy or decompress. Returns -1 on error,
+ otherwise 0. gz_fetch() will leave state->how as COPY or GZIP unless the
+ end of the input file has been reached and all data has been processed. */
+local int gz_fetch(state)
+ gz_statep state;
+{
+ z_streamp strm = &(state->strm);
+
+ do {
+ switch(state->how) {
+ case LOOK: /* -> LOOK, COPY (only if never GZIP), or GZIP */
+ if (gz_look(state) == -1)
+ return -1;
+ if (state->how == LOOK)
+ return 0;
+ break;
+ case COPY: /* -> COPY */
+ if (gz_load(state, state->out, state->size << 1, &(state->x.have))
+ == -1)
+ return -1;
+ state->x.next = state->out;
+ return 0;
+ case GZIP: /* -> GZIP or LOOK (if end of gzip stream) */
+ strm->avail_out = state->size << 1;
+ strm->next_out = state->out;
+ if (gz_decomp(state) == -1)
+ return -1;
+ }
+ } while (state->x.have == 0 && (!state->eof || strm->avail_in));
+ return 0;
+}
+
+/* Skip len uncompressed bytes of output. Return -1 on error, 0 on success. */
+local int gz_skip(state, len)
+ gz_statep state;
+ z_off64_t len;
+{
+ unsigned n;
+
+ /* skip over len bytes or reach end-of-file, whichever comes first */
+ while (len)
+ /* skip over whatever is in output buffer */
+ if (state->x.have) {
+ n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > len ?
+ (unsigned)len : state->x.have;
+ state->x.have -= n;
+ state->x.next += n;
+ state->x.pos += n;
+ len -= n;
+ }
+
+ /* output buffer empty -- return if we're at the end of the input */
+ else if (state->eof && state->strm.avail_in == 0)
+ break;
+
+ /* need more data to skip -- load up output buffer */
+ else {
+ /* get more output, looking for header if required */
+ if (gz_fetch(state) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+/* Read len bytes into buf from file, or less than len up to the end of the
+ input. Return the number of bytes read. If zero is returned, either the
+ end of file was reached, or there was an error. state->err must be
+ consulted in that case to determine which. */
+local z_size_t gz_read(state, buf, len)
+ gz_statep state;
+ voidp buf;
+ z_size_t len;
+{
+ z_size_t got;
+ unsigned n;
+
+ /* if len is zero, avoid unnecessary operations */
+ if (len == 0)
+ return 0;
+
+ /* process a skip request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_skip(state, state->skip) == -1)
+ return 0;
+ }
+
+ /* get len bytes to buf, or less than len if at the end */
+ got = 0;
+ do {
+ /* set n to the maximum amount of len that fits in an unsigned int */
+ n = -1;
+ if (n > len)
+ n = len;
+
+ /* first just try copying data from the output buffer */
+ if (state->x.have) {
+ if (state->x.have < n)
+ n = state->x.have;
+ memcpy(buf, state->x.next, n);
+ state->x.next += n;
+ state->x.have -= n;
+ }
+
+ /* output buffer empty -- return if we're at the end of the input */
+ else if (state->eof && state->strm.avail_in == 0) {
+ state->past = 1; /* tried to read past end */
+ break;
+ }
+
+ /* need output data -- for small len or new stream load up our output
+ buffer */
+ else if (state->how == LOOK || n < (state->size << 1)) {
+ /* get more output, looking for header if required */
+ if (gz_fetch(state) == -1)
+ return 0;
+ continue; /* no progress yet -- go back to copy above */
+ /* the copy above assures that we will leave with space in the
+ output buffer, allowing at least one gzungetc() to succeed */
+ }
+
+ /* large len -- read directly into user buffer */
+ else if (state->how == COPY) { /* read directly */
+ if (gz_load(state, (unsigned char *)buf, n, &n) == -1)
+ return 0;
+ }
+
+ /* large len -- decompress directly into user buffer */
+ else { /* state->how == GZIP */
+ state->strm.avail_out = n;
+ state->strm.next_out = (unsigned char *)buf;
+ if (gz_decomp(state) == -1)
+ return 0;
+ n = state->x.have;
+ state->x.have = 0;
+ }
+
+ /* update progress */
+ len -= n;
+ buf = (char *)buf + n;
+ got += n;
+ state->x.pos += n;
+ } while (len);
+
+ /* return number of bytes read into user buffer */
+ return got;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzread(file, buf, len)
+ gzFile file;
+ voidp buf;
+ unsigned len;
+{
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+
+ /* check that we're reading and that there's no (serious) error */
+ if (state->mode != GZ_READ ||
+ (state->err != Z_OK && state->err != Z_BUF_ERROR))
+ return -1;
+
+ /* since an int is returned, make sure len fits in one, otherwise return
+ with an error (this avoids a flaw in the interface) */
+ if ((int)len < 0) {
+ gz_error(state, Z_STREAM_ERROR, "request does not fit in an int");
+ return -1;
+ }
+
+ /* read len or fewer bytes to buf */
+ len = gz_read(state, buf, len);
+
+ /* check for an error */
+ if (len == 0 && state->err != Z_OK && state->err != Z_BUF_ERROR)
+ return -1;
+
+ /* return the number of bytes read (this is assured to fit in an int) */
+ return (int)len;
+}
+
+/* -- see zlib.h -- */
+z_size_t ZEXPORT gzfread(buf, size, nitems, file)
+ voidp buf;
+ z_size_t size;
+ z_size_t nitems;
+ gzFile file;
+{
+ z_size_t len;
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return 0;
+ state = (gz_statep)file;
+
+ /* check that we're reading and that there's no (serious) error */
+ if (state->mode != GZ_READ ||
+ (state->err != Z_OK && state->err != Z_BUF_ERROR))
+ return 0;
+
+ /* compute bytes to read -- error on overflow */
+ len = nitems * size;
+ if (size && len / size != nitems) {
+ gz_error(state, Z_STREAM_ERROR, "request does not fit in a size_t");
+ return 0;
+ }
+
+ /* read len or fewer bytes to buf, return the number of full items read */
+ return len ? gz_read(state, buf, len) / size : 0;
+}
+
+/* -- see zlib.h -- */
+#ifdef Z_PREFIX_SET
+# undef z_gzgetc
+#else
+# undef gzgetc
+#endif
+int ZEXPORT gzgetc(file)
+ gzFile file;
+{
+ int ret;
+ unsigned char buf[1];
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+
+ /* check that we're reading and that there's no (serious) error */
+ if (state->mode != GZ_READ ||
+ (state->err != Z_OK && state->err != Z_BUF_ERROR))
+ return -1;
+
+ /* try output buffer (no need to check for skip request) */
+ if (state->x.have) {
+ state->x.have--;
+ state->x.pos++;
+ return *(state->x.next)++;
+ }
+
+ /* nothing there -- try gz_read() */
+ ret = gz_read(state, buf, 1);
+ return ret < 1 ? -1 : buf[0];
+}
+
+int ZEXPORT gzgetc_(file)
+gzFile file;
+{
+ return gzgetc(file);
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzungetc(c, file)
+ int c;
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+
+ /* check that we're reading and that there's no (serious) error */
+ if (state->mode != GZ_READ ||
+ (state->err != Z_OK && state->err != Z_BUF_ERROR))
+ return -1;
+
+ /* process a skip request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_skip(state, state->skip) == -1)
+ return -1;
+ }
+
+ /* can't push EOF */
+ if (c < 0)
+ return -1;
+
+ /* if output buffer empty, put byte at end (allows more pushing) */
+ if (state->x.have == 0) {
+ state->x.have = 1;
+ state->x.next = state->out + (state->size << 1) - 1;
+ state->x.next[0] = (unsigned char)c;
+ state->x.pos--;
+ state->past = 0;
+ return c;
+ }
+
+ /* if no room, give up (must have already done a gzungetc()) */
+ if (state->x.have == (state->size << 1)) {
+ gz_error(state, Z_DATA_ERROR, "out of room to push characters");
+ return -1;
+ }
+
+ /* slide output data if needed and insert byte before existing data */
+ if (state->x.next == state->out) {
+ unsigned char *src = state->out + state->x.have;
+ unsigned char *dest = state->out + (state->size << 1);
+ while (src > state->out)
+ *--dest = *--src;
+ state->x.next = dest;
+ }
+ state->x.have++;
+ state->x.next--;
+ state->x.next[0] = (unsigned char)c;
+ state->x.pos--;
+ state->past = 0;
+ return c;
+}
+
+/* -- see zlib.h -- */
+char * ZEXPORT gzgets(file, buf, len)
+ gzFile file;
+ char *buf;
+ int len;
+{
+ unsigned left, n;
+ char *str;
+ unsigned char *eol;
+ gz_statep state;
+
+ /* check parameters and get internal structure */
+ if (file == NULL || buf == NULL || len < 1)
+ return NULL;
+ state = (gz_statep)file;
+
+ /* check that we're reading and that there's no (serious) error */
+ if (state->mode != GZ_READ ||
+ (state->err != Z_OK && state->err != Z_BUF_ERROR))
+ return NULL;
+
+ /* process a skip request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_skip(state, state->skip) == -1)
+ return NULL;
+ }
+
+ /* copy output bytes up to new line or len - 1, whichever comes first --
+ append a terminating zero to the string (we don't check for a zero in
+ the contents, let the user worry about that) */
+ str = buf;
+ left = (unsigned)len - 1;
+ if (left) do {
+ /* assure that something is in the output buffer */
+ if (state->x.have == 0 && gz_fetch(state) == -1)
+ return NULL; /* error */
+ if (state->x.have == 0) { /* end of file */
+ state->past = 1; /* read past end */
+ break; /* return what we have */
+ }
+
+ /* look for end-of-line in current output buffer */
+ n = state->x.have > left ? left : state->x.have;
+ eol = (unsigned char *)memchr(state->x.next, '\n', n);
+ if (eol != NULL)
+ n = (unsigned)(eol - state->x.next) + 1;
+
+ /* copy through end-of-line, or remainder if not found */
+ memcpy(buf, state->x.next, n);
+ state->x.have -= n;
+ state->x.next += n;
+ state->x.pos += n;
+ left -= n;
+ buf += n;
+ } while (left && eol == NULL);
+
+ /* return terminated string, or if nothing, end of file */
+ if (buf == str)
+ return NULL;
+ buf[0] = 0;
+ return str;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzdirect(file)
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return 0;
+ state = (gz_statep)file;
+
+ /* if the state is not known, but we can find out, then do so (this is
+ mainly for right after a gzopen() or gzdopen()) */
+ if (state->mode == GZ_READ && state->how == LOOK && state->x.have == 0)
+ (void)gz_look(state);
+
+ /* return 1 if transparent, 0 if processing a gzip stream */
+ return state->direct;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzclose_r(file)
+ gzFile file;
+{
+ int ret, err;
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return Z_STREAM_ERROR;
+ state = (gz_statep)file;
+
+ /* check that we're reading */
+ if (state->mode != GZ_READ)
+ return Z_STREAM_ERROR;
+
+ /* free memory and close file */
+ if (state->size) {
+ inflateEnd(&(state->strm));
+ free(state->out);
+ free(state->in);
+ }
+ err = state->err == Z_BUF_ERROR ? Z_BUF_ERROR : Z_OK;
+ gz_error(state, Z_OK, NULL);
+ free(state->path);
+ ret = close(state->fd);
+ free(state);
+ return ret ? Z_ERRNO : err;
+}
diff --git a/lib/zlib/gzwrite.c b/lib/zlib/gzwrite.c
new file mode 100644
index 0000000..c7b5651
--- /dev/null
+++ b/lib/zlib/gzwrite.c
@@ -0,0 +1,665 @@
+/* gzwrite.c -- zlib functions for writing gzip files
+ * Copyright (C) 2004-2017 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+/* Local functions */
+local int gz_init OF((gz_statep));
+local int gz_comp OF((gz_statep, int));
+local int gz_zero OF((gz_statep, z_off64_t));
+local z_size_t gz_write OF((gz_statep, voidpc, z_size_t));
+
+/* Initialize state for writing a gzip file. Mark initialization by setting
+ state->size to non-zero. Return -1 on a memory allocation failure, or 0 on
+ success. */
+local int gz_init(state)
+ gz_statep state;
+{
+ int ret;
+ z_streamp strm = &(state->strm);
+
+ /* allocate input buffer (double size for gzprintf) */
+ state->in = (unsigned char *)malloc(state->want << 1);
+ if (state->in == NULL) {
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+
+ /* only need output buffer and deflate state if compressing */
+ if (!state->direct) {
+ /* allocate output buffer */
+ state->out = (unsigned char *)malloc(state->want);
+ if (state->out == NULL) {
+ free(state->in);
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+
+ /* allocate deflate memory, set up for gzip compression */
+ strm->zalloc = Z_NULL;
+ strm->zfree = Z_NULL;
+ strm->opaque = Z_NULL;
+ ret = deflateInit2(strm, state->level, Z_DEFLATED,
+ MAX_WBITS + 16, DEF_MEM_LEVEL, state->strategy);
+ if (ret != Z_OK) {
+ free(state->out);
+ free(state->in);
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+ strm->next_in = NULL;
+ }
+
+ /* mark state as initialized */
+ state->size = state->want;
+
+ /* initialize write buffer if compressing */
+ if (!state->direct) {
+ strm->avail_out = state->size;
+ strm->next_out = state->out;
+ state->x.next = strm->next_out;
+ }
+ return 0;
+}
+
+/* Compress whatever is at avail_in and next_in and write to the output file.
+ Return -1 if there is an error writing to the output file or if gz_init()
+ fails to allocate memory, otherwise 0. flush is assumed to be a valid
+ deflate() flush value. If flush is Z_FINISH, then the deflate() state is
+ reset to start a new gzip stream. If gz->direct is true, then simply write
+ to the output file without compressing, and ignore flush. */
+local int gz_comp(state, flush)
+ gz_statep state;
+ int flush;
+{
+ int ret, writ;
+ unsigned have, put, max = ((unsigned)-1 >> 2) + 1;
+ z_streamp strm = &(state->strm);
+
+ /* allocate memory if this is the first time through */
+ if (state->size == 0 && gz_init(state) == -1)
+ return -1;
+
+ /* write directly if requested */
+ if (state->direct) {
+ while (strm->avail_in) {
+ put = strm->avail_in > max ? max : strm->avail_in;
+ writ = write(state->fd, strm->next_in, put);
+ if (writ < 0) {
+ gz_error(state, Z_ERRNO, zstrerror());
+ return -1;
+ }
+ strm->avail_in -= (unsigned)writ;
+ strm->next_in += writ;
+ }
+ return 0;
+ }
+
+ /* run deflate() on provided input until it produces no more output */
+ ret = Z_OK;
+ do {
+ /* write out current buffer contents if full, or if flushing, but if
+ doing Z_FINISH then don't write until we get to Z_STREAM_END */
+ if (strm->avail_out == 0 || (flush != Z_NO_FLUSH &&
+ (flush != Z_FINISH || ret == Z_STREAM_END))) {
+ while (strm->next_out > state->x.next) {
+ put = strm->next_out - state->x.next > (int)max ? max :
+ (unsigned)(strm->next_out - state->x.next);
+ writ = write(state->fd, state->x.next, put);
+ if (writ < 0) {
+ gz_error(state, Z_ERRNO, zstrerror());
+ return -1;
+ }
+ state->x.next += writ;
+ }
+ if (strm->avail_out == 0) {
+ strm->avail_out = state->size;
+ strm->next_out = state->out;
+ state->x.next = state->out;
+ }
+ }
+
+ /* compress */
+ have = strm->avail_out;
+ ret = deflate(strm, flush);
+ if (ret == Z_STREAM_ERROR) {
+ gz_error(state, Z_STREAM_ERROR,
+ "internal error: deflate stream corrupt");
+ return -1;
+ }
+ have -= strm->avail_out;
+ } while (have);
+
+ /* if that completed a deflate stream, allow another to start */
+ if (flush == Z_FINISH)
+ deflateReset(strm);
+
+ /* all done, no errors */
+ return 0;
+}
+
+/* Compress len zeros to output. Return -1 on a write error or memory
+ allocation failure by gz_comp(), or 0 on success. */
+local int gz_zero(state, len)
+ gz_statep state;
+ z_off64_t len;
+{
+ int first;
+ unsigned n;
+ z_streamp strm = &(state->strm);
+
+ /* consume whatever's left in the input buffer */
+ if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
+ return -1;
+
+ /* compress len zeros (len guaranteed > 0) */
+ first = 1;
+ while (len) {
+ n = GT_OFF(state->size) || (z_off64_t)state->size > len ?
+ (unsigned)len : state->size;
+ if (first) {
+ memset(state->in, 0, n);
+ first = 0;
+ }
+ strm->avail_in = n;
+ strm->next_in = state->in;
+ state->x.pos += n;
+ if (gz_comp(state, Z_NO_FLUSH) == -1)
+ return -1;
+ len -= n;
+ }
+ return 0;
+}
+
+/* Write len bytes from buf to file. Return the number of bytes written. If
+ the returned value is less than len, then there was an error. */
+local z_size_t gz_write(state, buf, len)
+ gz_statep state;
+ voidpc buf;
+ z_size_t len;
+{
+ z_size_t put = len;
+
+ /* if len is zero, avoid unnecessary operations */
+ if (len == 0)
+ return 0;
+
+ /* allocate memory if this is the first time through */
+ if (state->size == 0 && gz_init(state) == -1)
+ return 0;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return 0;
+ }
+
+ /* for small len, copy to input buffer, otherwise compress directly */
+ if (len < state->size) {
+ /* copy to input buffer, compress when full */
+ do {
+ unsigned have, copy;
+
+ if (state->strm.avail_in == 0)
+ state->strm.next_in = state->in;
+ have = (unsigned)((state->strm.next_in + state->strm.avail_in) -
+ state->in);
+ copy = state->size - have;
+ if (copy > len)
+ copy = len;
+ memcpy(state->in + have, buf, copy);
+ state->strm.avail_in += copy;
+ state->x.pos += copy;
+ buf = (const char *)buf + copy;
+ len -= copy;
+ if (len && gz_comp(state, Z_NO_FLUSH) == -1)
+ return 0;
+ } while (len);
+ }
+ else {
+ /* consume whatever's left in the input buffer */
+ if (state->strm.avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
+ return 0;
+
+ /* directly compress user buffer to file */
+ state->strm.next_in = (z_const Bytef *)buf;
+ do {
+ unsigned n = (unsigned)-1;
+ if (n > len)
+ n = len;
+ state->strm.avail_in = n;
+ state->x.pos += n;
+ if (gz_comp(state, Z_NO_FLUSH) == -1)
+ return 0;
+ len -= n;
+ } while (len);
+ }
+
+ /* input was all buffered or compressed */
+ return put;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzwrite(file, buf, len)
+ gzFile file;
+ voidpc buf;
+ unsigned len;
+{
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return 0;
+ state = (gz_statep)file;
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return 0;
+
+ /* since an int is returned, make sure len fits in one, otherwise return
+ with an error (this avoids a flaw in the interface) */
+ if ((int)len < 0) {
+ gz_error(state, Z_DATA_ERROR, "requested length does not fit in int");
+ return 0;
+ }
+
+ /* write len bytes from buf (the return value will fit in an int) */
+ return (int)gz_write(state, buf, len);
+}
+
+/* -- see zlib.h -- */
+z_size_t ZEXPORT gzfwrite(buf, size, nitems, file)
+ voidpc buf;
+ z_size_t size;
+ z_size_t nitems;
+ gzFile file;
+{
+ z_size_t len;
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return 0;
+ state = (gz_statep)file;
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return 0;
+
+ /* compute bytes to read -- error on overflow */
+ len = nitems * size;
+ if (size && len / size != nitems) {
+ gz_error(state, Z_STREAM_ERROR, "request does not fit in a size_t");
+ return 0;
+ }
+
+ /* write len bytes to buf, return the number of full items written */
+ return len ? gz_write(state, buf, len) / size : 0;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzputc(file, c)
+ gzFile file;
+ int c;
+{
+ unsigned have;
+ unsigned char buf[1];
+ gz_statep state;
+ z_streamp strm;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ strm = &(state->strm);
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return -1;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return -1;
+ }
+
+ /* try writing to input buffer for speed (state->size == 0 if buffer not
+ initialized) */
+ if (state->size) {
+ if (strm->avail_in == 0)
+ strm->next_in = state->in;
+ have = (unsigned)((strm->next_in + strm->avail_in) - state->in);
+ if (have < state->size) {
+ state->in[have] = (unsigned char)c;
+ strm->avail_in++;
+ state->x.pos++;
+ return c & 0xff;
+ }
+ }
+
+ /* no room in buffer or not initialized, use gz_write() */
+ buf[0] = (unsigned char)c;
+ if (gz_write(state, buf, 1) != 1)
+ return -1;
+ return c & 0xff;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzputs(file, str)
+ gzFile file;
+ const char *str;
+{
+ int ret;
+ z_size_t len;
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return -1;
+
+ /* write string */
+ len = strlen(str);
+ ret = gz_write(state, str, len);
+ return ret == 0 && len != 0 ? -1 : ret;
+}
+
+#if defined(STDC) || defined(Z_HAVE_STDARG_H)
+#include <stdarg.h>
+
+/* -- see zlib.h -- */
+int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va)
+{
+ int len;
+ unsigned left;
+ char *next;
+ gz_statep state;
+ z_streamp strm;
+
+ /* get internal structure */
+ if (file == NULL)
+ return Z_STREAM_ERROR;
+ state = (gz_statep)file;
+ strm = &(state->strm);
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return Z_STREAM_ERROR;
+
+ /* make sure we have some buffer space */
+ if (state->size == 0 && gz_init(state) == -1)
+ return state->err;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return state->err;
+ }
+
+ /* do the printf() into the input buffer, put length in len -- the input
+ buffer is double-sized just for this function, so there is guaranteed to
+ be state->size bytes available after the current contents */
+ if (strm->avail_in == 0)
+ strm->next_in = state->in;
+ next = (char *)(state->in + (strm->next_in - state->in) + strm->avail_in);
+ next[state->size - 1] = 0;
+#ifdef NO_vsnprintf
+# ifdef HAS_vsprintf_void
+ (void)vsprintf(next, format, va);
+ for (len = 0; len < state->size; len++)
+ if (next[len] == 0) break;
+# else
+ len = vsprintf(next, format, va);
+# endif
+#else
+# ifdef HAS_vsnprintf_void
+ (void)vsnprintf(next, state->size, format, va);
+ len = strlen(next);
+# else
+ len = vsnprintf(next, state->size, format, va);
+# endif
+#endif
+
+ /* check that printf() results fit in buffer */
+ if (len == 0 || (unsigned)len >= state->size || next[state->size - 1] != 0)
+ return 0;
+
+ /* update buffer and position, compress first half if past that */
+ strm->avail_in += (unsigned)len;
+ state->x.pos += len;
+ if (strm->avail_in >= state->size) {
+ left = strm->avail_in - state->size;
+ strm->avail_in = state->size;
+ if (gz_comp(state, Z_NO_FLUSH) == -1)
+ return state->err;
+ memcpy(state->in, state->in + state->size, left);
+ strm->next_in = state->in;
+ strm->avail_in = left;
+ }
+ return len;
+}
+
+int ZEXPORTVA gzprintf(gzFile file, const char *format, ...)
+{
+ va_list va;
+ int ret;
+
+ va_start(va, format);
+ ret = gzvprintf(file, format, va);
+ va_end(va);
+ return ret;
+}
+
+#else /* !STDC && !Z_HAVE_STDARG_H */
+
+/* -- see zlib.h -- */
+int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
+ a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ gzFile file;
+ const char *format;
+ int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
+ a11, a12, a13, a14, a15, a16, a17, a18, a19, a20;
+{
+ unsigned len, left;
+ char *next;
+ gz_statep state;
+ z_streamp strm;
+
+ /* get internal structure */
+ if (file == NULL)
+ return Z_STREAM_ERROR;
+ state = (gz_statep)file;
+ strm = &(state->strm);
+
+ /* check that can really pass pointer in ints */
+ if (sizeof(int) != sizeof(void *))
+ return Z_STREAM_ERROR;
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return Z_STREAM_ERROR;
+
+ /* make sure we have some buffer space */
+ if (state->size == 0 && gz_init(state) == -1)
+ return state->error;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return state->error;
+ }
+
+ /* do the printf() into the input buffer, put length in len -- the input
+ buffer is double-sized just for this function, so there is guaranteed to
+ be state->size bytes available after the current contents */
+ if (strm->avail_in == 0)
+ strm->next_in = state->in;
+ next = (char *)(strm->next_in + strm->avail_in);
+ next[state->size - 1] = 0;
+#ifdef NO_snprintf
+# ifdef HAS_sprintf_void
+ sprintf(next, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
+ a13, a14, a15, a16, a17, a18, a19, a20);
+ for (len = 0; len < size; len++)
+ if (next[len] == 0)
+ break;
+# else
+ len = sprintf(next, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11,
+ a12, a13, a14, a15, a16, a17, a18, a19, a20);
+# endif
+#else
+# ifdef HAS_snprintf_void
+ snprintf(next, state->size, format, a1, a2, a3, a4, a5, a6, a7, a8, a9,
+ a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
+ len = strlen(next);
+# else
+ len = snprintf(next, state->size, format, a1, a2, a3, a4, a5, a6, a7, a8,
+ a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
+# endif
+#endif
+
+ /* check that printf() results fit in buffer */
+ if (len == 0 || len >= state->size || next[state->size - 1] != 0)
+ return 0;
+
+ /* update buffer and position, compress first half if past that */
+ strm->avail_in += len;
+ state->x.pos += len;
+ if (strm->avail_in >= state->size) {
+ left = strm->avail_in - state->size;
+ strm->avail_in = state->size;
+ if (gz_comp(state, Z_NO_FLUSH) == -1)
+ return state->err;
+ memcpy(state->in, state->in + state->size, left);
+ strm->next_in = state->in;
+ strm->avail_in = left;
+ }
+ return (int)len;
+}
+
+#endif
+
+/* -- see zlib.h -- */
+int ZEXPORT gzflush(file, flush)
+ gzFile file;
+ int flush;
+{
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return Z_STREAM_ERROR;
+ state = (gz_statep)file;
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return Z_STREAM_ERROR;
+
+ /* check flush parameter */
+ if (flush < 0 || flush > Z_FINISH)
+ return Z_STREAM_ERROR;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return state->err;
+ }
+
+ /* compress remaining data with requested flush */
+ (void)gz_comp(state, flush);
+ return state->err;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzsetparams(file, level, strategy)
+ gzFile file;
+ int level;
+ int strategy;
+{
+ gz_statep state;
+ z_streamp strm;
+
+ /* get internal structure */
+ if (file == NULL)
+ return Z_STREAM_ERROR;
+ state = (gz_statep)file;
+ strm = &(state->strm);
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return Z_STREAM_ERROR;
+
+ /* if no change is requested, then do nothing */
+ if (level == state->level && strategy == state->strategy)
+ return Z_OK;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return state->err;
+ }
+
+ /* change compression parameters for subsequent input */
+ if (state->size) {
+ /* flush previous input with previous parameters before changing */
+ if (strm->avail_in && gz_comp(state, Z_BLOCK) == -1)
+ return state->err;
+ deflateParams(strm, level, strategy);
+ }
+ state->level = level;
+ state->strategy = strategy;
+ return Z_OK;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzclose_w(file)
+ gzFile file;
+{
+ int ret = Z_OK;
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return Z_STREAM_ERROR;
+ state = (gz_statep)file;
+
+ /* check that we're writing */
+ if (state->mode != GZ_WRITE)
+ return Z_STREAM_ERROR;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ ret = state->err;
+ }
+
+ /* flush, free memory, and close file */
+ if (gz_comp(state, Z_FINISH) == -1)
+ ret = state->err;
+ if (state->size) {
+ if (!state->direct) {
+ (void)deflateEnd(&(state->strm));
+ free(state->out);
+ }
+ free(state->in);
+ }
+ gz_error(state, Z_OK, NULL);
+ free(state->path);
+ if (close(state->fd) == -1)
+ ret = Z_ERRNO;
+ free(state);
+ return ret;
+}
diff --git a/lib/zlib/infback.c b/lib/zlib/infback.c
new file mode 100644
index 0000000..d41c202
--- /dev/null
+++ b/lib/zlib/infback.c
@@ -0,0 +1,641 @@
+/* infback.c -- inflate using a call-back interface
+ * Copyright (C) 1995-2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ This code is largely copied from inflate.c. Normally either infback.o or
+ inflate.o would be linked into an application--not both. The interface
+ with inffast.c is retained so that optimized assembler-coded versions of
+ inflate_fast() can be used with either inflate.c or infback.c.
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+/* function prototypes */
+local void fixedtables OF((struct inflate_state FAR *state));
+
+/*
+ strm provides memory allocation functions in zalloc and zfree, or
+ Z_NULL to use the library memory allocation functions.
+
+ windowBits is in the range 8..15, and window is a user-supplied
+ window and output buffer that is 2**windowBits bytes.
+ */
+int ZEXPORT inflateBackInit_(strm, windowBits, window, version, stream_size)
+z_streamp strm;
+int windowBits;
+unsigned char FAR *window;
+const char *version;
+int stream_size;
+{
+ struct inflate_state FAR *state;
+
+ if (version == Z_NULL || version[0] != ZLIB_VERSION[0] ||
+ stream_size != (int)(sizeof(z_stream)))
+ return Z_VERSION_ERROR;
+ if (strm == Z_NULL || window == Z_NULL ||
+ windowBits < 8 || windowBits > 15)
+ return Z_STREAM_ERROR;
+ strm->msg = Z_NULL; /* in case we return an error */
+ if (strm->zalloc == (alloc_func)0) {
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zalloc = zcalloc;
+ strm->opaque = (voidpf)0;
+#endif
+ }
+ if (strm->zfree == (free_func)0)
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zfree = zcfree;
+#endif
+ state = (struct inflate_state FAR *)ZALLOC(strm, 1,
+ sizeof(struct inflate_state));
+ if (state == Z_NULL) return Z_MEM_ERROR;
+ Tracev((stderr, "inflate: allocated\n"));
+ strm->state = (struct internal_state FAR *)state;
+ state->dmax = 32768U;
+ state->wbits = (uInt)windowBits;
+ state->wsize = 1U << windowBits;
+ state->window = window;
+ state->wnext = 0;
+ state->whave = 0;
+ return Z_OK;
+}
+
+/*
+ Return state with length and distance decoding tables and index sizes set to
+ fixed code decoding. Normally this returns fixed tables from inffixed.h.
+ If BUILDFIXED is defined, then instead this routine builds the tables the
+ first time it's called, and returns those tables the first time and
+ thereafter. This reduces the size of the code by about 2K bytes, in
+ exchange for a little execution time. However, BUILDFIXED should not be
+ used for threaded applications, since the rewriting of the tables and virgin
+ may not be thread-safe.
+ */
+local void fixedtables(state)
+struct inflate_state FAR *state;
+{
+#ifdef BUILDFIXED
+ static int virgin = 1;
+ static code *lenfix, *distfix;
+ static code fixed[544];
+
+ /* build fixed huffman tables if first call (may not be thread safe) */
+ if (virgin) {
+ unsigned sym, bits;
+ static code *next;
+
+ /* literal/length table */
+ sym = 0;
+ while (sym < 144) state->lens[sym++] = 8;
+ while (sym < 256) state->lens[sym++] = 9;
+ while (sym < 280) state->lens[sym++] = 7;
+ while (sym < 288) state->lens[sym++] = 8;
+ next = fixed;
+ lenfix = next;
+ bits = 9;
+ inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work);
+
+ /* distance table */
+ sym = 0;
+ while (sym < 32) state->lens[sym++] = 5;
+ distfix = next;
+ bits = 5;
+ inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work);
+
+ /* do this just once */
+ virgin = 0;
+ }
+#else /* !BUILDFIXED */
+# include "inffixed.h"
+#endif /* BUILDFIXED */
+ state->lencode = lenfix;
+ state->lenbits = 9;
+ state->distcode = distfix;
+ state->distbits = 5;
+}
+
+/* Macros for inflateBack(): */
+
+/* Load returned state from inflate_fast() */
+#define LOAD() \
+ do { \
+ put = strm->next_out; \
+ left = strm->avail_out; \
+ next = strm->next_in; \
+ have = strm->avail_in; \
+ hold = state->hold; \
+ bits = state->bits; \
+ } while (0)
+
+/* Set state from registers for inflate_fast() */
+#define RESTORE() \
+ do { \
+ strm->next_out = put; \
+ strm->avail_out = left; \
+ strm->next_in = next; \
+ strm->avail_in = have; \
+ state->hold = hold; \
+ state->bits = bits; \
+ } while (0)
+
+/* Clear the input bit accumulator */
+#define INITBITS() \
+ do { \
+ hold = 0; \
+ bits = 0; \
+ } while (0)
+
+/* Assure that some input is available. If input is requested, but denied,
+ then return a Z_BUF_ERROR from inflateBack(). */
+#define PULL() \
+ do { \
+ if (have == 0) { \
+ have = in(in_desc, &next); \
+ if (have == 0) { \
+ next = Z_NULL; \
+ ret = Z_BUF_ERROR; \
+ goto inf_leave; \
+ } \
+ } \
+ } while (0)
+
+/* Get a byte of input into the bit accumulator, or return from inflateBack()
+ with an error if there is no input available. */
+#define PULLBYTE() \
+ do { \
+ PULL(); \
+ have--; \
+ hold += (unsigned long)(*next++) << bits; \
+ bits += 8; \
+ } while (0)
+
+/* Assure that there are at least n bits in the bit accumulator. If there is
+ not enough available input to do that, then return from inflateBack() with
+ an error. */
+#define NEEDBITS(n) \
+ do { \
+ while (bits < (unsigned)(n)) \
+ PULLBYTE(); \
+ } while (0)
+
+/* Return the low n bits of the bit accumulator (n < 16) */
+#define BITS(n) \
+ ((unsigned)hold & ((1U << (n)) - 1))
+
+/* Remove n bits from the bit accumulator */
+#define DROPBITS(n) \
+ do { \
+ hold >>= (n); \
+ bits -= (unsigned)(n); \
+ } while (0)
+
+/* Remove zero to seven bits as needed to go to a byte boundary */
+#define BYTEBITS() \
+ do { \
+ hold >>= bits & 7; \
+ bits -= bits & 7; \
+ } while (0)
+
+/* Assure that some output space is available, by writing out the window
+ if it's full. If the write fails, return from inflateBack() with a
+ Z_BUF_ERROR. */
+#define ROOM() \
+ do { \
+ if (left == 0) { \
+ put = state->window; \
+ left = state->wsize; \
+ state->whave = left; \
+ if (out(out_desc, put, left)) { \
+ ret = Z_BUF_ERROR; \
+ goto inf_leave; \
+ } \
+ } \
+ } while (0)
+
+/*
+ strm provides the memory allocation functions and window buffer on input,
+ and provides information on the unused input on return. For Z_DATA_ERROR
+ returns, strm will also provide an error message.
+
+ in() and out() are the call-back input and output functions. When
+ inflateBack() needs more input, it calls in(). When inflateBack() has
+ filled the window with output, or when it completes with data in the
+ window, it calls out() to write out the data. The application must not
+ change the provided input until in() is called again or inflateBack()
+ returns. The application must not change the window/output buffer until
+ inflateBack() returns.
+
+ in() and out() are called with a descriptor parameter provided in the
+ inflateBack() call. This parameter can be a structure that provides the
+ information required to do the read or write, as well as accumulated
+ information on the input and output such as totals and check values.
+
+ in() should return zero on failure. out() should return non-zero on
+ failure. If either in() or out() fails, than inflateBack() returns a
+ Z_BUF_ERROR. strm->next_in can be checked for Z_NULL to see whether it
+ was in() or out() that caused in the error. Otherwise, inflateBack()
+ returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format
+ error, or Z_MEM_ERROR if it could not allocate memory for the state.
+ inflateBack() can also return Z_STREAM_ERROR if the input parameters
+ are not correct, i.e. strm is Z_NULL or the state was not initialized.
+ */
+int ZEXPORT inflateBack(strm, in, in_desc, out, out_desc)
+z_streamp strm;
+in_func in;
+void FAR *in_desc;
+out_func out;
+void FAR *out_desc;
+{
+ struct inflate_state FAR *state;
+ z_const unsigned char FAR *next; /* next input */
+ unsigned char FAR *put; /* next output */
+ unsigned have, left; /* available input and output */
+ unsigned long hold; /* bit buffer */
+ unsigned bits; /* bits in bit buffer */
+ unsigned copy; /* number of stored or match bytes to copy */
+ unsigned char FAR *from; /* where to copy match bytes from */
+ code here; /* current decoding table entry */
+ code last; /* parent table entry */
+ unsigned len; /* length to copy for repeats, bits to drop */
+ int ret; /* return code */
+ static const unsigned short order[19] = /* permutation of code lengths */
+ {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+ /* Check that the strm exists and that the state was initialized */
+ if (strm == Z_NULL || strm->state == Z_NULL)
+ return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+
+ /* Reset the state */
+ strm->msg = Z_NULL;
+ state->mode = TYPE;
+ state->last = 0;
+ state->whave = 0;
+ next = strm->next_in;
+ have = next != Z_NULL ? strm->avail_in : 0;
+ hold = 0;
+ bits = 0;
+ put = state->window;
+ left = state->wsize;
+
+ /* Inflate until end of block marked as last */
+ for (;;)
+ switch (state->mode) {
+ case TYPE:
+ /* determine and dispatch block type */
+ if (state->last) {
+ BYTEBITS();
+ state->mode = DONE;
+ break;
+ }
+ NEEDBITS(3);
+ state->last = BITS(1);
+ DROPBITS(1);
+ switch (BITS(2)) {
+ case 0: /* stored block */
+ Tracev((stderr, "inflate: stored block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = STORED;
+ break;
+ case 1: /* fixed block */
+ fixedtables(state);
+ Tracev((stderr, "inflate: fixed codes block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = LEN; /* decode codes */
+ break;
+ case 2: /* dynamic block */
+ Tracev((stderr, "inflate: dynamic codes block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = TABLE;
+ break;
+ case 3:
+ strm->msg = (char *)"invalid block type";
+ state->mode = BAD;
+ }
+ DROPBITS(2);
+ break;
+
+ case STORED:
+ /* get and verify stored block length */
+ BYTEBITS(); /* go to byte boundary */
+ NEEDBITS(32);
+ if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) {
+ strm->msg = (char *)"invalid stored block lengths";
+ state->mode = BAD;
+ break;
+ }
+ state->length = (unsigned)hold & 0xffff;
+ Tracev((stderr, "inflate: stored length %u\n",
+ state->length));
+ INITBITS();
+
+ /* copy stored block from input to output */
+ while (state->length != 0) {
+ copy = state->length;
+ PULL();
+ ROOM();
+ if (copy > have) copy = have;
+ if (copy > left) copy = left;
+ zmemcpy(put, next, copy);
+ have -= copy;
+ next += copy;
+ left -= copy;
+ put += copy;
+ state->length -= copy;
+ }
+ Tracev((stderr, "inflate: stored end\n"));
+ state->mode = TYPE;
+ break;
+
+ case TABLE:
+ /* get dynamic table entries descriptor */
+ NEEDBITS(14);
+ state->nlen = BITS(5) + 257;
+ DROPBITS(5);
+ state->ndist = BITS(5) + 1;
+ DROPBITS(5);
+ state->ncode = BITS(4) + 4;
+ DROPBITS(4);
+#ifndef PKZIP_BUG_WORKAROUND
+ if (state->nlen > 286 || state->ndist > 30) {
+ strm->msg = (char *)"too many length or distance symbols";
+ state->mode = BAD;
+ break;
+ }
+#endif
+ Tracev((stderr, "inflate: table sizes ok\n"));
+
+ /* get code length code lengths (not a typo) */
+ state->have = 0;
+ while (state->have < state->ncode) {
+ NEEDBITS(3);
+ state->lens[order[state->have++]] = (unsigned short)BITS(3);
+ DROPBITS(3);
+ }
+ while (state->have < 19)
+ state->lens[order[state->have++]] = 0;
+ state->next = state->codes;
+ state->lencode = (code const FAR *)(state->next);
+ state->lenbits = 7;
+ ret = inflate_table(CODES, state->lens, 19, &(state->next),
+ &(state->lenbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid code lengths set";
+ state->mode = BAD;
+ break;
+ }
+ Tracev((stderr, "inflate: code lengths ok\n"));
+
+ /* get length and distance code code lengths */
+ state->have = 0;
+ while (state->have < state->nlen + state->ndist) {
+ for (;;) {
+ here = state->lencode[BITS(state->lenbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if (here.val < 16) {
+ DROPBITS(here.bits);
+ state->lens[state->have++] = here.val;
+ }
+ else {
+ if (here.val == 16) {
+ NEEDBITS(here.bits + 2);
+ DROPBITS(here.bits);
+ if (state->have == 0) {
+ strm->msg = (char *)"invalid bit length repeat";
+ state->mode = BAD;
+ break;
+ }
+ len = (unsigned)(state->lens[state->have - 1]);
+ copy = 3 + BITS(2);
+ DROPBITS(2);
+ }
+ else if (here.val == 17) {
+ NEEDBITS(here.bits + 3);
+ DROPBITS(here.bits);
+ len = 0;
+ copy = 3 + BITS(3);
+ DROPBITS(3);
+ }
+ else {
+ NEEDBITS(here.bits + 7);
+ DROPBITS(here.bits);
+ len = 0;
+ copy = 11 + BITS(7);
+ DROPBITS(7);
+ }
+ if (state->have + copy > state->nlen + state->ndist) {
+ strm->msg = (char *)"invalid bit length repeat";
+ state->mode = BAD;
+ break;
+ }
+ while (copy--)
+ state->lens[state->have++] = (unsigned short)len;
+ }
+ }
+
+ /* handle error breaks in while */
+ if (state->mode == BAD) break;
+
+ /* check for end-of-block code (better have one) */
+ if (state->lens[256] == 0) {
+ strm->msg = (char *)"invalid code -- missing end-of-block";
+ state->mode = BAD;
+ break;
+ }
+
+ /* build code tables -- note: do not change the lenbits or distbits
+ values here (9 and 6) without reading the comments in inftrees.h
+ concerning the ENOUGH constants, which depend on those values */
+ state->next = state->codes;
+ state->lencode = (code const FAR *)(state->next);
+ state->lenbits = 9;
+ ret = inflate_table(LENS, state->lens, state->nlen, &(state->next),
+ &(state->lenbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid literal/lengths set";
+ state->mode = BAD;
+ break;
+ }
+ state->distcode = (code const FAR *)(state->next);
+ state->distbits = 6;
+ ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist,
+ &(state->next), &(state->distbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid distances set";
+ state->mode = BAD;
+ break;
+ }
+ Tracev((stderr, "inflate: codes ok\n"));
+ state->mode = LEN;
+ ZFALLTHROUGH;
+
+ case LEN:
+ /* use inflate_fast() if we have enough input and output */
+ if (have >= 6 && left >= 258) {
+ RESTORE();
+ if (state->whave < state->wsize)
+ state->whave = state->wsize - left;
+ inflate_fast(strm, state->wsize);
+ LOAD();
+ break;
+ }
+
+ /* get a literal, length, or end-of-block code */
+ for (;;) {
+ here = state->lencode[BITS(state->lenbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if (here.op && (here.op & 0xf0) == 0) {
+ last = here;
+ for (;;) {
+ here = state->lencode[last.val +
+ (BITS(last.bits + last.op) >> last.bits)];
+ if ((unsigned)(last.bits + here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ DROPBITS(last.bits);
+ }
+ DROPBITS(here.bits);
+ state->length = (unsigned)here.val;
+
+ /* process literal */
+ if (here.op == 0) {
+ Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ "inflate: literal '%c'\n" :
+ "inflate: literal 0x%02x\n", here.val));
+ ROOM();
+ *put++ = (unsigned char)(state->length);
+ left--;
+ state->mode = LEN;
+ break;
+ }
+
+ /* process end of block */
+ if (here.op & 32) {
+ Tracevv((stderr, "inflate: end of block\n"));
+ state->mode = TYPE;
+ break;
+ }
+
+ /* invalid code */
+ if (here.op & 64) {
+ strm->msg = (char *)"invalid literal/length code";
+ state->mode = BAD;
+ break;
+ }
+
+ /* length code -- get extra bits, if any */
+ state->extra = (unsigned)(here.op) & 15;
+ if (state->extra != 0) {
+ NEEDBITS(state->extra);
+ state->length += BITS(state->extra);
+ DROPBITS(state->extra);
+ }
+ Tracevv((stderr, "inflate: length %u\n", state->length));
+
+ /* get distance code */
+ for (;;) {
+ here = state->distcode[BITS(state->distbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if ((here.op & 0xf0) == 0) {
+ last = here;
+ for (;;) {
+ here = state->distcode[last.val +
+ (BITS(last.bits + last.op) >> last.bits)];
+ if ((unsigned)(last.bits + here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ DROPBITS(last.bits);
+ }
+ DROPBITS(here.bits);
+ if (here.op & 64) {
+ strm->msg = (char *)"invalid distance code";
+ state->mode = BAD;
+ break;
+ }
+ state->offset = (unsigned)here.val;
+
+ /* get distance extra bits, if any */
+ state->extra = (unsigned)(here.op) & 15;
+ if (state->extra != 0) {
+ NEEDBITS(state->extra);
+ state->offset += BITS(state->extra);
+ DROPBITS(state->extra);
+ }
+ if (state->offset > state->wsize - (state->whave < state->wsize ?
+ left : 0)) {
+ strm->msg = (char *)"invalid distance too far back";
+ state->mode = BAD;
+ break;
+ }
+ Tracevv((stderr, "inflate: distance %u\n", state->offset));
+
+ /* copy match from window to output */
+ do {
+ ROOM();
+ copy = state->wsize - state->offset;
+ if (copy < left) {
+ from = put + copy;
+ copy = left - copy;
+ }
+ else {
+ from = put - state->offset;
+ copy = left;
+ }
+ if (copy > state->length) copy = state->length;
+ state->length -= copy;
+ left -= copy;
+ do {
+ *put++ = *from++;
+ } while (--copy);
+ } while (state->length != 0);
+ break;
+
+ case DONE:
+ /* inflate stream terminated properly -- write leftover output */
+ ret = Z_STREAM_END;
+ if (left < state->wsize) {
+ if (out(out_desc, state->window, state->wsize - left))
+ ret = Z_BUF_ERROR;
+ }
+ goto inf_leave;
+
+ case BAD:
+ ret = Z_DATA_ERROR;
+ goto inf_leave;
+
+ default: /* can't happen, but makes compilers happy */
+ ret = Z_STREAM_ERROR;
+ goto inf_leave;
+ }
+
+ /* Return unused input */
+ inf_leave:
+ strm->next_in = next;
+ strm->avail_in = have;
+ return ret;
+}
+
+int ZEXPORT inflateBackEnd(strm)
+z_streamp strm;
+{
+ if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0)
+ return Z_STREAM_ERROR;
+ ZFREE(strm, strm->state);
+ strm->state = Z_NULL;
+ Tracev((stderr, "inflate: end\n"));
+ return Z_OK;
+}
diff --git a/lib/zlib/inffast.c b/lib/zlib/inffast.c
new file mode 100644
index 0000000..0dbd1db
--- /dev/null
+++ b/lib/zlib/inffast.c
@@ -0,0 +1,323 @@
+/* inffast.c -- fast decoding
+ * Copyright (C) 1995-2017 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+#ifdef ASMINF
+# pragma message("Assembler code may have bugs -- use at your own risk")
+#else
+
+/*
+ Decode literal, length, and distance codes and write out the resulting
+ literal and match bytes until either not enough input or output is
+ available, an end-of-block is encountered, or a data error is encountered.
+ When large enough input and output buffers are supplied to inflate(), for
+ example, a 16K input buffer and a 64K output buffer, more than 95% of the
+ inflate execution time is spent in this routine.
+
+ Entry assumptions:
+
+ state->mode == LEN
+ strm->avail_in >= 6
+ strm->avail_out >= 258
+ start >= strm->avail_out
+ state->bits < 8
+
+ On return, state->mode is one of:
+
+ LEN -- ran out of enough output space or enough available input
+ TYPE -- reached end of block code, inflate() to interpret next block
+ BAD -- error in block data
+
+ Notes:
+
+ - The maximum input bits used by a length/distance pair is 15 bits for the
+ length code, 5 bits for the length extra, 15 bits for the distance code,
+ and 13 bits for the distance extra. This totals 48 bits, or six bytes.
+ Therefore if strm->avail_in >= 6, then there is enough input to avoid
+ checking for available input while decoding.
+
+ - The maximum bytes that a single length/distance pair can output is 258
+ bytes, which is the maximum length that can be coded. inflate_fast()
+ requires strm->avail_out >= 258 for each loop to avoid checking for
+ output space.
+ */
+void ZLIB_INTERNAL inflate_fast(strm, start)
+z_streamp strm;
+unsigned start; /* inflate()'s starting value for strm->avail_out */
+{
+ struct inflate_state FAR *state;
+ z_const unsigned char FAR *in; /* local strm->next_in */
+ z_const unsigned char FAR *last; /* have enough input while in < last */
+ unsigned char FAR *out; /* local strm->next_out */
+ unsigned char FAR *beg; /* inflate()'s initial strm->next_out */
+ unsigned char FAR *end; /* while out < end, enough space available */
+#ifdef INFLATE_STRICT
+ unsigned dmax; /* maximum distance from zlib header */
+#endif
+ unsigned wsize; /* window size or zero if not using window */
+ unsigned whave; /* valid bytes in the window */
+ unsigned wnext; /* window write index */
+ unsigned char FAR *window; /* allocated sliding window, if wsize != 0 */
+ unsigned long hold; /* local strm->hold */
+ unsigned bits; /* local strm->bits */
+ code const FAR *lcode; /* local strm->lencode */
+ code const FAR *dcode; /* local strm->distcode */
+ unsigned lmask; /* mask for first level of length codes */
+ unsigned dmask; /* mask for first level of distance codes */
+ code here; /* retrieved table entry */
+ unsigned op; /* code bits, operation, extra bits, or */
+ /* window position, window bytes to copy */
+ unsigned len; /* match length, unused bytes */
+ unsigned dist; /* match distance */
+ unsigned char FAR *from; /* where to copy match from */
+
+ /* copy state to local variables */
+ state = (struct inflate_state FAR *)strm->state;
+ in = strm->next_in;
+ last = in + (strm->avail_in - 5);
+ out = strm->next_out;
+ beg = out - (start - strm->avail_out);
+ end = out + (strm->avail_out - 257);
+#ifdef INFLATE_STRICT
+ dmax = state->dmax;
+#endif
+ wsize = state->wsize;
+ whave = state->whave;
+ wnext = state->wnext;
+ window = state->window;
+ hold = state->hold;
+ bits = state->bits;
+ lcode = state->lencode;
+ dcode = state->distcode;
+ lmask = (1U << state->lenbits) - 1;
+ dmask = (1U << state->distbits) - 1;
+
+ /* decode literals and length/distances until end-of-block or not enough
+ input data or output space */
+ do {
+ if (bits < 15) {
+ hold += (unsigned long)(*in++) << bits;
+ bits += 8;
+ hold += (unsigned long)(*in++) << bits;
+ bits += 8;
+ }
+ here = lcode[hold & lmask];
+ dolen:
+ op = (unsigned)(here.bits);
+ hold >>= op;
+ bits -= op;
+ op = (unsigned)(here.op);
+ if (op == 0) { /* literal */
+ Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ "inflate: literal '%c'\n" :
+ "inflate: literal 0x%02x\n", here.val));
+ *out++ = (unsigned char)(here.val);
+ }
+ else if (op & 16) { /* length base */
+ len = (unsigned)(here.val);
+ op &= 15; /* number of extra bits */
+ if (op) {
+ if (bits < op) {
+ hold += (unsigned long)(*in++) << bits;
+ bits += 8;
+ }
+ len += (unsigned)hold & ((1U << op) - 1);
+ hold >>= op;
+ bits -= op;
+ }
+ Tracevv((stderr, "inflate: length %u\n", len));
+ if (bits < 15) {
+ hold += (unsigned long)(*in++) << bits;
+ bits += 8;
+ hold += (unsigned long)(*in++) << bits;
+ bits += 8;
+ }
+ here = dcode[hold & dmask];
+ dodist:
+ op = (unsigned)(here.bits);
+ hold >>= op;
+ bits -= op;
+ op = (unsigned)(here.op);
+ if (op & 16) { /* distance base */
+ dist = (unsigned)(here.val);
+ op &= 15; /* number of extra bits */
+ if (bits < op) {
+ hold += (unsigned long)(*in++) << bits;
+ bits += 8;
+ if (bits < op) {
+ hold += (unsigned long)(*in++) << bits;
+ bits += 8;
+ }
+ }
+ dist += (unsigned)hold & ((1U << op) - 1);
+#ifdef INFLATE_STRICT
+ if (dist > dmax) {
+ strm->msg = (char *)"invalid distance too far back";
+ state->mode = BAD;
+ break;
+ }
+#endif
+ hold >>= op;
+ bits -= op;
+ Tracevv((stderr, "inflate: distance %u\n", dist));
+ op = (unsigned)(out - beg); /* max distance in output */
+ if (dist > op) { /* see if copy from window */
+ op = dist - op; /* distance back in window */
+ if (op > whave) {
+ if (state->sane) {
+ strm->msg =
+ (char *)"invalid distance too far back";
+ state->mode = BAD;
+ break;
+ }
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+ if (len <= op - whave) {
+ do {
+ *out++ = 0;
+ } while (--len);
+ continue;
+ }
+ len -= op - whave;
+ do {
+ *out++ = 0;
+ } while (--op > whave);
+ if (op == 0) {
+ from = out - dist;
+ do {
+ *out++ = *from++;
+ } while (--len);
+ continue;
+ }
+#endif
+ }
+ from = window;
+ if (wnext == 0) { /* very common case */
+ from += wsize - op;
+ if (op < len) { /* some from window */
+ len -= op;
+ do {
+ *out++ = *from++;
+ } while (--op);
+ from = out - dist; /* rest from output */
+ }
+ }
+ else if (wnext < op) { /* wrap around window */
+ from += wsize + wnext - op;
+ op -= wnext;
+ if (op < len) { /* some from end of window */
+ len -= op;
+ do {
+ *out++ = *from++;
+ } while (--op);
+ from = window;
+ if (wnext < len) { /* some from start of window */
+ op = wnext;
+ len -= op;
+ do {
+ *out++ = *from++;
+ } while (--op);
+ from = out - dist; /* rest from output */
+ }
+ }
+ }
+ else { /* contiguous in window */
+ from += wnext - op;
+ if (op < len) { /* some from window */
+ len -= op;
+ do {
+ *out++ = *from++;
+ } while (--op);
+ from = out - dist; /* rest from output */
+ }
+ }
+ while (len > 2) {
+ *out++ = *from++;
+ *out++ = *from++;
+ *out++ = *from++;
+ len -= 3;
+ }
+ if (len) {
+ *out++ = *from++;
+ if (len > 1)
+ *out++ = *from++;
+ }
+ }
+ else {
+ from = out - dist; /* copy direct from output */
+ do { /* minimum length is three */
+ *out++ = *from++;
+ *out++ = *from++;
+ *out++ = *from++;
+ len -= 3;
+ } while (len > 2);
+ if (len) {
+ *out++ = *from++;
+ if (len > 1)
+ *out++ = *from++;
+ }
+ }
+ }
+ else if ((op & 64) == 0) { /* 2nd level distance code */
+ here = dcode[here.val + (hold & ((1U << op) - 1))];
+ goto dodist;
+ }
+ else {
+ strm->msg = (char *)"invalid distance code";
+ state->mode = BAD;
+ break;
+ }
+ }
+ else if ((op & 64) == 0) { /* 2nd level length code */
+ here = lcode[here.val + (hold & ((1U << op) - 1))];
+ goto dolen;
+ }
+ else if (op & 32) { /* end-of-block */
+ Tracevv((stderr, "inflate: end of block\n"));
+ state->mode = TYPE;
+ break;
+ }
+ else {
+ strm->msg = (char *)"invalid literal/length code";
+ state->mode = BAD;
+ break;
+ }
+ } while (in < last && out < end);
+
+ /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
+ len = bits >> 3;
+ in -= len;
+ bits -= len << 3;
+ hold &= (1U << bits) - 1;
+
+ /* update state and return */
+ strm->next_in = in;
+ strm->next_out = out;
+ strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last));
+ strm->avail_out = (unsigned)(out < end ?
+ 257 + (end - out) : 257 - (out - end));
+ state->hold = hold;
+ state->bits = bits;
+ return;
+}
+
+/*
+ inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe):
+ - Using bit fields for code structure
+ - Different op definition to avoid & for extra bits (do & for table bits)
+ - Three separate decoding do-loops for direct, window, and wnext == 0
+ - Special case for distance > 1 copies to do overlapped load and store copy
+ - Explicit branch predictions (based on measured branch probabilities)
+ - Deferring match copy and interspersed it with decoding subsequent codes
+ - Swapping literal/length else
+ - Swapping window/direct else
+ - Larger unrolled copy loops (three is about right)
+ - Moving len -= 3 statement into middle of loop
+ */
+
+#endif /* !ASMINF */
diff --git a/lib/zlib/inffast.h b/lib/zlib/inffast.h
new file mode 100644
index 0000000..e5c1aa4
--- /dev/null
+++ b/lib/zlib/inffast.h
@@ -0,0 +1,11 @@
+/* inffast.h -- header to use inffast.c
+ * Copyright (C) 1995-2003, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+void ZLIB_INTERNAL inflate_fast OF((z_streamp strm, unsigned start));
diff --git a/lib/zlib/inffixed.h b/lib/zlib/inffixed.h
new file mode 100644
index 0000000..d628327
--- /dev/null
+++ b/lib/zlib/inffixed.h
@@ -0,0 +1,94 @@
+ /* inffixed.h -- table for decoding fixed codes
+ * Generated automatically by makefixed().
+ */
+
+ /* WARNING: this file should *not* be used by applications.
+ It is part of the implementation of this library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+ static const code lenfix[512] = {
+ {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48},
+ {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128},
+ {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59},
+ {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176},
+ {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20},
+ {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100},
+ {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8},
+ {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216},
+ {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76},
+ {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114},
+ {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2},
+ {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148},
+ {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42},
+ {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86},
+ {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15},
+ {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236},
+ {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62},
+ {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142},
+ {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31},
+ {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162},
+ {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25},
+ {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105},
+ {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4},
+ {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202},
+ {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69},
+ {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125},
+ {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13},
+ {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195},
+ {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35},
+ {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91},
+ {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19},
+ {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246},
+ {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55},
+ {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135},
+ {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99},
+ {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190},
+ {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16},
+ {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96},
+ {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6},
+ {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209},
+ {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72},
+ {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116},
+ {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4},
+ {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153},
+ {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44},
+ {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82},
+ {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11},
+ {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229},
+ {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58},
+ {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138},
+ {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51},
+ {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173},
+ {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30},
+ {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110},
+ {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0},
+ {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195},
+ {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65},
+ {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121},
+ {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9},
+ {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258},
+ {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37},
+ {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93},
+ {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23},
+ {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251},
+ {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51},
+ {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131},
+ {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67},
+ {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183},
+ {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23},
+ {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103},
+ {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9},
+ {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223},
+ {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79},
+ {0,9,255}
+ };
+
+ static const code distfix[32] = {
+ {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025},
+ {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193},
+ {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385},
+ {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577},
+ {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073},
+ {22,5,193},{64,5,0}
+ };
diff --git a/lib/zlib/inflate.c b/lib/zlib/inflate.c
new file mode 100644
index 0000000..57ad931
--- /dev/null
+++ b/lib/zlib/inflate.c
@@ -0,0 +1,1583 @@
+/* inflate.c -- zlib decompression
+ * Copyright (C) 1995-2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ * Change history:
+ *
+ * 1.2.beta0 24 Nov 2002
+ * - First version -- complete rewrite of inflate to simplify code, avoid
+ * creation of window when not needed, minimize use of window when it is
+ * needed, make inffast.c even faster, implement gzip decoding, and to
+ * improve code readability and style over the previous zlib inflate code
+ *
+ * 1.2.beta1 25 Nov 2002
+ * - Use pointers for available input and output checking in inffast.c
+ * - Remove input and output counters in inffast.c
+ * - Change inffast.c entry and loop from avail_in >= 7 to >= 6
+ * - Remove unnecessary second byte pull from length extra in inffast.c
+ * - Unroll direct copy to three copies per loop in inffast.c
+ *
+ * 1.2.beta2 4 Dec 2002
+ * - Change external routine names to reduce potential conflicts
+ * - Correct filename to inffixed.h for fixed tables in inflate.c
+ * - Make hbuf[] unsigned char to match parameter type in inflate.c
+ * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset)
+ * to avoid negation problem on Alphas (64 bit) in inflate.c
+ *
+ * 1.2.beta3 22 Dec 2002
+ * - Add comments on state->bits assertion in inffast.c
+ * - Add comments on op field in inftrees.h
+ * - Fix bug in reuse of allocated window after inflateReset()
+ * - Remove bit fields--back to byte structure for speed
+ * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths
+ * - Change post-increments to pre-increments in inflate_fast(), PPC biased?
+ * - Add compile time option, POSTINC, to use post-increments instead (Intel?)
+ * - Make MATCH copy in inflate() much faster for when inflate_fast() not used
+ * - Use local copies of stream next and avail values, as well as local bit
+ * buffer and bit count in inflate()--for speed when inflate_fast() not used
+ *
+ * 1.2.beta4 1 Jan 2003
+ * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings
+ * - Move a comment on output buffer sizes from inffast.c to inflate.c
+ * - Add comments in inffast.c to introduce the inflate_fast() routine
+ * - Rearrange window copies in inflate_fast() for speed and simplification
+ * - Unroll last copy for window match in inflate_fast()
+ * - Use local copies of window variables in inflate_fast() for speed
+ * - Pull out common wnext == 0 case for speed in inflate_fast()
+ * - Make op and len in inflate_fast() unsigned for consistency
+ * - Add FAR to lcode and dcode declarations in inflate_fast()
+ * - Simplified bad distance check in inflate_fast()
+ * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new
+ * source file infback.c to provide a call-back interface to inflate for
+ * programs like gzip and unzip -- uses window as output buffer to avoid
+ * window copying
+ *
+ * 1.2.beta5 1 Jan 2003
+ * - Improved inflateBack() interface to allow the caller to provide initial
+ * input in strm.
+ * - Fixed stored blocks bug in inflateBack()
+ *
+ * 1.2.beta6 4 Jan 2003
+ * - Added comments in inffast.c on effectiveness of POSTINC
+ * - Typecasting all around to reduce compiler warnings
+ * - Changed loops from while (1) or do {} while (1) to for (;;), again to
+ * make compilers happy
+ * - Changed type of window in inflateBackInit() to unsigned char *
+ *
+ * 1.2.beta7 27 Jan 2003
+ * - Changed many types to unsigned or unsigned short to avoid warnings
+ * - Added inflateCopy() function
+ *
+ * 1.2.0 9 Mar 2003
+ * - Changed inflateBack() interface to provide separate opaque descriptors
+ * for the in() and out() functions
+ * - Changed inflateBack() argument and in_func typedef to swap the length
+ * and buffer address return values for the input function
+ * - Check next_in and next_out for Z_NULL on entry to inflate()
+ *
+ * The history for versions after 1.2.0 are in ChangeLog in zlib distribution.
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+#ifdef MAKEFIXED
+# ifndef BUILDFIXED
+# define BUILDFIXED
+# endif
+#endif
+
+/* function prototypes */
+local int inflateStateCheck OF((z_streamp strm));
+local void fixedtables OF((struct inflate_state FAR *state));
+local int updatewindow OF((z_streamp strm, const unsigned char FAR *end,
+ unsigned copy));
+#ifdef BUILDFIXED
+ void makefixed OF((void));
+#endif
+local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf,
+ unsigned len));
+
+local int inflateStateCheck(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+ if (strm == Z_NULL ||
+ strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0)
+ return 1;
+ state = (struct inflate_state FAR *)strm->state;
+ if (state == Z_NULL || state->strm != strm ||
+ state->mode < HEAD || state->mode > SYNC)
+ return 1;
+ return 0;
+}
+
+int ZEXPORT inflateResetKeep(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ strm->total_in = strm->total_out = state->total = 0;
+ strm->msg = Z_NULL;
+ if (state->wrap) /* to support ill-conceived Java test suite */
+ strm->adler = state->wrap & 1;
+ state->mode = HEAD;
+ state->last = 0;
+ state->havedict = 0;
+ state->dmax = 32768U;
+ state->head = Z_NULL;
+ state->hold = 0;
+ state->bits = 0;
+ state->lencode = state->distcode = state->next = state->codes;
+ state->sane = 1;
+ state->back = -1;
+ Tracev((stderr, "inflate: reset\n"));
+ return Z_OK;
+}
+
+int ZEXPORT inflateReset(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ state->wsize = 0;
+ state->whave = 0;
+ state->wnext = 0;
+ return inflateResetKeep(strm);
+}
+
+int ZEXPORT inflateReset2(strm, windowBits)
+z_streamp strm;
+int windowBits;
+{
+ int wrap;
+ struct inflate_state FAR *state;
+
+ /* get the state */
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+
+ /* extract wrap request from windowBits parameter */
+ if (windowBits < 0) {
+ wrap = 0;
+ windowBits = -windowBits;
+ }
+ else {
+ wrap = (windowBits >> 4) + 5;
+#ifdef GUNZIP
+ if (windowBits < 48)
+ windowBits &= 15;
+#endif
+ }
+
+ /* set number of window bits, free window if different */
+ if (windowBits && (windowBits < 8 || windowBits > 15))
+ return Z_STREAM_ERROR;
+ if (state->window != Z_NULL && state->wbits != (unsigned)windowBits) {
+ ZFREE(strm, state->window);
+ state->window = Z_NULL;
+ }
+
+ /* update state and reset the rest of it */
+ state->wrap = wrap;
+ state->wbits = (unsigned)windowBits;
+ return inflateReset(strm);
+}
+
+int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size)
+z_streamp strm;
+int windowBits;
+const char *version;
+int stream_size;
+{
+ int ret;
+ struct inflate_state FAR *state;
+
+ if (version == Z_NULL || version[0] != ZLIB_VERSION[0] ||
+ stream_size != (int)(sizeof(z_stream)))
+ return Z_VERSION_ERROR;
+ if (strm == Z_NULL) return Z_STREAM_ERROR;
+ strm->msg = Z_NULL; /* in case we return an error */
+ if (strm->zalloc == (alloc_func)0) {
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zalloc = zcalloc;
+ strm->opaque = (voidpf)0;
+#endif
+ }
+ if (strm->zfree == (free_func)0)
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zfree = zcfree;
+#endif
+ state = (struct inflate_state FAR *)
+ ZALLOC(strm, 1, sizeof(struct inflate_state));
+ if (state == Z_NULL) return Z_MEM_ERROR;
+ Tracev((stderr, "inflate: allocated\n"));
+ strm->state = (struct internal_state FAR *)state;
+ state->strm = strm;
+ state->window = Z_NULL;
+ state->mode = HEAD; /* to pass state test in inflateReset2() */
+ ret = inflateReset2(strm, windowBits);
+ if (ret != Z_OK) {
+ ZFREE(strm, state);
+ strm->state = Z_NULL;
+ }
+ return ret;
+}
+
+int ZEXPORT inflateInit_(strm, version, stream_size)
+z_streamp strm;
+const char *version;
+int stream_size;
+{
+ return inflateInit2_(strm, DEF_WBITS, version, stream_size);
+}
+
+int ZEXPORT inflatePrime(strm, bits, value)
+z_streamp strm;
+int bits;
+int value;
+{
+ struct inflate_state FAR *state;
+
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if (bits < 0) {
+ state->hold = 0;
+ state->bits = 0;
+ return Z_OK;
+ }
+ if (bits > 16 || state->bits + (uInt)bits > 32) return Z_STREAM_ERROR;
+ value &= (1L << bits) - 1;
+ state->hold += (unsigned)value << state->bits;
+ state->bits += (uInt)bits;
+ return Z_OK;
+}
+
+/*
+ Return state with length and distance decoding tables and index sizes set to
+ fixed code decoding. Normally this returns fixed tables from inffixed.h.
+ If BUILDFIXED is defined, then instead this routine builds the tables the
+ first time it's called, and returns those tables the first time and
+ thereafter. This reduces the size of the code by about 2K bytes, in
+ exchange for a little execution time. However, BUILDFIXED should not be
+ used for threaded applications, since the rewriting of the tables and virgin
+ may not be thread-safe.
+ */
+local void fixedtables(state)
+struct inflate_state FAR *state;
+{
+#ifdef BUILDFIXED
+ static int virgin = 1;
+ static code *lenfix, *distfix;
+ static code fixed[544];
+
+ /* build fixed huffman tables if first call (may not be thread safe) */
+ if (virgin) {
+ unsigned sym, bits;
+ static code *next;
+
+ /* literal/length table */
+ sym = 0;
+ while (sym < 144) state->lens[sym++] = 8;
+ while (sym < 256) state->lens[sym++] = 9;
+ while (sym < 280) state->lens[sym++] = 7;
+ while (sym < 288) state->lens[sym++] = 8;
+ next = fixed;
+ lenfix = next;
+ bits = 9;
+ inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work);
+
+ /* distance table */
+ sym = 0;
+ while (sym < 32) state->lens[sym++] = 5;
+ distfix = next;
+ bits = 5;
+ inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work);
+
+ /* do this just once */
+ virgin = 0;
+ }
+#else /* !BUILDFIXED */
+# include "inffixed.h"
+#endif /* BUILDFIXED */
+ state->lencode = lenfix;
+ state->lenbits = 9;
+ state->distcode = distfix;
+ state->distbits = 5;
+}
+
+#ifdef MAKEFIXED
+#include <stdio.h>
+
+/*
+ Write out the inffixed.h that is #include'd above. Defining MAKEFIXED also
+ defines BUILDFIXED, so the tables are built on the fly. makefixed() writes
+ those tables to stdout, which would be piped to inffixed.h. A small program
+ can simply call makefixed to do this:
+
+ void makefixed(void);
+
+ int main(void)
+ {
+ makefixed();
+ return 0;
+ }
+
+ Then that can be linked with zlib built with MAKEFIXED defined and run:
+
+ a.out > inffixed.h
+ */
+void makefixed()
+{
+ unsigned low, size;
+ struct inflate_state state;
+
+ fixedtables(&state);
+ puts(" /* inffixed.h -- table for decoding fixed codes");
+ puts(" * Generated automatically by makefixed().");
+ puts(" */");
+ puts("");
+ puts(" /* WARNING: this file should *not* be used by applications.");
+ puts(" It is part of the implementation of this library and is");
+ puts(" subject to change. Applications should only use zlib.h.");
+ puts(" */");
+ puts("");
+ size = 1U << 9;
+ printf(" static const code lenfix[%u] = {", size);
+ low = 0;
+ for (;;) {
+ if ((low % 7) == 0) printf("\n ");
+ printf("{%u,%u,%d}", (low & 127) == 99 ? 64 : state.lencode[low].op,
+ state.lencode[low].bits, state.lencode[low].val);
+ if (++low == size) break;
+ putchar(',');
+ }
+ puts("\n };");
+ size = 1U << 5;
+ printf("\n static const code distfix[%u] = {", size);
+ low = 0;
+ for (;;) {
+ if ((low % 6) == 0) printf("\n ");
+ printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits,
+ state.distcode[low].val);
+ if (++low == size) break;
+ putchar(',');
+ }
+ puts("\n };");
+}
+#endif /* MAKEFIXED */
+
+/*
+ Update the window with the last wsize (normally 32K) bytes written before
+ returning. If window does not exist yet, create it. This is only called
+ when a window is already in use, or when output has been written during this
+ inflate call, but the end of the deflate stream has not been reached yet.
+ It is also called to create a window for dictionary data when a dictionary
+ is loaded.
+
+ Providing output buffers larger than 32K to inflate() should provide a speed
+ advantage, since only the last 32K of output is copied to the sliding window
+ upon return from inflate(), and since all distances after the first 32K of
+ output will fall in the output data, making match copies simpler and faster.
+ The advantage may be dependent on the size of the processor's data caches.
+ */
+local int updatewindow(strm, end, copy)
+z_streamp strm;
+const Bytef *end;
+unsigned copy;
+{
+ struct inflate_state FAR *state;
+ unsigned dist;
+
+ state = (struct inflate_state FAR *)strm->state;
+
+ /* if it hasn't been done already, allocate space for the window */
+ if (state->window == Z_NULL) {
+ state->window = (unsigned char FAR *)
+ ZALLOC(strm, 1U << state->wbits,
+ sizeof(unsigned char));
+ if (state->window == Z_NULL) return 1;
+ }
+
+ /* if window not in use yet, initialize */
+ if (state->wsize == 0) {
+ state->wsize = 1U << state->wbits;
+ state->wnext = 0;
+ state->whave = 0;
+ }
+
+ /* copy state->wsize or less output bytes into the circular window */
+ if (copy >= state->wsize) {
+ zmemcpy(state->window, end - state->wsize, state->wsize);
+ state->wnext = 0;
+ state->whave = state->wsize;
+ }
+ else {
+ dist = state->wsize - state->wnext;
+ if (dist > copy) dist = copy;
+ zmemcpy(state->window + state->wnext, end - copy, dist);
+ copy -= dist;
+ if (copy) {
+ zmemcpy(state->window, end - copy, copy);
+ state->wnext = copy;
+ state->whave = state->wsize;
+ }
+ else {
+ state->wnext += dist;
+ if (state->wnext == state->wsize) state->wnext = 0;
+ if (state->whave < state->wsize) state->whave += dist;
+ }
+ }
+ return 0;
+}
+
+/* Macros for inflate(): */
+
+/* check function to use adler32() for zlib or crc32() for gzip */
+#ifdef GUNZIP
+# define UPDATE(check, buf, len) \
+ (state->flags ? crc32(check, buf, len) : adler32(check, buf, len))
+#else
+# define UPDATE(check, buf, len) adler32(check, buf, len)
+#endif
+
+/* check macros for header crc */
+#ifdef GUNZIP
+# define CRC2(check, word) \
+ do { \
+ hbuf[0] = (unsigned char)(word); \
+ hbuf[1] = (unsigned char)((word) >> 8); \
+ check = crc32(check, hbuf, 2); \
+ } while (0)
+
+# define CRC4(check, word) \
+ do { \
+ hbuf[0] = (unsigned char)(word); \
+ hbuf[1] = (unsigned char)((word) >> 8); \
+ hbuf[2] = (unsigned char)((word) >> 16); \
+ hbuf[3] = (unsigned char)((word) >> 24); \
+ check = crc32(check, hbuf, 4); \
+ } while (0)
+#endif
+
+/* Load registers with state in inflate() for speed */
+#define LOAD() \
+ do { \
+ put = strm->next_out; \
+ left = strm->avail_out; \
+ next = strm->next_in; \
+ have = strm->avail_in; \
+ hold = state->hold; \
+ bits = state->bits; \
+ } while (0)
+
+/* Restore state from registers in inflate() */
+#define RESTORE() \
+ do { \
+ strm->next_out = put; \
+ strm->avail_out = left; \
+ strm->next_in = next; \
+ strm->avail_in = have; \
+ state->hold = hold; \
+ state->bits = bits; \
+ } while (0)
+
+/* Clear the input bit accumulator */
+#define INITBITS() \
+ do { \
+ hold = 0; \
+ bits = 0; \
+ } while (0)
+
+/* Get a byte of input into the bit accumulator, or return from inflate()
+ if there is no input available. */
+#define PULLBYTE() \
+ do { \
+ if (have == 0) goto inf_leave; \
+ have--; \
+ hold += (unsigned long)(*next++) << bits; \
+ bits += 8; \
+ } while (0)
+
+/* Assure that there are at least n bits in the bit accumulator. If there is
+ not enough available input to do that, then return from inflate(). */
+#define NEEDBITS(n) \
+ do { \
+ while (bits < (unsigned)(n)) \
+ PULLBYTE(); \
+ } while (0)
+
+/* Return the low n bits of the bit accumulator (n < 16) */
+#define BITS(n) \
+ ((unsigned)hold & ((1U << (n)) - 1))
+
+/* Remove n bits from the bit accumulator */
+#define DROPBITS(n) \
+ do { \
+ hold >>= (n); \
+ bits -= (unsigned)(n); \
+ } while (0)
+
+/* Remove zero to seven bits as needed to go to a byte boundary */
+#define BYTEBITS() \
+ do { \
+ hold >>= bits & 7; \
+ bits -= bits & 7; \
+ } while (0)
+
+/*
+ inflate() uses a state machine to process as much input data and generate as
+ much output data as possible before returning. The state machine is
+ structured roughly as follows:
+
+ for (;;) switch (state) {
+ ...
+ case STATEn:
+ if (not enough input data or output space to make progress)
+ return;
+ ... make progress ...
+ state = STATEm;
+ break;
+ ...
+ }
+
+ so when inflate() is called again, the same case is attempted again, and
+ if the appropriate resources are provided, the machine proceeds to the
+ next state. The NEEDBITS() macro is usually the way the state evaluates
+ whether it can proceed or should return. NEEDBITS() does the return if
+ the requested bits are not available. The typical use of the BITS macros
+ is:
+
+ NEEDBITS(n);
+ ... do something with BITS(n) ...
+ DROPBITS(n);
+
+ where NEEDBITS(n) either returns from inflate() if there isn't enough
+ input left to load n bits into the accumulator, or it continues. BITS(n)
+ gives the low n bits in the accumulator. When done, DROPBITS(n) drops
+ the low n bits off the accumulator. INITBITS() clears the accumulator
+ and sets the number of available bits to zero. BYTEBITS() discards just
+ enough bits to put the accumulator on a byte boundary. After BYTEBITS()
+ and a NEEDBITS(8), then BITS(8) would return the next byte in the stream.
+
+ NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return
+ if there is no input available. The decoding of variable length codes uses
+ PULLBYTE() directly in order to pull just enough bytes to decode the next
+ code, and no more.
+
+ Some states loop until they get enough input, making sure that enough
+ state information is maintained to continue the loop where it left off
+ if NEEDBITS() returns in the loop. For example, want, need, and keep
+ would all have to actually be part of the saved state in case NEEDBITS()
+ returns:
+
+ case STATEw:
+ while (want < need) {
+ NEEDBITS(n);
+ keep[want++] = BITS(n);
+ DROPBITS(n);
+ }
+ state = STATEx;
+ case STATEx:
+
+ As shown above, if the next state is also the next case, then the break
+ is omitted.
+
+ A state may also return if there is not enough output space available to
+ complete that state. Those states are copying stored data, writing a
+ literal byte, and copying a matching string.
+
+ When returning, a "goto inf_leave" is used to update the total counters,
+ update the check value, and determine whether any progress has been made
+ during that inflate() call in order to return the proper return code.
+ Progress is defined as a change in either strm->avail_in or strm->avail_out.
+ When there is a window, goto inf_leave will update the window with the last
+ output written. If a goto inf_leave occurs in the middle of decompression
+ and there is no window currently, goto inf_leave will create one and copy
+ output to the window for the next call of inflate().
+
+ In this implementation, the flush parameter of inflate() only affects the
+ return code (per zlib.h). inflate() always writes as much as possible to
+ strm->next_out, given the space available and the provided input--the effect
+ documented in zlib.h of Z_SYNC_FLUSH. Furthermore, inflate() always defers
+ the allocation of and copying into a sliding window until necessary, which
+ provides the effect documented in zlib.h for Z_FINISH when the entire input
+ stream available. So the only thing the flush parameter actually does is:
+ when flush is set to Z_FINISH, inflate() cannot return Z_OK. Instead it
+ will return Z_BUF_ERROR if it has not reached the end of the stream.
+ */
+
+int ZEXPORT inflate(strm, flush)
+z_streamp strm;
+int flush;
+{
+ struct inflate_state FAR *state;
+ z_const unsigned char FAR *next; /* next input */
+ unsigned char FAR *put; /* next output */
+ unsigned have, left; /* available input and output */
+ unsigned long hold; /* bit buffer */
+ unsigned bits; /* bits in bit buffer */
+ unsigned in, out; /* save starting available input and output */
+ unsigned copy; /* number of stored or match bytes to copy */
+ unsigned char FAR *from; /* where to copy match bytes from */
+ code here; /* current decoding table entry */
+ code last; /* parent table entry */
+ unsigned len; /* length to copy for repeats, bits to drop */
+ int ret; /* return code */
+#ifdef GUNZIP
+ unsigned char hbuf[4]; /* buffer for gzip header crc calculation */
+#endif
+ static const unsigned short order[19] = /* permutation of code lengths */
+ {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+ if (inflateStateCheck(strm) || strm->next_out == Z_NULL ||
+ (strm->next_in == Z_NULL && strm->avail_in != 0))
+ return Z_STREAM_ERROR;
+
+ state = (struct inflate_state FAR *)strm->state;
+ if (state->mode == TYPE) state->mode = TYPEDO; /* skip check */
+ LOAD();
+ in = have;
+ out = left;
+ ret = Z_OK;
+ for (;;)
+ switch (state->mode) {
+ case HEAD:
+ if (state->wrap == 0) {
+ state->mode = TYPEDO;
+ break;
+ }
+ NEEDBITS(16);
+#ifdef GUNZIP
+ if ((state->wrap & 2) && hold == 0x8b1f) { /* gzip header */
+ if (state->wbits == 0)
+ state->wbits = 15;
+ state->check = crc32(0L, Z_NULL, 0);
+ CRC2(state->check, hold);
+ INITBITS();
+ state->mode = FLAGS;
+ break;
+ }
+ state->flags = 0; /* expect zlib header */
+ if (state->head != Z_NULL)
+ state->head->done = -1;
+ if (!(state->wrap & 1) || /* check if zlib header allowed */
+#else
+ if (
+#endif
+ ((BITS(8) << 8) + (hold >> 8)) % 31) {
+ strm->msg = (char *)"incorrect header check";
+ state->mode = BAD;
+ break;
+ }
+ if (BITS(4) != Z_DEFLATED) {
+ strm->msg = (char *)"unknown compression method";
+ state->mode = BAD;
+ break;
+ }
+ DROPBITS(4);
+ len = BITS(4) + 8;
+ if (state->wbits == 0)
+ state->wbits = len;
+ if (len > 15 || len > state->wbits) {
+ strm->msg = (char *)"invalid window size";
+ state->mode = BAD;
+ break;
+ }
+ state->dmax = 1U << len;
+ Tracev((stderr, "inflate: zlib header ok\n"));
+ strm->adler = state->check = adler32(0L, Z_NULL, 0);
+ state->mode = hold & 0x200 ? DICTID : TYPE;
+ INITBITS();
+ break;
+#ifdef GUNZIP
+ case FLAGS:
+ NEEDBITS(16);
+ state->flags = (int)(hold);
+ if ((state->flags & 0xff) != Z_DEFLATED) {
+ strm->msg = (char *)"unknown compression method";
+ state->mode = BAD;
+ break;
+ }
+ if (state->flags & 0xe000) {
+ strm->msg = (char *)"unknown header flags set";
+ state->mode = BAD;
+ break;
+ }
+ if (state->head != Z_NULL)
+ state->head->text = (int)((hold >> 8) & 1);
+ if ((state->flags & 0x0200) && (state->wrap & 4))
+ CRC2(state->check, hold);
+ INITBITS();
+ state->mode = TIME;
+ ZFALLTHROUGH;
+ case TIME:
+ NEEDBITS(32);
+ if (state->head != Z_NULL)
+ state->head->time = hold;
+ if ((state->flags & 0x0200) && (state->wrap & 4))
+ CRC4(state->check, hold);
+ INITBITS();
+ state->mode = OS;
+ ZFALLTHROUGH;
+ case OS:
+ NEEDBITS(16);
+ if (state->head != Z_NULL) {
+ state->head->xflags = (int)(hold & 0xff);
+ state->head->os = (int)(hold >> 8);
+ }
+ if ((state->flags & 0x0200) && (state->wrap & 4))
+ CRC2(state->check, hold);
+ INITBITS();
+ state->mode = EXLEN;
+ ZFALLTHROUGH;
+ case EXLEN:
+ if (state->flags & 0x0400) {
+ NEEDBITS(16);
+ state->length = (unsigned)(hold);
+ if (state->head != Z_NULL)
+ state->head->extra_len = (unsigned)hold;
+ if ((state->flags & 0x0200) && (state->wrap & 4))
+ CRC2(state->check, hold);
+ INITBITS();
+ }
+ else if (state->head != Z_NULL)
+ state->head->extra = Z_NULL;
+ state->mode = EXTRA;
+ ZFALLTHROUGH;
+ case EXTRA:
+ if (state->flags & 0x0400) {
+ copy = state->length;
+ if (copy > have) copy = have;
+ if (copy) {
+ if (state->head != Z_NULL &&
+ state->head->extra != Z_NULL) {
+ len = state->head->extra_len - state->length;
+ zmemcpy(state->head->extra + len, next,
+ len + copy > state->head->extra_max ?
+ state->head->extra_max - len : copy);
+ }
+ if ((state->flags & 0x0200) && (state->wrap & 4))
+ state->check = crc32(state->check, next, copy);
+ have -= copy;
+ next += copy;
+ state->length -= copy;
+ }
+ if (state->length) goto inf_leave;
+ }
+ state->length = 0;
+ state->mode = NAME;
+ ZFALLTHROUGH;
+ case NAME:
+ if (state->flags & 0x0800) {
+ if (have == 0) goto inf_leave;
+ copy = 0;
+ do {
+ len = (unsigned)(next[copy++]);
+ if (state->head != Z_NULL &&
+ state->head->name != Z_NULL &&
+ state->length < state->head->name_max)
+ state->head->name[state->length++] = (Bytef)len;
+ } while (len && copy < have);
+ if ((state->flags & 0x0200) && (state->wrap & 4))
+ state->check = crc32(state->check, next, copy);
+ have -= copy;
+ next += copy;
+ if (len) goto inf_leave;
+ }
+ else if (state->head != Z_NULL)
+ state->head->name = Z_NULL;
+ state->length = 0;
+ state->mode = COMMENT;
+ ZFALLTHROUGH;
+ case COMMENT:
+ if (state->flags & 0x1000) {
+ if (have == 0) goto inf_leave;
+ copy = 0;
+ do {
+ len = (unsigned)(next[copy++]);
+ if (state->head != Z_NULL &&
+ state->head->comment != Z_NULL &&
+ state->length < state->head->comm_max)
+ state->head->comment[state->length++] = (Bytef)len;
+ } while (len && copy < have);
+ if ((state->flags & 0x0200) && (state->wrap & 4))
+ state->check = crc32(state->check, next, copy);
+ have -= copy;
+ next += copy;
+ if (len) goto inf_leave;
+ }
+ else if (state->head != Z_NULL)
+ state->head->comment = Z_NULL;
+ state->mode = HCRC;
+ ZFALLTHROUGH;
+ case HCRC:
+ if (state->flags & 0x0200) {
+ NEEDBITS(16);
+ if ((state->wrap & 4) && hold != (state->check & 0xffff)) {
+ strm->msg = (char *)"header crc mismatch";
+ state->mode = BAD;
+ break;
+ }
+ INITBITS();
+ }
+ if (state->head != Z_NULL) {
+ state->head->hcrc = (int)((state->flags >> 9) & 1);
+ state->head->done = 1;
+ }
+ strm->adler = state->check = crc32(0L, Z_NULL, 0);
+ state->mode = TYPE;
+ break;
+#endif
+ case DICTID:
+ NEEDBITS(32);
+ strm->adler = state->check = ZSWAP32(hold);
+ INITBITS();
+ state->mode = DICT;
+ ZFALLTHROUGH;
+ case DICT:
+ if (state->havedict == 0) {
+ RESTORE();
+ return Z_NEED_DICT;
+ }
+ strm->adler = state->check = adler32(0L, Z_NULL, 0);
+ state->mode = TYPE;
+ ZFALLTHROUGH;
+ case TYPE:
+ if (flush == Z_BLOCK || flush == Z_TREES) goto inf_leave;
+ ZFALLTHROUGH;
+ case TYPEDO:
+ if (state->last) {
+ BYTEBITS();
+ state->mode = CHECK;
+ break;
+ }
+ NEEDBITS(3);
+ state->last = BITS(1);
+ DROPBITS(1);
+ switch (BITS(2)) {
+ case 0: /* stored block */
+ Tracev((stderr, "inflate: stored block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = STORED;
+ break;
+ case 1: /* fixed block */
+ fixedtables(state);
+ Tracev((stderr, "inflate: fixed codes block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = LEN_; /* decode codes */
+ if (flush == Z_TREES) {
+ DROPBITS(2);
+ goto inf_leave;
+ }
+ break;
+ case 2: /* dynamic block */
+ Tracev((stderr, "inflate: dynamic codes block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = TABLE;
+ break;
+ case 3:
+ strm->msg = (char *)"invalid block type";
+ state->mode = BAD;
+ }
+ DROPBITS(2);
+ break;
+ case STORED:
+ BYTEBITS(); /* go to byte boundary */
+ NEEDBITS(32);
+ if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) {
+ strm->msg = (char *)"invalid stored block lengths";
+ state->mode = BAD;
+ break;
+ }
+ state->length = (unsigned)hold & 0xffff;
+ Tracev((stderr, "inflate: stored length %u\n",
+ state->length));
+ INITBITS();
+ state->mode = COPY_;
+ if (flush == Z_TREES) goto inf_leave;
+ ZFALLTHROUGH;
+ case COPY_:
+ state->mode = COPY;
+ ZFALLTHROUGH;
+ case COPY:
+ copy = state->length;
+ if (copy) {
+ if (copy > have) copy = have;
+ if (copy > left) copy = left;
+ if (copy == 0) goto inf_leave;
+ zmemcpy(put, next, copy);
+ have -= copy;
+ next += copy;
+ left -= copy;
+ put += copy;
+ state->length -= copy;
+ break;
+ }
+ Tracev((stderr, "inflate: stored end\n"));
+ state->mode = TYPE;
+ break;
+ case TABLE:
+ NEEDBITS(14);
+ state->nlen = BITS(5) + 257;
+ DROPBITS(5);
+ state->ndist = BITS(5) + 1;
+ DROPBITS(5);
+ state->ncode = BITS(4) + 4;
+ DROPBITS(4);
+#ifndef PKZIP_BUG_WORKAROUND
+ if (state->nlen > 286 || state->ndist > 30) {
+ strm->msg = (char *)"too many length or distance symbols";
+ state->mode = BAD;
+ break;
+ }
+#endif
+ Tracev((stderr, "inflate: table sizes ok\n"));
+ state->have = 0;
+ state->mode = LENLENS;
+ ZFALLTHROUGH;
+ case LENLENS:
+ while (state->have < state->ncode) {
+ NEEDBITS(3);
+ state->lens[order[state->have++]] = (unsigned short)BITS(3);
+ DROPBITS(3);
+ }
+ while (state->have < 19)
+ state->lens[order[state->have++]] = 0;
+ state->next = state->codes;
+ state->lencode = (const code FAR *)(state->next);
+ state->lenbits = 7;
+ ret = inflate_table(CODES, state->lens, 19, &(state->next),
+ &(state->lenbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid code lengths set";
+ state->mode = BAD;
+ break;
+ }
+ Tracev((stderr, "inflate: code lengths ok\n"));
+ state->have = 0;
+ state->mode = CODELENS;
+ ZFALLTHROUGH;
+ case CODELENS:
+ while (state->have < state->nlen + state->ndist) {
+ for (;;) {
+ here = state->lencode[BITS(state->lenbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if (here.val < 16) {
+ DROPBITS(here.bits);
+ state->lens[state->have++] = here.val;
+ }
+ else {
+ if (here.val == 16) {
+ NEEDBITS(here.bits + 2);
+ DROPBITS(here.bits);
+ if (state->have == 0) {
+ strm->msg = (char *)"invalid bit length repeat";
+ state->mode = BAD;
+ break;
+ }
+ len = state->lens[state->have - 1];
+ copy = 3 + BITS(2);
+ DROPBITS(2);
+ }
+ else if (here.val == 17) {
+ NEEDBITS(here.bits + 3);
+ DROPBITS(here.bits);
+ len = 0;
+ copy = 3 + BITS(3);
+ DROPBITS(3);
+ }
+ else {
+ NEEDBITS(here.bits + 7);
+ DROPBITS(here.bits);
+ len = 0;
+ copy = 11 + BITS(7);
+ DROPBITS(7);
+ }
+ if (state->have + copy > state->nlen + state->ndist) {
+ strm->msg = (char *)"invalid bit length repeat";
+ state->mode = BAD;
+ break;
+ }
+ while (copy--)
+ state->lens[state->have++] = (unsigned short)len;
+ }
+ }
+
+ /* handle error breaks in while */
+ if (state->mode == BAD) break;
+
+ /* check for end-of-block code (better have one) */
+ if (state->lens[256] == 0) {
+ strm->msg = (char *)"invalid code -- missing end-of-block";
+ state->mode = BAD;
+ break;
+ }
+
+ /* build code tables -- note: do not change the lenbits or distbits
+ values here (9 and 6) without reading the comments in inftrees.h
+ concerning the ENOUGH constants, which depend on those values */
+ state->next = state->codes;
+ state->lencode = (const code FAR *)(state->next);
+ state->lenbits = 9;
+ ret = inflate_table(LENS, state->lens, state->nlen, &(state->next),
+ &(state->lenbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid literal/lengths set";
+ state->mode = BAD;
+ break;
+ }
+ state->distcode = (const code FAR *)(state->next);
+ state->distbits = 6;
+ ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist,
+ &(state->next), &(state->distbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid distances set";
+ state->mode = BAD;
+ break;
+ }
+ Tracev((stderr, "inflate: codes ok\n"));
+ state->mode = LEN_;
+ if (flush == Z_TREES) goto inf_leave;
+ ZFALLTHROUGH;
+ case LEN_:
+ state->mode = LEN;
+ ZFALLTHROUGH;
+ case LEN:
+ if (have >= 6 && left >= 258) {
+ RESTORE();
+ inflate_fast(strm, out);
+ LOAD();
+ if (state->mode == TYPE)
+ state->back = -1;
+ break;
+ }
+ state->back = 0;
+ for (;;) {
+ here = state->lencode[BITS(state->lenbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if (here.op && (here.op & 0xf0) == 0) {
+ last = here;
+ for (;;) {
+ here = state->lencode[last.val +
+ (BITS(last.bits + last.op) >> last.bits)];
+ if ((unsigned)(last.bits + here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ DROPBITS(last.bits);
+ state->back += last.bits;
+ }
+ DROPBITS(here.bits);
+ state->back += here.bits;
+ state->length = (unsigned)here.val;
+ if ((int)(here.op) == 0) {
+ Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ "inflate: literal '%c'\n" :
+ "inflate: literal 0x%02x\n", here.val));
+ state->mode = LIT;
+ break;
+ }
+ if (here.op & 32) {
+ Tracevv((stderr, "inflate: end of block\n"));
+ state->back = -1;
+ state->mode = TYPE;
+ break;
+ }
+ if (here.op & 64) {
+ strm->msg = (char *)"invalid literal/length code";
+ state->mode = BAD;
+ break;
+ }
+ state->extra = (unsigned)(here.op) & 15;
+ state->mode = LENEXT;
+ ZFALLTHROUGH;
+ case LENEXT:
+ if (state->extra) {
+ NEEDBITS(state->extra);
+ state->length += BITS(state->extra);
+ DROPBITS(state->extra);
+ state->back += state->extra;
+ }
+ Tracevv((stderr, "inflate: length %u\n", state->length));
+ state->was = state->length;
+ state->mode = DIST;
+ ZFALLTHROUGH;
+ case DIST:
+ for (;;) {
+ here = state->distcode[BITS(state->distbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if ((here.op & 0xf0) == 0) {
+ last = here;
+ for (;;) {
+ here = state->distcode[last.val +
+ (BITS(last.bits + last.op) >> last.bits)];
+ if ((unsigned)(last.bits + here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ DROPBITS(last.bits);
+ state->back += last.bits;
+ }
+ DROPBITS(here.bits);
+ state->back += here.bits;
+ if (here.op & 64) {
+ strm->msg = (char *)"invalid distance code";
+ state->mode = BAD;
+ break;
+ }
+ state->offset = (unsigned)here.val;
+ state->extra = (unsigned)(here.op) & 15;
+ state->mode = DISTEXT;
+ ZFALLTHROUGH;
+ case DISTEXT:
+ if (state->extra) {
+ NEEDBITS(state->extra);
+ state->offset += BITS(state->extra);
+ DROPBITS(state->extra);
+ state->back += state->extra;
+ }
+#ifdef INFLATE_STRICT
+ if (state->offset > state->dmax) {
+ strm->msg = (char *)"invalid distance too far back";
+ state->mode = BAD;
+ break;
+ }
+#endif
+ Tracevv((stderr, "inflate: distance %u\n", state->offset));
+ state->mode = MATCH;
+ ZFALLTHROUGH;
+ case MATCH:
+ if (left == 0) goto inf_leave;
+ copy = out - left;
+ if (state->offset > copy) { /* copy from window */
+ copy = state->offset - copy;
+ if (copy > state->whave) {
+ if (state->sane) {
+ strm->msg = (char *)"invalid distance too far back";
+ state->mode = BAD;
+ break;
+ }
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+ Trace((stderr, "inflate.c too far\n"));
+ copy -= state->whave;
+ if (copy > state->length) copy = state->length;
+ if (copy > left) copy = left;
+ left -= copy;
+ state->length -= copy;
+ do {
+ *put++ = 0;
+ } while (--copy);
+ if (state->length == 0) state->mode = LEN;
+ break;
+#endif
+ }
+ if (copy > state->wnext) {
+ copy -= state->wnext;
+ from = state->window + (state->wsize - copy);
+ }
+ else
+ from = state->window + (state->wnext - copy);
+ if (copy > state->length) copy = state->length;
+ }
+ else { /* copy from output */
+ from = put - state->offset;
+ copy = state->length;
+ }
+ if (copy > left) copy = left;
+ left -= copy;
+ state->length -= copy;
+ do {
+ *put++ = *from++;
+ } while (--copy);
+ if (state->length == 0) state->mode = LEN;
+ break;
+ case LIT:
+ if (left == 0) goto inf_leave;
+ *put++ = (unsigned char)(state->length);
+ left--;
+ state->mode = LEN;
+ break;
+ case CHECK:
+ if (state->wrap) {
+ NEEDBITS(32);
+ out -= left;
+ strm->total_out += out;
+ state->total += out;
+ if ((state->wrap & 4) && out)
+ strm->adler = state->check =
+ UPDATE(state->check, put - out, out);
+ out = left;
+ if ((state->wrap & 4) && (
+#ifdef GUNZIP
+ state->flags ? hold :
+#endif
+ ZSWAP32(hold)) != state->check) {
+ strm->msg = (char *)"incorrect data check";
+ state->mode = BAD;
+ break;
+ }
+ INITBITS();
+ Tracev((stderr, "inflate: check matches trailer\n"));
+ }
+#ifdef GUNZIP
+ state->mode = LENGTH;
+ ZFALLTHROUGH;
+ case LENGTH:
+ if (state->wrap && state->flags) {
+ NEEDBITS(32);
+ if (hold != (state->total & 0xffffffffUL)) {
+ strm->msg = (char *)"incorrect length check";
+ state->mode = BAD;
+ break;
+ }
+ INITBITS();
+ Tracev((stderr, "inflate: length matches trailer\n"));
+ }
+#endif
+ state->mode = DONE;
+ ZFALLTHROUGH;
+ case DONE:
+ ret = Z_STREAM_END;
+ goto inf_leave;
+ case BAD:
+ ret = Z_DATA_ERROR;
+ goto inf_leave;
+ case MEM:
+ return Z_MEM_ERROR;
+ case SYNC:
+ default:
+ return Z_STREAM_ERROR;
+ }
+
+ /*
+ Return from inflate(), updating the total counts and the check value.
+ If there was no progress during the inflate() call, return a buffer
+ error. Call updatewindow() to create and/or update the window state.
+ Note: a memory error from inflate() is non-recoverable.
+ */
+ inf_leave:
+ RESTORE();
+ if (state->wsize || (out != strm->avail_out && state->mode < BAD &&
+ (state->mode < CHECK || flush != Z_FINISH)))
+ if (updatewindow(strm, strm->next_out, out - strm->avail_out)) {
+ state->mode = MEM;
+ return Z_MEM_ERROR;
+ }
+ in -= strm->avail_in;
+ out -= strm->avail_out;
+ strm->total_in += in;
+ strm->total_out += out;
+ state->total += out;
+ if ((state->wrap & 4) && out)
+ strm->adler = state->check =
+ UPDATE(state->check, strm->next_out - out, out);
+ strm->data_type = (int)state->bits + (state->last ? 64 : 0) +
+ (state->mode == TYPE ? 128 : 0) +
+ (state->mode == LEN_ || state->mode == COPY_ ? 256 : 0);
+ if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK)
+ ret = Z_BUF_ERROR;
+ return ret;
+}
+
+int ZEXPORT inflateEnd(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+ if (inflateStateCheck(strm))
+ return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if (state->window != Z_NULL) ZFREE(strm, state->window);
+ ZFREE(strm, strm->state);
+ strm->state = Z_NULL;
+ Tracev((stderr, "inflate: end\n"));
+ return Z_OK;
+}
+
+int ZEXPORT inflateGetDictionary(strm, dictionary, dictLength)
+z_streamp strm;
+Bytef *dictionary;
+uInt *dictLength;
+{
+ struct inflate_state FAR *state;
+
+ /* check state */
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+
+ /* copy dictionary */
+ if (state->whave && dictionary != Z_NULL) {
+ zmemcpy(dictionary, state->window + state->wnext,
+ state->whave - state->wnext);
+ zmemcpy(dictionary + state->whave - state->wnext,
+ state->window, state->wnext);
+ }
+ if (dictLength != Z_NULL)
+ *dictLength = state->whave;
+ return Z_OK;
+}
+
+int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength)
+z_streamp strm;
+const Bytef *dictionary;
+uInt dictLength;
+{
+ struct inflate_state FAR *state;
+ unsigned long dictid;
+ int ret;
+
+ /* check state */
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if (state->wrap != 0 && state->mode != DICT)
+ return Z_STREAM_ERROR;
+
+ /* check for correct dictionary identifier */
+ if (state->mode == DICT) {
+ dictid = adler32(0L, Z_NULL, 0);
+ dictid = adler32(dictid, dictionary, dictLength);
+ if (dictid != state->check)
+ return Z_DATA_ERROR;
+ }
+
+ /* copy dictionary to window using updatewindow(), which will amend the
+ existing dictionary if appropriate */
+ ret = updatewindow(strm, dictionary + dictLength, dictLength);
+ if (ret) {
+ state->mode = MEM;
+ return Z_MEM_ERROR;
+ }
+ state->havedict = 1;
+ Tracev((stderr, "inflate: dictionary set\n"));
+ return Z_OK;
+}
+
+int ZEXPORT inflateGetHeader(strm, head)
+z_streamp strm;
+gz_headerp head;
+{
+ struct inflate_state FAR *state;
+
+ /* check state */
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if ((state->wrap & 2) == 0) return Z_STREAM_ERROR;
+
+ /* save header structure */
+ state->head = head;
+ head->done = 0;
+ return Z_OK;
+}
+
+/*
+ Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff. Return when found
+ or when out of input. When called, *have is the number of pattern bytes
+ found in order so far, in 0..3. On return *have is updated to the new
+ state. If on return *have equals four, then the pattern was found and the
+ return value is how many bytes were read including the last byte of the
+ pattern. If *have is less than four, then the pattern has not been found
+ yet and the return value is len. In the latter case, syncsearch() can be
+ called again with more data and the *have state. *have is initialized to
+ zero for the first call.
+ */
+local unsigned syncsearch(have, buf, len)
+unsigned FAR *have;
+const unsigned char FAR *buf;
+unsigned len;
+{
+ unsigned got;
+ unsigned next;
+
+ got = *have;
+ next = 0;
+ while (next < len && got < 4) {
+ if ((int)(buf[next]) == (got < 2 ? 0 : 0xff))
+ got++;
+ else if (buf[next])
+ got = 0;
+ else
+ got = 4 - got;
+ next++;
+ }
+ *have = got;
+ return next;
+}
+
+int ZEXPORT inflateSync(strm)
+z_streamp strm;
+{
+ unsigned len; /* number of bytes to look at or looked at */
+ unsigned long in, out; /* temporary to save total_in and total_out */
+ unsigned char buf[4]; /* to restore bit buffer to byte string */
+ struct inflate_state FAR *state;
+
+ /* check parameters */
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR;
+
+ /* if first time, start search in bit buffer */
+ if (state->mode != SYNC) {
+ state->mode = SYNC;
+ state->hold <<= state->bits & 7;
+ state->bits -= state->bits & 7;
+ len = 0;
+ while (state->bits >= 8) {
+ buf[len++] = (unsigned char)(state->hold);
+ state->hold >>= 8;
+ state->bits -= 8;
+ }
+ state->have = 0;
+ syncsearch(&(state->have), buf, len);
+ }
+
+ /* search available input */
+ len = syncsearch(&(state->have), strm->next_in, strm->avail_in);
+ strm->avail_in -= len;
+ strm->next_in += len;
+ strm->total_in += len;
+
+ /* return no joy or set up to restart inflate() on a new block */
+ if (state->have != 4) return Z_DATA_ERROR;
+ in = strm->total_in; out = strm->total_out;
+ inflateReset(strm);
+ strm->total_in = in; strm->total_out = out;
+ state->mode = TYPE;
+ return Z_OK;
+}
+
+/*
+ Returns true if inflate is currently at the end of a block generated by
+ Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
+ implementation to provide an additional safety check. PPP uses
+ Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored
+ block. When decompressing, PPP checks that at the end of input packet,
+ inflate is waiting for these length bytes.
+ */
+int ZEXPORT inflateSyncPoint(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ return state->mode == STORED && state->bits == 0;
+}
+
+int ZEXPORT inflateCopy(dest, source)
+z_streamp dest;
+z_streamp source;
+{
+ struct inflate_state FAR *state;
+ struct inflate_state FAR *copy;
+ unsigned char FAR *window;
+ unsigned wsize;
+
+ /* check input */
+ if (inflateStateCheck(source) || dest == Z_NULL)
+ return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)source->state;
+
+ /* allocate space */
+ copy = (struct inflate_state FAR *)
+ ZALLOC(source, 1, sizeof(struct inflate_state));
+ if (copy == Z_NULL) return Z_MEM_ERROR;
+ window = Z_NULL;
+ if (state->window != Z_NULL) {
+ window = (unsigned char FAR *)
+ ZALLOC(source, 1U << state->wbits, sizeof(unsigned char));
+ if (window == Z_NULL) {
+ ZFREE(source, copy);
+ return Z_MEM_ERROR;
+ }
+ }
+
+ /* copy state */
+ zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream));
+ zmemcpy((voidpf)copy, (voidpf)state, sizeof(struct inflate_state));
+ copy->strm = dest;
+ if (state->lencode >= state->codes &&
+ state->lencode <= state->codes + ENOUGH - 1) {
+ copy->lencode = copy->codes + (state->lencode - state->codes);
+ copy->distcode = copy->codes + (state->distcode - state->codes);
+ }
+ copy->next = copy->codes + (state->next - state->codes);
+ if (window != Z_NULL) {
+ wsize = 1U << state->wbits;
+ zmemcpy(window, state->window, wsize);
+ }
+ copy->window = window;
+ dest->state = (struct internal_state FAR *)copy;
+ return Z_OK;
+}
+
+int ZEXPORT inflateUndermine(strm, subvert)
+z_streamp strm;
+int subvert;
+{
+ struct inflate_state FAR *state;
+
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+ state->sane = !subvert;
+ return Z_OK;
+#else
+ (void)subvert;
+ state->sane = 1;
+ return Z_DATA_ERROR;
+#endif
+}
+
+int ZEXPORT inflateValidate(strm, check)
+z_streamp strm;
+int check;
+{
+ struct inflate_state FAR *state;
+
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if (check)
+ state->wrap |= 4;
+ else
+ state->wrap &= ~4;
+ return Z_OK;
+}
+
+long ZEXPORT inflateMark(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+
+ if (inflateStateCheck(strm))
+ return -(1L << 16);
+ state = (struct inflate_state FAR *)strm->state;
+ return (long)(((unsigned long)((long)state->back)) << 16) +
+ (state->mode == COPY ? state->length :
+ (state->mode == MATCH ? state->was - state->length : 0));
+}
+
+unsigned long ZEXPORT inflateCodesUsed(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+ if (inflateStateCheck(strm)) return (unsigned long)-1;
+ state = (struct inflate_state FAR *)strm->state;
+ return (unsigned long)(state->next - state->codes);
+}
diff --git a/lib/zlib/inflate.h b/lib/zlib/inflate.h
new file mode 100644
index 0000000..a46cce6
--- /dev/null
+++ b/lib/zlib/inflate.h
@@ -0,0 +1,125 @@
+/* inflate.h -- internal inflate state definition
+ * Copyright (C) 1995-2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+/* define NO_GZIP when compiling if you want to disable gzip header and
+ trailer decoding by inflate(). NO_GZIP would be used to avoid linking in
+ the crc code when it is not needed. For shared libraries, gzip decoding
+ should be left enabled. */
+#ifndef NO_GZIP
+# define GUNZIP
+#endif
+
+/* Possible inflate modes between inflate() calls */
+typedef enum {
+ HEAD = 16180, /* i: waiting for magic header */
+ FLAGS, /* i: waiting for method and flags (gzip) */
+ TIME, /* i: waiting for modification time (gzip) */
+ OS, /* i: waiting for extra flags and operating system (gzip) */
+ EXLEN, /* i: waiting for extra length (gzip) */
+ EXTRA, /* i: waiting for extra bytes (gzip) */
+ NAME, /* i: waiting for end of file name (gzip) */
+ COMMENT, /* i: waiting for end of comment (gzip) */
+ HCRC, /* i: waiting for header crc (gzip) */
+ DICTID, /* i: waiting for dictionary check value */
+ DICT, /* waiting for inflateSetDictionary() call */
+ TYPE, /* i: waiting for type bits, including last-flag bit */
+ TYPEDO, /* i: same, but skip check to exit inflate on new block */
+ STORED, /* i: waiting for stored size (length and complement) */
+ COPY_, /* i/o: same as COPY below, but only first time in */
+ COPY, /* i/o: waiting for input or output to copy stored block */
+ TABLE, /* i: waiting for dynamic block table lengths */
+ LENLENS, /* i: waiting for code length code lengths */
+ CODELENS, /* i: waiting for length/lit and distance code lengths */
+ LEN_, /* i: same as LEN below, but only first time in */
+ LEN, /* i: waiting for length/lit/eob code */
+ LENEXT, /* i: waiting for length extra bits */
+ DIST, /* i: waiting for distance code */
+ DISTEXT, /* i: waiting for distance extra bits */
+ MATCH, /* o: waiting for output space to copy string */
+ LIT, /* o: waiting for output space to write literal */
+ CHECK, /* i: waiting for 32-bit check value */
+ LENGTH, /* i: waiting for 32-bit length (gzip) */
+ DONE, /* finished check, done -- remain here until reset */
+ BAD, /* got a data error -- remain here until reset */
+ MEM, /* got an inflate() memory error -- remain here until reset */
+ SYNC /* looking for synchronization bytes to restart inflate() */
+} inflate_mode;
+
+/*
+ State transitions between above modes -
+
+ (most modes can go to BAD or MEM on error -- not shown for clarity)
+
+ Process header:
+ HEAD -> (gzip) or (zlib) or (raw)
+ (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME -> COMMENT ->
+ HCRC -> TYPE
+ (zlib) -> DICTID or TYPE
+ DICTID -> DICT -> TYPE
+ (raw) -> TYPEDO
+ Read deflate blocks:
+ TYPE -> TYPEDO -> STORED or TABLE or LEN_ or CHECK
+ STORED -> COPY_ -> COPY -> TYPE
+ TABLE -> LENLENS -> CODELENS -> LEN_
+ LEN_ -> LEN
+ Read deflate codes in fixed or dynamic block:
+ LEN -> LENEXT or LIT or TYPE
+ LENEXT -> DIST -> DISTEXT -> MATCH -> LEN
+ LIT -> LEN
+ Process trailer:
+ CHECK -> LENGTH -> DONE
+ */
+
+/* State maintained between inflate() calls -- approximately 7K bytes, not
+ including the allocated sliding window, which is up to 32K bytes. */
+struct inflate_state {
+ z_streamp strm; /* pointer back to this zlib stream */
+ inflate_mode mode; /* current inflate mode */
+ int last; /* true if processing last block */
+ int wrap; /* bit 0 true for zlib, bit 1 true for gzip,
+ bit 2 true to validate check value */
+ int havedict; /* true if dictionary provided */
+ int flags; /* gzip header method and flags (0 if zlib) */
+ unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */
+ unsigned long check; /* protected copy of check value */
+ unsigned long total; /* protected copy of output count */
+ gz_headerp head; /* where to save gzip header information */
+ /* sliding window */
+ unsigned wbits; /* log base 2 of requested window size */
+ unsigned wsize; /* window size or zero if not using window */
+ unsigned whave; /* valid bytes in the window */
+ unsigned wnext; /* window write index */
+ unsigned char FAR *window; /* allocated sliding window, if needed */
+ /* bit accumulator */
+ unsigned long hold; /* input bit accumulator */
+ unsigned bits; /* number of bits in "in" */
+ /* for string and stored block copying */
+ unsigned length; /* literal or length of data to copy */
+ unsigned offset; /* distance back to copy string from */
+ /* for table and code decoding */
+ unsigned extra; /* extra bits needed */
+ /* fixed and dynamic code tables */
+ code const FAR *lencode; /* starting table for length/literal codes */
+ code const FAR *distcode; /* starting table for distance codes */
+ unsigned lenbits; /* index bits for lencode */
+ unsigned distbits; /* index bits for distcode */
+ /* dynamic table building */
+ unsigned ncode; /* number of code length code lengths */
+ unsigned nlen; /* number of length code lengths */
+ unsigned ndist; /* number of distance code lengths */
+ unsigned have; /* number of code lengths in lens[] */
+ code FAR *next; /* next available space in codes[] */
+ unsigned short lens[320]; /* temporary storage for code lengths */
+ unsigned short work[288]; /* work area for code table building */
+ code codes[ENOUGH]; /* space for code tables */
+ int sane; /* if false, allow invalid distance too far */
+ int back; /* bits back of last unprocessed length/lit */
+ unsigned was; /* initial length of match */
+};
diff --git a/lib/zlib/inftrees.c b/lib/zlib/inftrees.c
new file mode 100644
index 0000000..2ea08fc
--- /dev/null
+++ b/lib/zlib/inftrees.c
@@ -0,0 +1,304 @@
+/* inftrees.c -- generate Huffman trees for efficient decoding
+ * Copyright (C) 1995-2017 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+
+#define MAXBITS 15
+
+const char inflate_copyright[] =
+ " inflate 1.2.11 Copyright 1995-2017 Mark Adler ";
+/*
+ If you use the zlib library in a product, an acknowledgment is welcome
+ in the documentation of your product. If for some reason you cannot
+ include such an acknowledgment, I would appreciate that you keep this
+ copyright string in the executable of your product.
+ */
+
+/*
+ Build a set of tables to decode the provided canonical Huffman code.
+ The code lengths are lens[0..codes-1]. The result starts at *table,
+ whose indices are 0..2^bits-1. work is a writable array of at least
+ lens shorts, which is used as a work area. type is the type of code
+ to be generated, CODES, LENS, or DISTS. On return, zero is success,
+ -1 is an invalid code, and +1 means that ENOUGH isn't enough. table
+ on return points to the next available entry's address. bits is the
+ requested root table index bits, and on return it is the actual root
+ table index bits. It will differ if the request is greater than the
+ longest code or if it is less than the shortest code.
+ */
+int ZLIB_INTERNAL inflate_table(type, lens, codes, table, bits, work)
+codetype type;
+unsigned short FAR *lens;
+unsigned codes;
+code FAR * FAR *table;
+unsigned FAR *bits;
+unsigned short FAR *work;
+{
+ unsigned len; /* a code's length in bits */
+ unsigned sym; /* index of code symbols */
+ unsigned min, max; /* minimum and maximum code lengths */
+ unsigned root; /* number of index bits for root table */
+ unsigned curr; /* number of index bits for current table */
+ unsigned drop; /* code bits to drop for sub-table */
+ int left; /* number of prefix codes available */
+ unsigned used; /* code entries in table used */
+ unsigned huff; /* Huffman code */
+ unsigned incr; /* for incrementing code, index */
+ unsigned fill; /* index for replicating entries */
+ unsigned low; /* low bits for current root entry */
+ unsigned mask; /* mask for low root bits */
+ code here; /* table entry for duplication */
+ code FAR *next; /* next available space in table */
+ const unsigned short FAR *base; /* base value table to use */
+ const unsigned short FAR *extra; /* extra bits table to use */
+ unsigned match; /* use base and extra for symbol >= match */
+ unsigned short count[MAXBITS+1]; /* number of codes of each length */
+ unsigned short offs[MAXBITS+1]; /* offsets in table for each length */
+ static const unsigned short lbase[31] = { /* Length codes 257..285 base */
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+ 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
+ static const unsigned short lext[31] = { /* Length codes 257..285 extra */
+ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
+ 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 77, 202};
+ static const unsigned short dbase[32] = { /* Distance codes 0..29 base */
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+ 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+ 8193, 12289, 16385, 24577, 0, 0};
+ static const unsigned short dext[32] = { /* Distance codes 0..29 extra */
+ 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
+ 23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
+ 28, 28, 29, 29, 64, 64};
+
+ /*
+ Process a set of code lengths to create a canonical Huffman code. The
+ code lengths are lens[0..codes-1]. Each length corresponds to the
+ symbols 0..codes-1. The Huffman code is generated by first sorting the
+ symbols by length from short to long, and retaining the symbol order
+ for codes with equal lengths. Then the code starts with all zero bits
+ for the first code of the shortest length, and the codes are integer
+ increments for the same length, and zeros are appended as the length
+ increases. For the deflate format, these bits are stored backwards
+ from their more natural integer increment ordering, and so when the
+ decoding tables are built in the large loop below, the integer codes
+ are incremented backwards.
+
+ This routine assumes, but does not check, that all of the entries in
+ lens[] are in the range 0..MAXBITS. The caller must assure this.
+ 1..MAXBITS is interpreted as that code length. zero means that that
+ symbol does not occur in this code.
+
+ The codes are sorted by computing a count of codes for each length,
+ creating from that a table of starting indices for each length in the
+ sorted table, and then entering the symbols in order in the sorted
+ table. The sorted table is work[], with that space being provided by
+ the caller.
+
+ The length counts are used for other purposes as well, i.e. finding
+ the minimum and maximum length codes, determining if there are any
+ codes at all, checking for a valid set of lengths, and looking ahead
+ at length counts to determine sub-table sizes when building the
+ decoding tables.
+ */
+
+ /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
+ for (len = 0; len <= MAXBITS; len++)
+ count[len] = 0;
+ for (sym = 0; sym < codes; sym++)
+ count[lens[sym]]++;
+
+ /* bound code lengths, force root to be within code lengths */
+ root = *bits;
+ for (max = MAXBITS; max >= 1; max--)
+ if (count[max] != 0) break;
+ if (root > max) root = max;
+ if (max == 0) { /* no symbols to code at all */
+ here.op = (unsigned char)64; /* invalid code marker */
+ here.bits = (unsigned char)1;
+ here.val = (unsigned short)0;
+ *(*table)++ = here; /* make a table to force an error */
+ *(*table)++ = here;
+ *bits = 1;
+ return 0; /* no symbols, but wait for decoding to report error */
+ }
+ for (min = 1; min < max; min++)
+ if (count[min] != 0) break;
+ if (root < min) root = min;
+
+ /* check for an over-subscribed or incomplete set of lengths */
+ left = 1;
+ for (len = 1; len <= MAXBITS; len++) {
+ left <<= 1;
+ left -= count[len];
+ if (left < 0) return -1; /* over-subscribed */
+ }
+ if (left > 0 && (type == CODES || max != 1))
+ return -1; /* incomplete set */
+
+ /* generate offsets into symbol table for each length for sorting */
+ offs[1] = 0;
+ for (len = 1; len < MAXBITS; len++)
+ offs[len + 1] = offs[len] + count[len];
+
+ /* sort symbols by length, by symbol order within each length */
+ for (sym = 0; sym < codes; sym++)
+ if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym;
+
+ /*
+ Create and fill in decoding tables. In this loop, the table being
+ filled is at next and has curr index bits. The code being used is huff
+ with length len. That code is converted to an index by dropping drop
+ bits off of the bottom. For codes where len is less than drop + curr,
+ those top drop + curr - len bits are incremented through all values to
+ fill the table with replicated entries.
+
+ root is the number of index bits for the root table. When len exceeds
+ root, sub-tables are created pointed to by the root entry with an index
+ of the low root bits of huff. This is saved in low to check for when a
+ new sub-table should be started. drop is zero when the root table is
+ being filled, and drop is root when sub-tables are being filled.
+
+ When a new sub-table is needed, it is necessary to look ahead in the
+ code lengths to determine what size sub-table is needed. The length
+ counts are used for this, and so count[] is decremented as codes are
+ entered in the tables.
+
+ used keeps track of how many table entries have been allocated from the
+ provided *table space. It is checked for LENS and DIST tables against
+ the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
+ the initial root table size constants. See the comments in inftrees.h
+ for more information.
+
+ sym increments through all symbols, and the loop terminates when
+ all codes of length max, i.e. all codes, have been processed. This
+ routine permits incomplete codes, so another loop after this one fills
+ in the rest of the decoding tables with invalid code markers.
+ */
+
+ /* set up for code type */
+ switch (type) {
+ case CODES:
+ base = extra = work; /* dummy value--not used */
+ match = 20;
+ break;
+ case LENS:
+ base = lbase;
+ extra = lext;
+ match = 257;
+ break;
+ default: /* DISTS */
+ base = dbase;
+ extra = dext;
+ match = 0;
+ }
+
+ /* initialize state for loop */
+ huff = 0; /* starting code */
+ sym = 0; /* starting code symbol */
+ len = min; /* starting code length */
+ next = *table; /* current table to fill in */
+ curr = root; /* current table index bits */
+ drop = 0; /* current bits to drop from code for index */
+ low = (unsigned)(-1); /* trigger new sub-table when len > root */
+ used = 1U << root; /* use root table entries */
+ mask = used - 1; /* mask for comparing low */
+
+ /* check available table space */
+ if ((type == LENS && used > ENOUGH_LENS) ||
+ (type == DISTS && used > ENOUGH_DISTS))
+ return 1;
+
+ /* process all codes and make table entries */
+ for (;;) {
+ /* create table entry */
+ here.bits = (unsigned char)(len - drop);
+ if (work[sym] + 1U < match) {
+ here.op = (unsigned char)0;
+ here.val = work[sym];
+ }
+ else if (work[sym] >= match) {
+ here.op = (unsigned char)(extra[work[sym] - match]);
+ here.val = base[work[sym] - match];
+ }
+ else {
+ here.op = (unsigned char)(32 + 64); /* end of block */
+ here.val = 0;
+ }
+
+ /* replicate for those indices with low len bits equal to huff */
+ incr = 1U << (len - drop);
+ fill = 1U << curr;
+ min = fill; /* save offset to next table */
+ do {
+ fill -= incr;
+ next[(huff >> drop) + fill] = here;
+ } while (fill != 0);
+
+ /* backwards increment the len-bit code huff */
+ incr = 1U << (len - 1);
+ while (huff & incr)
+ incr >>= 1;
+ if (incr != 0) {
+ huff &= incr - 1;
+ huff += incr;
+ }
+ else
+ huff = 0;
+
+ /* go to next symbol, update count, len */
+ sym++;
+ if (--(count[len]) == 0) {
+ if (len == max) break;
+ len = lens[work[sym]];
+ }
+
+ /* create new sub-table if needed */
+ if (len > root && (huff & mask) != low) {
+ /* if first time, transition to sub-tables */
+ if (drop == 0)
+ drop = root;
+
+ /* increment past last table */
+ next += min; /* here min is 1 << curr */
+
+ /* determine length of next table */
+ curr = len - drop;
+ left = (int)(1 << curr);
+ while (curr + drop < max) {
+ left -= count[curr + drop];
+ if (left <= 0) break;
+ curr++;
+ left <<= 1;
+ }
+
+ /* check for enough space */
+ used += 1U << curr;
+ if ((type == LENS && used > ENOUGH_LENS) ||
+ (type == DISTS && used > ENOUGH_DISTS))
+ return 1;
+
+ /* point entry in root table to sub-table */
+ low = huff & mask;
+ (*table)[low].op = (unsigned char)curr;
+ (*table)[low].bits = (unsigned char)root;
+ (*table)[low].val = (unsigned short)(next - *table);
+ }
+ }
+
+ /* fill in remaining table entry if code is incomplete (guaranteed to have
+ at most one remaining entry, since if the code is incomplete, the
+ maximum code length that was allowed to get this far is one bit) */
+ if (huff != 0) {
+ here.op = (unsigned char)64; /* invalid code marker */
+ here.bits = (unsigned char)(len - drop);
+ here.val = (unsigned short)0;
+ next[huff] = here;
+ }
+
+ /* set return parameters */
+ *table += used;
+ *bits = root;
+ return 0;
+}
diff --git a/lib/zlib/inftrees.h b/lib/zlib/inftrees.h
new file mode 100644
index 0000000..baa53a0
--- /dev/null
+++ b/lib/zlib/inftrees.h
@@ -0,0 +1,62 @@
+/* inftrees.h -- header to use inftrees.c
+ * Copyright (C) 1995-2005, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+/* Structure for decoding tables. Each entry provides either the
+ information needed to do the operation requested by the code that
+ indexed that table entry, or it provides a pointer to another
+ table that indexes more bits of the code. op indicates whether
+ the entry is a pointer to another table, a literal, a length or
+ distance, an end-of-block, or an invalid code. For a table
+ pointer, the low four bits of op is the number of index bits of
+ that table. For a length or distance, the low four bits of op
+ is the number of extra bits to get after the code. bits is
+ the number of bits in this code or part of the code to drop off
+ of the bit buffer. val is the actual byte to output in the case
+ of a literal, the base length or distance, or the offset from
+ the current table to the next table. Each entry is four bytes. */
+typedef struct {
+ unsigned char op; /* operation, extra bits, table bits */
+ unsigned char bits; /* bits in this part of the code */
+ unsigned short val; /* offset in table or code value */
+} code;
+
+/* op values as set by inflate_table():
+ 00000000 - literal
+ 0000tttt - table link, tttt != 0 is the number of table index bits
+ 0001eeee - length or distance, eeee is the number of extra bits
+ 01100000 - end of block
+ 01000000 - invalid code
+ */
+
+/* Maximum size of the dynamic table. The maximum number of code structures is
+ 1444, which is the sum of 852 for literal/length codes and 592 for distance
+ codes. These values were found by exhaustive searches using the program
+ examples/enough.c found in the zlib distribtution. The arguments to that
+ program are the number of symbols, the initial root table size, and the
+ maximum bit length of a code. "enough 286 9 15" for literal/length codes
+ returns returns 852, and "enough 30 6 15" for distance codes returns 592.
+ The initial root table size (9 or 6) is found in the fifth argument of the
+ inflate_table() calls in inflate.c and infback.c. If the root table size is
+ changed, then these maximum sizes would be need to be recalculated and
+ updated. */
+#define ENOUGH_LENS 852
+#define ENOUGH_DISTS 592
+#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS)
+
+/* Type of code to build for inflate_table() */
+typedef enum {
+ CODES,
+ LENS,
+ DISTS
+} codetype;
+
+int ZLIB_INTERNAL inflate_table OF((codetype type, unsigned short FAR *lens,
+ unsigned codes, code FAR * FAR *table,
+ unsigned FAR *bits, unsigned short FAR *work));
diff --git a/lib/zlib/trees.c b/lib/zlib/trees.c
new file mode 100644
index 0000000..50cf4b4
--- /dev/null
+++ b/lib/zlib/trees.c
@@ -0,0 +1,1203 @@
+/* trees.c -- output deflated data using Huffman coding
+ * Copyright (C) 1995-2017 Jean-loup Gailly
+ * detect_data_type() function provided freely by Cosmin Truta, 2006
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ * ALGORITHM
+ *
+ * The "deflation" process uses several Huffman trees. The more
+ * common source values are represented by shorter bit sequences.
+ *
+ * Each code tree is stored in a compressed form which is itself
+ * a Huffman encoding of the lengths of all the code strings (in
+ * ascending order by source values). The actual code strings are
+ * reconstructed from the lengths in the inflate process, as described
+ * in the deflate specification.
+ *
+ * REFERENCES
+ *
+ * Deutsch, L.P.,"'Deflate' Compressed Data Format Specification".
+ * Available in ftp.uu.net:/pub/archiving/zip/doc/deflate-1.1.doc
+ *
+ * Storer, James A.
+ * Data Compression: Methods and Theory, pp. 49-50.
+ * Computer Science Press, 1988. ISBN 0-7167-8156-5.
+ *
+ * Sedgewick, R.
+ * Algorithms, p290.
+ * Addison-Wesley, 1983. ISBN 0-201-06672-6.
+ */
+
+/* @(#) $Id$ */
+
+/* #define GEN_TREES_H */
+
+#include "deflate.h"
+
+#ifdef ZLIB_DEBUG
+# include <ctype.h>
+#endif
+
+/* ===========================================================================
+ * Constants
+ */
+
+#define MAX_BL_BITS 7
+/* Bit length codes must not exceed MAX_BL_BITS bits */
+
+#define END_BLOCK 256
+/* end of block literal code */
+
+#define REP_3_6 16
+/* repeat previous bit length 3-6 times (2 bits of repeat count) */
+
+#define REPZ_3_10 17
+/* repeat a zero length 3-10 times (3 bits of repeat count) */
+
+#define REPZ_11_138 18
+/* repeat a zero length 11-138 times (7 bits of repeat count) */
+
+local const int extra_lbits[LENGTH_CODES] /* extra bits for each length code */
+ = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0};
+
+local const int extra_dbits[D_CODES] /* extra bits for each distance code */
+ = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
+
+local const int extra_blbits[BL_CODES]/* extra bits for each bit length code */
+ = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7};
+
+local const uch bl_order[BL_CODES]
+ = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15};
+/* The lengths of the bit length codes are sent in order of decreasing
+ * probability, to avoid transmitting the lengths for unused bit length codes.
+ */
+
+/* ===========================================================================
+ * Local data. These are initialized only once.
+ */
+
+#define DIST_CODE_LEN 512 /* see definition of array dist_code below */
+
+#if defined(GEN_TREES_H) || !defined(STDC)
+/* non ANSI compilers may not accept trees.h */
+
+local ct_data static_ltree[L_CODES+2];
+/* The static literal tree. Since the bit lengths are imposed, there is no
+ * need for the L_CODES extra codes used during heap construction. However
+ * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
+ * below).
+ */
+
+local ct_data static_dtree[D_CODES];
+/* The static distance tree. (Actually a trivial tree since all codes use
+ * 5 bits.)
+ */
+
+uch _dist_code[DIST_CODE_LEN];
+/* Distance codes. The first 256 values correspond to the distances
+ * 3 .. 258, the last 256 values correspond to the top 8 bits of
+ * the 15 bit distances.
+ */
+
+uch _length_code[MAX_MATCH-MIN_MATCH+1];
+/* length code for each normalized match length (0 == MIN_MATCH) */
+
+local int base_length[LENGTH_CODES];
+/* First normalized length for each code (0 = MIN_MATCH) */
+
+local int base_dist[D_CODES];
+/* First normalized distance for each code (0 = distance of 1) */
+
+#else
+# include "trees.h"
+#endif /* GEN_TREES_H */
+
+struct static_tree_desc_s {
+ const ct_data *static_tree; /* static tree or NULL */
+ const intf *extra_bits; /* extra bits for each code or NULL */
+ int extra_base; /* base index for extra_bits */
+ int elems; /* max number of elements in the tree */
+ int max_length; /* max bit length for the codes */
+};
+
+local const static_tree_desc static_l_desc =
+{static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS};
+
+local const static_tree_desc static_d_desc =
+{static_dtree, extra_dbits, 0, D_CODES, MAX_BITS};
+
+local const static_tree_desc static_bl_desc =
+{(const ct_data *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS};
+
+/* ===========================================================================
+ * Local (static) routines in this file.
+ */
+
+local void tr_static_init OF((void));
+local void init_block OF((deflate_state *s));
+local void pqdownheap OF((deflate_state *s, ct_data *tree, int k));
+local void gen_bitlen OF((deflate_state *s, tree_desc *desc));
+local void gen_codes OF((ct_data *tree, int max_code, ushf *bl_count));
+local void build_tree OF((deflate_state *s, tree_desc *desc));
+local void scan_tree OF((deflate_state *s, ct_data *tree, int max_code));
+local void send_tree OF((deflate_state *s, ct_data *tree, int max_code));
+local int build_bl_tree OF((deflate_state *s));
+local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes,
+ int blcodes));
+local void compress_block OF((deflate_state *s, const ct_data *ltree,
+ const ct_data *dtree));
+local int detect_data_type OF((deflate_state *s));
+local unsigned bi_reverse OF((unsigned value, int length));
+local void bi_windup OF((deflate_state *s));
+local void bi_flush OF((deflate_state *s));
+
+#ifdef GEN_TREES_H
+local void gen_trees_header OF((void));
+#endif
+
+#ifndef ZLIB_DEBUG
+# define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len)
+ /* Send a code of the given tree. c and tree must not have side effects */
+
+#else /* !ZLIB_DEBUG */
+# define send_code(s, c, tree) \
+ { if (z_verbose>2) fprintf(stderr,"\ncd %3d ",(c)); \
+ send_bits(s, tree[c].Code, tree[c].Len); }
+#endif
+
+/* ===========================================================================
+ * Output a short LSB first on the stream.
+ * IN assertion: there is enough room in pendingBuf.
+ */
+#define put_short(s, w) { \
+ put_byte(s, (uch)((w) & 0xff)); \
+ put_byte(s, (uch)((ush)(w) >> 8)); \
+}
+
+/* ===========================================================================
+ * Send a value on a given number of bits.
+ * IN assertion: length <= 16 and value fits in length bits.
+ */
+#ifdef ZLIB_DEBUG
+local void send_bits OF((deflate_state *s, int value, int length));
+
+local void send_bits(s, value, length)
+ deflate_state *s;
+ int value; /* value to send */
+ int length; /* number of bits */
+{
+ Tracevv((stderr," l %2d v %4x ", length, value));
+ Assert(length > 0 && length <= 15, "invalid length");
+ s->bits_sent += (ulg)length;
+
+ /* If not enough room in bi_buf, use (valid) bits from bi_buf and
+ * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid))
+ * unused bits in value.
+ */
+ if (s->bi_valid > (int)Buf_size - length) {
+ s->bi_buf |= (ush)value << s->bi_valid;
+ put_short(s, s->bi_buf);
+ s->bi_buf = (ush)value >> (Buf_size - s->bi_valid);
+ s->bi_valid += length - Buf_size;
+ } else {
+ s->bi_buf |= (ush)value << s->bi_valid;
+ s->bi_valid += length;
+ }
+}
+#else /* !ZLIB_DEBUG */
+
+#define send_bits(s, value, length) \
+{ int len = length;\
+ if (s->bi_valid > (int)Buf_size - len) {\
+ int val = (int)value;\
+ s->bi_buf |= (ush)val << s->bi_valid;\
+ put_short(s, s->bi_buf);\
+ s->bi_buf = (ush)val >> (Buf_size - s->bi_valid);\
+ s->bi_valid += len - Buf_size;\
+ } else {\
+ s->bi_buf |= (ush)(value) << s->bi_valid;\
+ s->bi_valid += len;\
+ }\
+}
+#endif /* ZLIB_DEBUG */
+
+
+/* the arguments must not have side effects */
+
+/* ===========================================================================
+ * Initialize the various 'constant' tables.
+ */
+local void tr_static_init()
+{
+#if defined(GEN_TREES_H) || !defined(STDC)
+ static int static_init_done = 0;
+ int n; /* iterates over tree elements */
+ int bits; /* bit counter */
+ int length; /* length value */
+ int code; /* code value */
+ int dist; /* distance index */
+ ush bl_count[MAX_BITS+1];
+ /* number of codes at each bit length for an optimal tree */
+
+ if (static_init_done) return;
+
+ /* For some embedded targets, global variables are not initialized: */
+#ifdef NO_INIT_GLOBAL_POINTERS
+ static_l_desc.static_tree = static_ltree;
+ static_l_desc.extra_bits = extra_lbits;
+ static_d_desc.static_tree = static_dtree;
+ static_d_desc.extra_bits = extra_dbits;
+ static_bl_desc.extra_bits = extra_blbits;
+#endif
+
+ /* Initialize the mapping length (0..255) -> length code (0..28) */
+ length = 0;
+ for (code = 0; code < LENGTH_CODES-1; code++) {
+ base_length[code] = length;
+ for (n = 0; n < (1<<extra_lbits[code]); n++) {
+ _length_code[length++] = (uch)code;
+ }
+ }
+ Assert (length == 256, "tr_static_init: length != 256");
+ /* Note that the length 255 (match length 258) can be represented
+ * in two different ways: code 284 + 5 bits or code 285, so we
+ * overwrite length_code[255] to use the best encoding:
+ */
+ _length_code[length-1] = (uch)code;
+
+ /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+ dist = 0;
+ for (code = 0 ; code < 16; code++) {
+ base_dist[code] = dist;
+ for (n = 0; n < (1<<extra_dbits[code]); n++) {
+ _dist_code[dist++] = (uch)code;
+ }
+ }
+ Assert (dist == 256, "tr_static_init: dist != 256");
+ dist >>= 7; /* from now on, all distances are divided by 128 */
+ for ( ; code < D_CODES; code++) {
+ base_dist[code] = dist << 7;
+ for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) {
+ _dist_code[256 + dist++] = (uch)code;
+ }
+ }
+ Assert (dist == 256, "tr_static_init: 256+dist != 512");
+
+ /* Construct the codes of the static literal tree */
+ for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0;
+ n = 0;
+ while (n <= 143) static_ltree[n++].Len = 8, bl_count[8]++;
+ while (n <= 255) static_ltree[n++].Len = 9, bl_count[9]++;
+ while (n <= 279) static_ltree[n++].Len = 7, bl_count[7]++;
+ while (n <= 287) static_ltree[n++].Len = 8, bl_count[8]++;
+ /* Codes 286 and 287 do not exist, but we must include them in the
+ * tree construction to get a canonical Huffman tree (longest code
+ * all ones)
+ */
+ gen_codes((ct_data *)static_ltree, L_CODES+1, bl_count);
+
+ /* The static distance tree is trivial: */
+ for (n = 0; n < D_CODES; n++) {
+ static_dtree[n].Len = 5;
+ static_dtree[n].Code = bi_reverse((unsigned)n, 5);
+ }
+ static_init_done = 1;
+
+# ifdef GEN_TREES_H
+ gen_trees_header();
+# endif
+#endif /* defined(GEN_TREES_H) || !defined(STDC) */
+}
+
+/* ===========================================================================
+ * Genererate the file trees.h describing the static trees.
+ */
+#ifdef GEN_TREES_H
+# ifndef ZLIB_DEBUG
+# include <stdio.h>
+# endif
+
+# define SEPARATOR(i, last, width) \
+ ((i) == (last)? "\n};\n\n" : \
+ ((i) % (width) == (width)-1 ? ",\n" : ", "))
+
+void gen_trees_header()
+{
+ FILE *header = fopen("trees.h", "w");
+ int i;
+
+ Assert (header != NULL, "Can't open trees.h");
+ fprintf(header,
+ "/* header created automatically with -DGEN_TREES_H */\n\n");
+
+ fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n");
+ for (i = 0; i < L_CODES+2; i++) {
+ fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code,
+ static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5));
+ }
+
+ fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n");
+ for (i = 0; i < D_CODES; i++) {
+ fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code,
+ static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5));
+ }
+
+ fprintf(header, "const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = {\n");
+ for (i = 0; i < DIST_CODE_LEN; i++) {
+ fprintf(header, "%2u%s", _dist_code[i],
+ SEPARATOR(i, DIST_CODE_LEN-1, 20));
+ }
+
+ fprintf(header,
+ "const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= {\n");
+ for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) {
+ fprintf(header, "%2u%s", _length_code[i],
+ SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20));
+ }
+
+ fprintf(header, "local const int base_length[LENGTH_CODES] = {\n");
+ for (i = 0; i < LENGTH_CODES; i++) {
+ fprintf(header, "%1u%s", base_length[i],
+ SEPARATOR(i, LENGTH_CODES-1, 20));
+ }
+
+ fprintf(header, "local const int base_dist[D_CODES] = {\n");
+ for (i = 0; i < D_CODES; i++) {
+ fprintf(header, "%5u%s", base_dist[i],
+ SEPARATOR(i, D_CODES-1, 10));
+ }
+
+ fclose(header);
+}
+#endif /* GEN_TREES_H */
+
+/* ===========================================================================
+ * Initialize the tree data structures for a new zlib stream.
+ */
+void ZLIB_INTERNAL _tr_init(s)
+ deflate_state *s;
+{
+ tr_static_init();
+
+ s->l_desc.dyn_tree = s->dyn_ltree;
+ s->l_desc.stat_desc = &static_l_desc;
+
+ s->d_desc.dyn_tree = s->dyn_dtree;
+ s->d_desc.stat_desc = &static_d_desc;
+
+ s->bl_desc.dyn_tree = s->bl_tree;
+ s->bl_desc.stat_desc = &static_bl_desc;
+
+ s->bi_buf = 0;
+ s->bi_valid = 0;
+#ifdef ZLIB_DEBUG
+ s->compressed_len = 0L;
+ s->bits_sent = 0L;
+#endif
+
+ /* Initialize the first block of the first file: */
+ init_block(s);
+}
+
+/* ===========================================================================
+ * Initialize a new block.
+ */
+local void init_block(s)
+ deflate_state *s;
+{
+ int n; /* iterates over tree elements */
+
+ /* Initialize the trees. */
+ for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0;
+ for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0;
+ for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0;
+
+ s->dyn_ltree[END_BLOCK].Freq = 1;
+ s->opt_len = s->static_len = 0L;
+ s->last_lit = s->matches = 0;
+}
+
+#define SMALLEST 1
+/* Index within the heap array of least frequent node in the Huffman tree */
+
+
+/* ===========================================================================
+ * Remove the smallest element from the heap and recreate the heap with
+ * one less element. Updates heap and heap_len.
+ */
+#define pqremove(s, tree, top) \
+{\
+ top = s->heap[SMALLEST]; \
+ s->heap[SMALLEST] = s->heap[s->heap_len--]; \
+ pqdownheap(s, tree, SMALLEST); \
+}
+
+/* ===========================================================================
+ * Compares to subtrees, using the tree depth as tie breaker when
+ * the subtrees have equal frequency. This minimizes the worst case length.
+ */
+#define smaller(tree, n, m, depth) \
+ (tree[n].Freq < tree[m].Freq || \
+ (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m]))
+
+/* ===========================================================================
+ * Restore the heap property by moving down the tree starting at node k,
+ * exchanging a node with the smallest of its two sons if necessary, stopping
+ * when the heap property is re-established (each father smaller than its
+ * two sons).
+ */
+local void pqdownheap(s, tree, k)
+ deflate_state *s;
+ ct_data *tree; /* the tree to restore */
+ int k; /* node to move down */
+{
+ int v = s->heap[k];
+ int j = k << 1; /* left son of k */
+ while (j <= s->heap_len) {
+ /* Set j to the smallest of the two sons: */
+ if (j < s->heap_len &&
+ smaller(tree, s->heap[j+1], s->heap[j], s->depth)) {
+ j++;
+ }
+ /* Exit if v is smaller than both sons */
+ if (smaller(tree, v, s->heap[j], s->depth)) break;
+
+ /* Exchange v with the smallest son */
+ s->heap[k] = s->heap[j]; k = j;
+
+ /* And continue down the tree, setting j to the left son of k */
+ j <<= 1;
+ }
+ s->heap[k] = v;
+}
+
+/* ===========================================================================
+ * Compute the optimal bit lengths for a tree and update the total bit length
+ * for the current block.
+ * IN assertion: the fields freq and dad are set, heap[heap_max] and
+ * above are the tree nodes sorted by increasing frequency.
+ * OUT assertions: the field len is set to the optimal bit length, the
+ * array bl_count contains the frequencies for each bit length.
+ * The length opt_len is updated; static_len is also updated if stree is
+ * not null.
+ */
+local void gen_bitlen(s, desc)
+ deflate_state *s;
+ tree_desc *desc; /* the tree descriptor */
+{
+ ct_data *tree = desc->dyn_tree;
+ int max_code = desc->max_code;
+ const ct_data *stree = desc->stat_desc->static_tree;
+ const intf *extra = desc->stat_desc->extra_bits;
+ int base = desc->stat_desc->extra_base;
+ int max_length = desc->stat_desc->max_length;
+ int h; /* heap index */
+ int n, m; /* iterate over the tree elements */
+ int bits; /* bit length */
+ int xbits; /* extra bits */
+ ush f; /* frequency */
+ int overflow = 0; /* number of elements with bit length too large */
+
+ for (bits = 0; bits <= MAX_BITS; bits++) s->bl_count[bits] = 0;
+
+ /* In a first pass, compute the optimal bit lengths (which may
+ * overflow in the case of the bit length tree).
+ */
+ tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */
+
+ for (h = s->heap_max+1; h < HEAP_SIZE; h++) {
+ n = s->heap[h];
+ bits = tree[tree[n].Dad].Len + 1;
+ if (bits > max_length) bits = max_length, overflow++;
+ tree[n].Len = (ush)bits;
+ /* We overwrite tree[n].Dad which is no longer needed */
+
+ if (n > max_code) continue; /* not a leaf node */
+
+ s->bl_count[bits]++;
+ xbits = 0;
+ if (n >= base) xbits = extra[n-base];
+ f = tree[n].Freq;
+ s->opt_len += (ulg)f * (unsigned)(bits + xbits);
+ if (stree) s->static_len += (ulg)f * (unsigned)(stree[n].Len + xbits);
+ }
+ if (overflow == 0) return;
+
+ Tracev((stderr,"\nbit length overflow\n"));
+ /* This happens for example on obj2 and pic of the Calgary corpus */
+
+ /* Find the first bit length which could increase: */
+ do {
+ bits = max_length-1;
+ while (s->bl_count[bits] == 0) bits--;
+ s->bl_count[bits]--; /* move one leaf down the tree */
+ s->bl_count[bits+1] += 2; /* move one overflow item as its brother */
+ s->bl_count[max_length]--;
+ /* The brother of the overflow item also moves one step up,
+ * but this does not affect bl_count[max_length]
+ */
+ overflow -= 2;
+ } while (overflow > 0);
+
+ /* Now recompute all bit lengths, scanning in increasing frequency.
+ * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
+ * lengths instead of fixing only the wrong ones. This idea is taken
+ * from 'ar' written by Haruhiko Okumura.)
+ */
+ for (bits = max_length; bits != 0; bits--) {
+ n = s->bl_count[bits];
+ while (n != 0) {
+ m = s->heap[--h];
+ if (m > max_code) continue;
+ if ((unsigned) tree[m].Len != (unsigned) bits) {
+ Tracev((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
+ s->opt_len += ((ulg)bits - tree[m].Len) * tree[m].Freq;
+ tree[m].Len = (ush)bits;
+ }
+ n--;
+ }
+ }
+}
+
+/* ===========================================================================
+ * Generate the codes for a given tree and bit counts (which need not be
+ * optimal).
+ * IN assertion: the array bl_count contains the bit length statistics for
+ * the given tree and the field len is set for all tree elements.
+ * OUT assertion: the field code is set for all tree elements of non
+ * zero code length.
+ */
+local void gen_codes (tree, max_code, bl_count)
+ ct_data *tree; /* the tree to decorate */
+ int max_code; /* largest code with non zero frequency */
+ ushf *bl_count; /* number of codes at each bit length */
+{
+ ush next_code[MAX_BITS+1]; /* next code value for each bit length */
+ unsigned code = 0; /* running code value */
+ int bits; /* bit index */
+ int n; /* code index */
+
+ /* The distribution counts are first used to generate the code values
+ * without bit reversal.
+ */
+ for (bits = 1; bits <= MAX_BITS; bits++) {
+ code = (code + bl_count[bits-1]) << 1;
+ next_code[bits] = (ush)code;
+ }
+ /* Check that the bit counts in bl_count are consistent. The last code
+ * must be all ones.
+ */
+ Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+ "inconsistent bit counts");
+ Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+ for (n = 0; n <= max_code; n++) {
+ int len = tree[n].Len;
+ if (len == 0) continue;
+ /* Now reverse the bits */
+ tree[n].Code = (ush)bi_reverse(next_code[len]++, len);
+
+ Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
+ n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
+ }
+}
+
+/* ===========================================================================
+ * Construct one Huffman tree and assigns the code bit strings and lengths.
+ * Update the total bit length for the current block.
+ * IN assertion: the field freq is set for all tree elements.
+ * OUT assertions: the fields len and code are set to the optimal bit length
+ * and corresponding code. The length opt_len is updated; static_len is
+ * also updated if stree is not null. The field max_code is set.
+ */
+local void build_tree(s, desc)
+ deflate_state *s;
+ tree_desc *desc; /* the tree descriptor */
+{
+ ct_data *tree = desc->dyn_tree;
+ const ct_data *stree = desc->stat_desc->static_tree;
+ int elems = desc->stat_desc->elems;
+ int n, m; /* iterate over heap elements */
+ int max_code = -1; /* largest code with non zero frequency */
+ int node; /* new node being created */
+
+ /* Construct the initial heap, with least frequent element in
+ * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+ * heap[0] is not used.
+ */
+ s->heap_len = 0, s->heap_max = HEAP_SIZE;
+
+ for (n = 0; n < elems; n++) {
+ if (tree[n].Freq != 0) {
+ s->heap[++(s->heap_len)] = max_code = n;
+ s->depth[n] = 0;
+ } else {
+ tree[n].Len = 0;
+ }
+ }
+
+ /* The pkzip format requires that at least one distance code exists,
+ * and that at least one bit should be sent even if there is only one
+ * possible code. So to avoid special checks later on we force at least
+ * two codes of non zero frequency.
+ */
+ while (s->heap_len < 2) {
+ node = s->heap[++(s->heap_len)] = (max_code < 2 ? ++max_code : 0);
+ tree[node].Freq = 1;
+ s->depth[node] = 0;
+ s->opt_len--; if (stree) s->static_len -= stree[node].Len;
+ /* node is 0 or 1 so it does not have extra bits */
+ }
+ desc->max_code = max_code;
+
+ /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+ * establish sub-heaps of increasing lengths:
+ */
+ for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n);
+
+ /* Construct the Huffman tree by repeatedly combining the least two
+ * frequent nodes.
+ */
+ node = elems; /* next internal node of the tree */
+ do {
+ pqremove(s, tree, n); /* n = node of least frequency */
+ m = s->heap[SMALLEST]; /* m = node of next least frequency */
+
+ s->heap[--(s->heap_max)] = n; /* keep the nodes sorted by frequency */
+ s->heap[--(s->heap_max)] = m;
+
+ /* Create a new node father of n and m */
+ tree[node].Freq = tree[n].Freq + tree[m].Freq;
+ s->depth[node] = (uch)((s->depth[n] >= s->depth[m] ?
+ s->depth[n] : s->depth[m]) + 1);
+ tree[n].Dad = tree[m].Dad = (ush)node;
+#ifdef DUMP_BL_TREE
+ if (tree == s->bl_tree) {
+ fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)",
+ node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq);
+ }
+#endif
+ /* and insert the new node in the heap */
+ s->heap[SMALLEST] = node++;
+ pqdownheap(s, tree, SMALLEST);
+
+ } while (s->heap_len >= 2);
+
+ s->heap[--(s->heap_max)] = s->heap[SMALLEST];
+
+ /* At this point, the fields freq and dad are set. We can now
+ * generate the bit lengths.
+ */
+ gen_bitlen(s, (tree_desc *)desc);
+
+ /* The field len is now set, we can generate the bit codes */
+ gen_codes ((ct_data *)tree, max_code, s->bl_count);
+}
+
+/* ===========================================================================
+ * Scan a literal or distance tree to determine the frequencies of the codes
+ * in the bit length tree.
+ */
+local void scan_tree (s, tree, max_code)
+ deflate_state *s;
+ ct_data *tree; /* the tree to be scanned */
+ int max_code; /* and its largest code of non zero frequency */
+{
+ int n; /* iterates over all tree elements */
+ int prevlen = -1; /* last emitted length */
+ int curlen; /* length of current code */
+ int nextlen = tree[0].Len; /* length of next code */
+ int count = 0; /* repeat count of the current code */
+ int max_count = 7; /* max repeat count */
+ int min_count = 4; /* min repeat count */
+
+ if (nextlen == 0) max_count = 138, min_count = 3;
+ tree[max_code+1].Len = (ush)0xffff; /* guard */
+
+ for (n = 0; n <= max_code; n++) {
+ curlen = nextlen; nextlen = tree[n+1].Len;
+ if (++count < max_count && curlen == nextlen) {
+ continue;
+ } else if (count < min_count) {
+ s->bl_tree[curlen].Freq += count;
+ } else if (curlen != 0) {
+ if (curlen != prevlen) s->bl_tree[curlen].Freq++;
+ s->bl_tree[REP_3_6].Freq++;
+ } else if (count <= 10) {
+ s->bl_tree[REPZ_3_10].Freq++;
+ } else {
+ s->bl_tree[REPZ_11_138].Freq++;
+ }
+ count = 0; prevlen = curlen;
+ if (nextlen == 0) {
+ max_count = 138, min_count = 3;
+ } else if (curlen == nextlen) {
+ max_count = 6, min_count = 3;
+ } else {
+ max_count = 7, min_count = 4;
+ }
+ }
+}
+
+/* ===========================================================================
+ * Send a literal or distance tree in compressed form, using the codes in
+ * bl_tree.
+ */
+local void send_tree (s, tree, max_code)
+ deflate_state *s;
+ ct_data *tree; /* the tree to be scanned */
+ int max_code; /* and its largest code of non zero frequency */
+{
+ int n; /* iterates over all tree elements */
+ int prevlen = -1; /* last emitted length */
+ int curlen; /* length of current code */
+ int nextlen = tree[0].Len; /* length of next code */
+ int count = 0; /* repeat count of the current code */
+ int max_count = 7; /* max repeat count */
+ int min_count = 4; /* min repeat count */
+
+ /* tree[max_code+1].Len = -1; */ /* guard already set */
+ if (nextlen == 0) max_count = 138, min_count = 3;
+
+ for (n = 0; n <= max_code; n++) {
+ curlen = nextlen; nextlen = tree[n+1].Len;
+ if (++count < max_count && curlen == nextlen) {
+ continue;
+ } else if (count < min_count) {
+ do { send_code(s, curlen, s->bl_tree); } while (--count != 0);
+
+ } else if (curlen != 0) {
+ if (curlen != prevlen) {
+ send_code(s, curlen, s->bl_tree); count--;
+ }
+ Assert(count >= 3 && count <= 6, " 3_6?");
+ send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2);
+
+ } else if (count <= 10) {
+ send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3);
+
+ } else {
+ send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7);
+ }
+ count = 0; prevlen = curlen;
+ if (nextlen == 0) {
+ max_count = 138, min_count = 3;
+ } else if (curlen == nextlen) {
+ max_count = 6, min_count = 3;
+ } else {
+ max_count = 7, min_count = 4;
+ }
+ }
+}
+
+/* ===========================================================================
+ * Construct the Huffman tree for the bit lengths and return the index in
+ * bl_order of the last bit length code to send.
+ */
+local int build_bl_tree(s)
+ deflate_state *s;
+{
+ int max_blindex; /* index of last bit length code of non zero freq */
+
+ /* Determine the bit length frequencies for literal and distance trees */
+ scan_tree(s, (ct_data *)s->dyn_ltree, s->l_desc.max_code);
+ scan_tree(s, (ct_data *)s->dyn_dtree, s->d_desc.max_code);
+
+ /* Build the bit length tree: */
+ build_tree(s, (tree_desc *)(&(s->bl_desc)));
+ /* opt_len now includes the length of the tree representations, except
+ * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+ */
+
+ /* Determine the number of bit length codes to send. The pkzip format
+ * requires that at least 4 bit length codes be sent. (appnote.txt says
+ * 3 but the actual value used is 4.)
+ */
+ for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) {
+ if (s->bl_tree[bl_order[max_blindex]].Len != 0) break;
+ }
+ /* Update opt_len to include the bit length tree and counts */
+ s->opt_len += 3*((ulg)max_blindex+1) + 5+5+4;
+ Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
+ s->opt_len, s->static_len));
+
+ return max_blindex;
+}
+
+/* ===========================================================================
+ * Send the header for a block using dynamic Huffman trees: the counts, the
+ * lengths of the bit length codes, the literal tree and the distance tree.
+ * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ */
+local void send_all_trees(s, lcodes, dcodes, blcodes)
+ deflate_state *s;
+ int lcodes, dcodes, blcodes; /* number of codes for each tree */
+{
+ int rank; /* index in bl_order */
+
+ Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
+ Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
+ "too many codes");
+ Tracev((stderr, "\nbl counts: "));
+ send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */
+ send_bits(s, dcodes-1, 5);
+ send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */
+ for (rank = 0; rank < blcodes; rank++) {
+ Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
+ send_bits(s, s->bl_tree[bl_order[rank]].Len, 3);
+ }
+ Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));
+
+ send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */
+ Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));
+
+ send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */
+ Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
+}
+
+/* ===========================================================================
+ * Send a stored block
+ */
+void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last)
+ deflate_state *s;
+ charf *buf; /* input block */
+ ulg stored_len; /* length of input block */
+ int last; /* one if this is the last block for a file */
+{
+ send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */
+ bi_windup(s); /* align on byte boundary */
+ put_short(s, (ush)stored_len);
+ put_short(s, (ush)~stored_len);
+ zmemcpy(s->pending_buf + s->pending, (Bytef *)buf, stored_len);
+ s->pending += stored_len;
+#ifdef ZLIB_DEBUG
+ s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L;
+ s->compressed_len += (stored_len + 4) << 3;
+ s->bits_sent += 2*16;
+ s->bits_sent += stored_len<<3;
+#endif
+}
+
+/* ===========================================================================
+ * Flush the bits in the bit buffer to pending output (leaves at most 7 bits)
+ */
+void ZLIB_INTERNAL _tr_flush_bits(s)
+ deflate_state *s;
+{
+ bi_flush(s);
+}
+
+/* ===========================================================================
+ * Send one empty static block to give enough lookahead for inflate.
+ * This takes 10 bits, of which 7 may remain in the bit buffer.
+ */
+void ZLIB_INTERNAL _tr_align(s)
+ deflate_state *s;
+{
+ send_bits(s, STATIC_TREES<<1, 3);
+ send_code(s, END_BLOCK, static_ltree);
+#ifdef ZLIB_DEBUG
+ s->compressed_len += 10L; /* 3 for block type, 7 for EOB */
+#endif
+ bi_flush(s);
+}
+
+/* ===========================================================================
+ * Determine the best encoding for the current block: dynamic trees, static
+ * trees or store, and write out the encoded block.
+ */
+void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
+ deflate_state *s;
+ charf *buf; /* input block, or NULL if too old */
+ ulg stored_len; /* length of input block */
+ int last; /* one if this is the last block for a file */
+{
+ ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */
+ int max_blindex = 0; /* index of last bit length code of non zero freq */
+
+ /* Build the Huffman trees unless a stored block is forced */
+ if (s->level > 0) {
+
+ /* Check if the file is binary or text */
+ if (s->strm->data_type == Z_UNKNOWN)
+ s->strm->data_type = detect_data_type(s);
+
+ /* Construct the literal and distance trees */
+ build_tree(s, (tree_desc *)(&(s->l_desc)));
+ Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
+ s->static_len));
+
+ build_tree(s, (tree_desc *)(&(s->d_desc)));
+ Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
+ s->static_len));
+ /* At this point, opt_len and static_len are the total bit lengths of
+ * the compressed block data, excluding the tree representations.
+ */
+
+ /* Build the bit length tree for the above two trees, and get the index
+ * in bl_order of the last bit length code to send.
+ */
+ max_blindex = build_bl_tree(s);
+
+ /* Determine the best encoding. Compute the block lengths in bytes. */
+ opt_lenb = (s->opt_len+3+7)>>3;
+ static_lenb = (s->static_len+3+7)>>3;
+
+ Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
+ opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
+ s->last_lit));
+
+ if (static_lenb <= opt_lenb) opt_lenb = static_lenb;
+
+ } else {
+ Assert(buf != (char*)0, "lost buf");
+ opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
+ }
+
+#ifdef FORCE_STORED
+ if (buf != (char*)0) { /* force stored block */
+#else
+ if (stored_len+4 <= opt_lenb && buf != (char*)0) {
+ /* 4: two words for the lengths */
+#endif
+ /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+ * Otherwise we can't have processed more than WSIZE input bytes since
+ * the last block flush, because compression would have been
+ * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+ * transform a block into a stored block.
+ */
+ _tr_stored_block(s, buf, stored_len, last);
+
+#ifdef FORCE_STATIC
+ } else if (static_lenb >= 0) { /* force static trees */
+#else
+ } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) {
+#endif
+ send_bits(s, (STATIC_TREES<<1)+last, 3);
+ compress_block(s, (const ct_data *)static_ltree,
+ (const ct_data *)static_dtree);
+#ifdef ZLIB_DEBUG
+ s->compressed_len += 3 + s->static_len;
+#endif
+ } else {
+ send_bits(s, (DYN_TREES<<1)+last, 3);
+ send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1,
+ max_blindex+1);
+ compress_block(s, (const ct_data *)s->dyn_ltree,
+ (const ct_data *)s->dyn_dtree);
+#ifdef ZLIB_DEBUG
+ s->compressed_len += 3 + s->opt_len;
+#endif
+ }
+ Assert (s->compressed_len == s->bits_sent, "bad compressed size");
+ /* The above check is made mod 2^32, for files larger than 512 MB
+ * and uLong implemented on 32 bits.
+ */
+ init_block(s);
+
+ if (last) {
+ bi_windup(s);
+#ifdef ZLIB_DEBUG
+ s->compressed_len += 7; /* align on byte boundary */
+#endif
+ }
+ Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
+ s->compressed_len-7*last));
+}
+
+/* ===========================================================================
+ * Save the match info and tally the frequency counts. Return true if
+ * the current block must be flushed.
+ */
+int ZLIB_INTERNAL _tr_tally (s, dist, lc)
+ deflate_state *s;
+ unsigned dist; /* distance of matched string */
+ unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */
+{
+ s->d_buf[s->last_lit] = (ush)dist;
+ s->l_buf[s->last_lit++] = (uch)lc;
+ if (dist == 0) {
+ /* lc is the unmatched char */
+ s->dyn_ltree[lc].Freq++;
+ } else {
+ s->matches++;
+ /* Here, lc is the match length - MIN_MATCH */
+ dist--; /* dist = match distance - 1 */
+ Assert((ush)dist < (ush)MAX_DIST(s) &&
+ (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
+ (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match");
+
+ s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++;
+ s->dyn_dtree[d_code(dist)].Freq++;
+ }
+
+#ifdef TRUNCATE_BLOCK
+ /* Try to guess if it is profitable to stop the current block here */
+ if ((s->last_lit & 0x1fff) == 0 && s->level > 2) {
+ /* Compute an upper bound for the compressed length */
+ ulg out_length = (ulg)s->last_lit*8L;
+ ulg in_length = (ulg)((long)s->strstart - s->block_start);
+ int dcode;
+ for (dcode = 0; dcode < D_CODES; dcode++) {
+ out_length += (ulg)s->dyn_dtree[dcode].Freq *
+ (5L+extra_dbits[dcode]);
+ }
+ out_length >>= 3;
+ Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
+ s->last_lit, in_length, out_length,
+ 100L - out_length*100L/in_length));
+ if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1;
+ }
+#endif
+ return (s->last_lit == s->lit_bufsize-1);
+ /* We avoid equality with lit_bufsize because of wraparound at 64K
+ * on 16 bit machines and because stored blocks are restricted to
+ * 64K-1 bytes.
+ */
+}
+
+/* ===========================================================================
+ * Send the block data compressed using the given Huffman trees
+ */
+local void compress_block(s, ltree, dtree)
+ deflate_state *s;
+ const ct_data *ltree; /* literal tree */
+ const ct_data *dtree; /* distance tree */
+{
+ unsigned dist; /* distance of matched string */
+ int lc; /* match length or unmatched char (if dist == 0) */
+ unsigned lx = 0; /* running index in l_buf */
+ unsigned code; /* the code to send */
+ int extra; /* number of extra bits to send */
+
+ if (s->last_lit != 0) do {
+ dist = s->d_buf[lx];
+ lc = s->l_buf[lx++];
+ if (dist == 0) {
+ send_code(s, lc, ltree); /* send a literal byte */
+ Tracecv(isgraph(lc), (stderr," '%c' ", lc));
+ } else {
+ /* Here, lc is the match length - MIN_MATCH */
+ code = _length_code[lc];
+ send_code(s, code+LITERALS+1, ltree); /* send the length code */
+ extra = extra_lbits[code];
+ if (extra != 0) {
+ lc -= base_length[code];
+ send_bits(s, lc, extra); /* send the extra length bits */
+ }
+ dist--; /* dist is now the match distance - 1 */
+ code = d_code(dist);
+ Assert (code < D_CODES, "bad d_code");
+
+ send_code(s, code, dtree); /* send the distance code */
+ extra = extra_dbits[code];
+ if (extra != 0) {
+ dist -= (unsigned)base_dist[code];
+ send_bits(s, dist, extra); /* send the extra distance bits */
+ }
+ } /* literal or match pair ? */
+
+ /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
+ Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
+ "pendingBuf overflow");
+
+ } while (lx < s->last_lit);
+
+ send_code(s, END_BLOCK, ltree);
+}
+
+/* ===========================================================================
+ * Check if the data type is TEXT or BINARY, using the following algorithm:
+ * - TEXT if the two conditions below are satisfied:
+ * a) There are no non-portable control characters belonging to the
+ * "black list" (0..6, 14..25, 28..31).
+ * b) There is at least one printable character belonging to the
+ * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
+ * - BINARY otherwise.
+ * - The following partially-portable control characters form a
+ * "gray list" that is ignored in this detection algorithm:
+ * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
+ * IN assertion: the fields Freq of dyn_ltree are set.
+ */
+local int detect_data_type(s)
+ deflate_state *s;
+{
+ /* black_mask is the bit mask of black-listed bytes
+ * set bits 0..6, 14..25, and 28..31
+ * 0xf3ffc07f = binary 11110011111111111100000001111111
+ */
+ unsigned long black_mask = 0xf3ffc07fUL;
+ int n;
+
+ /* Check for non-textual ("black-listed") bytes. */
+ for (n = 0; n <= 31; n++, black_mask >>= 1)
+ if ((black_mask & 1) && (s->dyn_ltree[n].Freq != 0))
+ return Z_BINARY;
+
+ /* Check for textual ("white-listed") bytes. */
+ if (s->dyn_ltree[9].Freq != 0 || s->dyn_ltree[10].Freq != 0
+ || s->dyn_ltree[13].Freq != 0)
+ return Z_TEXT;
+ for (n = 32; n < LITERALS; n++)
+ if (s->dyn_ltree[n].Freq != 0)
+ return Z_TEXT;
+
+ /* There are no "black-listed" or "white-listed" bytes:
+ * this stream either is empty or has tolerated ("gray-listed") bytes only.
+ */
+ return Z_BINARY;
+}
+
+/* ===========================================================================
+ * Reverse the first len bits of a code, using straightforward code (a faster
+ * method would use a table)
+ * IN assertion: 1 <= len <= 15
+ */
+local unsigned bi_reverse(code, len)
+ unsigned code; /* the value to invert */
+ int len; /* its bit length */
+{
+ register unsigned res = 0;
+ do {
+ res |= code & 1;
+ code >>= 1, res <<= 1;
+ } while (--len > 0);
+ return res >> 1;
+}
+
+/* ===========================================================================
+ * Flush the bit buffer, keeping at most 7 bits in it.
+ */
+local void bi_flush(s)
+ deflate_state *s;
+{
+ if (s->bi_valid == 16) {
+ put_short(s, s->bi_buf);
+ s->bi_buf = 0;
+ s->bi_valid = 0;
+ } else if (s->bi_valid >= 8) {
+ put_byte(s, (Byte)s->bi_buf);
+ s->bi_buf >>= 8;
+ s->bi_valid -= 8;
+ }
+}
+
+/* ===========================================================================
+ * Flush the bit buffer and align the output on a byte boundary
+ */
+local void bi_windup(s)
+ deflate_state *s;
+{
+ if (s->bi_valid > 8) {
+ put_short(s, s->bi_buf);
+ } else if (s->bi_valid > 0) {
+ put_byte(s, (Byte)s->bi_buf);
+ }
+ s->bi_buf = 0;
+ s->bi_valid = 0;
+#ifdef ZLIB_DEBUG
+ s->bits_sent = (s->bits_sent+7) & ~7;
+#endif
+}
diff --git a/lib/zlib/trees.h b/lib/zlib/trees.h
new file mode 100644
index 0000000..d35639d
--- /dev/null
+++ b/lib/zlib/trees.h
@@ -0,0 +1,128 @@
+/* header created automatically with -DGEN_TREES_H */
+
+local const ct_data static_ltree[L_CODES+2] = {
+{{ 12},{ 8}}, {{140},{ 8}}, {{ 76},{ 8}}, {{204},{ 8}}, {{ 44},{ 8}},
+{{172},{ 8}}, {{108},{ 8}}, {{236},{ 8}}, {{ 28},{ 8}}, {{156},{ 8}},
+{{ 92},{ 8}}, {{220},{ 8}}, {{ 60},{ 8}}, {{188},{ 8}}, {{124},{ 8}},
+{{252},{ 8}}, {{ 2},{ 8}}, {{130},{ 8}}, {{ 66},{ 8}}, {{194},{ 8}},
+{{ 34},{ 8}}, {{162},{ 8}}, {{ 98},{ 8}}, {{226},{ 8}}, {{ 18},{ 8}},
+{{146},{ 8}}, {{ 82},{ 8}}, {{210},{ 8}}, {{ 50},{ 8}}, {{178},{ 8}},
+{{114},{ 8}}, {{242},{ 8}}, {{ 10},{ 8}}, {{138},{ 8}}, {{ 74},{ 8}},
+{{202},{ 8}}, {{ 42},{ 8}}, {{170},{ 8}}, {{106},{ 8}}, {{234},{ 8}},
+{{ 26},{ 8}}, {{154},{ 8}}, {{ 90},{ 8}}, {{218},{ 8}}, {{ 58},{ 8}},
+{{186},{ 8}}, {{122},{ 8}}, {{250},{ 8}}, {{ 6},{ 8}}, {{134},{ 8}},
+{{ 70},{ 8}}, {{198},{ 8}}, {{ 38},{ 8}}, {{166},{ 8}}, {{102},{ 8}},
+{{230},{ 8}}, {{ 22},{ 8}}, {{150},{ 8}}, {{ 86},{ 8}}, {{214},{ 8}},
+{{ 54},{ 8}}, {{182},{ 8}}, {{118},{ 8}}, {{246},{ 8}}, {{ 14},{ 8}},
+{{142},{ 8}}, {{ 78},{ 8}}, {{206},{ 8}}, {{ 46},{ 8}}, {{174},{ 8}},
+{{110},{ 8}}, {{238},{ 8}}, {{ 30},{ 8}}, {{158},{ 8}}, {{ 94},{ 8}},
+{{222},{ 8}}, {{ 62},{ 8}}, {{190},{ 8}}, {{126},{ 8}}, {{254},{ 8}},
+{{ 1},{ 8}}, {{129},{ 8}}, {{ 65},{ 8}}, {{193},{ 8}}, {{ 33},{ 8}},
+{{161},{ 8}}, {{ 97},{ 8}}, {{225},{ 8}}, {{ 17},{ 8}}, {{145},{ 8}},
+{{ 81},{ 8}}, {{209},{ 8}}, {{ 49},{ 8}}, {{177},{ 8}}, {{113},{ 8}},
+{{241},{ 8}}, {{ 9},{ 8}}, {{137},{ 8}}, {{ 73},{ 8}}, {{201},{ 8}},
+{{ 41},{ 8}}, {{169},{ 8}}, {{105},{ 8}}, {{233},{ 8}}, {{ 25},{ 8}},
+{{153},{ 8}}, {{ 89},{ 8}}, {{217},{ 8}}, {{ 57},{ 8}}, {{185},{ 8}},
+{{121},{ 8}}, {{249},{ 8}}, {{ 5},{ 8}}, {{133},{ 8}}, {{ 69},{ 8}},
+{{197},{ 8}}, {{ 37},{ 8}}, {{165},{ 8}}, {{101},{ 8}}, {{229},{ 8}},
+{{ 21},{ 8}}, {{149},{ 8}}, {{ 85},{ 8}}, {{213},{ 8}}, {{ 53},{ 8}},
+{{181},{ 8}}, {{117},{ 8}}, {{245},{ 8}}, {{ 13},{ 8}}, {{141},{ 8}},
+{{ 77},{ 8}}, {{205},{ 8}}, {{ 45},{ 8}}, {{173},{ 8}}, {{109},{ 8}},
+{{237},{ 8}}, {{ 29},{ 8}}, {{157},{ 8}}, {{ 93},{ 8}}, {{221},{ 8}},
+{{ 61},{ 8}}, {{189},{ 8}}, {{125},{ 8}}, {{253},{ 8}}, {{ 19},{ 9}},
+{{275},{ 9}}, {{147},{ 9}}, {{403},{ 9}}, {{ 83},{ 9}}, {{339},{ 9}},
+{{211},{ 9}}, {{467},{ 9}}, {{ 51},{ 9}}, {{307},{ 9}}, {{179},{ 9}},
+{{435},{ 9}}, {{115},{ 9}}, {{371},{ 9}}, {{243},{ 9}}, {{499},{ 9}},
+{{ 11},{ 9}}, {{267},{ 9}}, {{139},{ 9}}, {{395},{ 9}}, {{ 75},{ 9}},
+{{331},{ 9}}, {{203},{ 9}}, {{459},{ 9}}, {{ 43},{ 9}}, {{299},{ 9}},
+{{171},{ 9}}, {{427},{ 9}}, {{107},{ 9}}, {{363},{ 9}}, {{235},{ 9}},
+{{491},{ 9}}, {{ 27},{ 9}}, {{283},{ 9}}, {{155},{ 9}}, {{411},{ 9}},
+{{ 91},{ 9}}, {{347},{ 9}}, {{219},{ 9}}, {{475},{ 9}}, {{ 59},{ 9}},
+{{315},{ 9}}, {{187},{ 9}}, {{443},{ 9}}, {{123},{ 9}}, {{379},{ 9}},
+{{251},{ 9}}, {{507},{ 9}}, {{ 7},{ 9}}, {{263},{ 9}}, {{135},{ 9}},
+{{391},{ 9}}, {{ 71},{ 9}}, {{327},{ 9}}, {{199},{ 9}}, {{455},{ 9}},
+{{ 39},{ 9}}, {{295},{ 9}}, {{167},{ 9}}, {{423},{ 9}}, {{103},{ 9}},
+{{359},{ 9}}, {{231},{ 9}}, {{487},{ 9}}, {{ 23},{ 9}}, {{279},{ 9}},
+{{151},{ 9}}, {{407},{ 9}}, {{ 87},{ 9}}, {{343},{ 9}}, {{215},{ 9}},
+{{471},{ 9}}, {{ 55},{ 9}}, {{311},{ 9}}, {{183},{ 9}}, {{439},{ 9}},
+{{119},{ 9}}, {{375},{ 9}}, {{247},{ 9}}, {{503},{ 9}}, {{ 15},{ 9}},
+{{271},{ 9}}, {{143},{ 9}}, {{399},{ 9}}, {{ 79},{ 9}}, {{335},{ 9}},
+{{207},{ 9}}, {{463},{ 9}}, {{ 47},{ 9}}, {{303},{ 9}}, {{175},{ 9}},
+{{431},{ 9}}, {{111},{ 9}}, {{367},{ 9}}, {{239},{ 9}}, {{495},{ 9}},
+{{ 31},{ 9}}, {{287},{ 9}}, {{159},{ 9}}, {{415},{ 9}}, {{ 95},{ 9}},
+{{351},{ 9}}, {{223},{ 9}}, {{479},{ 9}}, {{ 63},{ 9}}, {{319},{ 9}},
+{{191},{ 9}}, {{447},{ 9}}, {{127},{ 9}}, {{383},{ 9}}, {{255},{ 9}},
+{{511},{ 9}}, {{ 0},{ 7}}, {{ 64},{ 7}}, {{ 32},{ 7}}, {{ 96},{ 7}},
+{{ 16},{ 7}}, {{ 80},{ 7}}, {{ 48},{ 7}}, {{112},{ 7}}, {{ 8},{ 7}},
+{{ 72},{ 7}}, {{ 40},{ 7}}, {{104},{ 7}}, {{ 24},{ 7}}, {{ 88},{ 7}},
+{{ 56},{ 7}}, {{120},{ 7}}, {{ 4},{ 7}}, {{ 68},{ 7}}, {{ 36},{ 7}},
+{{100},{ 7}}, {{ 20},{ 7}}, {{ 84},{ 7}}, {{ 52},{ 7}}, {{116},{ 7}},
+{{ 3},{ 8}}, {{131},{ 8}}, {{ 67},{ 8}}, {{195},{ 8}}, {{ 35},{ 8}},
+{{163},{ 8}}, {{ 99},{ 8}}, {{227},{ 8}}
+};
+
+local const ct_data static_dtree[D_CODES] = {
+{{ 0},{ 5}}, {{16},{ 5}}, {{ 8},{ 5}}, {{24},{ 5}}, {{ 4},{ 5}},
+{{20},{ 5}}, {{12},{ 5}}, {{28},{ 5}}, {{ 2},{ 5}}, {{18},{ 5}},
+{{10},{ 5}}, {{26},{ 5}}, {{ 6},{ 5}}, {{22},{ 5}}, {{14},{ 5}},
+{{30},{ 5}}, {{ 1},{ 5}}, {{17},{ 5}}, {{ 9},{ 5}}, {{25},{ 5}},
+{{ 5},{ 5}}, {{21},{ 5}}, {{13},{ 5}}, {{29},{ 5}}, {{ 3},{ 5}},
+{{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}}
+};
+
+const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = {
+ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8,
+ 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10,
+10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13,
+13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17,
+18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22,
+23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
+};
+
+const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12,
+13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16,
+17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19,
+19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22,
+22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
+23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28
+};
+
+local const int base_length[LENGTH_CODES] = {
+0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56,
+64, 80, 96, 112, 128, 160, 192, 224, 0
+};
+
+local const int base_dist[D_CODES] = {
+ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24,
+ 32, 48, 64, 96, 128, 192, 256, 384, 512, 768,
+ 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576
+};
+
diff --git a/lib/zlib/uncompr.c b/lib/zlib/uncompr.c
new file mode 100644
index 0000000..f03a1a8
--- /dev/null
+++ b/lib/zlib/uncompr.c
@@ -0,0 +1,93 @@
+/* uncompr.c -- decompress a memory buffer
+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#define ZLIB_INTERNAL
+#include "zlib.h"
+
+/* ===========================================================================
+ Decompresses the source buffer into the destination buffer. *sourceLen is
+ the byte length of the source buffer. Upon entry, *destLen is the total size
+ of the destination buffer, which must be large enough to hold the entire
+ uncompressed data. (The size of the uncompressed data must have been saved
+ previously by the compressor and transmitted to the decompressor by some
+ mechanism outside the scope of this compression library.) Upon exit,
+ *destLen is the size of the decompressed data and *sourceLen is the number
+ of source bytes consumed. Upon return, source + *sourceLen points to the
+ first unused input byte.
+
+ uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_BUF_ERROR if there was not enough room in the output buffer, or
+ Z_DATA_ERROR if the input data was corrupted, including if the input data is
+ an incomplete zlib stream.
+*/
+int ZEXPORT uncompress2 (dest, destLen, source, sourceLen)
+ Bytef *dest;
+ uLongf *destLen;
+ const Bytef *source;
+ uLong *sourceLen;
+{
+ z_stream stream;
+ int err;
+ const uInt max = (uInt)-1;
+ uLong len, left;
+ Byte buf[1]; /* for detection of incomplete stream when *destLen == 0 */
+
+ len = *sourceLen;
+ if (*destLen) {
+ left = *destLen;
+ *destLen = 0;
+ }
+ else {
+ left = 1;
+ dest = buf;
+ }
+
+ stream.next_in = (z_const Bytef *)source;
+ stream.avail_in = 0;
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+ stream.opaque = (voidpf)0;
+
+ err = inflateInit(&stream);
+ if (err != Z_OK) return err;
+
+ stream.next_out = dest;
+ stream.avail_out = 0;
+
+ do {
+ if (stream.avail_out == 0) {
+ stream.avail_out = left > (uLong)max ? max : (uInt)left;
+ left -= stream.avail_out;
+ }
+ if (stream.avail_in == 0) {
+ stream.avail_in = len > (uLong)max ? max : (uInt)len;
+ len -= stream.avail_in;
+ }
+ err = inflate(&stream, Z_NO_FLUSH);
+ } while (err == Z_OK);
+
+ *sourceLen -= len + stream.avail_in;
+ if (dest != buf)
+ *destLen = stream.total_out;
+ else if (stream.total_out && err == Z_BUF_ERROR)
+ left = 1;
+
+ inflateEnd(&stream);
+ return err == Z_STREAM_END ? Z_OK :
+ err == Z_NEED_DICT ? Z_DATA_ERROR :
+ err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
+ err;
+}
+
+int ZEXPORT uncompress (dest, destLen, source, sourceLen)
+ Bytef *dest;
+ uLongf *destLen;
+ const Bytef *source;
+ uLong sourceLen;
+{
+ return uncompress2(dest, destLen, source, &sourceLen);
+}
diff --git a/lib/zlib/zconf.h.in b/lib/zlib/zconf.h.in
new file mode 100644
index 0000000..0b6bd53
--- /dev/null
+++ b/lib/zlib/zconf.h.in
@@ -0,0 +1,563 @@
+/* zconf.h -- configuration of the zlib compression library
+ * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#ifndef ZCONF_H
+#define ZCONF_H
+
+/* The following four defines are enabled by sudo's configure script. */
+#undef HAVE_FALLTHROUGH_ATTRIBUTE
+#undef HAVE_DSO_VISIBILITY
+#undef HAVE_MEMCPY
+#undef HAVE_UNISTD_H
+#undef HAVE_VSNPRINTF
+#undef _FILE_OFFSET_BITS
+#undef _LARGE_FILES
+#undef const
+
+/* We build sudo and its libs with -fvisibility=hidden where supported. */
+#ifdef HAVE_DSO_VISIBILITY
+# if defined(__GNUC__)
+# define ZEXTERN extern __attribute__((__visibility__("default")))
+# elif defined(__SUNPRO_C)
+# define ZEXTERN extern __global
+# elif defined(ZLIB_INTERNAL)
+# define ZEXTERN extern __declspec(dllexport)
+# else
+# define ZEXTERN extern __declspec(dllimport)
+# endif
+#endif
+
+#ifdef HAVE_FALLTHROUGH_ATTRIBUTE
+# define ZFALLTHROUGH __attribute__((__fallthrough__))
+#else
+# define ZFALLTHROUGH do { } while (0)
+#endif
+
+/*
+ * If you *really* need a unique prefix for all types and library functions,
+ * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
+ * Even better than compiling with -DZ_PREFIX would be to use configure to set
+ * this permanently in zconf.h using "./configure --zprefix".
+ */
+#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */
+# define Z_PREFIX_SET
+
+/* all linked symbols and init macros */
+# define _dist_code z__dist_code
+# define _length_code z__length_code
+# define _tr_align z__tr_align
+# define _tr_flush_bits z__tr_flush_bits
+# define _tr_flush_block z__tr_flush_block
+# define _tr_init z__tr_init
+# define _tr_stored_block z__tr_stored_block
+# define _tr_tally z__tr_tally
+# define adler32 z_adler32
+# define adler32_combine z_adler32_combine
+# define adler32_combine64 z_adler32_combine64
+# define adler32_z z_adler32_z
+# ifndef Z_SOLO
+# define compress z_compress
+# define compress2 z_compress2
+# define compressBound z_compressBound
+# endif
+# define crc32 z_crc32
+# define crc32_combine z_crc32_combine
+# define crc32_combine64 z_crc32_combine64
+# define crc32_z z_crc32_z
+# define deflate z_deflate
+# define deflateBound z_deflateBound
+# define deflateCopy z_deflateCopy
+# define deflateEnd z_deflateEnd
+# define deflateGetDictionary z_deflateGetDictionary
+# define deflateInit z_deflateInit
+# define deflateInit2 z_deflateInit2
+# define deflateInit2_ z_deflateInit2_
+# define deflateInit_ z_deflateInit_
+# define deflateParams z_deflateParams
+# define deflatePending z_deflatePending
+# define deflatePrime z_deflatePrime
+# define deflateReset z_deflateReset
+# define deflateResetKeep z_deflateResetKeep
+# define deflateSetDictionary z_deflateSetDictionary
+# define deflateSetHeader z_deflateSetHeader
+# define deflateTune z_deflateTune
+# define deflate_copyright z_deflate_copyright
+# define get_crc_table z_get_crc_table
+# ifndef Z_SOLO
+# define gz_error z_gz_error
+# define gz_intmax z_gz_intmax
+# define gz_strwinerror z_gz_strwinerror
+# define gzbuffer z_gzbuffer
+# define gzclearerr z_gzclearerr
+# define gzclose z_gzclose
+# define gzclose_r z_gzclose_r
+# define gzclose_w z_gzclose_w
+# define gzdirect z_gzdirect
+# define gzdopen z_gzdopen
+# define gzeof z_gzeof
+# define gzerror z_gzerror
+# define gzflush z_gzflush
+# define gzfread z_gzfread
+# define gzfwrite z_gzfwrite
+# define gzgetc z_gzgetc
+# define gzgetc_ z_gzgetc_
+# define gzgets z_gzgets
+# define gzoffset z_gzoffset
+# define gzoffset64 z_gzoffset64
+# define gzopen z_gzopen
+# define gzopen64 z_gzopen64
+# ifdef _WIN32
+# define gzopen_w z_gzopen_w
+# endif
+# define gzprintf z_gzprintf
+# define gzputc z_gzputc
+# define gzputs z_gzputs
+# define gzread z_gzread
+# define gzrewind z_gzrewind
+# define gzseek z_gzseek
+# define gzseek64 z_gzseek64
+# define gzsetparams z_gzsetparams
+# define gztell z_gztell
+# define gztell64 z_gztell64
+# define gzungetc z_gzungetc
+# define gzvprintf z_gzvprintf
+# define gzwrite z_gzwrite
+# endif
+# define inflate z_inflate
+# define inflateBack z_inflateBack
+# define inflateBackEnd z_inflateBackEnd
+# define inflateBackInit z_inflateBackInit
+# define inflateBackInit_ z_inflateBackInit_
+# define inflateCodesUsed z_inflateCodesUsed
+# define inflateCopy z_inflateCopy
+# define inflateEnd z_inflateEnd
+# define inflateGetDictionary z_inflateGetDictionary
+# define inflateGetHeader z_inflateGetHeader
+# define inflateInit z_inflateInit
+# define inflateInit2 z_inflateInit2
+# define inflateInit2_ z_inflateInit2_
+# define inflateInit_ z_inflateInit_
+# define inflateMark z_inflateMark
+# define inflatePrime z_inflatePrime
+# define inflateReset z_inflateReset
+# define inflateReset2 z_inflateReset2
+# define inflateResetKeep z_inflateResetKeep
+# define inflateSetDictionary z_inflateSetDictionary
+# define inflateSync z_inflateSync
+# define inflateSyncPoint z_inflateSyncPoint
+# define inflateUndermine z_inflateUndermine
+# define inflateValidate z_inflateValidate
+# define inflate_copyright z_inflate_copyright
+# define inflate_fast z_inflate_fast
+# define inflate_table z_inflate_table
+# ifndef Z_SOLO
+# define uncompress z_uncompress
+# define uncompress2 z_uncompress2
+# endif
+# define zError z_zError
+# ifndef Z_SOLO
+# define zcalloc z_zcalloc
+# define zcfree z_zcfree
+# endif
+# define zlibCompileFlags z_zlibCompileFlags
+# define zlibVersion z_zlibVersion
+
+/* all zlib typedefs in zlib.h and zconf.h */
+# define Byte z_Byte
+# define Bytef z_Bytef
+# define alloc_func z_alloc_func
+# define charf z_charf
+# define free_func z_free_func
+# ifndef Z_SOLO
+# define gzFile z_gzFile
+# endif
+# define gz_header z_gz_header
+# define gz_headerp z_gz_headerp
+# define in_func z_in_func
+# define intf z_intf
+# define out_func z_out_func
+# define uInt z_uInt
+# define uIntf z_uIntf
+# define uLong z_uLong
+# define uLongf z_uLongf
+# define voidp z_voidp
+# define voidpc z_voidpc
+# define voidpf z_voidpf
+
+/* all zlib structs in zlib.h and zconf.h */
+# define gz_header_s z_gz_header_s
+# define internal_state z_internal_state
+
+#endif
+
+#if defined(__MSDOS__) && !defined(MSDOS)
+# define MSDOS
+#endif
+#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
+# define OS2
+#endif
+#if defined(_WINDOWS) && !defined(WINDOWS)
+# define WINDOWS
+#endif
+#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
+# ifndef WIN32
+# define WIN32
+# endif
+#endif
+#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
+# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
+# ifndef SYS16BIT
+# define SYS16BIT
+# endif
+# endif
+#endif
+
+/*
+ * Compile with -DMAXSEG_64K if the alloc function cannot allocate more
+ * than 64k bytes at a time (needed on systems with 16-bit int).
+ */
+#ifdef SYS16BIT
+# define MAXSEG_64K
+#endif
+#ifdef MSDOS
+# define UNALIGNED_OK
+#endif
+
+#ifdef __STDC_VERSION__
+# ifndef STDC
+# define STDC
+# endif
+# if __STDC_VERSION__ >= 199901L
+# ifndef STDC99
+# define STDC99
+# endif
+# endif
+#endif
+#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
+# define STDC
+#endif
+#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
+# define STDC
+#endif
+#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
+# define STDC
+#endif
+#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
+# define STDC
+#endif
+
+#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */
+# define STDC
+#endif
+
+#ifndef STDC
+# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
+# define const /* note: need a more gentle solution here */
+# endif
+#endif
+
+#if defined(ZLIB_CONST) && !defined(z_const)
+# define z_const const
+#else
+# define z_const
+#endif
+
+#ifdef Z_SOLO
+ typedef unsigned long z_size_t;
+#else
+# define z_longlong long long
+# if defined(NO_SIZE_T)
+ typedef unsigned NO_SIZE_T z_size_t;
+# elif defined(STDC)
+# include <stddef.h>
+ typedef size_t z_size_t;
+# else
+ typedef unsigned long z_size_t;
+# endif
+# undef z_longlong
+#endif
+
+/* Maximum value for memLevel in deflateInit2 */
+#ifndef MAX_MEM_LEVEL
+# ifdef MAXSEG_64K
+# define MAX_MEM_LEVEL 8
+# else
+# define MAX_MEM_LEVEL 9
+# endif
+#endif
+
+/* Maximum value for windowBits in deflateInit2 and inflateInit2.
+ * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
+ * created by gzip. (Files created by minigzip can still be extracted by
+ * gzip.)
+ */
+#ifndef MAX_WBITS
+# define MAX_WBITS 15 /* 32K LZ77 window */
+#endif
+
+/* The memory requirements for deflate are (in bytes):
+ (1 << (windowBits+2)) + (1 << (memLevel+9))
+ that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
+ plus a few kilobytes for small objects. For example, if you want to reduce
+ the default memory requirements from 256K to 128K, compile with
+ make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
+ Of course this will generally degrade compression (there's no free lunch).
+
+ The memory requirements for inflate are (in bytes) 1 << windowBits
+ that is, 32K for windowBits=15 (default value) plus about 7 kilobytes
+ for small objects.
+*/
+
+ /* Type declarations */
+
+#ifndef OF /* function prototypes */
+# ifdef STDC
+# define OF(args) args
+# else
+# define OF(args) ()
+# endif
+#endif
+
+#ifndef Z_ARG /* function prototypes for stdarg */
+# if defined(STDC) || defined(Z_HAVE_STDARG_H)
+# define Z_ARG(args) args
+# else
+# define Z_ARG(args) ()
+# endif
+#endif
+
+/* The following definitions for FAR are needed only for MSDOS mixed
+ * model programming (small or medium model with some far allocations).
+ * This was tested only with MSC; for other MSDOS compilers you may have
+ * to define NO_MEMCPY in zutil.h. If you don't need the mixed model,
+ * just define FAR to be empty.
+ */
+#ifdef SYS16BIT
+# if defined(M_I86SM) || defined(M_I86MM)
+ /* MSC small or medium model */
+# define SMALL_MEDIUM
+# ifdef _MSC_VER
+# define FAR _far
+# else
+# define FAR far
+# endif
+# endif
+# if (defined(__SMALL__) || defined(__MEDIUM__))
+ /* Turbo C small or medium model */
+# define SMALL_MEDIUM
+# ifdef __BORLANDC__
+# define FAR _far
+# else
+# define FAR far
+# endif
+# endif
+#endif
+
+#if defined(WINDOWS) || defined(WIN32)
+ /* If building or using zlib as a DLL, define ZLIB_DLL.
+ * This is not mandatory, but it offers a little performance increase.
+ */
+# ifdef ZLIB_DLL
+# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
+# ifdef ZLIB_INTERNAL
+# define ZEXTERN extern __declspec(dllexport)
+# else
+# define ZEXTERN extern __declspec(dllimport)
+# endif
+# endif
+# endif /* ZLIB_DLL */
+ /* If building or using zlib with the WINAPI/WINAPIV calling convention,
+ * define ZLIB_WINAPI.
+ * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
+ */
+# ifdef ZLIB_WINAPI
+# ifdef FAR
+# undef FAR
+# endif
+# include <windows.h>
+ /* No need for _export, use ZLIB.DEF instead. */
+ /* For complete Windows compatibility, use WINAPI, not __stdcall. */
+# define ZEXPORT WINAPI
+# ifdef WIN32
+# define ZEXPORTVA WINAPIV
+# else
+# define ZEXPORTVA FAR CDECL
+# endif
+# endif
+#endif
+
+#if defined (__BEOS__)
+# ifdef ZLIB_DLL
+# ifdef ZLIB_INTERNAL
+# define ZEXPORT __declspec(dllexport)
+# define ZEXPORTVA __declspec(dllexport)
+# else
+# define ZEXPORT __declspec(dllimport)
+# define ZEXPORTVA __declspec(dllimport)
+# endif
+# endif
+#endif
+
+#ifndef ZEXTERN
+# define ZEXTERN extern
+#endif
+#ifndef ZEXPORT
+# define ZEXPORT
+#endif
+#ifndef ZEXPORTVA
+# define ZEXPORTVA
+#endif
+
+#ifndef FAR
+# define FAR
+#endif
+
+#if !defined(__MACTYPES__)
+typedef unsigned char Byte; /* 8 bits */
+#endif
+typedef unsigned int uInt; /* 16 bits or more */
+typedef unsigned long uLong; /* 32 bits or more */
+
+#ifdef SMALL_MEDIUM
+ /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
+# define Bytef Byte FAR
+#else
+ typedef Byte FAR Bytef;
+#endif
+typedef char FAR charf;
+typedef int FAR intf;
+typedef uInt FAR uIntf;
+typedef uLong FAR uLongf;
+
+#ifdef STDC
+ typedef void const *voidpc;
+ typedef void FAR *voidpf;
+ typedef void *voidp;
+#else
+ typedef Byte const *voidpc;
+ typedef Byte FAR *voidpf;
+ typedef Byte *voidp;
+#endif
+
+#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC)
+# include <limits.h>
+# if (UINT_MAX == 0xffffffffUL)
+# define Z_U4 unsigned
+# elif (ULONG_MAX == 0xffffffffUL)
+# define Z_U4 unsigned long
+# elif (USHRT_MAX == 0xffffffffUL)
+# define Z_U4 unsigned short
+# endif
+#endif
+
+#ifdef Z_U4
+ typedef Z_U4 z_crc_t;
+#else
+ typedef unsigned long z_crc_t;
+#endif
+
+#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */
+# define Z_HAVE_UNISTD_H
+#endif
+
+#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */
+# define Z_HAVE_STDARG_H
+#endif
+
+#ifdef STDC
+# ifndef Z_SOLO
+# include <sys/types.h> /* for off_t */
+# endif
+#endif
+
+#if defined(STDC) || defined(Z_HAVE_STDARG_H)
+# ifndef Z_SOLO
+# include <stdarg.h> /* for va_list */
+# endif
+#endif
+
+#ifdef _WIN32
+# ifndef Z_SOLO
+# include <stddef.h> /* for wchar_t */
+# endif
+#endif
+
+/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and
+ * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even
+ * though the former does not conform to the LFS document), but considering
+ * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as
+ * equivalently requesting no 64-bit operations
+ */
+#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1
+# undef _LARGEFILE64_SOURCE
+#endif
+
+#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H)
+# define Z_HAVE_UNISTD_H
+#endif
+#ifndef Z_SOLO
+# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE)
+# include <unistd.h> /* for SEEK_*, off_t, and _LFS64_LARGEFILE */
+# ifdef VMS
+# include <unixio.h> /* for off_t */
+# endif
+# ifndef z_off_t
+# define z_off_t off_t
+# endif
+# endif
+#endif
+
+#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0
+# define Z_LFS64
+#endif
+
+#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64)
+# define Z_LARGE64
+#endif
+
+#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64)
+# define Z_WANT64
+#endif
+
+#if !defined(SEEK_SET) && !defined(Z_SOLO)
+# define SEEK_SET 0 /* Seek from beginning of file. */
+# define SEEK_CUR 1 /* Seek from current position. */
+# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
+#endif
+
+#ifndef z_off_t
+# define z_off_t long
+#endif
+
+#if !defined(_WIN32) && defined(Z_LARGE64)
+# define z_off64_t off64_t
+#else
+# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO)
+# define z_off64_t __int64
+# else
+# define z_off64_t z_off_t
+# endif
+#endif
+
+/* MVS linker does not support external names larger than 8 bytes */
+#if defined(__MVS__)
+ #pragma map(deflateInit_,"DEIN")
+ #pragma map(deflateInit2_,"DEIN2")
+ #pragma map(deflateEnd,"DEEND")
+ #pragma map(deflateBound,"DEBND")
+ #pragma map(inflateInit_,"ININ")
+ #pragma map(inflateInit2_,"ININ2")
+ #pragma map(inflateEnd,"INEND")
+ #pragma map(inflateSync,"INSY")
+ #pragma map(inflateSetDictionary,"INSEDI")
+ #pragma map(compressBound,"CMBND")
+ #pragma map(inflate_table,"INTABL")
+ #pragma map(inflate_fast,"INFA")
+ #pragma map(inflate_copyright,"INCOPY")
+#endif
+
+#endif /* ZCONF_H */
diff --git a/lib/zlib/zlib.exp b/lib/zlib/zlib.exp
new file mode 100644
index 0000000..76ac35e
--- /dev/null
+++ b/lib/zlib/zlib.exp
@@ -0,0 +1,85 @@
+adler32
+adler32_combine
+adler32_combine64
+adler32_z
+compress
+compress2
+compressBound
+crc32
+crc32_combine
+crc32_combine64
+crc32_z
+deflate
+deflateBound
+deflateCopy
+deflateEnd
+deflateGetDictionary
+deflateInit2_
+deflateInit_
+deflateParams
+deflatePending
+deflatePrime
+deflateReset
+deflateResetKeep
+deflateSetDictionary
+deflateSetHeader
+deflateTune
+get_crc_table
+gzbuffer
+gzclearerr
+gzclose
+gzclose_r
+gzclose_w
+gzdirect
+gzdopen
+gzeof
+gzerror
+gzflush
+gzfread
+gzfwrite
+gzgetc
+gzgetc_
+gzgets
+gzoffset
+gzoffset64
+gzopen
+gzopen64
+gzprintf
+gzputc
+gzputs
+gzread
+gzrewind
+gzseek
+gzseek64
+gzsetparams
+gztell
+gztell64
+gzungetc
+gzvprintf
+gzwrite
+inflate
+inflateBack
+inflateBackEnd
+inflateBackInit_
+inflateCodesUsed
+inflateCopy
+inflateEnd
+inflateGetDictionary
+inflateGetHeader
+inflateInit2_
+inflateInit_
+inflateMark
+inflatePrime
+inflateReset
+inflateReset2
+inflateResetKeep
+inflateSetDictionary
+inflateSync
+inflateSyncPoint
+inflateUndermine
+inflateValidate
+uncompress
+uncompress2
+zError
+zlibCompileFlags
+zlibVersion
diff --git a/lib/zlib/zlib.h b/lib/zlib/zlib.h
new file mode 100644
index 0000000..f09cdaf
--- /dev/null
+++ b/lib/zlib/zlib.h
@@ -0,0 +1,1912 @@
+/* zlib.h -- interface of the 'zlib' general purpose compression library
+ version 1.2.11, January 15th, 2017
+
+ Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Jean-loup Gailly Mark Adler
+ jloup@gzip.org madler@alumni.caltech.edu
+
+
+ The data format used by the zlib library is described by RFCs (Request for
+ Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950
+ (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format).
+*/
+
+#ifndef ZLIB_H
+#define ZLIB_H
+
+#include "zconf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ZLIB_VERSION "1.2.11"
+#define ZLIB_VERNUM 0x12b0
+#define ZLIB_VER_MAJOR 1
+#define ZLIB_VER_MINOR 2
+#define ZLIB_VER_REVISION 11
+#define ZLIB_VER_SUBREVISION 0
+
+/*
+ The 'zlib' compression library provides in-memory compression and
+ decompression functions, including integrity checks of the uncompressed data.
+ This version of the library supports only one compression method (deflation)
+ but other algorithms will be added later and will have the same stream
+ interface.
+
+ Compression can be done in a single step if the buffers are large enough,
+ or can be done by repeated calls of the compression function. In the latter
+ case, the application must provide more input and/or consume the output
+ (providing more output space) before each call.
+
+ The compressed data format used by default by the in-memory functions is
+ the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped
+ around a deflate stream, which is itself documented in RFC 1951.
+
+ The library also supports reading and writing files in gzip (.gz) format
+ with an interface similar to that of stdio using the functions that start
+ with "gz". The gzip format is different from the zlib format. gzip is a
+ gzip wrapper, documented in RFC 1952, wrapped around a deflate stream.
+
+ This library can optionally read and write gzip and raw deflate streams in
+ memory as well.
+
+ The zlib format was designed to be compact and fast for use in memory
+ and on communications channels. The gzip format was designed for single-
+ file compression on file systems, has a larger header than zlib to maintain
+ directory information, and uses a different, slower check method than zlib.
+
+ The library does not install any signal handler. The decoder checks
+ the consistency of the compressed data, so the library should never crash
+ even in the case of corrupted input.
+*/
+
+typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size));
+typedef void (*free_func) OF((voidpf opaque, voidpf address));
+
+struct internal_state;
+
+typedef struct z_stream_s {
+ z_const Bytef *next_in; /* next input byte */
+ uInt avail_in; /* number of bytes available at next_in */
+ uLong total_in; /* total number of input bytes read so far */
+
+ Bytef *next_out; /* next output byte will go here */
+ uInt avail_out; /* remaining free space at next_out */
+ uLong total_out; /* total number of bytes output so far */
+
+ z_const char *msg; /* last error message, NULL if no error */
+ struct internal_state FAR *state; /* not visible by applications */
+
+ alloc_func zalloc; /* used to allocate the internal state */
+ free_func zfree; /* used to free the internal state */
+ voidpf opaque; /* private data object passed to zalloc and zfree */
+
+ int data_type; /* best guess about the data type: binary or text
+ for deflate, or the decoding state for inflate */
+ uLong adler; /* Adler-32 or CRC-32 value of the uncompressed data */
+ uLong reserved; /* reserved for future use */
+} z_stream;
+
+typedef z_stream FAR *z_streamp;
+
+/*
+ gzip header information passed to and from zlib routines. See RFC 1952
+ for more details on the meanings of these fields.
+*/
+typedef struct gz_header_s {
+ int text; /* true if compressed data believed to be text */
+ uLong time; /* modification time */
+ int xflags; /* extra flags (not used when writing a gzip file) */
+ int os; /* operating system */
+ Bytef *extra; /* pointer to extra field or Z_NULL if none */
+ uInt extra_len; /* extra field length (valid if extra != Z_NULL) */
+ uInt extra_max; /* space at extra (only when reading header) */
+ Bytef *name; /* pointer to zero-terminated file name or Z_NULL */
+ uInt name_max; /* space at name (only when reading header) */
+ Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */
+ uInt comm_max; /* space at comment (only when reading header) */
+ int hcrc; /* true if there was or will be a header crc */
+ int done; /* true when done reading gzip header (not used
+ when writing a gzip file) */
+} gz_header;
+
+typedef gz_header FAR *gz_headerp;
+
+/*
+ The application must update next_in and avail_in when avail_in has dropped
+ to zero. It must update next_out and avail_out when avail_out has dropped
+ to zero. The application must initialize zalloc, zfree and opaque before
+ calling the init function. All other fields are set by the compression
+ library and must not be updated by the application.
+
+ The opaque value provided by the application will be passed as the first
+ parameter for calls of zalloc and zfree. This can be useful for custom
+ memory management. The compression library attaches no meaning to the
+ opaque value.
+
+ zalloc must return Z_NULL if there is not enough memory for the object.
+ If zlib is used in a multi-threaded application, zalloc and zfree must be
+ thread safe. In that case, zlib is thread-safe. When zalloc and zfree are
+ Z_NULL on entry to the initialization function, they are set to internal
+ routines that use the standard library functions malloc() and free().
+
+ On 16-bit systems, the functions zalloc and zfree must be able to allocate
+ exactly 65536 bytes, but will not be required to allocate more than this if
+ the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, pointers
+ returned by zalloc for objects of exactly 65536 bytes *must* have their
+ offset normalized to zero. The default allocation function provided by this
+ library ensures this (see zutil.c). To reduce memory requirements and avoid
+ any allocation of 64K objects, at the expense of compression ratio, compile
+ the library with -DMAX_WBITS=14 (see zconf.h).
+
+ The fields total_in and total_out can be used for statistics or progress
+ reports. After compression, total_in holds the total size of the
+ uncompressed data and may be saved for use by the decompressor (particularly
+ if the decompressor wants to decompress everything in a single step).
+*/
+
+ /* constants */
+
+#define Z_NO_FLUSH 0
+#define Z_PARTIAL_FLUSH 1
+#define Z_SYNC_FLUSH 2
+#define Z_FULL_FLUSH 3
+#define Z_FINISH 4
+#define Z_BLOCK 5
+#define Z_TREES 6
+/* Allowed flush values; see deflate() and inflate() below for details */
+
+#define Z_OK 0
+#define Z_STREAM_END 1
+#define Z_NEED_DICT 2
+#define Z_ERRNO (-1)
+#define Z_STREAM_ERROR (-2)
+#define Z_DATA_ERROR (-3)
+#define Z_MEM_ERROR (-4)
+#define Z_BUF_ERROR (-5)
+#define Z_VERSION_ERROR (-6)
+/* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+
+#define Z_NO_COMPRESSION 0
+#define Z_BEST_SPEED 1
+#define Z_BEST_COMPRESSION 9
+#define Z_DEFAULT_COMPRESSION (-1)
+/* compression levels */
+
+#define Z_FILTERED 1
+#define Z_HUFFMAN_ONLY 2
+#define Z_RLE 3
+#define Z_FIXED 4
+#define Z_DEFAULT_STRATEGY 0
+/* compression strategy; see deflateInit2() below for details */
+
+#define Z_BINARY 0
+#define Z_TEXT 1
+#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */
+#define Z_UNKNOWN 2
+/* Possible values of the data_type field for deflate() */
+
+#define Z_DEFLATED 8
+/* The deflate compression method (the only one supported in this version) */
+
+#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */
+
+#define zlib_version zlibVersion()
+/* for compatibility with versions < 1.0.2 */
+
+
+ /* basic functions */
+
+ZEXTERN const char * ZEXPORT zlibVersion OF((void));
+/* The application can compare zlibVersion and ZLIB_VERSION for consistency.
+ If the first character differs, the library code actually used is not
+ compatible with the zlib.h header file used by the application. This check
+ is automatically made by deflateInit and inflateInit.
+ */
+
+/*
+ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level));
+
+ Initializes the internal stream state for compression. The fields
+ zalloc, zfree and opaque must be initialized before by the caller. If
+ zalloc and zfree are set to Z_NULL, deflateInit updates them to use default
+ allocation functions.
+
+ The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9:
+ 1 gives best speed, 9 gives best compression, 0 gives no compression at all
+ (the input data is simply copied a block at a time). Z_DEFAULT_COMPRESSION
+ requests a default compromise between speed and compression (currently
+ equivalent to level 6).
+
+ deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_STREAM_ERROR if level is not a valid compression level, or
+ Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible
+ with the version assumed by the caller (ZLIB_VERSION). msg is set to null
+ if there is no error message. deflateInit does not perform any compression:
+ this will be done by deflate().
+*/
+
+
+ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
+/*
+ deflate compresses as much data as possible, and stops when the input
+ buffer becomes empty or the output buffer becomes full. It may introduce
+ some output latency (reading input without producing any output) except when
+ forced to flush.
+
+ The detailed semantics are as follows. deflate performs one or both of the
+ following actions:
+
+ - Compress more input starting at next_in and update next_in and avail_in
+ accordingly. If not all input can be processed (because there is not
+ enough room in the output buffer), next_in and avail_in are updated and
+ processing will resume at this point for the next call of deflate().
+
+ - Generate more output starting at next_out and update next_out and avail_out
+ accordingly. This action is forced if the parameter flush is non zero.
+ Forcing flush frequently degrades the compression ratio, so this parameter
+ should be set only when necessary. Some output may be provided even if
+ flush is zero.
+
+ Before the call of deflate(), the application should ensure that at least
+ one of the actions is possible, by providing more input and/or consuming more
+ output, and updating avail_in or avail_out accordingly; avail_out should
+ never be zero before the call. The application can consume the compressed
+ output when it wants, for example when the output buffer is full (avail_out
+ == 0), or after each call of deflate(). If deflate returns Z_OK and with
+ zero avail_out, it must be called again after making room in the output
+ buffer because there might be more output pending. See deflatePending(),
+ which can be used if desired to determine whether or not there is more ouput
+ in that case.
+
+ Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to
+ decide how much data to accumulate before producing output, in order to
+ maximize compression.
+
+ If the parameter flush is set to Z_SYNC_FLUSH, all pending output is
+ flushed to the output buffer and the output is aligned on a byte boundary, so
+ that the decompressor can get all input data available so far. (In
+ particular avail_in is zero after the call if enough output space has been
+ provided before the call.) Flushing may degrade compression for some
+ compression algorithms and so it should be used only when necessary. This
+ completes the current deflate block and follows it with an empty stored block
+ that is three bits plus filler bits to the next byte, followed by four bytes
+ (00 00 ff ff).
+
+ If flush is set to Z_PARTIAL_FLUSH, all pending output is flushed to the
+ output buffer, but the output is not aligned to a byte boundary. All of the
+ input data so far will be available to the decompressor, as for Z_SYNC_FLUSH.
+ This completes the current deflate block and follows it with an empty fixed
+ codes block that is 10 bits long. This assures that enough bytes are output
+ in order for the decompressor to finish the block before the empty fixed
+ codes block.
+
+ If flush is set to Z_BLOCK, a deflate block is completed and emitted, as
+ for Z_SYNC_FLUSH, but the output is not aligned on a byte boundary, and up to
+ seven bits of the current block are held to be written as the next byte after
+ the next deflate block is completed. In this case, the decompressor may not
+ be provided enough bits at this point in order to complete decompression of
+ the data provided so far to the compressor. It may need to wait for the next
+ block to be emitted. This is for advanced applications that need to control
+ the emission of deflate blocks.
+
+ If flush is set to Z_FULL_FLUSH, all output is flushed as with
+ Z_SYNC_FLUSH, and the compression state is reset so that decompression can
+ restart from this point if previous compressed data has been damaged or if
+ random access is desired. Using Z_FULL_FLUSH too often can seriously degrade
+ compression.
+
+ If deflate returns with avail_out == 0, this function must be called again
+ with the same value of the flush parameter and more output space (updated
+ avail_out), until the flush is complete (deflate returns with non-zero
+ avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that
+ avail_out is greater than six to avoid repeated flush markers due to
+ avail_out == 0 on return.
+
+ If the parameter flush is set to Z_FINISH, pending input is processed,
+ pending output is flushed and deflate returns with Z_STREAM_END if there was
+ enough output space. If deflate returns with Z_OK or Z_BUF_ERROR, this
+ function must be called again with Z_FINISH and more output space (updated
+ avail_out) but no more input data, until it returns with Z_STREAM_END or an
+ error. After deflate has returned Z_STREAM_END, the only possible operations
+ on the stream are deflateReset or deflateEnd.
+
+ Z_FINISH can be used in the first deflate call after deflateInit if all the
+ compression is to be done in a single step. In order to complete in one
+ call, avail_out must be at least the value returned by deflateBound (see
+ below). Then deflate is guaranteed to return Z_STREAM_END. If not enough
+ output space is provided, deflate will not return Z_STREAM_END, and it must
+ be called again as described above.
+
+ deflate() sets strm->adler to the Adler-32 checksum of all input read
+ so far (that is, total_in bytes). If a gzip stream is being generated, then
+ strm->adler will be the CRC-32 checksum of the input read so far. (See
+ deflateInit2 below.)
+
+ deflate() may update strm->data_type if it can make a good guess about
+ the input data type (Z_BINARY or Z_TEXT). If in doubt, the data is
+ considered binary. This field is only for information purposes and does not
+ affect the compression algorithm in any manner.
+
+ deflate() returns Z_OK if some progress has been made (more input
+ processed or more output produced), Z_STREAM_END if all input has been
+ consumed and all output has been produced (only when flush is set to
+ Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example
+ if next_in or next_out was Z_NULL or the state was inadvertently written over
+ by the application), or Z_BUF_ERROR if no progress is possible (for example
+ avail_in or avail_out was zero). Note that Z_BUF_ERROR is not fatal, and
+ deflate() can be called again with more input and more output space to
+ continue compressing.
+*/
+
+
+ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm));
+/*
+ All dynamically allocated data structures for this stream are freed.
+ This function discards any unprocessed input and does not flush any pending
+ output.
+
+ deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the
+ stream state was inconsistent, Z_DATA_ERROR if the stream was freed
+ prematurely (some input or output was discarded). In the error case, msg
+ may be set but then points to a static string (which must not be
+ deallocated).
+*/
+
+
+/*
+ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm));
+
+ Initializes the internal stream state for decompression. The fields
+ next_in, avail_in, zalloc, zfree and opaque must be initialized before by
+ the caller. In the current version of inflate, the provided input is not
+ read or consumed. The allocation of a sliding window will be deferred to
+ the first call of inflate (if the decompression does not complete on the
+ first call). If zalloc and zfree are set to Z_NULL, inflateInit updates
+ them to use default allocation functions.
+
+ inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
+ version assumed by the caller, or Z_STREAM_ERROR if the parameters are
+ invalid, such as a null pointer to the structure. msg is set to null if
+ there is no error message. inflateInit does not perform any decompression.
+ Actual decompression will be done by inflate(). So next_in, and avail_in,
+ next_out, and avail_out are unused and unchanged. The current
+ implementation of inflateInit() does not process any header information --
+ that is deferred until inflate() is called.
+*/
+
+
+ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));
+/*
+ inflate decompresses as much data as possible, and stops when the input
+ buffer becomes empty or the output buffer becomes full. It may introduce
+ some output latency (reading input without producing any output) except when
+ forced to flush.
+
+ The detailed semantics are as follows. inflate performs one or both of the
+ following actions:
+
+ - Decompress more input starting at next_in and update next_in and avail_in
+ accordingly. If not all input can be processed (because there is not
+ enough room in the output buffer), then next_in and avail_in are updated
+ accordingly, and processing will resume at this point for the next call of
+ inflate().
+
+ - Generate more output starting at next_out and update next_out and avail_out
+ accordingly. inflate() provides as much output as possible, until there is
+ no more input data or no more space in the output buffer (see below about
+ the flush parameter).
+
+ Before the call of inflate(), the application should ensure that at least
+ one of the actions is possible, by providing more input and/or consuming more
+ output, and updating the next_* and avail_* values accordingly. If the
+ caller of inflate() does not provide both available input and available
+ output space, it is possible that there will be no progress made. The
+ application can consume the uncompressed output when it wants, for example
+ when the output buffer is full (avail_out == 0), or after each call of
+ inflate(). If inflate returns Z_OK and with zero avail_out, it must be
+ called again after making room in the output buffer because there might be
+ more output pending.
+
+ The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH,
+ Z_BLOCK, or Z_TREES. Z_SYNC_FLUSH requests that inflate() flush as much
+ output as possible to the output buffer. Z_BLOCK requests that inflate()
+ stop if and when it gets to the next deflate block boundary. When decoding
+ the zlib or gzip format, this will cause inflate() to return immediately
+ after the header and before the first block. When doing a raw inflate,
+ inflate() will go ahead and process the first block, and will return when it
+ gets to the end of that block, or when it runs out of data.
+
+ The Z_BLOCK option assists in appending to or combining deflate streams.
+ To assist in this, on return inflate() always sets strm->data_type to the
+ number of unused bits in the last byte taken from strm->next_in, plus 64 if
+ inflate() is currently decoding the last block in the deflate stream, plus
+ 128 if inflate() returned immediately after decoding an end-of-block code or
+ decoding the complete header up to just before the first byte of the deflate
+ stream. The end-of-block will not be indicated until all of the uncompressed
+ data from that block has been written to strm->next_out. The number of
+ unused bits may in general be greater than seven, except when bit 7 of
+ data_type is set, in which case the number of unused bits will be less than
+ eight. data_type is set as noted here every time inflate() returns for all
+ flush options, and so can be used to determine the amount of currently
+ consumed input in bits.
+
+ The Z_TREES option behaves as Z_BLOCK does, but it also returns when the
+ end of each deflate block header is reached, before any actual data in that
+ block is decoded. This allows the caller to determine the length of the
+ deflate block header for later use in random access within a deflate block.
+ 256 is added to the value of strm->data_type when inflate() returns
+ immediately after reaching the end of the deflate block header.
+
+ inflate() should normally be called until it returns Z_STREAM_END or an
+ error. However if all decompression is to be performed in a single step (a
+ single call of inflate), the parameter flush should be set to Z_FINISH. In
+ this case all pending input is processed and all pending output is flushed;
+ avail_out must be large enough to hold all of the uncompressed data for the
+ operation to complete. (The size of the uncompressed data may have been
+ saved by the compressor for this purpose.) The use of Z_FINISH is not
+ required to perform an inflation in one step. However it may be used to
+ inform inflate that a faster approach can be used for the single inflate()
+ call. Z_FINISH also informs inflate to not maintain a sliding window if the
+ stream completes, which reduces inflate's memory footprint. If the stream
+ does not complete, either because not all of the stream is provided or not
+ enough output space is provided, then a sliding window will be allocated and
+ inflate() can be called again to continue the operation as if Z_NO_FLUSH had
+ been used.
+
+ In this implementation, inflate() always flushes as much output as
+ possible to the output buffer, and always uses the faster approach on the
+ first call. So the effects of the flush parameter in this implementation are
+ on the return value of inflate() as noted below, when inflate() returns early
+ when Z_BLOCK or Z_TREES is used, and when inflate() avoids the allocation of
+ memory for a sliding window when Z_FINISH is used.
+
+ If a preset dictionary is needed after this call (see inflateSetDictionary
+ below), inflate sets strm->adler to the Adler-32 checksum of the dictionary
+ chosen by the compressor and returns Z_NEED_DICT; otherwise it sets
+ strm->adler to the Adler-32 checksum of all output produced so far (that is,
+ total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described
+ below. At the end of the stream, inflate() checks that its computed Adler-32
+ checksum is equal to that saved by the compressor and returns Z_STREAM_END
+ only if the checksum is correct.
+
+ inflate() can decompress and check either zlib-wrapped or gzip-wrapped
+ deflate data. The header type is detected automatically, if requested when
+ initializing with inflateInit2(). Any information contained in the gzip
+ header is not retained unless inflateGetHeader() is used. When processing
+ gzip-wrapped deflate data, strm->adler32 is set to the CRC-32 of the output
+ produced so far. The CRC-32 is checked against the gzip trailer, as is the
+ uncompressed length, modulo 2^32.
+
+ inflate() returns Z_OK if some progress has been made (more input processed
+ or more output produced), Z_STREAM_END if the end of the compressed data has
+ been reached and all uncompressed output has been produced, Z_NEED_DICT if a
+ preset dictionary is needed at this point, Z_DATA_ERROR if the input data was
+ corrupted (input stream not conforming to the zlib format or incorrect check
+ value, in which case strm->msg points to a string with a more specific
+ error), Z_STREAM_ERROR if the stream structure was inconsistent (for example
+ next_in or next_out was Z_NULL, or the state was inadvertently written over
+ by the application), Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR
+ if no progress was possible or if there was not enough room in the output
+ buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and
+ inflate() can be called again with more input and more output space to
+ continue decompressing. If Z_DATA_ERROR is returned, the application may
+ then call inflateSync() to look for a good compression block if a partial
+ recovery of the data is to be attempted.
+*/
+
+
+ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm));
+/*
+ All dynamically allocated data structures for this stream are freed.
+ This function discards any unprocessed input and does not flush any pending
+ output.
+
+ inflateEnd returns Z_OK if success, or Z_STREAM_ERROR if the stream state
+ was inconsistent.
+*/
+
+
+ /* Advanced functions */
+
+/*
+ The following functions are needed only in some special applications.
+*/
+
+/*
+ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm,
+ int level,
+ int method,
+ int windowBits,
+ int memLevel,
+ int strategy));
+
+ This is another version of deflateInit with more compression options. The
+ fields next_in, zalloc, zfree and opaque must be initialized before by the
+ caller.
+
+ The method parameter is the compression method. It must be Z_DEFLATED in
+ this version of the library.
+
+ The windowBits parameter is the base two logarithm of the window size
+ (the size of the history buffer). It should be in the range 8..15 for this
+ version of the library. Larger values of this parameter result in better
+ compression at the expense of memory usage. The default value is 15 if
+ deflateInit is used instead.
+
+ For the current implementation of deflate(), a windowBits value of 8 (a
+ window size of 256 bytes) is not supported. As a result, a request for 8
+ will result in 9 (a 512-byte window). In that case, providing 8 to
+ inflateInit2() will result in an error when the zlib header with 9 is
+ checked against the initialization of inflate(). The remedy is to not use 8
+ with deflateInit2() with this initialization, or at least in that case use 9
+ with inflateInit2().
+
+ windowBits can also be -8..-15 for raw deflate. In this case, -windowBits
+ determines the window size. deflate() will then generate raw deflate data
+ with no zlib header or trailer, and will not compute a check value.
+
+ windowBits can also be greater than 15 for optional gzip encoding. Add
+ 16 to windowBits to write a simple gzip header and trailer around the
+ compressed data instead of a zlib wrapper. The gzip header will have no
+ file name, no extra data, no comment, no modification time (set to zero), no
+ header crc, and the operating system will be set to the appropriate value,
+ if the operating system was determined at compile time. If a gzip stream is
+ being written, strm->adler is a CRC-32 instead of an Adler-32.
+
+ For raw deflate or gzip encoding, a request for a 256-byte window is
+ rejected as invalid, since only the zlib header provides a means of
+ transmitting the window size to the decompressor.
+
+ The memLevel parameter specifies how much memory should be allocated
+ for the internal compression state. memLevel=1 uses minimum memory but is
+ slow and reduces compression ratio; memLevel=9 uses maximum memory for
+ optimal speed. The default value is 8. See zconf.h for total memory usage
+ as a function of windowBits and memLevel.
+
+ The strategy parameter is used to tune the compression algorithm. Use the
+ value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a
+ filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no
+ string match), or Z_RLE to limit match distances to one (run-length
+ encoding). Filtered data consists mostly of small values with a somewhat
+ random distribution. In this case, the compression algorithm is tuned to
+ compress them better. The effect of Z_FILTERED is to force more Huffman
+ coding and less string matching; it is somewhat intermediate between
+ Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as
+ fast as Z_HUFFMAN_ONLY, but give better compression for PNG image data. The
+ strategy parameter only affects the compression ratio but not the
+ correctness of the compressed output even if it is not set appropriately.
+ Z_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler
+ decoder for special applications.
+
+ deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_STREAM_ERROR if any parameter is invalid (such as an invalid
+ method), or Z_VERSION_ERROR if the zlib library version (zlib_version) is
+ incompatible with the version assumed by the caller (ZLIB_VERSION). msg is
+ set to null if there is no error message. deflateInit2 does not perform any
+ compression: this will be done by deflate().
+*/
+
+ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm,
+ const Bytef *dictionary,
+ uInt dictLength));
+/*
+ Initializes the compression dictionary from the given byte sequence
+ without producing any compressed output. When using the zlib format, this
+ function must be called immediately after deflateInit, deflateInit2 or
+ deflateReset, and before any call of deflate. When doing raw deflate, this
+ function must be called either before any call of deflate, or immediately
+ after the completion of a deflate block, i.e. after all input has been
+ consumed and all output has been delivered when using any of the flush
+ options Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, or Z_FULL_FLUSH. The
+ compressor and decompressor must use exactly the same dictionary (see
+ inflateSetDictionary).
+
+ The dictionary should consist of strings (byte sequences) that are likely
+ to be encountered later in the data to be compressed, with the most commonly
+ used strings preferably put towards the end of the dictionary. Using a
+ dictionary is most useful when the data to be compressed is short and can be
+ predicted with good accuracy; the data can then be compressed better than
+ with the default empty dictionary.
+
+ Depending on the size of the compression data structures selected by
+ deflateInit or deflateInit2, a part of the dictionary may in effect be
+ discarded, for example if the dictionary is larger than the window size
+ provided in deflateInit or deflateInit2. Thus the strings most likely to be
+ useful should be put at the end of the dictionary, not at the front. In
+ addition, the current implementation of deflate will use at most the window
+ size minus 262 bytes of the provided dictionary.
+
+ Upon return of this function, strm->adler is set to the Adler-32 value
+ of the dictionary; the decompressor may later use this value to determine
+ which dictionary has been used by the compressor. (The Adler-32 value
+ applies to the whole dictionary even if only a subset of the dictionary is
+ actually used by the compressor.) If a raw deflate was requested, then the
+ Adler-32 value is not computed and strm->adler is not set.
+
+ deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a
+ parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is
+ inconsistent (for example if deflate has already been called for this stream
+ or if not at a block boundary for raw deflate). deflateSetDictionary does
+ not perform any compression: this will be done by deflate().
+*/
+
+ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm,
+ Bytef *dictionary,
+ uInt *dictLength));
+/*
+ Returns the sliding dictionary being maintained by deflate. dictLength is
+ set to the number of bytes in the dictionary, and that many bytes are copied
+ to dictionary. dictionary must have enough space, where 32768 bytes is
+ always enough. If deflateGetDictionary() is called with dictionary equal to
+ Z_NULL, then only the dictionary length is returned, and nothing is copied.
+ Similary, if dictLength is Z_NULL, then it is not set.
+
+ deflateGetDictionary() may return a length less than the window size, even
+ when more than the window size in input has been provided. It may return up
+ to 258 bytes less in that case, due to how zlib's implementation of deflate
+ manages the sliding window and lookahead for matches, where matches can be
+ up to 258 bytes long. If the application needs the last window-size bytes of
+ input, then that would need to be saved by the application outside of zlib.
+
+ deflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the
+ stream state is inconsistent.
+*/
+
+ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest,
+ z_streamp source));
+/*
+ Sets the destination stream as a complete copy of the source stream.
+
+ This function can be useful when several compression strategies will be
+ tried, for example when there are several ways of pre-processing the input
+ data with a filter. The streams that will be discarded should then be freed
+ by calling deflateEnd. Note that deflateCopy duplicates the internal
+ compression state which can be quite large, so this strategy is slow and can
+ consume lots of memory.
+
+ deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
+ (such as zalloc being Z_NULL). msg is left unchanged in both source and
+ destination.
+*/
+
+ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm));
+/*
+ This function is equivalent to deflateEnd followed by deflateInit, but
+ does not free and reallocate the internal compression state. The stream
+ will leave the compression level and any other attributes that may have been
+ set unchanged.
+
+ deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent (such as zalloc or state being Z_NULL).
+*/
+
+ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm,
+ int level,
+ int strategy));
+/*
+ Dynamically update the compression level and compression strategy. The
+ interpretation of level and strategy is as in deflateInit2(). This can be
+ used to switch between compression and straight copy of the input data, or
+ to switch to a different kind of input data requiring a different strategy.
+ If the compression approach (which is a function of the level) or the
+ strategy is changed, and if any input has been consumed in a previous
+ deflate() call, then the input available so far is compressed with the old
+ level and strategy using deflate(strm, Z_BLOCK). There are three approaches
+ for the compression levels 0, 1..3, and 4..9 respectively. The new level
+ and strategy will take effect at the next call of deflate().
+
+ If a deflate(strm, Z_BLOCK) is performed by deflateParams(), and it does
+ not have enough output space to complete, then the parameter change will not
+ take effect. In this case, deflateParams() can be called again with the
+ same parameters and more output space to try again.
+
+ In order to assure a change in the parameters on the first try, the
+ deflate stream should be flushed using deflate() with Z_BLOCK or other flush
+ request until strm.avail_out is not zero, before calling deflateParams().
+ Then no more input data should be provided before the deflateParams() call.
+ If this is done, the old level and strategy will be applied to the data
+ compressed before deflateParams(), and the new level and strategy will be
+ applied to the the data compressed after deflateParams().
+
+ deflateParams returns Z_OK on success, Z_STREAM_ERROR if the source stream
+ state was inconsistent or if a parameter was invalid, or Z_BUF_ERROR if
+ there was not enough output space to complete the compression of the
+ available input data before a change in the strategy or approach. Note that
+ in the case of a Z_BUF_ERROR, the parameters are not changed. A return
+ value of Z_BUF_ERROR is not fatal, in which case deflateParams() can be
+ retried with more output space.
+*/
+
+ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm,
+ int good_length,
+ int max_lazy,
+ int nice_length,
+ int max_chain));
+/*
+ Fine tune deflate's internal compression parameters. This should only be
+ used by someone who understands the algorithm used by zlib's deflate for
+ searching for the best matching string, and even then only by the most
+ fanatic optimizer trying to squeeze out the last compressed bit for their
+ specific input data. Read the deflate.c source code for the meaning of the
+ max_lazy, good_length, nice_length, and max_chain parameters.
+
+ deflateTune() can be called after deflateInit() or deflateInit2(), and
+ returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream.
+ */
+
+ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm,
+ uLong sourceLen));
+/*
+ deflateBound() returns an upper bound on the compressed size after
+ deflation of sourceLen bytes. It must be called after deflateInit() or
+ deflateInit2(), and after deflateSetHeader(), if used. This would be used
+ to allocate an output buffer for deflation in a single pass, and so would be
+ called before deflate(). If that first deflate() call is provided the
+ sourceLen input bytes, an output buffer allocated to the size returned by
+ deflateBound(), and the flush value Z_FINISH, then deflate() is guaranteed
+ to return Z_STREAM_END. Note that it is possible for the compressed size to
+ be larger than the value returned by deflateBound() if flush options other
+ than Z_FINISH or Z_NO_FLUSH are used.
+*/
+
+ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm,
+ unsigned *pending,
+ int *bits));
+/*
+ deflatePending() returns the number of bytes and bits of output that have
+ been generated, but not yet provided in the available output. The bytes not
+ provided would be due to the available output space having being consumed.
+ The number of bits of output not provided are between 0 and 7, where they
+ await more bits to join them in order to fill out a full byte. If pending
+ or bits are Z_NULL, then those values are not set.
+
+ deflatePending returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent.
+ */
+
+ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm,
+ int bits,
+ int value));
+/*
+ deflatePrime() inserts bits in the deflate output stream. The intent
+ is that this function is used to start off the deflate output with the bits
+ leftover from a previous deflate stream when appending to it. As such, this
+ function can only be used for raw deflate, and must be used before the first
+ deflate() call after a deflateInit2() or deflateReset(). bits must be less
+ than or equal to 16, and that many of the least significant bits of value
+ will be inserted in the output.
+
+ deflatePrime returns Z_OK if success, Z_BUF_ERROR if there was not enough
+ room in the internal buffer to insert the bits, or Z_STREAM_ERROR if the
+ source stream state was inconsistent.
+*/
+
+ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm,
+ gz_headerp head));
+/*
+ deflateSetHeader() provides gzip header information for when a gzip
+ stream is requested by deflateInit2(). deflateSetHeader() may be called
+ after deflateInit2() or deflateReset() and before the first call of
+ deflate(). The text, time, os, extra field, name, and comment information
+ in the provided gz_header structure are written to the gzip header (xflag is
+ ignored -- the extra flags are set according to the compression level). The
+ caller must assure that, if not Z_NULL, name and comment are terminated with
+ a zero byte, and that if extra is not Z_NULL, that extra_len bytes are
+ available there. If hcrc is true, a gzip header crc is included. Note that
+ the current versions of the command-line version of gzip (up through version
+ 1.3.x) do not support header crc's, and will report that it is a "multi-part
+ gzip file" and give up.
+
+ If deflateSetHeader is not used, the default gzip header has text false,
+ the time set to zero, and os set to 255, with no extra, name, or comment
+ fields. The gzip header is returned to the default state by deflateReset().
+
+ deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent.
+*/
+
+/*
+ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm,
+ int windowBits));
+
+ This is another version of inflateInit with an extra parameter. The
+ fields next_in, avail_in, zalloc, zfree and opaque must be initialized
+ before by the caller.
+
+ The windowBits parameter is the base two logarithm of the maximum window
+ size (the size of the history buffer). It should be in the range 8..15 for
+ this version of the library. The default value is 15 if inflateInit is used
+ instead. windowBits must be greater than or equal to the windowBits value
+ provided to deflateInit2() while compressing, or it must be equal to 15 if
+ deflateInit2() was not used. If a compressed stream with a larger window
+ size is given as input, inflate() will return with the error code
+ Z_DATA_ERROR instead of trying to allocate a larger window.
+
+ windowBits can also be zero to request that inflate use the window size in
+ the zlib header of the compressed stream.
+
+ windowBits can also be -8..-15 for raw inflate. In this case, -windowBits
+ determines the window size. inflate() will then process raw deflate data,
+ not looking for a zlib or gzip header, not generating a check value, and not
+ looking for any check values for comparison at the end of the stream. This
+ is for use with other formats that use the deflate compressed data format
+ such as zip. Those formats provide their own check values. If a custom
+ format is developed using the raw deflate format for compressed data, it is
+ recommended that a check value such as an Adler-32 or a CRC-32 be applied to
+ the uncompressed data as is done in the zlib, gzip, and zip formats. For
+ most applications, the zlib format should be used as is. Note that comments
+ above on the use in deflateInit2() applies to the magnitude of windowBits.
+
+ windowBits can also be greater than 15 for optional gzip decoding. Add
+ 32 to windowBits to enable zlib and gzip decoding with automatic header
+ detection, or add 16 to decode only the gzip format (the zlib format will
+ return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is a
+ CRC-32 instead of an Adler-32. Unlike the gunzip utility and gzread() (see
+ below), inflate() will not automatically decode concatenated gzip streams.
+ inflate() will return Z_STREAM_END at the end of the gzip stream. The state
+ would need to be reset to continue decoding a subsequent gzip stream.
+
+ inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
+ version assumed by the caller, or Z_STREAM_ERROR if the parameters are
+ invalid, such as a null pointer to the structure. msg is set to null if
+ there is no error message. inflateInit2 does not perform any decompression
+ apart from possibly reading the zlib header if present: actual decompression
+ will be done by inflate(). (So next_in and avail_in may be modified, but
+ next_out and avail_out are unused and unchanged.) The current implementation
+ of inflateInit2() does not process any header information -- that is
+ deferred until inflate() is called.
+*/
+
+ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm,
+ const Bytef *dictionary,
+ uInt dictLength));
+/*
+ Initializes the decompression dictionary from the given uncompressed byte
+ sequence. This function must be called immediately after a call of inflate,
+ if that call returned Z_NEED_DICT. The dictionary chosen by the compressor
+ can be determined from the Adler-32 value returned by that call of inflate.
+ The compressor and decompressor must use exactly the same dictionary (see
+ deflateSetDictionary). For raw inflate, this function can be called at any
+ time to set the dictionary. If the provided dictionary is smaller than the
+ window and there is already data in the window, then the provided dictionary
+ will amend what's there. The application must insure that the dictionary
+ that was used for compression is provided.
+
+ inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a
+ parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is
+ inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the
+ expected one (incorrect Adler-32 value). inflateSetDictionary does not
+ perform any decompression: this will be done by subsequent calls of
+ inflate().
+*/
+
+ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm,
+ Bytef *dictionary,
+ uInt *dictLength));
+/*
+ Returns the sliding dictionary being maintained by inflate. dictLength is
+ set to the number of bytes in the dictionary, and that many bytes are copied
+ to dictionary. dictionary must have enough space, where 32768 bytes is
+ always enough. If inflateGetDictionary() is called with dictionary equal to
+ Z_NULL, then only the dictionary length is returned, and nothing is copied.
+ Similary, if dictLength is Z_NULL, then it is not set.
+
+ inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the
+ stream state is inconsistent.
+*/
+
+ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm));
+/*
+ Skips invalid compressed data until a possible full flush point (see above
+ for the description of deflate with Z_FULL_FLUSH) can be found, or until all
+ available input is skipped. No output is provided.
+
+ inflateSync searches for a 00 00 FF FF pattern in the compressed data.
+ All full flush points have this pattern, but not all occurrences of this
+ pattern are full flush points.
+
+ inflateSync returns Z_OK if a possible full flush point has been found,
+ Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point
+ has been found, or Z_STREAM_ERROR if the stream structure was inconsistent.
+ In the success case, the application may save the current current value of
+ total_in which indicates where valid compressed data was found. In the
+ error case, the application may repeatedly call inflateSync, providing more
+ input each time, until success or end of the input data.
+*/
+
+ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest,
+ z_streamp source));
+/*
+ Sets the destination stream as a complete copy of the source stream.
+
+ This function can be useful when randomly accessing a large stream. The
+ first pass through the stream can periodically record the inflate state,
+ allowing restarting inflate at those points when randomly accessing the
+ stream.
+
+ inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
+ (such as zalloc being Z_NULL). msg is left unchanged in both source and
+ destination.
+*/
+
+ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm));
+/*
+ This function is equivalent to inflateEnd followed by inflateInit,
+ but does not free and reallocate the internal decompression state. The
+ stream will keep attributes that may have been set by inflateInit2.
+
+ inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent (such as zalloc or state being Z_NULL).
+*/
+
+ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm,
+ int windowBits));
+/*
+ This function is the same as inflateReset, but it also permits changing
+ the wrap and window size requests. The windowBits parameter is interpreted
+ the same as it is for inflateInit2. If the window size is changed, then the
+ memory allocated for the window is freed, and the window will be reallocated
+ by inflate() if needed.
+
+ inflateReset2 returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent (such as zalloc or state being Z_NULL), or if
+ the windowBits parameter is invalid.
+*/
+
+ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm,
+ int bits,
+ int value));
+/*
+ This function inserts bits in the inflate input stream. The intent is
+ that this function is used to start inflating at a bit position in the
+ middle of a byte. The provided bits will be used before any bytes are used
+ from next_in. This function should only be used with raw inflate, and
+ should be used before the first inflate() call after inflateInit2() or
+ inflateReset(). bits must be less than or equal to 16, and that many of the
+ least significant bits of value will be inserted in the input.
+
+ If bits is negative, then the input stream bit buffer is emptied. Then
+ inflatePrime() can be called again to put bits in the buffer. This is used
+ to clear out bits leftover after feeding inflate a block description prior
+ to feeding inflate codes.
+
+ inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent.
+*/
+
+ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm));
+/*
+ This function returns two values, one in the lower 16 bits of the return
+ value, and the other in the remaining upper bits, obtained by shifting the
+ return value down 16 bits. If the upper value is -1 and the lower value is
+ zero, then inflate() is currently decoding information outside of a block.
+ If the upper value is -1 and the lower value is non-zero, then inflate is in
+ the middle of a stored block, with the lower value equaling the number of
+ bytes from the input remaining to copy. If the upper value is not -1, then
+ it is the number of bits back from the current bit position in the input of
+ the code (literal or length/distance pair) currently being processed. In
+ that case the lower value is the number of bytes already emitted for that
+ code.
+
+ A code is being processed if inflate is waiting for more input to complete
+ decoding of the code, or if it has completed decoding but is waiting for
+ more output space to write the literal or match data.
+
+ inflateMark() is used to mark locations in the input data for random
+ access, which may be at bit positions, and to note those cases where the
+ output of a code may span boundaries of random access blocks. The current
+ location in the input stream can be determined from avail_in and data_type
+ as noted in the description for the Z_BLOCK flush parameter for inflate.
+
+ inflateMark returns the value noted above, or -65536 if the provided
+ source stream state was inconsistent.
+*/
+
+ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm,
+ gz_headerp head));
+/*
+ inflateGetHeader() requests that gzip header information be stored in the
+ provided gz_header structure. inflateGetHeader() may be called after
+ inflateInit2() or inflateReset(), and before the first call of inflate().
+ As inflate() processes the gzip stream, head->done is zero until the header
+ is completed, at which time head->done is set to one. If a zlib stream is
+ being decoded, then head->done is set to -1 to indicate that there will be
+ no gzip header information forthcoming. Note that Z_BLOCK or Z_TREES can be
+ used to force inflate() to return immediately after header processing is
+ complete and before any actual data is decompressed.
+
+ The text, time, xflags, and os fields are filled in with the gzip header
+ contents. hcrc is set to true if there is a header CRC. (The header CRC
+ was valid if done is set to one.) If extra is not Z_NULL, then extra_max
+ contains the maximum number of bytes to write to extra. Once done is true,
+ extra_len contains the actual extra field length, and extra contains the
+ extra field, or that field truncated if extra_max is less than extra_len.
+ If name is not Z_NULL, then up to name_max characters are written there,
+ terminated with a zero unless the length is greater than name_max. If
+ comment is not Z_NULL, then up to comm_max characters are written there,
+ terminated with a zero unless the length is greater than comm_max. When any
+ of extra, name, or comment are not Z_NULL and the respective field is not
+ present in the header, then that field is set to Z_NULL to signal its
+ absence. This allows the use of deflateSetHeader() with the returned
+ structure to duplicate the header. However if those fields are set to
+ allocated memory, then the application will need to save those pointers
+ elsewhere so that they can be eventually freed.
+
+ If inflateGetHeader is not used, then the header information is simply
+ discarded. The header is always checked for validity, including the header
+ CRC if present. inflateReset() will reset the process to discard the header
+ information. The application would need to call inflateGetHeader() again to
+ retrieve the header from the next gzip stream.
+
+ inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent.
+*/
+
+/*
+ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits,
+ unsigned char FAR *window));
+
+ Initialize the internal stream state for decompression using inflateBack()
+ calls. The fields zalloc, zfree and opaque in strm must be initialized
+ before the call. If zalloc and zfree are Z_NULL, then the default library-
+ derived memory allocation routines are used. windowBits is the base two
+ logarithm of the window size, in the range 8..15. window is a caller
+ supplied buffer of that size. Except for special applications where it is
+ assured that deflate was used with small window sizes, windowBits must be 15
+ and a 32K byte window must be supplied to be able to decompress general
+ deflate streams.
+
+ See inflateBack() for the usage of these routines.
+
+ inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of
+ the parameters are invalid, Z_MEM_ERROR if the internal state could not be
+ allocated, or Z_VERSION_ERROR if the version of the library does not match
+ the version of the header file.
+*/
+
+typedef unsigned (*in_func) OF((void FAR *,
+ z_const unsigned char FAR * FAR *));
+typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned));
+
+ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm,
+ in_func in, void FAR *in_desc,
+ out_func out, void FAR *out_desc));
+/*
+ inflateBack() does a raw inflate with a single call using a call-back
+ interface for input and output. This is potentially more efficient than
+ inflate() for file i/o applications, in that it avoids copying between the
+ output and the sliding window by simply making the window itself the output
+ buffer. inflate() can be faster on modern CPUs when used with large
+ buffers. inflateBack() trusts the application to not change the output
+ buffer passed by the output function, at least until inflateBack() returns.
+
+ inflateBackInit() must be called first to allocate the internal state
+ and to initialize the state with the user-provided window buffer.
+ inflateBack() may then be used multiple times to inflate a complete, raw
+ deflate stream with each call. inflateBackEnd() is then called to free the
+ allocated state.
+
+ A raw deflate stream is one with no zlib or gzip header or trailer.
+ This routine would normally be used in a utility that reads zip or gzip
+ files and writes out uncompressed files. The utility would decode the
+ header and process the trailer on its own, hence this routine expects only
+ the raw deflate stream to decompress. This is different from the default
+ behavior of inflate(), which expects a zlib header and trailer around the
+ deflate stream.
+
+ inflateBack() uses two subroutines supplied by the caller that are then
+ called by inflateBack() for input and output. inflateBack() calls those
+ routines until it reads a complete deflate stream and writes out all of the
+ uncompressed data, or until it encounters an error. The function's
+ parameters and return types are defined above in the in_func and out_func
+ typedefs. inflateBack() will call in(in_desc, &buf) which should return the
+ number of bytes of provided input, and a pointer to that input in buf. If
+ there is no input available, in() must return zero -- buf is ignored in that
+ case -- and inflateBack() will return a buffer error. inflateBack() will
+ call out(out_desc, buf, len) to write the uncompressed data buf[0..len-1].
+ out() should return zero on success, or non-zero on failure. If out()
+ returns non-zero, inflateBack() will return with an error. Neither in() nor
+ out() are permitted to change the contents of the window provided to
+ inflateBackInit(), which is also the buffer that out() uses to write from.
+ The length written by out() will be at most the window size. Any non-zero
+ amount of input may be provided by in().
+
+ For convenience, inflateBack() can be provided input on the first call by
+ setting strm->next_in and strm->avail_in. If that input is exhausted, then
+ in() will be called. Therefore strm->next_in must be initialized before
+ calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called
+ immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in
+ must also be initialized, and then if strm->avail_in is not zero, input will
+ initially be taken from strm->next_in[0 .. strm->avail_in - 1].
+
+ The in_desc and out_desc parameters of inflateBack() is passed as the
+ first parameter of in() and out() respectively when they are called. These
+ descriptors can be optionally used to pass any information that the caller-
+ supplied in() and out() functions need to do their job.
+
+ On return, inflateBack() will set strm->next_in and strm->avail_in to
+ pass back any unused input that was provided by the last in() call. The
+ return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR
+ if in() or out() returned an error, Z_DATA_ERROR if there was a format error
+ in the deflate stream (in which case strm->msg is set to indicate the nature
+ of the error), or Z_STREAM_ERROR if the stream was not properly initialized.
+ In the case of Z_BUF_ERROR, an input or output error can be distinguished
+ using strm->next_in which will be Z_NULL only if in() returned an error. If
+ strm->next_in is not Z_NULL, then the Z_BUF_ERROR was due to out() returning
+ non-zero. (in() will always be called before out(), so strm->next_in is
+ assured to be defined if out() returns non-zero.) Note that inflateBack()
+ cannot return Z_OK.
+*/
+
+ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm));
+/*
+ All memory allocated by inflateBackInit() is freed.
+
+ inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream
+ state was inconsistent.
+*/
+
+ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void));
+/* Return flags indicating compile-time options.
+
+ Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other:
+ 1.0: size of uInt
+ 3.2: size of uLong
+ 5.4: size of voidpf (pointer)
+ 7.6: size of z_off_t
+
+ Compiler, assembler, and debug options:
+ 8: ZLIB_DEBUG
+ 9: ASMV or ASMINF -- use ASM code
+ 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention
+ 11: 0 (reserved)
+
+ One-time table building (smaller code, but not thread-safe if true):
+ 12: BUILDFIXED -- build static block decoding tables when needed
+ 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed
+ 14,15: 0 (reserved)
+
+ Library content (indicates missing functionality):
+ 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking
+ deflate code when not needed)
+ 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect
+ and decode gzip streams (to avoid linking crc code)
+ 18-19: 0 (reserved)
+
+ Operation variations (changes in library functionality):
+ 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate
+ 21: FASTEST -- deflate algorithm with only one, lowest compression level
+ 22,23: 0 (reserved)
+
+ The sprintf variant used by gzprintf (zero is best):
+ 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format
+ 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure!
+ 26: 0 = returns value, 1 = void -- 1 means inferred string length returned
+
+ Remainder:
+ 27-31: 0 (reserved)
+ */
+
+#ifndef Z_SOLO
+
+ /* utility functions */
+
+/*
+ The following utility functions are implemented on top of the basic
+ stream-oriented functions. To simplify the interface, some default options
+ are assumed (compression level and memory usage, standard memory allocation
+ functions). The source code of these utility functions can be modified if
+ you need special options.
+*/
+
+ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen,
+ const Bytef *source, uLong sourceLen));
+/*
+ Compresses the source buffer into the destination buffer. sourceLen is
+ the byte length of the source buffer. Upon entry, destLen is the total size
+ of the destination buffer, which must be at least the value returned by
+ compressBound(sourceLen). Upon exit, destLen is the actual size of the
+ compressed data. compress() is equivalent to compress2() with a level
+ parameter of Z_DEFAULT_COMPRESSION.
+
+ compress returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_BUF_ERROR if there was not enough room in the output
+ buffer.
+*/
+
+ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen,
+ const Bytef *source, uLong sourceLen,
+ int level));
+/*
+ Compresses the source buffer into the destination buffer. The level
+ parameter has the same meaning as in deflateInit. sourceLen is the byte
+ length of the source buffer. Upon entry, destLen is the total size of the
+ destination buffer, which must be at least the value returned by
+ compressBound(sourceLen). Upon exit, destLen is the actual size of the
+ compressed data.
+
+ compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_BUF_ERROR if there was not enough room in the output buffer,
+ Z_STREAM_ERROR if the level parameter is invalid.
+*/
+
+ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen));
+/*
+ compressBound() returns an upper bound on the compressed size after
+ compress() or compress2() on sourceLen bytes. It would be used before a
+ compress() or compress2() call to allocate the destination buffer.
+*/
+
+ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen,
+ const Bytef *source, uLong sourceLen));
+/*
+ Decompresses the source buffer into the destination buffer. sourceLen is
+ the byte length of the source buffer. Upon entry, destLen is the total size
+ of the destination buffer, which must be large enough to hold the entire
+ uncompressed data. (The size of the uncompressed data must have been saved
+ previously by the compressor and transmitted to the decompressor by some
+ mechanism outside the scope of this compression library.) Upon exit, destLen
+ is the actual size of the uncompressed data.
+
+ uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_BUF_ERROR if there was not enough room in the output
+ buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. In
+ the case where there is not enough room, uncompress() will fill the output
+ buffer with the uncompressed data up to that point.
+*/
+
+ZEXTERN int ZEXPORT uncompress2 OF((Bytef *dest, uLongf *destLen,
+ const Bytef *source, uLong *sourceLen));
+/*
+ Same as uncompress, except that sourceLen is a pointer, where the
+ length of the source is *sourceLen. On return, *sourceLen is the number of
+ source bytes consumed.
+*/
+
+ /* gzip file access functions */
+
+/*
+ This library supports reading and writing files in gzip (.gz) format with
+ an interface similar to that of stdio, using the functions that start with
+ "gz". The gzip format is different from the zlib format. gzip is a gzip
+ wrapper, documented in RFC 1952, wrapped around a deflate stream.
+*/
+
+typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */
+
+/*
+ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode));
+
+ Opens a gzip (.gz) file for reading or writing. The mode parameter is as
+ in fopen ("rb" or "wb") but can also include a compression level ("wb9") or
+ a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only
+ compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F'
+ for fixed code compression as in "wb9F". (See the description of
+ deflateInit2 for more information about the strategy parameter.) 'T' will
+ request transparent writing or appending with no compression and not using
+ the gzip format.
+
+ "a" can be used instead of "w" to request that the gzip stream that will
+ be written be appended to the file. "+" will result in an error, since
+ reading and writing to the same gzip file is not supported. The addition of
+ "x" when writing will create the file exclusively, which fails if the file
+ already exists. On systems that support it, the addition of "e" when
+ reading or writing will set the flag to close the file on an execve() call.
+
+ These functions, as well as gzip, will read and decode a sequence of gzip
+ streams in a file. The append function of gzopen() can be used to create
+ such a file. (Also see gzflush() for another way to do this.) When
+ appending, gzopen does not test whether the file begins with a gzip stream,
+ nor does it look for the end of the gzip streams to begin appending. gzopen
+ will simply append a gzip stream to the existing file.
+
+ gzopen can be used to read a file which is not in gzip format; in this
+ case gzread will directly read from the file without decompression. When
+ reading, this will be detected automatically by looking for the magic two-
+ byte gzip header.
+
+ gzopen returns NULL if the file could not be opened, if there was
+ insufficient memory to allocate the gzFile state, or if an invalid mode was
+ specified (an 'r', 'w', or 'a' was not provided, or '+' was provided).
+ errno can be checked to determine if the reason gzopen failed was that the
+ file could not be opened.
+*/
+
+ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode));
+/*
+ gzdopen associates a gzFile with the file descriptor fd. File descriptors
+ are obtained from calls like open, dup, creat, pipe or fileno (if the file
+ has been previously opened with fopen). The mode parameter is as in gzopen.
+
+ The next call of gzclose on the returned gzFile will also close the file
+ descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor
+ fd. If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd,
+ mode);. The duplicated descriptor should be saved to avoid a leak, since
+ gzdopen does not close fd if it fails. If you are using fileno() to get the
+ file descriptor from a FILE *, then you will have to use dup() to avoid
+ double-close()ing the file descriptor. Both gzclose() and fclose() will
+ close the associated file descriptor, so they need to have different file
+ descriptors.
+
+ gzdopen returns NULL if there was insufficient memory to allocate the
+ gzFile state, if an invalid mode was specified (an 'r', 'w', or 'a' was not
+ provided, or '+' was provided), or if fd is -1. The file descriptor is not
+ used until the next gz* read, write, seek, or close operation, so gzdopen
+ will not detect if fd is invalid (unless fd is -1).
+*/
+
+ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size));
+/*
+ Set the internal buffer size used by this library's functions. The
+ default buffer size is 8192 bytes. This function must be called after
+ gzopen() or gzdopen(), and before any other calls that read or write the
+ file. The buffer memory allocation is always deferred to the first read or
+ write. Three times that size in buffer space is allocated. A larger buffer
+ size of, for example, 64K or 128K bytes will noticeably increase the speed
+ of decompression (reading).
+
+ The new buffer size also affects the maximum length for gzprintf().
+
+ gzbuffer() returns 0 on success, or -1 on failure, such as being called
+ too late.
+*/
+
+ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy));
+/*
+ Dynamically update the compression level or strategy. See the description
+ of deflateInit2 for the meaning of these parameters. Previously provided
+ data is flushed before the parameter change.
+
+ gzsetparams returns Z_OK if success, Z_STREAM_ERROR if the file was not
+ opened for writing, Z_ERRNO if there is an error writing the flushed data,
+ or Z_MEM_ERROR if there is a memory allocation error.
+*/
+
+ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len));
+/*
+ Reads the given number of uncompressed bytes from the compressed file. If
+ the input file is not in gzip format, gzread copies the given number of
+ bytes into the buffer directly from the file.
+
+ After reaching the end of a gzip stream in the input, gzread will continue
+ to read, looking for another gzip stream. Any number of gzip streams may be
+ concatenated in the input file, and will all be decompressed by gzread().
+ If something other than a gzip stream is encountered after a gzip stream,
+ that remaining trailing garbage is ignored (and no error is returned).
+
+ gzread can be used to read a gzip file that is being concurrently written.
+ Upon reaching the end of the input, gzread will return with the available
+ data. If the error code returned by gzerror is Z_OK or Z_BUF_ERROR, then
+ gzclearerr can be used to clear the end of file indicator in order to permit
+ gzread to be tried again. Z_OK indicates that a gzip stream was completed
+ on the last gzread. Z_BUF_ERROR indicates that the input file ended in the
+ middle of a gzip stream. Note that gzread does not return -1 in the event
+ of an incomplete gzip stream. This error is deferred until gzclose(), which
+ will return Z_BUF_ERROR if the last gzread ended in the middle of a gzip
+ stream. Alternatively, gzerror can be used before gzclose to detect this
+ case.
+
+ gzread returns the number of uncompressed bytes actually read, less than
+ len for end of file, or -1 for error. If len is too large to fit in an int,
+ then nothing is read, -1 is returned, and the error state is set to
+ Z_STREAM_ERROR.
+*/
+
+ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems,
+ gzFile file));
+/*
+ Read up to nitems items of size size from file to buf, otherwise operating
+ as gzread() does. This duplicates the interface of stdio's fread(), with
+ size_t request and return types. If the library defines size_t, then
+ z_size_t is identical to size_t. If not, then z_size_t is an unsigned
+ integer type that can contain a pointer.
+
+ gzfread() returns the number of full items read of size size, or zero if
+ the end of the file was reached and a full item could not be read, or if
+ there was an error. gzerror() must be consulted if zero is returned in
+ order to determine if there was an error. If the multiplication of size and
+ nitems overflows, i.e. the product does not fit in a z_size_t, then nothing
+ is read, zero is returned, and the error state is set to Z_STREAM_ERROR.
+
+ In the event that the end of file is reached and only a partial item is
+ available at the end, i.e. the remaining uncompressed data length is not a
+ multiple of size, then the final partial item is nevetheless read into buf
+ and the end-of-file flag is set. The length of the partial item read is not
+ provided, but could be inferred from the result of gztell(). This behavior
+ is the same as the behavior of fread() implementations in common libraries,
+ but it prevents the direct use of gzfread() to read a concurrently written
+ file, reseting and retrying on end-of-file, when size is not 1.
+*/
+
+ZEXTERN int ZEXPORT gzwrite OF((gzFile file,
+ voidpc buf, unsigned len));
+/*
+ Writes the given number of uncompressed bytes into the compressed file.
+ gzwrite returns the number of uncompressed bytes written or 0 in case of
+ error.
+*/
+
+ZEXTERN z_size_t ZEXPORT gzfwrite OF((voidpc buf, z_size_t size,
+ z_size_t nitems, gzFile file));
+/*
+ gzfwrite() writes nitems items of size size from buf to file, duplicating
+ the interface of stdio's fwrite(), with size_t request and return types. If
+ the library defines size_t, then z_size_t is identical to size_t. If not,
+ then z_size_t is an unsigned integer type that can contain a pointer.
+
+ gzfwrite() returns the number of full items written of size size, or zero
+ if there was an error. If the multiplication of size and nitems overflows,
+ i.e. the product does not fit in a z_size_t, then nothing is written, zero
+ is returned, and the error state is set to Z_STREAM_ERROR.
+*/
+
+ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...));
+/*
+ Converts, formats, and writes the arguments to the compressed file under
+ control of the format string, as in fprintf. gzprintf returns the number of
+ uncompressed bytes actually written, or a negative zlib error code in case
+ of error. The number of uncompressed bytes written is limited to 8191, or
+ one less than the buffer size given to gzbuffer(). The caller should assure
+ that this limit is not exceeded. If it is exceeded, then gzprintf() will
+ return an error (0) with nothing written. In this case, there may also be a
+ buffer overflow with unpredictable consequences, which is possible only if
+ zlib was compiled with the insecure functions sprintf() or vsprintf()
+ because the secure snprintf() or vsnprintf() functions were not available.
+ This can be determined using zlibCompileFlags().
+*/
+
+ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s));
+/*
+ Writes the given null-terminated string to the compressed file, excluding
+ the terminating null character.
+
+ gzputs returns the number of characters written, or -1 in case of error.
+*/
+
+ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len));
+/*
+ Reads bytes from the compressed file until len-1 characters are read, or a
+ newline character is read and transferred to buf, or an end-of-file
+ condition is encountered. If any characters are read or if len == 1, the
+ string is terminated with a null character. If no characters are read due
+ to an end-of-file or len < 1, then the buffer is left untouched.
+
+ gzgets returns buf which is a null-terminated string, or it returns NULL
+ for end-of-file or in case of error. If there was an error, the contents at
+ buf are indeterminate.
+*/
+
+ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c));
+/*
+ Writes c, converted to an unsigned char, into the compressed file. gzputc
+ returns the value that was written, or -1 in case of error.
+*/
+
+ZEXTERN int ZEXPORT gzgetc OF((gzFile file));
+/*
+ Reads one byte from the compressed file. gzgetc returns this byte or -1
+ in case of end of file or error. This is implemented as a macro for speed.
+ As such, it does not do all of the checking the other functions do. I.e.
+ it does not check to see if file is NULL, nor whether the structure file
+ points to has been clobbered or not.
+*/
+
+ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file));
+/*
+ Push one character back onto the stream to be read as the first character
+ on the next read. At least one character of push-back is allowed.
+ gzungetc() returns the character pushed, or -1 on failure. gzungetc() will
+ fail if c is -1, and may fail if a character has been pushed but not read
+ yet. If gzungetc is used immediately after gzopen or gzdopen, at least the
+ output buffer size of pushed characters is allowed. (See gzbuffer above.)
+ The pushed character will be discarded if the stream is repositioned with
+ gzseek() or gzrewind().
+*/
+
+ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush));
+/*
+ Flushes all pending output into the compressed file. The parameter flush
+ is as in the deflate() function. The return value is the zlib error number
+ (see function gzerror below). gzflush is only permitted when writing.
+
+ If the flush parameter is Z_FINISH, the remaining data is written and the
+ gzip stream is completed in the output. If gzwrite() is called again, a new
+ gzip stream will be started in the output. gzread() is able to read such
+ concatenated gzip streams.
+
+ gzflush should be called only when strictly necessary because it will
+ degrade compression if called too often.
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file,
+ z_off_t offset, int whence));
+
+ Sets the starting position for the next gzread or gzwrite on the given
+ compressed file. The offset represents a number of bytes in the
+ uncompressed data stream. The whence parameter is defined as in lseek(2);
+ the value SEEK_END is not supported.
+
+ If the file is opened for reading, this function is emulated but can be
+ extremely slow. If the file is opened for writing, only forward seeks are
+ supported; gzseek then compresses a sequence of zeroes up to the new
+ starting position.
+
+ gzseek returns the resulting offset location as measured in bytes from
+ the beginning of the uncompressed stream, or -1 in case of error, in
+ particular if the file is opened for writing and the new starting position
+ would be before the current position.
+*/
+
+ZEXTERN int ZEXPORT gzrewind OF((gzFile file));
+/*
+ Rewinds the given file. This function is supported only for reading.
+
+ gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET)
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file));
+
+ Returns the starting position for the next gzread or gzwrite on the given
+ compressed file. This position represents a number of bytes in the
+ uncompressed data stream, and is zero when starting, even if appending or
+ reading a gzip stream from the middle of a file using gzdopen().
+
+ gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR)
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file));
+
+ Returns the current offset in the file being read or written. This offset
+ includes the count of bytes that precede the gzip stream, for example when
+ appending or when using gzdopen() for reading. When reading, the offset
+ does not include as yet unused buffered input. This information can be used
+ for a progress indicator. On error, gzoffset() returns -1.
+*/
+
+ZEXTERN int ZEXPORT gzeof OF((gzFile file));
+/*
+ Returns true (1) if the end-of-file indicator has been set while reading,
+ false (0) otherwise. Note that the end-of-file indicator is set only if the
+ read tried to go past the end of the input, but came up short. Therefore,
+ just like feof(), gzeof() may return false even if there is no more data to
+ read, in the event that the last read request was for the exact number of
+ bytes remaining in the input file. This will happen if the input file size
+ is an exact multiple of the buffer size.
+
+ If gzeof() returns true, then the read functions will return no more data,
+ unless the end-of-file indicator is reset by gzclearerr() and the input file
+ has grown since the previous end of file was detected.
+*/
+
+ZEXTERN int ZEXPORT gzdirect OF((gzFile file));
+/*
+ Returns true (1) if file is being copied directly while reading, or false
+ (0) if file is a gzip stream being decompressed.
+
+ If the input file is empty, gzdirect() will return true, since the input
+ does not contain a gzip stream.
+
+ If gzdirect() is used immediately after gzopen() or gzdopen() it will
+ cause buffers to be allocated to allow reading the file to determine if it
+ is a gzip file. Therefore if gzbuffer() is used, it should be called before
+ gzdirect().
+
+ When writing, gzdirect() returns true (1) if transparent writing was
+ requested ("wT" for the gzopen() mode), or false (0) otherwise. (Note:
+ gzdirect() is not needed when writing. Transparent writing must be
+ explicitly requested, so the application already knows the answer. When
+ linking statically, using gzdirect() will include all of the zlib code for
+ gzip file reading and decompression, which may not be desired.)
+*/
+
+ZEXTERN int ZEXPORT gzclose OF((gzFile file));
+/*
+ Flushes all pending output if necessary, closes the compressed file and
+ deallocates the (de)compression state. Note that once file is closed, you
+ cannot call gzerror with file, since its structures have been deallocated.
+ gzclose must not be called more than once on the same file, just as free
+ must not be called more than once on the same allocation.
+
+ gzclose will return Z_STREAM_ERROR if file is not valid, Z_ERRNO on a
+ file operation error, Z_MEM_ERROR if out of memory, Z_BUF_ERROR if the
+ last read ended in the middle of a gzip stream, or Z_OK on success.
+*/
+
+ZEXTERN int ZEXPORT gzclose_r OF((gzFile file));
+ZEXTERN int ZEXPORT gzclose_w OF((gzFile file));
+/*
+ Same as gzclose(), but gzclose_r() is only for use when reading, and
+ gzclose_w() is only for use when writing or appending. The advantage to
+ using these instead of gzclose() is that they avoid linking in zlib
+ compression or decompression code that is not used when only reading or only
+ writing respectively. If gzclose() is used, then both compression and
+ decompression code will be included the application when linking to a static
+ zlib library.
+*/
+
+ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum));
+/*
+ Returns the error message for the last error which occurred on the given
+ compressed file. errnum is set to zlib error number. If an error occurred
+ in the file system and not in the compression library, errnum is set to
+ Z_ERRNO and the application may consult errno to get the exact error code.
+
+ The application must not modify the returned string. Future calls to
+ this function may invalidate the previously returned string. If file is
+ closed, then the string previously returned by gzerror will no longer be
+ available.
+
+ gzerror() should be used to distinguish errors from end-of-file for those
+ functions above that do not distinguish those cases in their return values.
+*/
+
+ZEXTERN void ZEXPORT gzclearerr OF((gzFile file));
+/*
+ Clears the error and end-of-file flags for file. This is analogous to the
+ clearerr() function in stdio. This is useful for continuing to read a gzip
+ file that is being written concurrently.
+*/
+
+#endif /* !Z_SOLO */
+
+ /* checksum functions */
+
+/*
+ These functions are not related to compression but are exported
+ anyway because they might be useful in applications using the compression
+ library.
+*/
+
+ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len));
+/*
+ Update a running Adler-32 checksum with the bytes buf[0..len-1] and
+ return the updated checksum. If buf is Z_NULL, this function returns the
+ required initial value for the checksum.
+
+ An Adler-32 checksum is almost as reliable as a CRC-32 but can be computed
+ much faster.
+
+ Usage example:
+
+ uLong adler = adler32(0L, Z_NULL, 0);
+
+ while (read_buffer(buffer, length) != EOF) {
+ adler = adler32(adler, buffer, length);
+ }
+ if (adler != original_adler) error();
+*/
+
+ZEXTERN uLong ZEXPORT adler32_z OF((uLong adler, const Bytef *buf,
+ z_size_t len));
+/*
+ Same as adler32(), but with a size_t length.
+*/
+
+/*
+ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2,
+ z_off_t len2));
+
+ Combine two Adler-32 checksums into one. For two sequences of bytes, seq1
+ and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for
+ each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of
+ seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. Note
+ that the z_off_t type (like off_t) is a signed integer. If len2 is
+ negative, the result has no meaning or utility.
+*/
+
+ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len));
+/*
+ Update a running CRC-32 with the bytes buf[0..len-1] and return the
+ updated CRC-32. If buf is Z_NULL, this function returns the required
+ initial value for the crc. Pre- and post-conditioning (one's complement) is
+ performed within this function so it shouldn't be done by the application.
+
+ Usage example:
+
+ uLong crc = crc32(0L, Z_NULL, 0);
+
+ while (read_buffer(buffer, length) != EOF) {
+ crc = crc32(crc, buffer, length);
+ }
+ if (crc != original_crc) error();
+*/
+
+ZEXTERN uLong ZEXPORT crc32_z OF((uLong adler, const Bytef *buf,
+ z_size_t len));
+/*
+ Same as crc32(), but with a size_t length.
+*/
+
+/*
+ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2));
+
+ Combine two CRC-32 check values into one. For two sequences of bytes,
+ seq1 and seq2 with lengths len1 and len2, CRC-32 check values were
+ calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32
+ check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and
+ len2.
+*/
+
+
+ /* various hacks, don't look :) */
+
+/* deflateInit and inflateInit are macros to allow checking the zlib version
+ * and the compiler's view of z_stream:
+ */
+ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level,
+ const char *version, int stream_size));
+ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm,
+ const char *version, int stream_size));
+ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method,
+ int windowBits, int memLevel,
+ int strategy, const char *version,
+ int stream_size));
+ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits,
+ const char *version, int stream_size));
+ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits,
+ unsigned char FAR *window,
+ const char *version,
+ int stream_size));
+#ifdef Z_PREFIX_SET
+# define z_deflateInit(strm, level) \
+ deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream))
+# define z_inflateInit(strm) \
+ inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream))
+# define z_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
+ deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
+ (strategy), ZLIB_VERSION, (int)sizeof(z_stream))
+# define z_inflateInit2(strm, windowBits) \
+ inflateInit2_((strm), (windowBits), ZLIB_VERSION, \
+ (int)sizeof(z_stream))
+# define z_inflateBackInit(strm, windowBits, window) \
+ inflateBackInit_((strm), (windowBits), (window), \
+ ZLIB_VERSION, (int)sizeof(z_stream))
+#else
+# define deflateInit(strm, level) \
+ deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream))
+# define inflateInit(strm) \
+ inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream))
+# define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
+ deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
+ (strategy), ZLIB_VERSION, (int)sizeof(z_stream))
+# define inflateInit2(strm, windowBits) \
+ inflateInit2_((strm), (windowBits), ZLIB_VERSION, \
+ (int)sizeof(z_stream))
+# define inflateBackInit(strm, windowBits, window) \
+ inflateBackInit_((strm), (windowBits), (window), \
+ ZLIB_VERSION, (int)sizeof(z_stream))
+#endif
+
+#ifndef Z_SOLO
+
+/* gzgetc() macro and its supporting function and exposed data structure. Note
+ * that the real internal state is much larger than the exposed structure.
+ * This abbreviated structure exposes just enough for the gzgetc() macro. The
+ * user should not mess with these exposed elements, since their names or
+ * behavior could change in the future, perhaps even capriciously. They can
+ * only be used by the gzgetc() macro. You have been warned.
+ */
+struct gzFile_s {
+ unsigned have;
+ unsigned char *next;
+ z_off64_t pos;
+};
+ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */
+#ifdef Z_PREFIX_SET
+# undef z_gzgetc
+# define z_gzgetc(g) \
+ ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : (gzgetc)(g))
+#else
+# define gzgetc(g) \
+ ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : (gzgetc)(g))
+#endif
+
+/* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or
+ * change the regular functions to 64 bits if _FILE_OFFSET_BITS is 64 (if
+ * both are true, the application gets the *64 functions, and the regular
+ * functions are changed to 64 bits) -- in case these are set on systems
+ * without large file support, _LFS64_LARGEFILE must also be true
+ */
+#ifdef Z_LARGE64
+ ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
+ ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int));
+ ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile));
+ ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile));
+ ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off64_t));
+ ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t));
+#endif
+
+#if !defined(ZLIB_INTERNAL) && defined(Z_WANT64)
+# ifdef Z_PREFIX_SET
+# define z_gzopen z_gzopen64
+# define z_gzseek z_gzseek64
+# define z_gztell z_gztell64
+# define z_gzoffset z_gzoffset64
+# define z_adler32_combine z_adler32_combine64
+# define z_crc32_combine z_crc32_combine64
+# else
+# define gzopen gzopen64
+# define gzseek gzseek64
+# define gztell gztell64
+# define gzoffset gzoffset64
+# define adler32_combine adler32_combine64
+# define crc32_combine crc32_combine64
+# endif
+# ifndef Z_LARGE64
+ ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
+ ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int));
+ ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile));
+ ZEXTERN z_off_t ZEXPORT gzoffset64 OF((gzFile));
+ ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t));
+ ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t));
+# endif
+#else
+ ZEXTERN gzFile ZEXPORT gzopen OF((const char *, const char *));
+ ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile, z_off_t, int));
+ ZEXTERN z_off_t ZEXPORT gztell OF((gzFile));
+ ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile));
+ ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t));
+ ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t));
+#endif
+
+#else /* Z_SOLO */
+
+ ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t));
+ ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t));
+
+#endif /* !Z_SOLO */
+
+/* undocumented functions */
+ZEXTERN const char * ZEXPORT zError OF((int));
+ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp));
+ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void));
+ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int));
+ZEXTERN int ZEXPORT inflateValidate OF((z_streamp, int));
+ZEXTERN unsigned long ZEXPORT inflateCodesUsed OF ((z_streamp));
+ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp));
+ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp));
+#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(Z_SOLO)
+ZEXTERN gzFile ZEXPORT gzopen_w OF((const wchar_t *path,
+ const char *mode));
+#endif
+#if defined(STDC) || defined(Z_HAVE_STDARG_H)
+# ifndef Z_SOLO
+ZEXTERN int ZEXPORTVA gzvprintf Z_ARG((gzFile file,
+ const char *format,
+ va_list va));
+# endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZLIB_H */
diff --git a/lib/zlib/zutil.c b/lib/zlib/zutil.c
new file mode 100644
index 0000000..a76c6b0
--- /dev/null
+++ b/lib/zlib/zutil.c
@@ -0,0 +1,325 @@
+/* zutil.c -- target dependent utility functions for the compression library
+ * Copyright (C) 1995-2017 Jean-loup Gailly
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#include "zutil.h"
+#ifndef Z_SOLO
+# include "gzguts.h"
+#endif
+
+z_const char * const z_errmsg[10] = {
+ (z_const char *)"need dictionary", /* Z_NEED_DICT 2 */
+ (z_const char *)"stream end", /* Z_STREAM_END 1 */
+ (z_const char *)"", /* Z_OK 0 */
+ (z_const char *)"file error", /* Z_ERRNO (-1) */
+ (z_const char *)"stream error", /* Z_STREAM_ERROR (-2) */
+ (z_const char *)"data error", /* Z_DATA_ERROR (-3) */
+ (z_const char *)"insufficient memory", /* Z_MEM_ERROR (-4) */
+ (z_const char *)"buffer error", /* Z_BUF_ERROR (-5) */
+ (z_const char *)"incompatible version",/* Z_VERSION_ERROR (-6) */
+ (z_const char *)""
+};
+
+
+const char * ZEXPORT zlibVersion()
+{
+ return ZLIB_VERSION;
+}
+
+uLong ZEXPORT zlibCompileFlags()
+{
+ uLong flags;
+
+ flags = 0;
+ switch ((int)(sizeof(uInt))) {
+ case 2: break;
+ case 4: flags += 1; break;
+ case 8: flags += 2; break;
+ default: flags += 3;
+ }
+ switch ((int)(sizeof(uLong))) {
+ case 2: break;
+ case 4: flags += 1 << 2; break;
+ case 8: flags += 2 << 2; break;
+ default: flags += 3 << 2;
+ }
+ switch ((int)(sizeof(voidpf))) {
+ case 2: break;
+ case 4: flags += 1 << 4; break;
+ case 8: flags += 2 << 4; break;
+ default: flags += 3 << 4;
+ }
+ switch ((int)(sizeof(z_off_t))) {
+ case 2: break;
+ case 4: flags += 1 << 6; break;
+ case 8: flags += 2 << 6; break;
+ default: flags += 3 << 6;
+ }
+#ifdef ZLIB_DEBUG
+ flags += 1 << 8;
+#endif
+#if defined(ASMV) || defined(ASMINF)
+ flags += 1 << 9;
+#endif
+#ifdef ZLIB_WINAPI
+ flags += 1 << 10;
+#endif
+#ifdef BUILDFIXED
+ flags += 1 << 12;
+#endif
+#ifdef DYNAMIC_CRC_TABLE
+ flags += 1 << 13;
+#endif
+#ifdef NO_GZCOMPRESS
+ flags += 1L << 16;
+#endif
+#ifdef NO_GZIP
+ flags += 1L << 17;
+#endif
+#ifdef PKZIP_BUG_WORKAROUND
+ flags += 1L << 20;
+#endif
+#ifdef FASTEST
+ flags += 1L << 21;
+#endif
+#if defined(STDC) || defined(Z_HAVE_STDARG_H)
+# ifdef NO_vsnprintf
+ flags += 1L << 25;
+# ifdef HAS_vsprintf_void
+ flags += 1L << 26;
+# endif
+# else
+# ifdef HAS_vsnprintf_void
+ flags += 1L << 26;
+# endif
+# endif
+#else
+ flags += 1L << 24;
+# ifdef NO_snprintf
+ flags += 1L << 25;
+# ifdef HAS_sprintf_void
+ flags += 1L << 26;
+# endif
+# else
+# ifdef HAS_snprintf_void
+ flags += 1L << 26;
+# endif
+# endif
+#endif
+ return flags;
+}
+
+#ifdef ZLIB_DEBUG
+#include <stdlib.h>
+# ifndef verbose
+# define verbose 0
+# endif
+int ZLIB_INTERNAL z_verbose = verbose;
+
+void ZLIB_INTERNAL z_error (m)
+ char *m;
+{
+ fprintf(stderr, "%s\n", m);
+ exit(1);
+}
+#endif
+
+/* exported to allow conversion of error code to string for compress() and
+ * uncompress()
+ */
+const char * ZEXPORT zError(err)
+ int err;
+{
+ return ERR_MSG(err);
+}
+
+#if defined(_WIN32_WCE)
+ /* The Microsoft C Run-Time Library for Windows CE doesn't have
+ * errno. We define it as a global variable to simplify porting.
+ * Its value is always 0 and should not be used.
+ */
+ int errno = 0;
+#endif
+
+#ifndef HAVE_MEMCPY
+
+void ZLIB_INTERNAL zmemcpy(dest, source, len)
+ Bytef* dest;
+ const Bytef* source;
+ uInt len;
+{
+ if (len == 0) return;
+ do {
+ *dest++ = *source++; /* ??? to be unrolled */
+ } while (--len != 0);
+}
+
+int ZLIB_INTERNAL zmemcmp(s1, s2, len)
+ const Bytef* s1;
+ const Bytef* s2;
+ uInt len;
+{
+ uInt j;
+
+ for (j = 0; j < len; j++) {
+ if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1;
+ }
+ return 0;
+}
+
+void ZLIB_INTERNAL zmemzero(dest, len)
+ Bytef* dest;
+ uInt len;
+{
+ if (len == 0) return;
+ do {
+ *dest++ = 0; /* ??? to be unrolled */
+ } while (--len != 0);
+}
+#endif
+
+#ifndef Z_SOLO
+
+#ifdef SYS16BIT
+
+#ifdef __TURBOC__
+/* Turbo C in 16-bit mode */
+
+# define MY_ZCALLOC
+
+/* Turbo C malloc() does not allow dynamic allocation of 64K bytes
+ * and farmalloc(64K) returns a pointer with an offset of 8, so we
+ * must fix the pointer. Warning: the pointer must be put back to its
+ * original form in order to free it, use zcfree().
+ */
+
+#define MAX_PTR 10
+/* 10*64K = 640K */
+
+local int next_ptr = 0;
+
+typedef struct ptr_table_s {
+ voidpf org_ptr;
+ voidpf new_ptr;
+} ptr_table;
+
+local ptr_table table[MAX_PTR];
+/* This table is used to remember the original form of pointers
+ * to large buffers (64K). Such pointers are normalized with a zero offset.
+ * Since MSDOS is not a preemptive multitasking OS, this table is not
+ * protected from concurrent access. This hack doesn't work anyway on
+ * a protected system like OS/2. Use Microsoft C instead.
+ */
+
+voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
+{
+ voidpf buf;
+ ulg bsize = (ulg)items*size;
+
+ (void)opaque;
+
+ /* If we allocate less than 65520 bytes, we assume that farmalloc
+ * will return a usable pointer which doesn't have to be normalized.
+ */
+ if (bsize < 65520L) {
+ buf = farmalloc(bsize);
+ if (*(ush*)&buf != 0) return buf;
+ } else {
+ buf = farmalloc(bsize + 16L);
+ }
+ if (buf == NULL || next_ptr >= MAX_PTR) return NULL;
+ table[next_ptr].org_ptr = buf;
+
+ /* Normalize the pointer to seg:0 */
+ *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4;
+ *(ush*)&buf = 0;
+ table[next_ptr++].new_ptr = buf;
+ return buf;
+}
+
+void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
+{
+ int n;
+
+ (void)opaque;
+
+ if (*(ush*)&ptr != 0) { /* object < 64K */
+ farfree(ptr);
+ return;
+ }
+ /* Find the original pointer */
+ for (n = 0; n < next_ptr; n++) {
+ if (ptr != table[n].new_ptr) continue;
+
+ farfree(table[n].org_ptr);
+ while (++n < next_ptr) {
+ table[n-1] = table[n];
+ }
+ next_ptr--;
+ return;
+ }
+ Assert(0, "zcfree: ptr not found");
+}
+
+#endif /* __TURBOC__ */
+
+
+#ifdef M_I86
+/* Microsoft C in 16-bit mode */
+
+# define MY_ZCALLOC
+
+#if (!defined(_MSC_VER) || (_MSC_VER <= 600))
+# define _halloc halloc
+# define _hfree hfree
+#endif
+
+voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size)
+{
+ (void)opaque;
+ return _halloc((long)items, size);
+}
+
+void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
+{
+ (void)opaque;
+ _hfree(ptr);
+}
+
+#endif /* M_I86 */
+
+#endif /* SYS16BIT */
+
+
+#ifndef MY_ZCALLOC /* Any system without a special alloc function */
+
+#ifndef STDC
+extern voidp malloc OF((uInt size));
+extern voidp calloc OF((uInt items, uInt size));
+extern void free OF((voidpf ptr));
+#endif
+
+voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
+ voidpf opaque;
+ unsigned items;
+ unsigned size;
+{
+ (void)opaque;
+ return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) :
+ (voidpf)calloc(items, size);
+}
+
+void ZLIB_INTERNAL zcfree (opaque, ptr)
+ voidpf opaque;
+ voidpf ptr;
+{
+ (void)opaque;
+ free(ptr);
+}
+
+#endif /* MY_ZCALLOC */
+
+#endif /* !Z_SOLO */
diff --git a/lib/zlib/zutil.h b/lib/zlib/zutil.h
new file mode 100644
index 0000000..b079ea6
--- /dev/null
+++ b/lib/zlib/zutil.h
@@ -0,0 +1,271 @@
+/* zutil.h -- internal interface and configuration of the compression library
+ * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+/* @(#) $Id$ */
+
+#ifndef ZUTIL_H
+#define ZUTIL_H
+
+#ifdef HAVE_HIDDEN
+# define ZLIB_INTERNAL __attribute__((visibility ("hidden")))
+#else
+# define ZLIB_INTERNAL
+#endif
+
+#include "zlib.h"
+
+#if defined(STDC) && !defined(Z_SOLO)
+# if !(defined(_WIN32_WCE) && defined(_MSC_VER))
+# include <stddef.h>
+# endif
+# include <string.h>
+# include <stdlib.h>
+#endif
+
+#ifdef Z_SOLO
+ typedef long ptrdiff_t; /* guess -- will be caught if guess is wrong */
+#endif
+
+#ifndef local
+# define local static
+#endif
+/* since "static" is used to mean two completely different things in C, we
+ define "local" for the non-static meaning of "static", for readability
+ (compile with -Dlocal if your debugger can't find static symbols) */
+
+typedef unsigned char uch;
+typedef uch FAR uchf;
+typedef unsigned short ush;
+typedef ush FAR ushf;
+typedef unsigned long ulg;
+
+extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
+/* (size given to avoid silly warnings with Visual C++) */
+
+#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)]
+
+#define ERR_RETURN(strm,err) \
+ return (strm->msg = ERR_MSG(err), (err))
+/* To be used only when the state is known to be valid */
+
+ /* common constants */
+
+#ifndef DEF_WBITS
+# define DEF_WBITS MAX_WBITS
+#endif
+/* default windowBits for decompression. MAX_WBITS is for compression only */
+
+#if MAX_MEM_LEVEL >= 8
+# define DEF_MEM_LEVEL 8
+#else
+# define DEF_MEM_LEVEL MAX_MEM_LEVEL
+#endif
+/* default memLevel */
+
+#define STORED_BLOCK 0
+#define STATIC_TREES 1
+#define DYN_TREES 2
+/* The three kinds of block type */
+
+#define MIN_MATCH 3
+#define MAX_MATCH 258
+/* The minimum and maximum match lengths */
+
+#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */
+
+ /* target dependencies */
+
+#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32))
+# define OS_CODE 0x00
+# ifndef Z_SOLO
+# if defined(__TURBOC__) || defined(__BORLANDC__)
+# if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__))
+ /* Allow compilation with ANSI keywords only enabled */
+ void _Cdecl farfree( void *block );
+ void *_Cdecl farmalloc( unsigned long nbytes );
+# else
+# include <alloc.h>
+# endif
+# else /* MSC or DJGPP */
+# include <malloc.h>
+# endif
+# endif
+#endif
+
+#ifdef AMIGA
+# define OS_CODE 1
+#endif
+
+#if defined(VAXC) || defined(VMS)
+# define OS_CODE 2
+# define F_OPEN(name, mode) \
+ fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512")
+#endif
+
+#ifdef __370__
+# if __TARGET_LIB__ < 0x20000000
+# define OS_CODE 4
+# elif __TARGET_LIB__ < 0x40000000
+# define OS_CODE 11
+# else
+# define OS_CODE 8
+# endif
+#endif
+
+#if defined(ATARI) || defined(atarist)
+# define OS_CODE 5
+#endif
+
+#ifdef OS2
+# define OS_CODE 6
+# if defined(M_I86) && !defined(Z_SOLO)
+# include <malloc.h>
+# endif
+#endif
+
+#if defined(MACOS) || defined(TARGET_OS_MAC)
+# define OS_CODE 7
+# ifndef Z_SOLO
+# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os
+# include <unix.h> /* for fdopen */
+# else
+# ifndef fdopen
+# define fdopen(fd,mode) NULL /* No fdopen() */
+# endif
+# endif
+# endif
+#endif
+
+#ifdef __acorn
+# define OS_CODE 13
+#endif
+
+#if defined(WIN32) && !defined(__CYGWIN__)
+# define OS_CODE 10
+#endif
+
+#ifdef _BEOS_
+# define OS_CODE 16
+#endif
+
+#ifdef __TOS_OS400__
+# define OS_CODE 18
+#endif
+
+#ifdef __APPLE__
+# define OS_CODE 19
+#endif
+
+#if defined(_BEOS_) || defined(RISCOS)
+# define fdopen(fd,mode) NULL /* No fdopen() */
+#endif
+
+#if (defined(_MSC_VER) && (_MSC_VER > 600)) && !defined __INTERIX
+# if defined(_WIN32_WCE)
+# define fdopen(fd,mode) NULL /* No fdopen() */
+# ifndef _PTRDIFF_T_DEFINED
+ typedef int ptrdiff_t;
+# define _PTRDIFF_T_DEFINED
+# endif
+# else
+# define fdopen(fd,type) _fdopen(fd,type)
+# endif
+#endif
+
+#if defined(__BORLANDC__) && !defined(MSDOS)
+ #pragma warn -8004
+ #pragma warn -8008
+ #pragma warn -8066
+#endif
+
+/* provide prototypes for these when building zlib without LFS */
+#if !defined(_WIN32) && \
+ (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0)
+ ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t));
+ ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t));
+#endif
+
+ /* common defaults */
+
+#ifndef OS_CODE
+# define OS_CODE 3 /* assume Unix */
+#endif
+
+#ifndef F_OPEN
+# define F_OPEN(name, mode) fopen((name), (mode))
+#endif
+
+ /* functions */
+
+#if defined(pyr) || defined(Z_SOLO)
+# define NO_MEMCPY
+#endif
+#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__)
+ /* Use our own functions for small and medium model with MSC <= 5.0.
+ * You may have to use the same strategy for Borland C (untested).
+ * The __SC__ check is for Symantec.
+ */
+# define NO_MEMCPY
+#endif
+#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY)
+# define HAVE_MEMCPY
+#endif
+#ifdef HAVE_MEMCPY
+# ifdef SMALL_MEDIUM /* MSDOS small or medium model */
+# define zmemcpy _fmemcpy
+# define zmemcmp _fmemcmp
+# define zmemzero(dest, len) _fmemset(dest, 0, len)
+# else
+# define zmemcpy memcpy
+# define zmemcmp memcmp
+# define zmemzero(dest, len) memset(dest, 0, len)
+# endif
+#else
+ void ZLIB_INTERNAL zmemcpy OF((Bytef* dest, const Bytef* source, uInt len));
+ int ZLIB_INTERNAL zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len));
+ void ZLIB_INTERNAL zmemzero OF((Bytef* dest, uInt len));
+#endif
+
+/* Diagnostic functions */
+#ifdef ZLIB_DEBUG
+# include <stdio.h>
+ extern int ZLIB_INTERNAL z_verbose;
+ extern void ZLIB_INTERNAL z_error OF((char *m));
+# define Assert(cond,msg) {if(!(cond)) z_error(msg);}
+# define Trace(x) {if (z_verbose>=0) fprintf x ;}
+# define Tracev(x) {if (z_verbose>0) fprintf x ;}
+# define Tracevv(x) {if (z_verbose>1) fprintf x ;}
+# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;}
+# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;}
+#else
+# define Assert(cond,msg)
+# define Trace(x)
+# define Tracev(x)
+# define Tracevv(x)
+# define Tracec(c,x)
+# define Tracecv(c,x)
+#endif
+
+#ifndef Z_SOLO
+ voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items,
+ unsigned size));
+ void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr));
+#endif
+
+#define ZALLOC(strm, items, size) \
+ (*((strm)->zalloc))((strm)->opaque, (items), (size))
+#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr))
+#define TRY_FREE(s, p) {if (p) ZFREE(s, p);}
+
+/* Reverse the bytes in a 32-bit value */
+#define ZSWAP32(q) ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \
+ (((q) & 0xff00) << 8) + (((q) & 0xff) << 24))
+
+#endif /* ZUTIL_H */