chiark / gitweb /
Makefiles: Use Final.sd.mk to implementing RECHECK_RM master
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 16 Feb 2020 18:06:23 +0000 (18:06 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 16 Feb 2020 18:51:43 +0000 (18:51 +0000)
This is now read by make after all the other makefiles.  This allows
us to move the addition of {stest,mtest}/d-* to RECHECK_RM from
Dir.sd.mk into test-common.sd.mk, where it belongs.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
219 files changed:
.dir-locals.el
.gitignore
CREDITS
DEVELOPER-CERTIFICATE [new symlink]
Dir.sd.mk [new file with mode: 0644]
Final.sd.mk [new file with mode: 0644]
INSTALL
LICENSE.txt [deleted file]
Makefile.in [deleted file]
NOTES
NOTES.peer-keys [new file with mode: 0644]
README
README.make-secnet-sites [new file with mode: 0644]
Suffix.sd.mk [new file with mode: 0644]
ac_prog_cc_no_writeable_strings.m4
aclocal.m4 [new file with mode: 0644]
aes.c
aes.h
argparseactionnoyes.py [new file with mode: 0644]
autogen.sh [new file with mode: 0755]
base91-c/.gitignore [new file with mode: 0644]
base91-c/AWK/README [new file with mode: 0644]
base91-c/AWK/b91dec.awk [new file with mode: 0755]
base91-c/DOS-asm/b91enc.asm [new file with mode: 0644]
base91-c/DOS-asm/readme.txt [new file with mode: 0644]
base91-c/Java/b91cli.java [new file with mode: 0644]
base91-c/Java/basE91.java [new file with mode: 0644]
base91-c/Java/build_jar.sh [new file with mode: 0755]
base91-c/Java/license.txt [new file with mode: 0644]
base91-c/Java/manifest.mf [new file with mode: 0644]
base91-c/Java/readme.txt [new file with mode: 0644]
base91-c/LICENSE [new file with mode: 0644]
base91-c/Makefile [new file with mode: 0644]
base91-c/NEWS [new file with mode: 0644]
base91-c/PHP4/README [new file with mode: 0644]
base91-c/PHP4/base91.php [new file with mode: 0644]
base91-c/README [new file with mode: 0644]
base91-c/base91.1 [new file with mode: 0644]
base91-c/base91.c [new file with mode: 0644]
base91-c/base91.h [new file with mode: 0644]
base91-c/cli.c [new file with mode: 0644]
base91-c/lentest.c [new file with mode: 0644]
base91-c/test/Makefile [new file with mode: 0644]
base91-c/test/test.sh [new file with mode: 0644]
base91-python/.gitignore [new file with mode: 0644]
base91-python/README.md [new file with mode: 0644]
base91-python/base91/__init__.py [new file with mode: 0644]
base91-python/setup.py [new file with mode: 0644]
base91.py [new symlink]
base91s/Dir.sd.mk [new file with mode: 0644]
base91s/base91.c.patch [new file with mode: 0644]
comm-common.c [new file with mode: 0644]
comm-common.h [new file with mode: 0644]
common.make.in [new file with mode: 0644]
comprehensive-test [new file with mode: 0755]
conffile.c
conffile.fl
conffile.h
conffile.y
conffile_internal.h
config.h.in
configure
configure.ac [new file with mode: 0644]
configure.in [deleted file]
debian/changelog
debian/control
debian/copyright
debian/rules
depend.sh [deleted file]
dh.c
eax-aes-test.c
eax-serpent-test.c
eax-test.c
eax-test.h
eax.c
example.conf
hackypar.c
hackypar.h
hexdebug.h
ipaddr.c
ipaddr.h
ipaddr.py [deleted file]
ipaddrset-test.expected [new file with mode: 0644]
ipaddrset-test.py [new file with mode: 0755]
ipaddrset.py [new file with mode: 0644]
linux/if_tun.h [deleted file]
log.c
magic.h
make-secnet-sites
md5.c
md5.h
modules.c
msgcode-test.c [new file with mode: 0644]
mtest/Dir.sd.mk [new file with mode: 0644]
mtest/Ginside.sites [new file with mode: 0644]
mtest/Goutside.sites [new file with mode: 0644]
mtest/common.tcl [new file with mode: 0644]
mtest/delegations.sites [new file with mode: 0644]
mtest/e-basic.conf [new file with mode: 0644]
mtest/e-filter.sites [new file with mode: 0644]
mtest/e-userv.sites [new file with mode: 0644]
mtest/header.sites [new file with mode: 0644]
mtest/t-basic [new file with mode: 0755]
mtest/t-filter [new file with mode: 0755]
mtest/t-prefix [new file with mode: 0755]
mtest/t-userv [new file with mode: 0755]
netlink.c
netlink.h
osdep.c [new file with mode: 0644]
osdep.h [new file with mode: 0644]
parallel-test.make [new file with mode: 0644]
parallel-test.sh [new file with mode: 0755]
polypath-interface-monitor-linux [new file with mode: 0755]
polypath.c [new file with mode: 0644]
pretest-to-tested [new file with mode: 0755]
privcache.c [new file with mode: 0644]
process.c
process.h
pubkeys.c [new file with mode: 0644]
pubkeys.fl.pl [new file with mode: 0755]
pubkeys.h [new file with mode: 0644]
random.c
resolver.c
rsa.c
secnet-wireshark.lua [new file with mode: 0644]
secnet.8
secnet.c
secnet.h
serpent.c
serpentsboxes.h
setup.mac
sha1.c
site.c
slip.c
stest/Dir.sd.mk [new file with mode: 0644]
stest/common.tcl [new file with mode: 0644]
stest/t-Cnonnego-on [new file with mode: 0755]
stest/t-Cnonnego-onr [new file with mode: 0755]
stest/t-basic-kex [new file with mode: 0755]
stest/t-nonnego-on [new file with mode: 0755]
stest/t-nonnego-oo [new file with mode: 0755]
stest/udp-preload.c [new file with mode: 0644]
subdirmk/.gitignore [new file with mode: 0644]
subdirmk/DEVELOPER-CERTIFICATE [new file with mode: 0644]
subdirmk/LGPL-2 [new file with mode: 0644]
subdirmk/README [new file with mode: 0644]
subdirmk/autogen.sh [new file with mode: 0755]
subdirmk/cdeps.sd.mk [new file with mode: 0644]
subdirmk/clean.sd.mk [new file with mode: 0644]
subdirmk/example/.gitignore [new file with mode: 0644]
subdirmk/example/DEVELOPER-CERTIFICATE [new symlink]
subdirmk/example/Dir.sd.mk [new file with mode: 0644]
subdirmk/example/Final.sd.mk [new file with mode: 0644]
subdirmk/example/LGPL-2 [new symlink]
subdirmk/example/Suffix.sd.mk [new file with mode: 0644]
subdirmk/example/autogen.sh [new symlink]
subdirmk/example/configure.ac [new file with mode: 0644]
subdirmk/example/lib/Dir.sd.mk [new file with mode: 0644]
subdirmk/example/lib/t/Dir.sd.mk [new file with mode: 0644]
subdirmk/example/lib/t/toytest.c [new file with mode: 0644]
subdirmk/example/lib/toylib.c [new file with mode: 0644]
subdirmk/example/lib/toylib.h [new file with mode: 0644]
subdirmk/example/src/Dir.sd.mk [new file with mode: 0644]
subdirmk/example/src/toy.c [new file with mode: 0644]
subdirmk/example/subdirmk [new symlink]
subdirmk/generate [new file with mode: 0755]
subdirmk/regen.mk.in [new file with mode: 0644]
subdirmk/subdirmk.ac [new file with mode: 0644]
subdirmk/tests/.gitignore [new file with mode: 0644]
subdirmk/tests/advance-tested [new file with mode: 0755]
subdirmk/tests/build-common [new file with mode: 0644]
subdirmk/tests/check [new file with mode: 0755]
subdirmk/tests/example/.gitignore [new file with mode: 0644]
subdirmk/tests/example/check [new file with mode: 0755]
subdirmk/tests/filter/.gitignore [new file with mode: 0644]
subdirmk/tests/filter/Dir.mk.expected [new file with mode: 0644]
subdirmk/tests/filter/Dir.sd.mk [new file with mode: 0644]
subdirmk/tests/filter/Final.mk.expected [new file with mode: 0644]
subdirmk/tests/filter/Final.sd.mk [new file with mode: 0644]
subdirmk/tests/filter/Prefix.sd.mk [new file with mode: 0644]
subdirmk/tests/filter/Suffix.sd.mk [new file with mode: 0644]
subdirmk/tests/filter/check [new file with mode: 0755]
subdirmk/tests/filter/extract-doctests [new file with mode: 0755]
subdirmk/tests/filter/main.mk.expected [new file with mode: 0644]
subdirmk/tests/filter/stderr.expected [new file with mode: 0644]
subdirmk/tests/filter/sub/Dir.mk.expected [new file with mode: 0644]
subdirmk/tests/filter/sub/dir/Dir.mk.expected [new file with mode: 0644]
subdirmk/tests/filter/sub/dir/Dir.sd.mk [new file with mode: 0644]
subdirmk/tests/filter/update-expected [new file with mode: 0755]
subdirmk/tests/intree/.gitignore [new file with mode: 0644]
subdirmk/tests/intree/check [new file with mode: 0755]
subdirmk/tests/make-release [new file with mode: 0755]
subdirmk/tests/tests.mk [new file with mode: 0644]
subdirmk/usual.mk.in [new file with mode: 0644]
test-common.sd.mk [new file with mode: 0644]
test-common.tcl [new file with mode: 0644]
test-example/Dir.sd.mk [new file with mode: 0644]
test-example/Makefile [deleted file]
test-example/README
test-example/common.conf
test-example/fake-userv [new file with mode: 0755]
test-example/inside-polypath.conf [new file with mode: 0644]
test-example/inside.conf
test-example/outside-random.conf [new file with mode: 0644]
test-example/outside-unshare.conf [new file with mode: 0644]
test-example/outside.conf
test-example/random-fake-userv [new file with mode: 0755]
test-example/rsa1-sites2.key.b64 [new file with mode: 0644]
test-example/rsa1-sites2.key.pub [new file with mode: 0644]
test-example/sites
transform-cbcmac.c
transform-common.h
transform-eax.c
tun.c
udp.c
uk.org.greenend.secnet.plist
unaligned.h
util.c
util.h

index 0dbd236b67327247f8326d9177afc6243569e9af..95ea27ff3bca1c61ed57f3f7873d82062352a36d 100644 (file)
@@ -2,4 +2,8 @@
 ;;; See Info node `(emacs) Directory Variables' for more information.
 
 ((c-mode
-  (c-basic-offset . 4)))
+  (c-basic-offset . 4))
+ (python-mode
+  (indent-tabs-mode . t)
+  (python-indent-offset . 8)
+  (python-indent . 8)))
index a445369e25e6c9b822497d54e64ae5235b83e4c7..761332a2f876547b6f5fd2d4efe187d55a658974 100644 (file)
@@ -1,24 +1,40 @@
 *.o
-*.d
+.*.d
 *.pyc
 conffile.tab.[ch]
 conffile.yy.[ch]
+pubkeys.fl
+pubkeys.yy.[ch]
 /version.c
 /secnet
 /eax-*-test
 /eax-*-test.confirm
+/ipaddrset-test.new
+/ipaddrset.confirm
 
 /config.log
 /config.h
 /config.status
-/stamp-h
-/Makefile
+/config.stamp
+/config.stamp.in
+Makefile
+/common.make
+/test-common.make
+
+msgcode-test
+msgcode-test.confirm
 
 autom4te.cache
 
 *~
+*.tmp
 TAGS
 
+.makefiles.stamp
+Dir.mk
+/main.mk
+/Final.mk
+
 debian/files
 debian/secnet.debhelper.log
 debian/*.debhelper
@@ -28,6 +44,15 @@ debian/secnet.substvars
 /build
 
 test-example/*.key
+test-example/*.privkeys
+test-example/pubkeys
 test-example/sites.conf
+test-example/sites-nonego.conf
 test-example/bogus-setup-request
 build-stamp
+
+[sm]test/d-*
+stest/udp-preload.so
+
+base91s/*.[ch]
+base91s/base91s
diff --git a/CREDITS b/CREDITS
index c934ab7d3424c4f16cdeb35c0066affce2af4bef..e326cb825c793bd3126aa763585330d86c43b1be 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -1,13 +1,19 @@
 Stephen Early <steve@greenend.org.uk> - original author
+Ian Jackson <ijackson@chiark.greenend.org.uk> - current maintainer
+Mark Wooding <mdw@distorted.org.uk> - much useful stuff
 Ross Anderson, Eli Biham, Lars Knudsen - serpent
 Colin Plumb, Ian Jackson - MD5 implementation
-Ian Jackson - hacky parallelism, serpent keying and endianness bugfixes
-Steve Reid <sreid@sea-to-sky.net>, James H. Brown <jbrown@burgoyne.com> - 
-  SHA1 implementation
-Cendio Systems AB - ipaddr.py
-Mark Martinec <mark.martinec@ijs.si> - portable snprintf
+Steve Reid, James H. Brown, Saul Kravitz - SHA1 implementation
+Vincent Rijmen, Antoon Bosselaers, Paulo Barreto - Rijndael (AES) implementation
+Guido Draheim - ac_prog_cc_no_writeable_strings.m4
+Free Software Foundation and Scott G. Miller - SHA-512 implementation
+Free Software Foundation and Paul Eggert - u64.h
+Massachusetts Institute of Technology - install-sh
 
 Simon Tatham, Jonathan Amery, Ian Jackson - testing and debugging
 Simon Tatham - RSA signatures using Chinese Remainder Theorem
 Simon Tatham - endianness cleanups in transform.c
-Richard Kettlewell, Peter Benie - assorted bugfixes
+Richard Kettlewell, Matthew Vernon, Peter Benie - assorted bugfixes
+"Omnifarious" and "btel" on Stackoverflow - python yes/no arg parsing
+Joachim Henke - basE91 encoding format and the corresponding C library
+Adrien Béraud, Guillaume Jacquenot, SunDwarf - python basE91 library
diff --git a/DEVELOPER-CERTIFICATE b/DEVELOPER-CERTIFICATE
new file mode 120000 (symlink)
index 0000000..d56384e
--- /dev/null
@@ -0,0 +1 @@
+subdirmk/DEVELOPER-CERTIFICATE
\ No newline at end of file
diff --git a/Dir.sd.mk b/Dir.sd.mk
new file mode 100644 (file)
index 0000000..a2c54eb
--- /dev/null
+++ b/Dir.sd.mk
@@ -0,0 +1,265 @@
+# Makefile for secnet
+#
+# This file is part of secnet.
+# See README for full list of copyright holders.
+#
+# secnet is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# 
+# secnet is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# version 3 along with secnet; if not, see
+# https://www.gnu.org/licenses/gpl.html.
+
+.PHONY:        all clean realclean distclean dist install
+
+PACKAGE:=secnet
+VERSION=0.6.0
+
+VPATH:=@srcdir@
+srcdir:=@srcdir@
+include common.make
+
+INSTALL:=@INSTALL@
+INSTALL_PROGRAM:=@INSTALL_PROGRAM@
+INSTALL_SCRIPT:=@INSTALL_SCRIPT@
+INSTALL_DATA:=@INSTALL_DATA@
+
+prefix:=$(DESTDIR)@prefix@
+exec_prefix:=@exec_prefix@
+sbindir:=@sbindir@
+sysconfdir:=$(DESTDIR)@sysconfdir@
+datarootdir:=@datarootdir@
+transform:=@program_transform_name@
+mandir:=@mandir@
+
+ALL_CFLAGS:=@DEFS@ -I$(srcdir) -I. $(CFLAGS) $(EXTRA_CFLAGS)
+CPPFLAGS:=@CPPFLAGS@ -DDATAROOTDIR='"$(datarootdir)"' $(EXTRA_CPPFLAGS)
+LDFLAGS:=@LDFLAGS@ $(EXTRA_LDFLAGS)
+LDLIBS:=@LIBS@ $(EXTRA_LDLIBS)
+
+&:local+global OBJECTS TARGETS
+
+TARGETS:=secnet
+
+OBJECTS:=secnet.o util.o conffile.yy.o conffile.tab.o conffile.o modules.o \
+       resolver.o random.o udp.o site.o transform-cbcmac.o transform-eax.o \
+       comm-common.o polypath.o privcache.o pubkeys.o pubkeys.yy.o \
+       netlink.o rsa.o dh.o serpent.o serpentbe.o \
+       md5.o sha512.o tun.o slip.o sha1.o ipaddr.o log.o \
+       process.o osdep.o @LIBOBJS@ \
+       hackypar.o base91s/base91.o
+# version.o is handled specially below and in the link rule for secnet.
+
+PYMODULES := ipaddrset.py argparseactionnoyes.py base91.py
+
+TEST_OBJECTS:=eax-aes-test.o eax-serpent-test.o eax-serpentbe-test.o \
+               eax-test.o aes.o
+
+ifeq (version.o,$(MAKECMDGOALS))
+OBJECTS:=
+TEST_OBJECTS:=
+endif
+
+&OBJECTS += $(OBJECTS) $(TEST_OBJECTS)
+
+STALE_PYTHON_FILES=    $(foreach e, py pyc, \
+                       $(foreach p, /usr /usr/local, \
+                       $(foreach l, ipaddr, \
+                       $(DESTDIR)$p/share/secnet/$l.$e \
+                       )))
+
+%.yy.c %.yy.h: %.fl
+       flex --header=$*.yy.h -o$*.yy.c $<
+
+%.tab.c %.tab.h:       %.y
+       bison -d -o $@ $<
+
+%.o: %.c
+       $(CC) $(CPPFLAGS) $(ALL_CFLAGS) $(CDEPS_CFLAGS) -c $< -o $@
+
+$(OBJECTS): conffile.yy.h pubkeys.yy.h base91s/base91.h
+# ^ we can't write this as a dependency on the %.o %.c rule
+#   because (say) conffile.yy.c isn't mentioned so doesn't "ought
+#   to exist" in make's mind.  But specifying it explicitly like this
+#   works.
+
+all::  $(TARGETS)
+
+${srcdir}/config.h.in: configure.ac
+       cd ${srcdir} && autoheader
+       touch $@
+
+MAKEFILE_TEMPLATES += config.h.in
+CONFIG_STATUS_OUTPUTS += config.h
+
+# Manual dependencies section
+conffile.yy.c: conffile.tab.c
+%.tab.c: %.y
+# End of manual dependencies section
+
+%.yy.o: ALL_CFLAGS += -Wno-sign-compare -Wno-unused-function
+
+secnet:        $(OBJECTS)
+       $(MAKE) -f main.mk version.o # *.o $(filter-out %.o, $^)
+       $(CC) $(LDFLAGS) $(ALL_CFLAGS) -o $@ $(OBJECTS) version.o $(LDLIBS)
+# We (always) regenerate the version, but only if we regenerate the
+# binary.  (This is necessary as the version string is can depend on
+# any of the source files, eg to see whether "+" is needed.)
+
+ifneq (,$(wildcard .git/HEAD))
+# If we have (eg) committed, relink and thus regenerate the version
+# with the new info from git describe.
+secnet: Makefile .git/HEAD $(wildcard $(shell sed -n 's#^ref: #.git/#p' .git/HEAD))
+secnet: $(wildcard .git/packed-refs)
+endif
+
+TESTDIRS=stest mtest
+
+&TARGETS_check = eax-aes-test.confirm eax-serpent-test.confirm \
+       eax-serpentbe-test.confirm ipaddrset.confirm
+
+&TARGETS_fullcheck += $(&TARGETS_check)
+&TARGETS_fullcheck += msgcode-test.confirm
+
+RECHECK_RM += $(&TARGETS_check)
+
+recheck: check
+
+.PHONY: FORCE
+version.c: FORCE
+       echo "#include \"secnet.h\"" >$@.new
+       @set -ex; if test -e .git && type -p git >/dev/null; then \
+               v=$$(git describe --match 'v*'); v=$${v#v}; \
+               if ! git diff --quiet HEAD; then v="$$v+"; fi; \
+       else \
+               v="$(VERSION)"; \
+       fi; \
+       echo "char version[]=\"secnet $$v\";" >>$@.new
+       mv -f $@.new $@
+
+eax-%-test: eax-%-test.o eax-test.o %.o
+       $(CC) $(LDFLAGS) $(ALL_CFLAGS) -o $@ $^
+
+eax-%-test.confirm: eax-%-test eax-%-test.vectors
+       ./$< <$(srcdir)/eax-$*-test.vectors >$@.new
+       mv -f $@.new $@
+
+&CDEPS_OBJECTS += msgcode-test.o
+
+msgcode-test: msgcode-test.o
+       $(CC) $(LDFLAGS) $(ALL_CFLAGS) -o $@ $^
+
+msgcode-test.confirm: msgcode-test
+       ./msgcode-test
+       touch $@
+
+ipaddrset.confirm: ipaddrset-test.py ipaddrset.py ipaddrset-test.expected
+       $(srcdir)/ipaddrset-test.py >ipaddrset-test.new
+       diff -u $(srcdir)/ipaddrset-test.expected ipaddrset-test.new
+       touch $@
+
+&CLEAN += & pubkeys.fl
+
+pubkeys.fl: ${srcdir}/pubkeys.fl.pl
+       ${srcdir}/pubkeys.fl.pl >$@.tmp && mv -f $@.tmp $@
+
+.PRECIOUS: eax-%-test
+
+installdirs:
+       $(INSTALL) -d $(prefix)/share/secnet $(sbindir)
+       $(INSTALL) -d $(mandir)/man8
+       $(INSTALL) -d $(datarootdir)/secnet
+
+install: installdirs
+       set -e; ok=true; for f in $(STALE_PYTHON_FILES); do \
+               if test -e $$f; then \
+                       echo >&\&2 "ERROR: $$f still exists "\
+                               "- try \`make install-force'"; \
+                       ok=false; \
+               fi; \
+       done; \
+       $$ok
+       $(INSTALL_PROGRAM) secnet $(sbindir)/`echo secnet|sed '$(transform)'`
+       $(INSTALL_PROGRAM) ${srcdir}/make-secnet-sites $(sbindir)/`echo make-secnet-sites|sed '$(transform)'`
+       set -e; for m in $(PYMODULES); do \
+               $(INSTALL_DATA) ${srcdir}/$$m $(prefix)/share/secnet/$$m; \
+               done
+       $(INSTALL_SCRIPT) ${srcdir}/polypath-interface-monitor-linux \
+               $(datarootdir)/secnet/.
+       $(INSTALL_DATA) ${srcdir}/secnet.8 $(mandir)/man8/secnet.8
+
+install-force:
+       rm -f $(STALE_PYTHON_FILES)
+       $(MAKE) -f main.mk install
+
+&CLEAN += .version.d
+&CLEAN += $(TARGETS) $(&TARGETS_check) $(&TARGETS_fullcheck)
+
+clean::
+       $(RM) -f *.o *.yy.[ch] *.tab.[ch] core version.c
+       $(RM) -f *.pyc *~ eax-*-test.confirm eax-*-test
+       $(RM) -rf __pycache__
+       $(RM) -f msgcode-test.confirm msgcode-test
+
+realclean::    clean
+       $(RM) -f *~ Makefile config.h \
+       config.log config.status config.cache \
+       config.stamp Makefile.bak
+
+distclean::    realclean
+
+include subdirmk/regen.mk
+
+&:warn !single-char-var
+# Release checklist:
+#
+#  0. Use this checklist from Dir.sd.mk
+#
+#  1. Check that the tree has what you want
+#
+#  2. Update changelog:
+#         gbp dch --since=<PREVIOUS VERSION>
+#     and then edit debian/changelog.
+#
+#  3. Update VERSION (in this file, above) and
+#     finalise debian/changelog (removing ~ from version) and commit.
+#
+#  4. Build source and binaries:
+#       dgit -wgf sbuild -A -c stretch -j8
+#
+#  5. dpkg -i on zealot just to check
+#       dpkg -i ~ian/things/Fvpn/bpd/secnet_${VERSION}_amd64.deb
+#
+#  6. run it on chiark
+#     check we can still ping davenant and chiark
+#
+#  7. Make git tag and source tarball signature:
+#       git-tag -u general -m "secnet $VERSION" -s v${VERSION//\~/_}
+#       gpg -u general --detach-sign ../bpd/secnet_$VERSION.tar.gz
+#
+#  8. Publish the branch and distriubtion files:
+#       git-push origin v${VERSION//\~/_} v${VERSION//\~/_}~0:master
+#       dcmd rsync -v ../bpd/secnet_${VERSION}_multi.changes chiark:/home/ianmdlvl/public-html/secnet/download/
+#
+#  9. Sort out html.  On chiark as user secnet:
+#       cd ~secnet/public-html/release/
+#       mkdir $VERSION
+#       cd $VERSION
+#       ln -s /home/ianmdlvl/public-html/secnet/download/secnet?$VERSION* .
+#       ln -sfn $VERSION ../current
+#
+# 10. write and post a release announcement
+#       cd ../bpd
+#       dcmd sha256sum secnet_${VERSION}_multi.changes
+#       ...
+#       gpg --clearsign ../release-announcement
+#       rsync -vP ../release-announcement.asc c:mail/d/
+#
+# 11. bump changelog version in master, to new version with ~
diff --git a/Final.sd.mk b/Final.sd.mk
new file mode 100644 (file)
index 0000000..fc26601
--- /dev/null
@@ -0,0 +1,25 @@
+# Final.sd.mk for secnet
+#
+# This file is part of secnet.
+# See README for full list of copyright holders.
+#
+# secnet is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# 
+# secnet is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# version 3 along with secnet; if not, see
+# https://www.gnu.org/licenses/gpl.html.
+
+# This contrives to delete things before make starts, if the user
+# said "recheck".  The alternative is having recheck be a target
+# which contains the rm's and then runs $(MAKE) again but then
+# we recursively re-enter make in parallel, which is Bad.
+$(eval $(if $(filter recheck,$(MAKECMDGOALS)), \
+       $(shell set -x; rm -rf $(RECHECK_RM) )))
diff --git a/INSTALL b/INSTALL
index c0236bcc99bb126a3b006c527490204adb8abf35..c9db17412f68bd1a105a03e1b2940899146705f7 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -27,6 +27,10 @@ linux/Documentation/networking/tuntap.txt
 If you're using TUN/TAP on a platform other than Linux-2.4, see
 http://vtun.sourceforge.net/tun/
 
+You will probably be using the supplied `make-secnet-sites' program to
+generate your VPN's list of sites as a secnet configuration from a
+more-human-writeable form.
+
 ** System and network configuration
 
 If you intend to start secnet as root, I suggest you create a userid
@@ -109,6 +113,9 @@ If installing for the first time, do
 # cd /etc/secnet
 # ssh-keygen -f key -t rsa1 -N ""
 
+(You may need ssh-keygen1, instead, which might be found in
+openssh-client-ssh1.)
+
 [On BSD use
 $ LDFLAGS="-L/usr/local/lib" ./configure
 $ gmake CFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib"
diff --git a/LICENSE.txt b/LICENSE.txt
deleted file mode 100644 (file)
index aeb06a7..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-The Frontier Artistic License Version 1.0
-Derived from the Artistic License at OpenSource.org.
-Submitted to OpenSource.org for Open Source Initiative certification.
-   
-Preamble
-
-The intent of this document is to state the conditions under which a
-Package may be copied, such that the Copyright Holder maintains some
-semblance of artistic control over the development of the package,
-while giving the users of the package the right to use and distribute
-the Package in a more-or-less customary fashion, plus the right to
-make reasonable modifications.
-   
-Definitions
-
-  "Package" refers to the script, suite, file, or collection of
-  scripts, suites, and/or files distributed by the Copyright Holder,
-  and to derivatives of that Package created through textual modification.
-
-  "Standard Version" refers to such a Package if it has not been
-  modified, or has been modified in accordance with the wishes of
-  the Copyright Holder.
-
-  "Copyright Holder" is whoever is named in the copyright statement
-  or statements for the package.
-
-  "You" is you, if you're thinking about copying or distributing
-  this Package.
-
-  "Reasonable copying fee" is whatever you can justify on the basis
-  of media cost, duplication charges, time of people involved, and
-  so on. (You will not be required to justify it to the Copyright
-  Holder, but only to the computing community at large as a market
-  that must bear the fee.)
-
-  "Freely Available" means that no fee is charged for the item
-  itself, though there may be fees involved in handling the item.
-  It also means that recipients of the item may redistribute it under
-  the same conditions they received it.
-       
-
-Terms
-
-1. You may make and give away verbatim copies of the source form of
-the Standard Version of this Package without restriction, provided
-that you duplicate all of the original copyright notices and
-associated disclaimers.
-   
-2. You may apply bug fixes, portability fixes, and other modifications
-derived from the Public Domain or from the Copyright Holder. A Package
-modified in such a way shall still be considered the Standard Version.
-   
-3. You may otherwise modify your copy of this Package in any way,
-provided that you insert a prominent notice in each changed script,
-suite, or file stating how and when you changed that script, suite,
-or file, and provided that you do at least ONE of the following:
-   
-  a) Use the modified Package only within your corporation or
-  organization, or retain the modified Package solely for personal use.
-     
-  b) Place your modifications in the Public Domain or otherwise make
-  them Freely Available, such as by posting said modifications to Usenet
-  or an equivalent medium, or placing the modifications on a major archive
-  site such as ftp.uu.net, or by allowing the Copyright Holder to include
-  your modifications in the Standard Version of the Package.
-     
-  c) Rename any non-standard executables so the names do not conflict
-  with standard executables, which must also be provided, and provide
-  a separate manual page (or equivalent) for each non-standard executable
-  that clearly documents how it differs from the Standard Version.
-     
-  d) Make other distribution arrangements with the Copyright Holder.
-     
-4. You may distribute the programs of this Package in object code or
-executable form, provided that you do at least ONE of the following:
-   
-  a) Distribute a Standard Version of the executables and library
-  files, together with instructions (in the manual page or
-  equivalent) on where to get the Standard Version.
-     
-  b) Accompany the distribution with the machine-readable source of
-  the Package with your modifications.
-     
-  c) Accompany any non-standard executables with their corresponding
-  Standard Version executables, give the non-standard executables
-  non-standard names, and clearly document the differences in manual
-  pages (or equivalent), together with instructions on where to get
-  the Standard Version.
-     
-  d) Make other distribution arrangements with the Copyright Holder.
-     
-5. You may charge a reasonable copying fee for any distribution of
-this Package. You may charge any fee you choose for support of this
-Package. You may not charge a fee for this Package itself. However,
-you may distribute this Package in aggregate with other (possibly
-commercial) programs as part of a larger (possibly commercial)
-software distribution provided that you do not advertise this Package
-as a product of your own.
-   
-6. The scripts and library files supplied as input to or produced as
-output from the programs of this Package do not automatically fall
-under the copyright of this Package, but belong to whomever generated
-them, and may be sold commercially, and may be aggregated with this
-Package.
-   
-7. Scripts, suites, or programs supplied by you that depend on or
-otherwise make use of this Package shall not be considered part of
-this Package.
-   
-8. The name of the Copyright Holder may not be used to endorse or
-promote products derived from this software without specific prior
-written permission.
-   
-9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
-MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-   
-                        The End
-
-
-http://www.spinwardstars.com/frontier/fal.html
diff --git a/Makefile.in b/Makefile.in
deleted file mode 100644 (file)
index 5a140fb..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-# Makefile for secnet
-# Copyright (C) 1995-2001 Stephen Early <steve@greenend.org.uk>
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-.PHONY:        all clean realclean distclean dist install
-
-PACKAGE:=secnet
-VERSION:=0.2.1
-
-@SET_MAKE@
-
-srcdir:=@srcdir@
-VPATH:=@srcdir@
-
-SHELL:=/bin/sh
-RM:=@RM@
-CC:=@CC@
-INSTALL:=@INSTALL@
-INSTALL_PROGRAM:=@INSTALL_PROGRAM@
-
-CFLAGS:=-Wall @WRITESTRINGS@ @CFLAGS@ -Werror \
-       -W -Wno-unused \
-       -Wno-pointer-sign -Wstrict-prototypes -Wmissing-prototypes \
-       -Wmissing-declarations -Wnested-externs -Wredundant-decls \
-       -Wpointer-arith -Wformat=2 -Winit-self \
-       -Wswitch-enum -Wunused-variable -Wbad-function-cast \
-       -Wno-strict-aliasing -fno-strict-aliasing
-ALL_CFLAGS:=@DEFS@ -I$(srcdir) -I. $(CFLAGS) $(EXTRA_CFLAGS)
-CPPFLAGS:=@CPPFLAGS@ $(EXTRA_CPPFLAGS)
-LDFLAGS:=@LDFLAGS@ $(EXTRA_LDFLAGS)
-LDLIBS:=@LIBS@ $(EXTRA_LDLIBS)
-
-prefix:=@prefix@
-exec_prefix:=@exec_prefix@
-sbindir:=@sbindir@
-sysconfdir:=@sysconfdir@
-transform:=@program_transform_name@
-mandir:=@mandir@
-
-TARGETS:=secnet
-
-OBJECTS:=secnet.o util.o conffile.yy.o conffile.tab.o conffile.o modules.o \
-       resolver.o random.o udp.o site.o transform-cbcmac.o transform-eax.o \
-       netlink.o rsa.o dh.o serpent.o serpentbe.o \
-       md5.o sha512.o version.o tun.o slip.o sha1.o ipaddr.o log.o \
-       process.o @LIBOBJS@ \
-       hackypar.o
-
-TEST_OBJECTS:=eax-aes-test.o eax-serpent-test.o eax-serpentbe-test.o \
-               eax-test.o aes.o
-
-%.c:   %.y
-
-%.yy.c:        %.fl
-       flex --header=$*.yy.h -o$@ $<
-
-%.tab.c %.tab.h:       %.y
-       bison -d -o $@ $<
-
-%.o: %.c
-       $(CC) $(CPPFLAGS) $(ALL_CFLAGS) -c $< -o $@
-
-all:   $(TARGETS) check
-
-# Automatic remaking of configuration files, from autoconf documentation
-${srcdir}/configure: configure.in
-       cd ${srcdir} && autoconf
-
-# autoheader might not change config.h.in, so touch a stamp file.
-${srcdir}/config.h.in: stamp-h.in
-${srcdir}/stamp-h.in: configure.in
-       cd ${srcdir} && autoheader
-       echo timestamp > ${srcdir}/stamp-h.in
-
-config.h: stamp-h
-stamp-h: config.h.in config.status
-       ./config.status
-
-Makefile: Makefile.in config.status
-       ./config.status
-
-config.status: configure
-       ./config.status --recheck
-# End of config file remaking rules
-
-# C and header file dependency rules
-SOURCES:=$(OBJECTS:.o=.c) $(TEST_OBJECTS:.o=.c)
-DEPENDS:=$(OBJECTS:.o=.d) $(TEST_OBJECTS:.o=.d)
-
-$(DEPENDS): ${srcdir}/depend.sh
-
-%.d: %.c
-       ${srcdir}/depend.sh $(srcdir) $(CPPFLAGS) $(ALL_CFLAGS) $< > $@
-
--include $(DEPENDS)
-
-# Manual dependencies section
-conffile.yy.c: conffile.fl conffile.tab.c
-conffile.yy.h: conffile.yy.c
-conffile.tab.c:        conffile.y
-# End of manual dependencies section
-
-secnet:        $(OBJECTS)
-       $(CC) $(LDFLAGS) $(ALL_CFLAGS) -o $@ $(OBJECTS) $(LDLIBS)
-
-check: eax-aes-test.confirm eax-serpent-test.confirm \
-       eax-serpentbe-test.confirm
-
-version.c: Makefile
-       echo "#include \"secnet.h\"" >$@.new
-       echo "char version[]=\"secnet $(VERSION)\";" >>$@.new
-       mv -f $@.new $@
-
-eax-%-test: eax-%-test.o eax-test.o %.o
-       $(CC) $(LDFLAGS) $(ALL_CFLAGS) -o $@ $^
-
-eax-%-test.confirm: eax-%-test eax-%-test.vectors
-       ./$< <$(srcdir)/eax-$*-test.vectors >$@.new
-       mv -f $@.new $@
-
-.PRECIOUS: eax-%-test
-
-installdirs:
-       $(INSTALL) -d $(prefix)/share/secnet $(sbindir)
-       $(INSTALL) -d $(mandir)/man8
-
-install: installdirs
-       $(INSTALL_PROGRAM) secnet $(sbindir)/`echo secnet|sed '$(transform)'`
-       $(INSTALL_PROGRAM) ${srcdir}/make-secnet-sites $(sbindir)/`echo make-secnet-sites|sed '$(transform)'`
-       $(INSTALL) ${srcdir}/ipaddr.py $(prefix)/share/secnet/ipaddr.py
-       $(INSTALL) secnet.8 $(mandir)/man8/secnet.8
-
-clean:
-       $(RM) -f *.o *.yy.c *.tab.[ch] $(TARGETS) core version.c
-       $(RM) -f *.d *~ eax-*-test.confirm eax-*-test
-
-realclean:     clean
-       $(RM) -f *~ Makefile config.h  *.d \
-       config.log config.status config.cache \
-       stamp-h Makefile.bak
-
-distclean:     realclean
-
-pfname:=$(PACKAGE)-$(VERSION)
-tarfname:=../$(pfname).tar
-dist:
-       $(RM) -rf $(tarfname) $(tarfname).gz
-       git archive --format=tar --prefix=$(pfname)/ HEAD -o $(tarfname)
-       gzip -9f $(tarfname)
-
-# Release checklist:
-#  1. Check that the tree has what you want
-#
-#  2. Update VERSION (above) and debian/changelog
-#     but DO NOT COMMIT
-#
-#  3. Run
-#       ./configure
-#       make dist
-#     and check that the resulting tarball looks OK.
-#     Eg, untar it and build it, or have it reviewed.
-#
-#  3. Commit the updates to VERSION (above) and debian/changelog
-#
-#  4. git-tag -s v$(VERSION)
-#
-#  5. git-push origin
-#
-#  6. Run, again,
-#       make dist
-#
-#  7. gpg --detach-sign ../secnet-$(VERSION).tar.gz
-#
-#  8. rsync -v ../secnet-$VERSION.tar.gz* \
-#        chiark:/home/ianmdlvl/public-html/secnet/download/
-#
-#  9. On chiark:
-#       tar zxf ~ianmdlvl/public-html/secnet/download/secnet-$(VERSION).tar.gz
-#       cd secnet-$(VERSION)
-#       debian/rules build
-#       fakeroot debian/rules binary
-#       mv ../secnet_0.1.18.1-1_i386.deb ~/public-html/secnet/download/
-#
-#  10. On chiark as user secnet:
-#       cd ~secnet/
-#       rsync ~ianmdlvl/public-html/secnet/download/secnet* .
-#
-#  11. write and post a release announcement
diff --git a/NOTES b/NOTES
index 40cbf04ecea71b01c329443248c7c3f110ebf1a0..001c118e96f9401592560ea3332583b653ac2c55 100644 (file)
--- a/NOTES
+++ b/NOTES
@@ -140,6 +140,75 @@ before going to background.
 'normal' and 'failed' runs output to stdout/stderr before
 backgrounding, then thereafter output only to log destinations.
 
+** Site long-term keys
+
+We use authenticated DH.  Sites identify themselves to each other
+using long-term signing keys.
+
+These signing keys may be for a variety of algorithms.  (An algorithm
+specifies completely how to do a signature and verification.)
+
+Each site may have several keys.  This helps support key rollover and
+algorithm agility.  Several keys of different algorithms can form a
+key group.  Usually a key group consists of keys generated at the same
+time.  A key is identified by a 4-byte group id (invented by its
+publisher and opaque) plus a 1-byte algorithm id (defined by the
+protocol spec for each algorithm).
+
+Keys are published in key sets.  A key set is a collection of key
+groups (including older keys as well as newer ones) published at a
+particular time.  Key sets have their own 4-byte ids; these are
+invented by the publisher but are ordered using sequence number
+arithmetic.  This allows reliers to favour new sets over old ones.
+
+Within each key set, some groups may be marked as `fallback'.  This
+means a group that should be tolerated by a relier only if the relier
+doesn't support any non-fallback keys.
+
+Keys within groups, and groups within sets, are ordered (by the
+publisher of the set), from most to least preferred.
+
+When deciding which public keys to accept, a relier should:
+  Process each group within the key set.
+    Discard unknown algorithms.
+    Choose a preferred algorithm:
+      Earliest in the group
+      (or local config could have algorithm prefererence).
+  Discard empty groups.
+  Discard unneeded fallback groups:
+    If any (non-empty) non-fallback groups found, discard all
+    fallback groups.  Otherwise there are only fallback groups;
+    discard all but first group in the set.
+  Discard any keys exceeding limit on number of keys honoured:
+    Limit is at least 4
+    Discard keys later in the set
+  In wire protocol, offer the resulting subset of keyids to
+  the peer and a allow the signer to select which key to use
+  from that subset.
+
+In configuration and key management, long-term private and public keys
+are octet strings.  Private keys are generally stored in disk files,
+one key per file.  The octet string for a private key should identify
+the algorithm so that passing the private key to the code for the 
+wrong algorithm does not produce results which would leak or weaken
+the key.  The octet string for a public key need not identify the
+algorithm; when it's loaded the algorithm will be known from context.
+
+The group id 00000000 is special.  It should contain only one key,
+algorithm 00.  Key 0000000000 refers to the rsa1 key promulgated
+before the key rollover/advertisement protocols, or the key which
+should be used by sites running old software.
+
+The key set id 00000000 is special and is considered older than all
+othere key sets (ie this is an exception to the sequence number
+arithmetic).  It is the implied key set id of the rsa1 key
+promulgated before the key rollover/advertisement protocols.
+
+The algorithm 00 is special and refers to the old rsa1 signature
+protocol but unusually does not identify the hash function.  The hash
+function is conventional and must be specified out of band.  In known
+existing installations it is SHA-1.
+
 ** Protocols
 
 *** Protocol environment:
@@ -198,6 +267,14 @@ The optional additional data after the sender's name consists of some
 initial subset of the following list of items:
  * A 32-bit integer with a set of capability flags, representing the
    abilities of the sender.
+ * In MSG3/MSG4: a 16-bit integer being the sender's MTU, or zero.
+   (In other messages: nothing.)  See below.
+ * In MSG2/MSG3: a list of the peer's public keys that the sender will
+   accept: (i) a 1-byte integer count (ii) that many 5-byte key ids.
+   If not present, implicitly only the special key id 0000000000.
+ * In MSG3/MSG4: an 8-bit integer being an index into the
+   receiver's public key acceptance list, with which the message
+   is signed.  If not present, implicitly the key id 00000000000.
  * More data which is yet to be defined and which must be ignored
    by receivers.
 The optional additional data after the receiver's name is not
@@ -207,22 +284,70 @@ Capability flag bits must be in one the following two categories:
 
 1. Early capability flags must be advertised in MSG1 or MSG2, as
    applicable.  If MSG3 or MSG4 advertise any "early" capability bits,
-   MSG1 or MSG3 (as applicable) must have advertised them too.  Sadly,
-   advertising an early capability flag will produce MSG1s which are
-   not understood by versions of secnet which predate the capability
-   mechanism.
-
-2. Late capability flags are advertised in MSG2 or MSG3, as
-   applicable.  They may also appear in MSG1, but this is not
-   guaranteed.  MSG4 must advertise the same set as MSG2.
-
-No capability flags are currently defined.  Unknown capability flags
-should be treated as late ones.
+   MSG1 or MSG3 (as applicable) must have advertised them too.
+
+2. Late capability flags may be advertised only in MSG2 or MSG3, as
+   applicable.  They are only in MSG1 with newer secnets; older
+   versions omit them.  MSG4 must advertise the same set as MSG2.
+
+Currently, the low 16 bits are allocated for negotiating bulk-crypto
+transforms.  Bits 8 to 15 are used by Secnet as default capability
+numbers for the various kinds of transform closures: bit 8 is for the
+original CBCMAC-based transform, and bit 9 for the new EAX transform;
+bits 10 to 15 are reserved for future expansion.  The the low eight bits
+are reserved for local use, e.g., to allow migration from one set of
+parameters for a particular transform to a different, incompatible set
+of parameters for the same transform.  Bit 31, if advertised by both
+ends, indicates that a mobile end gets priority in case of crossed MSG1.
+The remaining bits have not yet been assigned a purpose.
+
+Whether a capability number is early depends on its meaning, rather than
+being a static property of its number.  That said, the mobile-end-gets
+priority bit (31) is always sent as an `early' capability bit.
+
+
+MTU handling
+
+In older versions of secnet, secnet was not capable of fragmentation
+or sending ICMP Frag Needed.  Administrators were expected to configure
+consistent MTUs across the network.
+
+It is still the case in the current version that the MTUs need to be
+configured reasonably coherently across the network: the allocated
+buffer sizes must be sufficient to cope with packets from all other
+peers.
+
+However, provided the buffers are sufficient, all packets will be
+processed properly: a secnet receiving a packet larger than the
+applicable MTU for its delivery will either fragment it, or reject it
+with ICMP Frag Needed.
+
+The MTU additional data field allows secnet to advertise an MTU to the
+peer.  This allows the sending end to handle overlarge packets, before
+they are transmitted across the underlying public network.  This can
+therefore be used to work around underlying network braindamage
+affecting large packets.
+
+If the MTU additional data field is zero or not present, then the peer
+should use locally-configured MTU information (normally, its local
+netlink MTU) instead.
+
+If it is nonzero, the peer may send packets up to the advertised size
+(and if that size is bigger than the peer's administratively
+configured size, the advertiser promises that its buffers can handle
+such a large packet).
+
+A secnet instance should not assume that just because it has
+advertised an mtu which is lower than usual for the vpn, the peer will
+honour it, unless the administrator knows that the peers are
+sufficiently modern to understand the mtu advertisement option.  So
+secnet will still accept packets which exceed the link MTU (whether
+negotiated or assumed).
 
 
 Messages:
 
-1) A->B: *,iA,msg1,A+,B+,nA
+1) A->B: i*,iA,msg1,A+,B+,nA
 
 i* must be encoded as 0.  (However, it is permitted for a site to use
 zero as its "index" for another site.)
diff --git a/NOTES.peer-keys b/NOTES.peer-keys
new file mode 100644 (file)
index 0000000..2c58213
--- /dev/null
@@ -0,0 +1,112 @@
+peerkeys files
+--------------
+
+ <F>            live file, loaded on startup, updated by secnet
+                 (only).  * in-memory peerkeys_current is kept
+                 synced with this file
+
+ <F>~update     update file from config manager, checked before
+                 every key exchange.  config manager must rename
+                 this file into place; it will be renamed and
+                 then removed by secnet.
+
+ <F>~proc       update file being processed by secnet.
+                 only secnet may write or remove.
+
+ <F>~incoming   update file from peer, being received by secnet
+                 may be incomplete, unverified, or even malicious
+                 only secnet may write or remove.
+
+ <F>~tmp        update file from config manager, only mss may
+                 write or rename
+
+secnet discards updates that are not more recent than (by
+serial) the live file.  But it may not process updates
+immediately.
+
+The implied keyset to be used is MAX(live, proc, update).
+
+secnet does:
+ check live vs proc, either mv proc live or rm proc
+ if proc doesn't exist, mv update proc
+
+make-secnet-sites does:
+ write: rename something onto update
+ read: read update,proc,live in that order and take max
+
+We support only one concurrent secnet, one concurrent
+writing make-secnet-sites, and any number of readers.
+We want to maintain a live file at all times as that
+is what secnet actually reads at startup and uses.
+
+Proof that this is sound:
+  Let us regard update,proc,live as i=0,1,2
+  Files contain public key sets and are manipulated as
+   a whole, and we may regard key sets with the same
+   serial as equivalent.
+  We talk below about reading as if it were atomic.
+   Actually the atomic operation is open(2); the
+   reading gets whatever that name refers to.  So
+   we can model this as an atomic read.
+  secnet eventually moves all data into the live file
+   or deletes it, so there should be no indefinitely
+   stale data; informally this means we can disregard
+   the possibility of very old serials and regard
+   serials as fully ordered.  (We don't bother with
+   a formal proof of this property.)
+  Consequently we will only think about the serial
+   and not the contents.  We treat absent files as
+   minimal (we will write -1 for convenience although
+   we don't mean a numerical value).  We write S(i).
+
+Invariant 1 for secnet's transformations is as follows:
+  Each file S(i) is only reduced (to S'(i)) if for some j S'(j)
+  >= S(i), with S'(j) either being >= S(i) beforehand, or
+  updated atomically together with S(i).
+
+Proof of invariant 1 for the secnet operations:
+  (a) check live vs proc, proc>live, mv:
+     j=2, i=1; S'(i)=-1, so S(i) is being reduced.  S'(j) is
+     equal to S(i), and the rename is atomic [1], so S'(j) and
+     S'(i) are updated simultaneously.  S(j) is being
+     increased.  (There are no hazards from concurrent writers;
+     only we ourselves (secnet) write to live or proc.)
+  (b) check live vs proc, proc<=live, rm:
+     j=2, i=1; S'(i)=-1, so S(i) is being reduced.  But
+     S(j) is >= $(i) throughout.  (Again, no concurrent
+     writer hazards.)
+  (c) mv update proc (when proc does not exist):
+     j=1, i=0; S(i) is being reduced to -1.  But simultaneously
+     S(j) is being increased to the old S(i).  Our precondition
+     (proc not existing) is not subject to a concurrent writer
+     hazards because only we write to proc; our action is
+     atomic and takes whatever update is available (if any).
+
+Proof of soundness for the mss reading operation:
+  Let M be MAX(\forall S) at the point where mss reads update.
+  Invariant 2: when mss reads S(k), MAX(K, S(k)..S(2)) >= M,
+  where K is the max S it has seen so far.  Clearly this is
+  true for k=0 (with K==-1).  secnet's operations never break
+  this invariant because if any S() is reduced, another one
+  counted must be increased.  mss's step operation
+  updates K with S(k), so MAX(K', S(k+1)..)=MAX(K, S(k)..),
+  and updates k to k+1, preserving the invariant.
+  At the end we have k=3 and K=>M.  Since secnet never
+  invents serials, K=M in the absence of an mss update
+  with a bigger S.
+
+Consideration of the mss update operation:
+  Successive serials from sites file updates etc. are supposed
+  to be increasing.  When this is true, M is increased.  A
+  concurrent reading mss which makes its first read after the
+  update will get the new data (by the proofs above).  This
+  seems to be the required property.
+
+QED.
+
+[1] From "Base Specifications issue 7",
+ 2.9.7 Thread Interactions with Regular File Operations
+ All of the following functions shall be atomic with respect to
+ each other in the effects specified in POSIX.1-2017 when they
+ operate on regular files or symbolic links:
+  ... rename ... open ...
diff --git a/README b/README
index 71a5a44094aede754a76cd593f6827982f4629db..200da3742ba9ef91d7b4c81273c22184e02883ca 100644 (file)
--- a/README
+++ b/README
@@ -2,13 +2,37 @@ secnet - flexible VPN software
 
 * Copying
 
-secnet is Copyright (C) 1995--2003 Stephen Early <steve@greenend.org.uk>
-It is distributed under the terms of the GNU General Public License,
-version 2 or later.  See the file COPYING for more information.
+secnet is
+  Copyright 1995-2003 Stephen Early <steve@greenend.org.uk>
+  Copyright 2002-2014 Ian Jackson <ijackson@chiark.greenend.org.uk>
+  Copyright 1991      Massachusetts Institute of Technology
+  Copyright 1998      Ross Anderson, Eli Biham, Lars Knudsen
+  Copyright 1993      Colin Plumb
+  Copyright 1998      James H. Brown, Steve Reid
+  Copyright 2000      Vincent Rijmen, Antoon Bosselaers, Paulo Barreto
+  Copyright 2001      Saul Kravitz
+  Copyright 2004      Fabrice Bellard
+  Copyright 2002      Guido Draheim
+  Copyright 2005-2010 Free Software Foundation, Inc.
+  Copyright 1995-2001 Jonathan Amery
+  Copyright 1995-2003 Peter Benie
+  Copyright 2011      Richard Kettlewell
+  Copyright 2012      Matthew Vernon
+  Copyright 2013-2019 Mark Wooding
+  Copyright 1995-2013 Simon Tatham
+
+secnet is distributed under the terms of the GNU General Public
+License, version 3 or later.  Some individual files have more
+permissive licences; where this is the case, it is documented in the
+header comment for the files in question.
+
+secnet is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+The file COPYING contains a copy of the GNU GPL v3.
 
-The IP address handling library in ipaddr.py is Copyright (C)
-1996--2000 Cendio Systems AB, and is distributed under the terms of
-the GPL.
 
 * Introduction
 
@@ -169,6 +193,17 @@ Usage: secnet [OPTION]...
       --help              display this help and exit
       --version           output version information and exit
 
+* base91s
+
+secnet defines a variant of the base91 encoding `basE91', from
+ http://base91.sourceforge.net/
+
+base91s is the same as baseE91 except that:
+ - in the encoded charset, `"' is replaced with `-'
+ - spaces, newlines etc. and other characters outside the charset
+    are not permitted (although in some places they may be ignored,
+    this is not guaranteed).
+
 * secnet builtin modules
 
 ** resolver
@@ -194,10 +229,95 @@ Defines:
   udp (closure => comm closure)
 
 udp: dict argument
-  address (string): IP address to listen and send on
+  address (string list): IPv6 or IPv4 addresses to listen and send on;
+   default is all local addresses
+  port (integer): UDP port to listen and send on; optional if you
+   don't need to have a stable address for your peers to talk to
+   (in which case your site ought probably to have `local-mobile true').
+  buffer (buffer closure): buffer for incoming packets
+  authbind (string): optional, path to authbind-helper program
+
+** polypath
+
+Defines:
+  polypath (closure => comm closure)
+
+polypath: dict argument
   port (integer): UDP port to listen and send on
   buffer (buffer closure): buffer for incoming packets
   authbind (string): optional, path to authbind-helper program
+  max-interfaces (number): optional, max number of different interfaces to
+   use (also, maximum steady-state amount of packet multiplication);
+   interfaces marked with `@' do not count.
+  interfaces (string list): which interfaces to process; each entry is
+   optionally `!' or `+' or `@' followed by a glob pattern (which is
+   applied to a prospective interface using fnmatch with no flags).
+   `+' or nothing means to process normally. `!' means to ignore;
+   `@' means to use only in conjunction with dedicated-interface-addr.
+   If no list is specified, or the list ends with a `!' entry, a
+   default list is used/appended:
+   "!tun*","!tap*","!sl*","!userv*","!lo","@hippo*","*".
+   Patterns which do not start with `*' or an alphanumeric need to be
+   preceded by `!' or `+' or `@'.
+  monitor-command (string list): Program to use to monitor appearance
+   and disappearance of addresses on local network interfaces.  Should
+   produce lines of the form `+|-<ifname> 4|6 <addr>' where <addr> is
+   an address literal.  Each - line should relate to a previously
+   printed + line.  On startup, should produce a + line for each
+   currently existing address.  secnet does filtering so there is no
+   need to strip out tun interfaces, multicast addresses, and so on.
+   The command is run as the user secnet is started as (not the one
+   which secnet may drop privilege to due to the configured `userid').
+   The default depends on the operating system.
+  permit-loopback (boolean): Normally, loopback IPv6 and IPv4
+   addresses on local interfaces are disregarded, because such
+   interfaces are not interesting for communicating with distant
+   hosts.  Setting this option will ignore that check, which can be
+   useful for testing.  Setting this option also removes "!lo*" from
+   the default interface pattern list.
+
+When using this comm, packets are sent out of every active interface
+on the host (where possible).  It is important that interfaces created
+by secnet itself are not included!  secnet's default filter list tries
+to do this.
+
+This comm only makes sense for sites which are mobile.  That is, the
+site closures used with this comm should all have the `local-mobile'
+parameter set to `true'.  When the local site site is not marked
+mobile the address selection machinery might fixate on an unsuitable
+address.
+
+polypath takes site-specific informtion as passed to the `comm-info'
+site closure parameter.  The entries understood in the dictionary
+are:
+  dedicated-interface-addr (string): IPv4 or IPv6 address
+   literal.  Interfaces specified with `@' in `interfaces' will be
+   used for the corresponding site iff the interface local address
+   is this address.
+
+For an interface to work with polypath, it must either have a suitable
+default route, or be a point-to-point interface.  In the general case
+this might mean that the host would have to have multiple default
+routes.  However in practice the most useful configuration is two
+interfaces being (1) wifi (2) mobile internet.
+
+I have had success on Linux by using network-manager for wifi and
+invoking ppp directly for mobile internet.  ppp sets up a
+point-to-point link, and does not add a default route if there already
+is one.  network-manager always sets up a default route.  The result
+is that the wifi always has a default route (so is useable); ppp
+(being a point-to-point link) does not need one.
+
+The use of polypath requires that secnet be started with root
+privilege, to make the setsockopt(,,SO_BINDTODEVICE,) calls.  If the
+configuration specifies that secnet should drop privilege (see
+`userid' above), secnet will keep a special process around for this
+purpose; that process will handle local network interface changes but
+does not deal with any packets, key exchange, etc.
+
+polypath support is only available when secnet is built against an
+IPv6-capable version of adns (because it wants features in the newer
+adns).
 
 ** log
 
@@ -206,7 +326,8 @@ Defines:
   syslog (closure => log closure)
 
 logfile: dict argument
-  filename (string): where to log to
+  filename (string): where to log to; default is stderr
+  prefix (string): added to messages [""]
   class (string list): what type of messages to log
     { "debug-config", M_DEBUG_CONFIG },
     { "debug-phase", M_DEBUG_PHASE },
@@ -271,28 +392,32 @@ site: dict argument
    them.
   resolver (resolver closure)
   random (randomsrc closure)
-  local-key (rsaprivkey closure)
-  address (string): optional, DNS name used to find our peer
+  key-cache (privcache closure)
+  local-key (sigprivkey closure): Deprecated; use key-cache instead.
+  address (string list): optional, DNS name(s) used to find our peer;
+    address literals are supported too if enclosed in `[' `]'.
   port (integer): mandatory if 'address' is specified: the port used
     to contact our peer
-  key (rsapubkey closure): our peer's public key
+  peer-keys (string): path (prefix) for peer public key set file(s);
+    see README.make-secnet-sites re `pub' etc. and NOTES.peer-keys.
+  key (sigpubkey closure): our peer's public key (obsolete)
   transform (transform closure): how to mangle packets sent between sites
   dh (dh closure)
-  hash (hash closure)
   key-lifetime (integer): max lifetime of a session key, in ms
     [one hour; mobile: 2 days]
   setup-retries (integer): max number of times to transmit a key negotiation
     packet [5; mobile: 30]
   setup-timeout (integer): time between retransmissions of key negotiation
     packets, in ms [2000; mobile: 1000]
-  wait-time (integer): after failed key setup, wait this long (in ms) before
-    allowing another attempt [20000; mobile: 10000]
+  wait-time (integer): after failed key setup, wait roughly this long
+    (in ms) before allowing another attempt [20000; mobile: 10000]
+    Actual wait time is randomly chosen between ~0.5x and ~1.5x this.
   renegotiate-time (integer): if we see traffic on the link after this time
     then renegotiate another session key immediately (in ms)
     [half key-lifetime, or key-lifetime minus 5 mins (mobile: 12 hours),
      whichever is longer].
   keepalive (bool): if True then attempt always to keep a valid session key.
-    Not actually currently implemented. [false]
+    [false]
   log-events (string list): types of events to log for this site
     unexpected: unexpected key setup packets (may be late retransmissions)
     setup-init: start of attempt to setup a session key
@@ -318,7 +443,11 @@ site: dict argument
     address may suddenly change couldn't communicate reliably because
     their contact addresses might both change at once. [false]
   mobile-peers-max (integer): Maximum number of peer port/addr pairs we
-    remember and send to.  Must be at least 1 and no more than 5.  [3]
+    remember and send to.  Must be at least 1 and no more than 5.
+    [4 if any address is configured, otherwise 3]
+  static-peers-max (integer): Maximum number of peer port/addr pairs
+    we can try for a static site.  Must be at least 1 and no more
+    than 5.  [4 or 3, as above]
   mobile-peer-expiry (integer): For "mobile" peers only, the length
     of time (in seconds) for which we will keep sending to multiple
     address/ports from which we have not seen incoming traffic. [120]
@@ -329,6 +458,22 @@ site: dict argument
     check that there are no links both ends of which are allegedly
     mobile (which is not supported, so those links are ignored) and
     to change some of the tuning parameter defaults. [false]
+  mtu-target (integer): Desired value of the inter-site MTU for this
+    peering.  This value will be advertised to the peer (which ought
+    to affect incoming packets), and if the peer advertises an MTU its
+    value will be combined with this setting to compute the inter-site
+    MTU.  (secnet will still accept packets which exceed the
+    (negotiated or assumed) inter-site MTU.)  Setting a lower
+    inter-site MTU can be used to try to restrict the sizes of the
+    packets sent over the underlying public network (e.g. to work
+    around network braindamage).  It is not normally useful to set a
+    larger value for mtu-target than the VPN's general MTU (which
+    should be reflected in the local private interface MTU, ie the mtu
+    parameter to netlink).  If this parameter is not set, or is set
+    to 0, the default is to use the local private link mtu.
+  comm-info (dict): Information for the comm, used when this site
+    wants to transmit.  If the comm does not support this, it is
+    ignored.
 
 Links involving mobile peers have some different tuning parameter
 default values, which are generally more aggressive about retrying key
@@ -375,7 +520,7 @@ a netlink closure:
       other tunnels as well as the host (used for mobile devices like laptops)
     soft: remove these routes from the host's routing table when
       the tunnel link quality is zero
-  mtu (integer): default MTU over this link; may be updated by tunnel code
+  mtu (integer): MTU of host's tunnel interface
 
 Netlink will dump its current routing table to the system/log on
 receipt of SIGUSR1.
@@ -414,11 +559,42 @@ tun: dict argument
 I recommend you don't specify the 'interface' option unless you're
 doing something that requires the interface name to be constant.
 
+** privcache
+
+Cache of dynamically loaded private keys.
+
+Defines:
+  priv-cache (closure => privcache closure)
+
+priv-cache: dict argument
+  privkeys (string): path prefix for private keys.  Each key is
+    looked for at this path prefix followed by the 10-character 
+    hex key id.
+  privcache-size (integer): optional, maximum number of private
+    keys to retain at once. [5]
+  privkey-max (integer): optional, maximum size of private key
+    file in bytes. [4095]
+
+** pubkeys
+
+Defines:
+  make-public (closure => sigpubkey closure)
+
+make-public: (
+  arg1: sigscheme name
+  arg2: base91s encoded public key data, according to algorithm
+
 ** rsa
 
 Defines:
-  rsa-private (closure => rsaprivkey closure)
-  rsa-public (closure => rsapubkey closure)
+  sigscheme algorithm 00 "rsa1"
+  rsa-private (closure => sigprivkey closure)
+  rsa-public (closure => sigpubkey closure)
+
+rsa1 sigscheme algorithm:
+  private key: SSH private key file, version 1, no password
+  public key: SSH public key file, version 1
+    (length, restrictions, email, etc., ignored)
 
 rsa-private: string[,bool]
   arg1: filename of SSH private key file (version 1, no password)
@@ -428,6 +604,11 @@ rsa-public: string,string
   arg1: encryption key (decimal)
   arg2: modulus (decimal)
 
+The sigscheme is hardcoded to use sha1.  Both rsa-private and
+rsa-public look for the following config key in their context:
+  hash (hash closure): hash function [sha1]
+
+
 ** dh
 
 Defines:
diff --git a/README.make-secnet-sites b/README.make-secnet-sites
new file mode 100644 (file)
index 0000000..cbf304e
--- /dev/null
@@ -0,0 +1,316 @@
+USAGE
+
+       make-secnet-sites [-P PREFIX] [--conf] [IN [OUTCONF]]
+       make-secnet-sites --filter [IN [OUT]]
+       make-secnet-sites -u|--userv HEADER GRPDIR SITESFILE GROUP
+
+       The `-P' option sets the PREFIX string, mentioned below in
+       `OUTPUT STRUCTURE'; the default is empty.
+
+       In --conf mode, `make-secnet-sites' reads a single input
+       file from IN (defaulting to standard input), and writes a Secnet
+       configuration fragment to OUTCONF (defaulting to standard output).
+
+       In --filter mode, `make-secnet-sites' reads a single input
+       file from IN (defaulting to standard input), and writes a
+       version of that sites file to OUT (defaulting to standard
+       output).  The output is filtered according to --output-version.
+
+       In --userv mode, `make-secnet-sites' expects to have been invoked
+       via GNU Userv.  It verifies that GROUP is listed in the
+       `USERV_GROUP' environment variable.  It then processes the
+       HEADER input, which should say `end-defintions' somewhere, to
+       enable restrictions, and then user input on standard input.  If
+       the combination of the two is acceptable, it writes a copy of
+       the user input to the file `GRPDIR/RGROUP' (the `R' is literal)
+       preceded by a comment logging the time and the value of the
+       `USERV_USER' environment variable, and writes a file named
+       SITESFILE consisting of the concatenation of:
+
+         * a header comment logging the time and the value of the
+           `USERV_USER' environment variable, and a reminder that this
+           is `make-secnet-sites' input;
+
+         * the HEADER, with any `include' lines replaced by the files
+           they include; and
+
+         * each of the `GRPDIR/R*' files, in some arbitrary order.
+
+       This SITESFILE can later be processed in the former mode to
+       produce Secnet configuration.
+
+
+OPTIONS
+
+       --output-version NUMBER
+
+               Write backward-compatible sites file output,
+               targeting a particular sites format.  Values of
+               NUMBER that are understood are:
+                   1   The original format, pre signing key
+                       negotiation.
+                   2   Signing key algorithm agility and negotiation.
+               If NUMBER is higher than make-secnet-sites supports,
+               it writes out what it can.
+
+       --pubkeys-install
+
+               Specifies that public keys are to be installed in the
+               live pubkeys area (and not hardcoded in secnet conf
+               files).  With this option, generated site configs
+               refer to keys in PUBKEYS; also, the generated secnet
+               configuration enables live peer public update.
+
+       --pubkeys-single
+
+               Specifies that one public key per site is to be
+               written directly into the sites.conf output.  If
+               --output-version=1, this is the rsa1 key 0000000000.
+               Otherwise it is an error if there are multiple public
+               keys defined for any site, in the input.
+               --pubkeys-single is the default.
+
+       --pubkeys-elide
+
+               In the sites.conf output, just write the peer-keys
+               entry referring to keys in PUBKEYS.  But do not write
+               public keys anywhere.
+
+       --pubkeys-dir PUBKEYS
+
+               Specifies the live pubkeys area pathname.
+               The default is /var/lib/secnet/pubkeys.
+
+               Key files are named
+                       PUBKEYS/peer.<mangled-peer-name>[~...]
+               mangled-peer-name is chosen by make-secnet-sites
+                       / => ,
+
+       --debug | -D
+
+               Increase amount of debugging output.
+
+
+INPUT SYNTAX
+
+       The input files have a simple line-based syntax.  Blank lines,
+       and lines beginning with a `#' character, are ignored.  Other
+       lines consist of a keyword followed by arguments, and separated
+       by horizontal whitespace.  There is no quoting, and it is not
+       possible to include horizontal whitespace in an argument.
+
+       An input file describes a number of virtual private networks
+       (`VPNs').  Each VPN consists of a number of locations, and each
+       location consists of a number of sites, thus forming (together
+       with the root) a fixed four-level hierarchy.  The root, VPNs,
+       locations, and sites can each have a number of properties
+       attached to them: each level in the hierarchy has a different
+       set of permissable properties.
+
+       Most keywords define properties on a `current' item in the
+       hierarchy.  Some change which item is current, possibly creating
+       a new item.  A few are special.
+
+       First, the navigation keywords.
+
+       vpn NAME
+               Switch to the VPN called NAME, which is a direct child
+               of the root, creating it if necessary.  Subsequent
+               properties, up until the next navigation keyword, are
+               attached directly to the VPN.
+
+               A VPN item becomes a dictionary named `NAME' within the
+               `PREFIXvpn-data' dictionary in the generated output.
+
+       location NAME [GROUP]
+               Switch to the location called NAME, which is a direct
+               child of the most recently mentioned VPN, creating it if
+               necessary.  The GROUP name may be omitted (and is anyway
+               ignored) if the location already exists.  It is an error
+               if there is no current VPN.  Subsequent properties, up
+               until the next navigation keyword, are attached directly
+               to the location.
+
+               A location item becomes a dictionary named `NAME' within
+               its parent VPN's dictionary in the generated output.
+
+       site NAME
+               Switch to the site called NAME, which is a direct
+               child of the most recently mentioned location, creating
+               it if necessary.  It is an error if there is no current
+               location.  Subsequent properties, up until the next
+               navigation keyword, are attached directly to the site.
+
+               A location item becomes a dictionary named `NAME' within
+               its parent location's dictionary in the generated
+               output.
+
+       Now, the special keywords.
+
+       include FILE
+               Read lines from FILE, as if they'd appeared at this
+               point in the input.  If the FILE name is relative, it is
+               interpreted relative to the directory containing the
+               most recently opened file.  (This seems to be a bug.)
+
+               The `include' keyword is only permitted before the
+               `end-defintions' marker in a HEADER file processed using
+               the `-u' option.
+
+       end-definitions
+               After this keyword, the following restrictions apply.
+
+                 * The `include' keyword can no longer be used.
+
+                 * It is not permitted to define new VPNs and
+                   locations.
+
+                 * It is not permitted to append new items to root,
+                   VPN, and location properties which are already
+                   defined.  (Assigning new properties is permitted.)
+
+                 * It is not permitted to define new VPN-level
+                   properties.
+
+       Finally, the properties.
+
+       Usually, if a property has already been defined on an item, then
+       it is an error to try to redefine it.  But some properties are
+       list-like: the values are accumulated into a single list.
+
+       Mostly, properties are written to corresponding assignments in
+       the generated Secnet configuration file, .  The entries below
+       describe how properties are translated into assignments.
+
+       contact EMAIL
+               Becomes a `Contact address' comment in the output.
+               Acceptable at all levels; required separately at VPN and
+               location levels.
+
+       dh P G
+               Assigns a Diffie--Hellman closure to the `dh' key,
+               constructed as `diffie-hellman(P, G)'. Acceptable at all
+               levels; required at site level.
+
+       hash HASH-NAME
+               Assigns the HASH-NAME to the `hash' key.  The HASH-NAME
+               must be one of `md5' or `sha1', and the corresponding
+               hash closure is used.  Acceptable at all levels;
+               required at site level.
+
+       key-lifetime INT
+       setup-timeout INT
+       setup-retries INT
+       wait-time INT
+       renegotiate-time INT
+               Assign integers to the like-named key.  Acceptable at
+               all levels.
+               
+       restrict-nets NETWORK NETWORK ...
+               This item and its descendents may only define `networks'
+               and `peer' properties with addresses within the listed
+               NETWORKs, each of which has the form IPADDR/MASK, where
+               the IPADDR is an IPv4 address in dotted-quad form, and
+               the MASK is either a netmask in dotted-quad form or a
+               prefix length.  Becomes a comment n the output.
+               Acceptable at all levels.
+
+       networks NETWORK NETWORK ...
+               Assigns a list of NETWORKs to the `routes' key in a
+               netlink application (see below).  See `restrict-nets'
+               for the syntax of a NETWORK.  Acceptable only at site
+               level; required at site level.
+
+       address HOSTNAME PORT
+               Assigns HOSTNAME to the `address' key and PORT (an
+               integer) to the `port' key.  Acceptable only at site
+               level.  May be omitted for mobile sites.
+
+       peer IPADDR
+               Assigns IPADDR to the `ptp-address' key in a netlink
+               application (see below).  IPADDR must be an IPv4 address
+               in dotted-quad form.  Acceptable only at site level;
+               required at site level.
+
+       pubkey HUNOZ E N
+               Assigns a public-key closure to the `key' key,
+               constructed as `rsa-public(E, N)'.  The argument HUNOZ
+               must be an integer, but is otherwise ignored; it's
+               conventionally the length of N in bits.
+               Acceptable only at site level.  See `pub'.
+
+       mobile BOOL
+               Assigns BOOL to the `mobile' key.  Acceptable only at
+               site level, but optional.
+
+       Properties which can also appear in public key files.
+       (named by `peer-keys' key to secnet sites closure.)
+       These are acceptable to make-secnet-sites only at
+       site level.  See also `Site long-term keys' in NOTES.
+
+       pub ALG DATAB91S
+               Defines a public key.  ALG is an algorithm name and
+               DATA91S is the public key data, encoded according to
+               secnet-base91 (see below).
+               Gives make-public("ALG","DATAB91S") in sites.conf;
+               at least one `pub' or `pubkey' must be specified.
+
+       serial SETIDHEX
+               Specifies the key set id (8 hex digits representing
+               4 bytes: each pair is the value of the next byte).
+               May appear at most once.  If not present, 00000000.
+
+       pkg GROUPIDHEX
+       pkgf GROUPIDHEX
+               Specifies the key group id for subsequent keys.
+               pkgf indicates a fallback group.
+               May be repeated (with different id values).
+               If not specified, 00000000.
+
+
+OUTPUT STRUCTURE
+
+       The program produces a Secnet configuration fragment with the
+       structure described below, suitable for inclusion using the
+       `include' keyword.
+
+               PREFIXvpn-data {
+                 VPN {
+                   # Contact email address: EMAIL
+                   [ # restrict-nets: NETWORKS ]
+                   [ VPN-PROPERTIES ]
+                   LOCATION {
+                     # Contact email address: EMAIL
+                     [ # restrict-nets: NETWORKS ]
+                     [ LOCATION-PROPERTIES ]
+                     SITE {
+                       [ # Contact email address: EMAIL ]
+                       [ # restrict-nets: NETWORKS ]
+                       name "VPN/LOCATION/NAME";
+                       SITE-PROPERTIES
+                       link netlink {
+                         routes NETWORK ...;
+                         ptp-address IPADDR;
+                       };
+                     };
+                     [ MORE SITES ... ]
+                   };
+                   [ MORE LOCATIONS ... ]
+                 };
+                 [ MORE VPNS ... ]
+               };
+
+               PREFIXvpn {
+                 VPN {
+                   LOCATION PREFIXvpn-data/VPN/LOCATION/SITE, ...;
+                   [ MORE LOCATIONS ]
+                   all-sites LOCATION, ...;
+                 };
+               };
+
+               PREFIXall-sites PREFIXvpn/VPN/all-sites, ...;
+
+       Note in particular the implicit dependency on a pure closure
+       named `netlink' used to set the `link' key in each site
+       definition.  Usually, this will be constructed by a partial
+       application of the built-in `userv-ipif' or `tun' closures.
diff --git a/Suffix.sd.mk b/Suffix.sd.mk
new file mode 100644 (file)
index 0000000..5daff5e
--- /dev/null
@@ -0,0 +1,5 @@
+&TARGETS_check +=
+&TARGETS_fullcheck +=
+
+&:include subdirmk/cdeps.sd.mk
+&:include subdirmk/clean.sd.mk
index 273085cbc3779cddae4a90f01a35da5047010985..ee7aa5d8d5185e6105aa8a6e231f130e90c993d9 100644 (file)
@@ -16,7 +16,37 @@ dnl and IRIX C compiler.
 dnl
 dnl @version $Id: ac_prog_cc_no_writeable_strings.m4,v 1.1 2002/02/20 16:18:18 steve Exp $
 dnl @author Guido Draheim <guidod@gmx.de>
+
+dnl [This appears to be a previous version of
+dnl  ax_cflags_no_writable_strings.m4 which is nowadays to be found in
+dnl  the Autoconf Archive.  It was imported there on 2007-02-14
+dnl  in commit 16aee45643e593e2833e4dff19df7b5f14267a79 where the file
+dnl  has a GPLv2 permission notice.  Therefore I feel justified in
+dnl  adding the copyright permission notice below: -iwj]
+dnl
+dnl  This file is Free Software.  It has been copied into secnet.
+dnl
+dnl  Copyright 2002 Guido Draheim
+dnl
+dnl  You may redistribute secnet as a whole and/or modify it under the
+dnl  terms of the GNU General Public License as published by the Free
+dnl  Software Foundation; either version 3, or (at your option) any
+dnl  later version.
 dnl
+dnl  You may redistribute this file and/or modify it under the terms of
+dnl  the GNU General Public License as published by the Free Software
+dnl  Foundation; either version 2, or (at your option) any later
+dnl  version.
+dnl
+dnl  This software is distributed in the hope that it will be useful,
+dnl  but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl  GNU General Public License for more details.
+dnl
+dnl  You should have received a copy of the GNU General Public License
+dnl  along with this software; if not, see
+dnl  https://www.gnu.org/licenses/gpl.html.
+
 AC_DEFUN([AC_PROG_CC_NO_WRITEABLE_STRINGS], [
   pushdef([CV],ac_cv_prog_cc_no_writeable_strings)dnl
   hard=$2
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644 (file)
index 0000000..970fb7d
--- /dev/null
@@ -0,0 +1,35 @@
+# aclocal.m4 - package-specific macros for autoconf
+
+dnl This file is part of secnet.
+dnl See README for full list of copyright holders.
+dnl
+dnl secnet is free software; you can redistribute it and/or modify it
+dnl under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 3 of the License, or
+dnl (at your option) any later version.
+dnl 
+dnl secnet is distributed in the hope that it will be useful, but
+dnl WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+dnl General Public License for more details.
+dnl 
+dnl You should have received a copy of the GNU General Public License
+dnl version 3 along with secnet; if not, see
+dnl https://www.gnu.org/licenses/gpl.html.
+
+dnl This next macro came from adns.git,
+dnl (d8fa191ed7774818862febd6ade774cb7e149ab9).
+define(ADNS_C_GETFUNC,[
+ AC_CHECK_FUNC([$1],,[
+  AC_CHECK_LIB([$2],[$1],[$3],[
+    AC_MSG_ERROR([cannot find library function $1])
+  ])
+ ])
+])
+
+define(SECNET_C_GETFUNC,[
+ ADNS_C_GETFUNC($1,$2,[
+  LIBS="-l$2 $LIBS";
+  AC_MSG_WARN([$1 is in lib$2, urgh.  Must use -l$2.])
+ ])
+])
diff --git a/aes.c b/aes.c
index 4e155ab7e0b25244571de29e5a9cb95410def1a2..d82c81d7e59de8a48478d1e5f5d1f45f6be421d9 100644 (file)
--- a/aes.c
+++ b/aes.c
@@ -1,10 +1,43 @@
-/**
+/*
+ * aes.c - implementation of Rijndael
+ */
+/*
+ * This file is Free Software.  It has been modified to as part of its
+ * incorporation into secnet.
+ *
+ * Copyright 2000 Vincent Rijmen, Antoon Bosselaers, Paulo Barreto
+ * Copyright 2004 Fabrice Bellard
+ * Copyright 2013 Ian Jackson
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the permissive licence shown below.
  *
- * aes.c - integrated in QEMU by Fabrice Bellard from the OpenSSL project.
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+/*
+ * Integrated in QEMU by Fabrice Bellard from the OpenSSL project.
  *
  * Copied to the secnet tree by Ian Jackson from the upstream qemu git
  * tree revision 55616505876d6683130076b810a27c7889321560
  * and modified only to remove the include of qemu-common.h.
+ *
+ * (The changes by various qemu contributors between
+ * e4d4fe3c34cdd6e26f9b9975efec7d1e81ad00b6, where this file appeared
+ * in qemu in a commit by Fabrice Bellard, and 55616505 are too
+ * trivial to attract copyright, which is just as well because some of
+ * the commits are lacking a S-o-b.)
  */
 /*
  * rijndael-alg-fst.c
diff --git a/aes.h b/aes.h
index fc693560db4f273ff6844a68d34b68d4b35fd165..23259f611f7bce0669e65348719e7cba36c5ae0c 100644 (file)
--- a/aes.h
+++ b/aes.h
@@ -1,8 +1,32 @@
 /*
-  * aes.h
-  *
-  * Header file declaring AES functions.
-  *
+ * aes.h - Header file declaring AES functions.
+ */
+/*
+ * This file is Free Software.  It has been modified to as part of its
+ * incorporation into secnet.
+ *
+ * Copyright 2000 Vincent Rijmen, Antoon Bosselaers, Paulo Barreto
+ * Copyright 2004 Fabrice Bellard
+ * Copyright 2013 Ian Jackson
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the permissive licence shown below.
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+/*
   * Copied from the upstream qemu git tree revision
   *   55616505876d6683130076b810a27c7889321560
   * but was introduced there by Fabrice Bellard in
diff --git a/argparseactionnoyes.py b/argparseactionnoyes.py
new file mode 100644 (file)
index 0000000..a7eef77
--- /dev/null
@@ -0,0 +1,37 @@
+# Python argparse --[no-]foo options
+#
+# Copyright 2012 "Omnifarious" (a user on StackOverFlow)
+# Copyright 2013 "btel" (a user on StackOverFlow)
+#
+# https://stackoverflow.com/questions/9234258/in-python-argparse-is-it-possible-to-have-paired-no-something-something-arg/20422915#20422915
+#
+# CC-BY-SA 4.0
+# by virtue of
+# https://stackoverflow.com/legal/terms-of-service#licensing
+# which says everything is CC-BY-SA and has a link to v4.0
+# (And which is therefore compatible with secnet's GPLv3+)
+#
+# all retrieved 4.11.2019
+
+import argparse
+
+class ActionNoYes(argparse.Action):
+    def __init__(self, option_strings, dest, default=None, required=False, help=None):
+
+        if default is None:
+            raise ValueError('You must provide a default with Yes/No action')
+        if len(option_strings)!=1:
+            raise ValueError('Only single argument is allowed with YesNo action')
+        opt = option_strings[0]
+        if not opt.startswith('--'):
+            raise ValueError('Yes/No arguments must be prefixed with --')
+
+        opt = opt[2:]
+        opts = ['--' + opt, '--no-' + opt]
+        super(ActionNoYes, self).__init__(opts, dest, nargs=0, const=None, 
+                                          default=default, required=required, help=help)
+    def __call__(self, parser, namespace, values, option_strings=None):
+        if option_strings.startswith('--no-'):
+            setattr(namespace, self.dest, False)
+        else:
+            setattr(namespace, self.dest, True)
diff --git a/autogen.sh b/autogen.sh
new file mode 100755 (executable)
index 0000000..0459fda
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# This file is part of secnet.
+# See README for full list of copyright holders.
+#
+# secnet is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# 
+# secnet is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# version 3 along with secnet; if not, see
+# https://www.gnu.org/licenses/gpl.html.
+
+set -e
+autoconf
+autoheader
+# ^ although configure will run autoheader if we didn't, our
+#   objective is for users not to have to have recent autoconf
+#   installed, so we commit config.h.  ./autogen.sh regenerates
+#   the committed files.
\ No newline at end of file
diff --git a/base91-c/.gitignore b/base91-c/.gitignore
new file mode 100644 (file)
index 0000000..4863b4e
--- /dev/null
@@ -0,0 +1,8 @@
+/base91
+/lentest
+*.o
+/test/b91dec
+/test/b91enc
+/test/lentest
+/test/*.dat
+/test/*.b91
diff --git a/base91-c/AWK/README b/base91-c/AWK/README
new file mode 100644 (file)
index 0000000..ff1a834
--- /dev/null
@@ -0,0 +1,16 @@
+This is a (slow) AWK implementation of the basE91 decoder. It decodes from
+standard input to standard output.
+
+Example usage:
+
+       awk -f b91dec.awk < file.b91 > file.bin
+  or
+       ./b91dec.awk < file.b91 > file.bin
+
+
+Be careful on non-Unix systems! - During output, some ported versions of awk
+automatically convert byte values of 0x0A to the native line break sequence of
+the host system (e.g. 0x0D 0x0A under DOS/Windows). This can result in corrupt
+binary files.
+You should test on some examples and compare the output of b91dec.awk with the
+original data before relying on it.
diff --git a/base91-c/AWK/b91dec.awk b/base91-c/AWK/b91dec.awk
new file mode 100755 (executable)
index 0000000..8a15fc3
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/awk -f
+
+# basE91 decoder
+# Copyright (c) 2000-2006 Joachim Henke
+# http://base91.sourceforge.net/
+
+BEGIN {
+       b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~\""
+       for (i = 0; i < 256; ++i) {
+               --d[sprintf("%c", i)]
+       }
+       for (i = 0; i < 91; ++i) {
+               d[substr(b, i + 1, 1)] = i
+       }
+       b = 0
+       n = 0
+       v = -1
+}
+
+{
+       l = length($0)
+       for (i = 1; i <= l; ++i) {
+               c = d[substr($0, i, 1)]
+               if (c < 0) {
+                       continue
+               }
+               if (v < 0) {
+                       v = c
+               } else {
+                       v += c * 91
+                       b += v * 2 ^ n
+                       n += v % 8192 > 88 ? 13 : 14
+                       do {
+                               b -= c = b % 256
+                               printf "%c", c
+                               b /= 256
+                               n -= 8
+                       } while (n > 7)
+                       v = -1
+               }
+       }
+}
+
+END {
+       if (v + 1) {
+               printf "%c", b + v * 2 ^ n
+       }
+}
diff --git a/base91-c/DOS-asm/b91enc.asm b/base91-c/DOS-asm/b91enc.asm
new file mode 100644 (file)
index 0000000..c4d00e9
--- /dev/null
@@ -0,0 +1,152 @@
+; basE91 encoder for DOS\r
+;\r
+; Copyright (c) 2005-2006 Joachim Henke\r
+; All rights reserved.\r
+;\r
+; Redistribution and use in source and binary forms, with or without\r
+; modification, are permitted provided that the following conditions are met:\r
+;\r
+;  - Redistributions of source code must retain the above copyright notice,\r
+;    this list of conditions and the following disclaimer.\r
+;  - Redistributions in binary form must reproduce the above copyright notice,\r
+;    this list of conditions and the following disclaimer in the documentation\r
+;    and/or other materials provided with the distribution.\r
+;  - Neither the name of Joachim Henke nor the names of his contributors may\r
+;    be used to endorse or promote products derived from this software without\r
+;    specific prior written permission.\r
+;\r
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+; ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\r
+; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+; POSSIBILITY OF SUCH DAMAGE.\r
+\r
+bits 16\r
+cpu 8086\r
+org 256\r
+\r
+       xor sp, sp\r
+       mov si, ld_0    ; create lookup table\r
+       mov bp, 90\r
+lc_0:\r
+       mov bx, 90\r
+       mov ah, [bp + si]\r
+lc_1:\r
+       mov al, [bx + si]\r
+       push ax\r
+       dec bx\r
+       jns lc_1\r
+       dec bp\r
+       jns lc_0\r
+\r
+       inc bx\r
+       mov sp, a_stck\r
+lc_2:\r
+       push bx\r
+       push bx\r
+       jmp short lc_5\r
+lc_3:\r
+       mov ax, [si]\r
+       cmp cl, 6       ; bits in queue + 8 < 14?\r
+       sbb dx, dx\r
+       inc si\r
+       mov ch, ah\r
+       add bp, dx\r
+       sbb dx, dx\r
+       xor ch, al\r
+       and ah, dl\r
+       and ch, dl\r
+       sub si, dx\r
+       xor ch, al\r
+       shl ax, cl\r
+       add cl, 8\r
+       or bx, ax\r
+       test bp, bp\r
+       js lc_4\r
+\r
+       and bh, 0x1F    ; keep 13 bits\r
+       and dl, 8\r
+       and ah, 0x3F\r
+       cmp bx, byte 89 ; value in bit queue < 89?\r
+       sbb al, al\r
+       add dl, cl\r
+       and ah, al\r
+       mov cl, 13\r
+       or bh, ah       ; take 13 or 14 bits\r
+       sub cl, al\r
+       add bx, bx\r
+       mov ax, [bx + a_ltab]\r
+       mov bx, cx\r
+       add cl, 16\r
+       sub cl, dl\r
+       sub dl, bl\r
+       shr bx, cl      ; restore bit queue\r
+       mov cl, dl\r
+       stosw\r
+       dec bp\r
+       jns lc_3\r
+lc_4:\r
+       push bx\r
+       mov ah, 0x40\r
+       push cx\r
+       mov bx, 1\r
+       lea cx, [di - a_obuf]\r
+       mov dx, a_obuf\r
+       int 0x21        ; write to standard output\r
+\r
+       dec bx\r
+lc_5:\r
+       mov ah, 0x3F\r
+       mov cx, s_ibuf\r
+       mov dx, a_ibuf\r
+       int 0x21        ; read from standard input\r
+\r
+       cld\r
+       pop cx\r
+       mov si, dx\r
+       mov di, a_obuf\r
+       pop bx\r
+       add bp, ax      ; ax = 0 -> EOF\r
+       jc lc_3\r
+\r
+       push ax\r
+       test cl, cl\r
+       jz lc_6\r
+\r
+       cmp bx, byte 91 ; value in bit queue < 91?\r
+       sbb dx, dx\r
+       cmp cl, 8       ; less than 8 bits in queue?\r
+       sbb cx, cx\r
+       add bx, bx\r
+       and cx, dx\r
+       mov dx, a_obuf\r
+       mov ax, [bx + a_ltab]\r
+       inc cx\r
+       mov bx, 1\r
+       inc cx\r
+       stosw\r
+       mov ah, 0x40\r
+       int 0x21        ; write out 1 or 2 bytes\r
+lc_6:\r
+       retn    ; exit program\r
+ld_0:\r
+       db 'ABCDEFGHIJKLM'\r
+       db 'NOPQRSTUVWXYZ'\r
+       db 'abcdefghijklm'\r
+       db 'nopqrstuvwxyz'\r
+       db '0123456789!#$'\r
+       db '%&()*+,./:;<='\r
+       db '>?@[]^_`{|}~"'\r
+\r
+\r
+a_stck equ ((lc_2 - $$) + 256) & 510\r
+a_ltab equ 48974\r
+a_obuf equ ((ld_0 - $$) + 257) & 510\r
+s_ibuf equ ((a_ltab - a_obuf - 2) << 4) / 29\r
+a_ibuf equ a_ltab - s_ibuf\r
diff --git a/base91-c/DOS-asm/readme.txt b/base91-c/DOS-asm/readme.txt
new file mode 100644 (file)
index 0000000..a3c0f47
--- /dev/null
@@ -0,0 +1,12 @@
+This is a compact 16-bit assembly implementation of the basE91 encoder for DOS.\r
+It encodes from standard input to standard output. Minimum system requirements:\r
+DOS 2.0, 8086 processor\r
+\r
+Example usage:\r
+\r
+       b91enc < file.bin > file.b91\r
+\r
+\r
+Assemble with NASM [http://nasm.sourceforge.net/]:\r
+\r
+       nasm -O2 -o b91enc.com b91enc.asm\r
diff --git a/base91-c/Java/b91cli.java b/base91-c/Java/b91cli.java
new file mode 100644 (file)
index 0000000..7d39990
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * basE91 command line front-end
+ *
+ * Copyright (c) 2000-2006 Joachim Henke
+ * 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 Joachim Henke nor the names of his 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 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.
+ */
+
+import java.io.*;
+
+public class b91cli
+{
+       private static void encode(InputStream is, OutputStream os)
+       {
+               int s;
+               byte[] ibuf = new byte[53248];
+               byte[] obuf = new byte[65536];
+               basE91 b91 = new basE91();
+
+               try {
+                       while ((s = is.read(ibuf)) > 0) {
+                               s = b91.encode(ibuf, s, obuf);
+                               os.write(obuf, 0, s);
+                       }
+                       s = b91.encEnd(obuf);
+                       os.write(obuf, 0, s);
+               } catch (Exception e) {
+                       System.err.println(e);
+               }
+       }
+
+       private static void encodeWrap(InputStream is, OutputStream os)
+       {
+               int i, s;
+               int n = 0;
+               byte[] ibuf = new byte[53248];
+               byte[] obuf = new byte[65536];
+               char[] line = new char[76];
+               basE91 b91 = new basE91();
+
+               try {
+                       PrintStream ps = new PrintStream(os, false, "US-ASCII");
+
+                       while ((s = is.read(ibuf)) > 0) {
+                               s = b91.encode(ibuf, s, obuf);
+                               for (i = 0; i < s; ++i) {
+                                       line[n++] = (char) obuf[i];
+                                       if (n == 76) {
+                                               ps.println(line);
+                                               n = 0;
+                                       }
+                               }
+                       }
+                       s = b91.encEnd(obuf);
+                       for (i = 0; i < s; ++i) {
+                               line[n++] = (char) obuf[i];
+                               if (n == 76) {
+                                       ps.println(line);
+                                       n = 0;
+                               }
+                       }
+                       if (n > 0)
+                               ps.println(new String(line, 0, n));
+               } catch (Exception e) {
+                       System.err.println(e);
+               }
+       }
+
+       private static void decode(InputStream is, OutputStream os)
+       {
+               int s;
+               byte[] ibuf = new byte[65536];
+               byte[] obuf = new byte[57344];
+               basE91 b91 = new basE91();
+
+               try {
+                       while ((s = is.read(ibuf)) > 0) {
+                               s = b91.decode(ibuf, s, obuf);
+                               os.write(obuf, 0, s);
+                       }
+                       s = b91.decEnd(obuf);
+                       os.write(obuf, 0, s);
+               } catch (Exception e) {
+                       System.err.println(e);
+               }
+       }
+
+       private static void errExit(String msg)
+       {
+               System.err.println("syntax error - " + msg + "\nTry `-h' option for more information.");
+               System.exit(3);
+       }
+
+       public static void main(String[] args)
+       {
+               int i;
+               boolean enc = true;
+               boolean lbr = true;
+               String ifn = null;
+               String ofn = null;
+
+               for (i = 0; i < args.length; ++i)
+                       if (args[i].length() == 2 && args[i].charAt(0) == '-')
+                               switch (args[i].charAt(1)) {
+                               case 'd':
+                                       enc = false;
+                                       break;
+                               case 'u':
+                                       lbr = false;
+                                       break;
+                               case 'h':
+                                       System.out.println("Usage: base91 [OPTION] infile [outfile]\n\n  -d\tdecode a basE91 encoded file\n  -u\tleave encoder output unformatted (disable line wrapping)\n  -h\tdisplay this help and exit\n  -V\toutput version information and exit");
+                                       return;
+                               case 'V':
+                                       System.out.println("base91 0.6.0\nCopyright (c) 2000-2006 Joachim Henke");
+                                       return;
+                               default:
+                                       errExit("invalid option: " + args[i]);
+                               }
+                       else if (ifn == null)
+                               ifn = args[i];
+                       else if (ofn == null)
+                               ofn = args[i];
+                       else
+                               errExit("too many arguments: " + args[i]);
+               if (ifn == null)
+                       errExit("file name missing");
+               if (ofn == null)
+                       if (enc)
+                               ofn = ifn + (lbr ? "_b91.txt" : ".b91");
+                       else {
+                               String lifn = ifn.toLowerCase();
+                               if (ifn.length() > 4 && lifn.endsWith(".b91"))
+                                       ofn = ifn.substring(0, ifn.length() - 4);
+                               else if (ifn.length() > 8 && lifn.endsWith("_b91.txt"))
+                                       ofn = ifn.substring(0, ifn.length() - 8);
+                               else
+                                       ofn = ifn + ".bin";
+                       }
+
+               try {
+                       FileInputStream ifs = new FileInputStream(ifn);
+                       FileOutputStream ofs = new FileOutputStream(ofn);
+
+                       if (enc)
+                               if (lbr)
+                                       encodeWrap(ifs, ofs);
+                               else
+                                       encode(ifs, ofs);
+                       else
+                               decode(ifs, ofs);
+                       ifs.close();
+                       ofs.close();
+               } catch (Exception e) {
+                       System.err.println(e);
+               }
+       }
+}
diff --git a/base91-c/Java/basE91.java b/base91-c/Java/basE91.java
new file mode 100644 (file)
index 0000000..56d7fda
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * basE91 encoding/decoding routines
+ *
+ * Copyright (c) 2000-2006 Joachim Henke
+ * 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 Joachim Henke nor the names of his 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 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.
+ */
+
+public class basE91
+{
+       private int ebq, en, dbq, dn, dv;
+       public final byte[] enctab;
+       private final byte[] dectab;
+
+       public int encode(byte[] ib, int n, byte[] ob)
+       {
+               int i, c = 0;
+
+               for (i = 0; i < n; ++i) {
+                       ebq |= (ib[i] & 255) << en;
+                       en += 8;
+                       if (en > 13) {
+                               int ev = ebq & 8191;
+
+                               if (ev > 88) {
+                                       ebq >>= 13;
+                                       en -= 13;
+                               } else {
+                                       ev = ebq & 16383;
+                                       ebq >>= 14;
+                                       en -= 14;
+                               }
+                               ob[c++] = enctab[ev % 91];
+                               ob[c++] = enctab[ev / 91];
+                       }
+               }
+               return c;
+       }
+
+       public int encEnd(byte[] ob)
+       {
+               int c = 0;
+
+               if (en > 0) {
+                       ob[c++] = enctab[ebq % 91];
+                       if (en > 7 || ebq > 90)
+                               ob[c++] = enctab[ebq / 91];
+               }
+               encReset();
+               return c;
+       }
+
+       public void encReset()
+       {
+               ebq = 0;
+               en = 0;
+       }
+
+       public int decode(byte[] ib, int n, byte[] ob)
+       {
+               int i, c = 0;
+
+               for (i = 0; i < n; ++i) {
+                       if (dectab[ib[i]] == -1)
+                               continue;
+                       if (dv == -1)
+                               dv = dectab[ib[i]];
+                       else {
+                               dv += dectab[ib[i]] * 91;
+                               dbq |= dv << dn;
+                               dn += (dv & 8191) > 88 ? 13 : 14;
+                               do {
+                                       ob[c++] = (byte) dbq;
+                                       dbq >>= 8;
+                                       dn -= 8;
+                               } while (dn > 7);
+                               dv = -1;
+                       }
+               }
+               return c;
+       }
+
+       public int decEnd(byte[] ob)
+       {
+               int c = 0;
+
+               if (dv != -1)
+                       ob[c++] = (byte) (dbq | dv << dn);
+               decReset();
+               return c;
+       }
+
+       public void decReset()
+       {
+               dbq = 0;
+               dn = 0;
+               dv = -1;
+       }
+
+       public basE91()
+       {
+               int i;
+               String ts = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~\"";
+
+               enctab = ts.getBytes();
+               dectab = new byte[256];
+               for (i = 0; i < 256; ++i)
+                       dectab[i] = -1;
+               for (i = 0; i < 91; ++i)
+                       dectab[enctab[i]] = (byte) i;
+               encReset();
+               decReset();
+       }
+}
diff --git a/base91-c/Java/build_jar.sh b/base91-c/Java/build_jar.sh
new file mode 100755 (executable)
index 0000000..bd54b3e
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+javac -encoding US-ASCII -g:none -source 1.3 -target 1.2 basE91.java b91cli.java && \
+jar cvfm base91.jar manifest.mf b91cli.class basE91.class license.txt readme.txt
diff --git a/base91-c/Java/license.txt b/base91-c/Java/license.txt
new file mode 100644 (file)
index 0000000..8b952bd
--- /dev/null
@@ -0,0 +1,25 @@
+Copyright (c) 2000-2006 Joachim Henke\r
+All rights reserved.\r
+\r
+Redistribution and use in source and binary forms, with or without\r
+modification, are permitted provided that the following conditions are met:\r
+\r
+  - Redistributions of source code must retain the above copyright notice, this\r
+    list of conditions and the following disclaimer.\r
+  - Redistributions in binary form must reproduce the above copyright notice,\r
+    this list of conditions and the following disclaimer in the documentation\r
+    and/or other materials provided with the distribution.\r
+  - Neither the name of Joachim Henke nor the names of his contributors may be\r
+    used to endorse or promote products derived from this software without\r
+    specific prior written permission.\r
+\r
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND\r
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\r
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\r
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\r
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\r
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\r
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
diff --git a/base91-c/Java/manifest.mf b/base91-c/Java/manifest.mf
new file mode 100644 (file)
index 0000000..b06ff67
--- /dev/null
@@ -0,0 +1,4 @@
+Main-Class: b91cli
+Package-Title: basE91 command line tool
+Package-Version: 0.6.0
+Package-Vendor: Joachim Henke
diff --git a/base91-c/Java/readme.txt b/base91-c/Java/readme.txt
new file mode 100644 (file)
index 0000000..bc5a3ac
--- /dev/null
@@ -0,0 +1,26 @@
+This is an implementation of the basE91 encoder and decoder in Java.\r
+\r
+Syntax:\r
+       java -jar base91.jar [OPTION] infile [outfile]\r
+\r
+Options:\r
+\r
+-d     decode a basE91 encoded file;\r
+       all non-alphabet characters (such as newlines) are ignored\r
+\r
+-u     leave encoder output unformatted;\r
+       i. e., disable line wrapping after 76 characters\r
+\r
+-h     display short help and exit\r
+\r
+-V     output version information and exit\r
+\r
+\r
+If no outfile is given for encoding, it defaults to `infile_b91.txt' (or to\r
+`infile.b91' with the `-u' switch).\r
+On decoding, the added file extension is removed to generate the name for\r
+outfile; otherwise, if infile hasn't a default extension, the decoded data is\r
+written to `infile.bin'.\r
+\r
+For further information visit the basE91 home page at\r
+http://base91.sourceforge.net/\r
diff --git a/base91-c/LICENSE b/base91-c/LICENSE
new file mode 100644 (file)
index 0000000..e4a656c
--- /dev/null
@@ -0,0 +1,25 @@
+Copyright (c) 2000-2006 Joachim Henke
+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 Joachim Henke nor the names of his 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 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.
diff --git a/base91-c/Makefile b/base91-c/Makefile
new file mode 100644 (file)
index 0000000..feb0e45
--- /dev/null
@@ -0,0 +1,45 @@
+CFLAGS = -Wall -W -O2
+LDFLAGS = -s
+
+CC = gcc
+INSTALL = install
+INSTALL_DATA = $(INSTALL) -m 444
+INSTALL_PROGRAM = $(INSTALL) -m 555
+
+prefix = /usr/local
+exec_prefix = $(prefix)
+bindir = $(exec_prefix)/bin
+mandir = $(prefix)/share/man
+man1dir = $(mandir)/man1
+manext = .1
+
+BIN = base91 lentest
+
+.PHONY: all install check clean
+
+all: $(BIN)
+
+%.o: %.c
+       $(CC) $(CFLAGS) -c $<
+
+base91: cli.o base91.o
+       $(CC) $(LDFLAGS) -o $@ $^
+
+lentest: lentest.o base91.o
+
+install: all
+       mkdir -p $(DESTDIR)$(bindir)
+       $(INSTALL_PROGRAM) base91 $(DESTDIR)$(bindir)/base91
+       ln -sf base91 $(DESTDIR)$(bindir)/b91dec
+       ln -sf base91 $(DESTDIR)$(bindir)/b91enc
+       mkdir -p $(DESTDIR)$(man1dir)
+       $(INSTALL_DATA) base91.1 $(DESTDIR)$(man1dir)/base91$(manext)
+       ln -sf base91$(manext) $(DESTDIR)$(man1dir)/b91dec$(manext)
+       ln -sf base91$(manext) $(DESTDIR)$(man1dir)/b91enc$(manext)
+
+check: all
+       cd test && $(MAKE)
+
+clean:
+       -rm -f *.o $(BIN) core
+       cd test && $(MAKE) clean
diff --git a/base91-c/NEWS b/base91-c/NEWS
new file mode 100644 (file)
index 0000000..98dbf22
--- /dev/null
@@ -0,0 +1,64 @@
+[0.6.0] 2006-11-01
+* basE91 encoding/decoding routines restructured to be thread-safe
+* lots of type fixes
+* new core utility is `base91', with a behaviour similar to GNU base64
+* introduce `-w' switch for wrapping encoded output lines after given length
+* long option handling
+* use standard I/O functions for better portability
+* MinGW compatibility code added
+* minor extensions to `make check'
+* Java-tool wraps output lines by default; can be avoided with the `-u' switch
+* license changed to BSD
+
+[0.5.2] 2006-08-25
+* code cleanup
+* encoder for DOS rewritten to be faster and compatible down to Intel 8086
+
+[0.5.1] 2005-10-05
+* Java-b91enc now handles file extensions case insensitively
+* native DOS version of basE91 encoder added
+
+[0.5.0] 2005-06-24
+* ATTENTION: this version breaks backward compatibility because the basE91
+  alphabet was changed to reduce the occurrence of double quotes - sorry, I
+  should have done this long before
+* b91dec is installed as a link to b91enc
+* `-e' option added (complement to `-d')
+* build system should be more portable now
+
+[0.4.2] 2005-05-16
+* AWK basE91 decoder no longer depends on GNU extensions
+* Java byte code removed (distributed separately in a jar file)
+
+[0.4.1] 2005-05-07
+* some code cleanup
+* Java-b91enc can break encoded output to lines of 76 characters (`-b' switch)
+
+[0.4.0] 2005-04-26
+* improved encoder behaviour on stream ends (can save one byte sometimes)
+* allocate buffer memory dynamically; use overlapping buffers
+* new `-m' switch can be used for testing
+* verbose mode extended: `-vv' shows memory statistics
+* `make check' implemented - runs some basic tests
+
+[0.3.1] 2005-04-19
+* b91enc has a verbose mode now (`-v' switch)
+* Java-b91enc accepts command line syntax with only one FILE argument again
+
+[0.3.0] 2005-04-17
+* the code was restructured to allow a more universal use of the basE91 backend
+* version switch changed to `-V' which is more common - sorry for that
+* `make install' is possible now
+* changed Java-b91enc to be a bit more similar to the C version
+* implementation in PHP added
+
+[0.2.3] 2005-04-11
+* man page included (thanks to Kei!)
+* version (-v) switch added
+
+[0.2.2] 2005-04-10
+* fixed a bug in decoder that could result in corrupt output on 64-bit systems
+* Java class files included
+
+[0.2.1] 2005-04-09
+* first public release
diff --git a/base91-c/PHP4/README b/base91-c/PHP4/README
new file mode 100644 (file)
index 0000000..b357bad
--- /dev/null
@@ -0,0 +1,40 @@
+base91_encode -- Encodes data with basE91
+
+       string base91_encode ( string data )
+
+base91_encode() returns data encoded with basE91. This encoding is designed to
+make binary data survive transport through transport layers that are not 8-bit
+clean, such as mail bodies.
+
+basE91-encoded data takes at most 23% more space than the original data.
+
+Example:
+<?php
+  require_once 'base91.php';
+  $str = 'This is an encoded string';
+  echo base91_encode($str);
+?>
+
+This example will produce:
+
+nX,<:WRT%yV%!5:maref3+1RrUb64^M
+
+-----
+
+base91_decode -- Decodes data encoded with basE91
+
+       string base91_decode ( string encoded_data )
+
+base91_decode() decodes encoded_data ignoring non-alphabet characters and
+returns the original data. The returned data may be binary.
+
+Example:
+<?php
+  require_once 'base91.php';
+  $str = 'nX,<:WRT%yV%!5:maref3+1RrUb64^M';
+  echo base91_decode($str);
+?>
+
+This example will produce:
+
+This is an encoded string
diff --git a/base91-c/PHP4/base91.php b/base91-c/PHP4/base91.php
new file mode 100644 (file)
index 0000000..ea34f03
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+// Copyright (c) 2005-2006 Joachim Henke
+// http://base91.sourceforge.net/
+
+$b91_enctab = array(
+       'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+       'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+       'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+       'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$',
+       '%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=',
+       '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"'
+);
+$b91_dectab = array_flip($b91_enctab);
+
+function base91_decode($d)
+{
+       global $b91_dectab;
+       $l = strlen($d);
+       $v = -1;
+       for ($i = 0; $i < $l; ++$i) {
+               $c = $b91_dectab[$d{$i}];
+               if (!isset($c))
+                       continue;
+               if ($v < 0)
+                       $v = $c;
+               else {
+                       $v += $c * 91;
+                       $b |= $v << $n;
+                       $n += ($v & 8191) > 88 ? 13 : 14;
+                       do {
+                               $o .= chr($b & 255);
+                               $b >>= 8;
+                               $n -= 8;
+                       } while ($n > 7);
+                       $v = -1;
+               }
+       }
+       if ($v + 1)
+               $o .= chr(($b | $v << $n) & 255);
+       return $o;
+}
+
+function base91_encode($d)
+{
+       global $b91_enctab;
+       $l = strlen($d);
+       for ($i = 0; $i < $l; ++$i) {
+               $b |= ord($d{$i}) << $n;
+               $n += 8;
+               if ($n > 13) {
+                       $v = $b & 8191;
+                       if ($v > 88) {
+                               $b >>= 13;
+                               $n -= 13;
+                       } else {
+                               $v = $b & 16383;
+                               $b >>= 14;
+                               $n -= 14;
+                       }
+                       $o .= $b91_enctab[$v % 91] . $b91_enctab[$v / 91];
+               }
+       }
+       if ($n) {
+               $o .= $b91_enctab[$b % 91];
+               if ($n > 7 || $b > 90)
+                       $o .= $b91_enctab[$b / 91];
+       }
+       return $o;
+}
+?>
diff --git a/base91-c/README b/base91-c/README
new file mode 100644 (file)
index 0000000..731eaaa
--- /dev/null
@@ -0,0 +1,75 @@
+basE91 - converting binary data to ASCII text
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Copyright (c) 2000-2006 Joachim Henke
+
+
+basE91 is an advanced method for encoding binary data as ASCII characters. It
+is similar to UUencode or base64, but is more efficient. The overhead produced
+by basE91 depends on the input data. It amounts at most to 23% (versus 33% for
+base64) and can range down to 14%, which typically occurs on 0-byte blocks.
+This makes basE91 very useful for transferring larger files over binary
+insecure connections like e-mail or terminal lines.
+
+The current algorithm has been written with portability and simplicity in mind
+an is therefore not necessarily optimised for speed.
+
+
+* Alphabet
+
+As the name suggests, basE91 needs 91 characters to represent the encoded
+binary data in ASCII. From the 94 printable ASCII characters (0x21-0x7E), the
+following three ones have been omitted to build the basE91 alphabet:
+
+- (dash, 0x2D)
+' (apostrophe, 0x27)
+\ (backslash, 0x5C)
+
+The translation table is composed of the remaining characters as shown below.
+
+ 0 A   13 N    26 a    39 n    52 0    65 %    78 >
+ 1 B   14 O    27 b    40 o    53 1    66 &    79 ?
+ 2 C   15 P    28 c    41 p    54 2    67 (    80 @
+ 3 D   16 Q    29 d    42 q    55 3    68 )    81 [
+ 4 E   17 R    30 e    43 r    56 4    69 *    82 ]
+ 5 F   18 S    31 f    44 s    57 5    70 +    83 ^
+ 6 G   19 T    32 g    45 t    58 6    71 ,    84 _
+ 7 H   20 U    33 h    46 u    59 7    72 .    85 `
+ 8 I   21 V    34 i    47 v    60 8    73 /    86 {
+ 9 J   22 W    35 j    48 w    61 9    74 :    87 |
+10 K   23 X    36 k    49 x    62 !    75 ;    88 }
+11 L   24 Y    37 l    50 y    63 #    76 <    89 ~
+12 M   25 Z    38 m    51 z    64 $    77 =    90 "
+
+
+* Building
+
+1. `cd' to the directory containing the package's source code and type `make'
+   to compile the package
+
+2. optionally, type `make check' to run any self-tests that come with the
+   package
+
+3. type `make install' to install the program and documentation in `/usr/local'
+   (to specify another installation prefix than `/usr/local', type
+   `make prefix=PATH install' instead)
+
+4. you can remove the program binaries and object files from the source code
+   directory by typing `make clean'
+
+
+* Developer
+
+Joachim Henke <j-o@users.sourceforge.net>
+
+
+* Copying
+
+All source code in this package is released under the terms of the BSD license.
+See the file LICENSE for copying permission.
+
+
+* See also
+
+Please visit the basE91 home page [http://base91.sourceforge.net/] for the
+latest version and pre-compiled binaries.
diff --git a/base91-c/base91.1 b/base91-c/base91.1
new file mode 100644 (file)
index 0000000..3f31ded
--- /dev/null
@@ -0,0 +1,57 @@
+.TH BASE91 1 "November 2006" "base91 0.6.0" basE91
+.SH NAME
+base91, b91enc, b91dec \- basE91 encode/decode data
+.SH SYNOPSIS
+.B base91
+.RI [ OPTION "]... [" FILE ]
+.SH DESCRIPTION
+Convert binary data in FILE to plain ASCII text (or vice versa), writing to
+standard output. With no FILE, or when FILE is \-, read standard input.
+.TP
+.BR \-d ", " \-\-decode
+decode data (default for
+.BR b91dec );
+all non\-alphabet characters (such as newlines) are ignored
+.TP
+.BI "\-m " SIZE
+use maximum SIZE bytes of main memory for buffers (default 64K);
+SIZE may be followed by a multiplicative suffix:
+.I K
+1024,
+.I M
+1024*1024
+.TP
+.BR \-o ", " \-\-output =\fIFILE\fR
+write result to FILE instead of standard output
+.TP
+.BR \-v ", " \-\-verbose
+run in verbose mode and write some statistics to standard error;
+use it twice to increase verbosity
+.TP
+.BR \-w ", " \-\-wrap =\fICOLS\fR
+wrap encoded lines after COLS characters (default 76);
+use 0 to disable line wrapping (default for
+.BR b91enc )
+.TP
+.B \-\-help
+prints out the available program options
+.TP
+.B \-\-version
+output version information and exit
+.PP
+basE91 is an advanced method for encoding binary data as ASCII characters. It
+is similar to UUencode or base64, but is more efficient. The overhead produced
+by basE91 depends on the input data. It amounts at most to 23% (versus 33% for
+base64) and can range down to 14%, which typically occurs on 0\-byte blocks.
+This makes basE91 very useful for transferring larger files over binary
+insecure connections like e\-mail or terminal lines.
+.SH AUTHOR
+Written by Joachim Henke.
+.SH "REPORTING BUGS"
+Report bugs to <j\-o@users.sourceforge.net>.
+.SH COPYRIGHT
+Copyright (c) 2000\-2006 Joachim Henke
+.SH "SEE ALSO"
+base64(1), uuencode(1)
+
+http://base91.sourceforge.net/
diff --git a/base91-c/base91.c b/base91-c/base91.c
new file mode 100644 (file)
index 0000000..d1877e9
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * basE91 encoding/decoding routines
+ *
+ * Copyright (c) 2000-2006 Joachim Henke
+ * 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 Joachim Henke nor the names of his 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 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.
+ */
+
+#include "base91.h"
+
+const unsigned char enctab[91] = {
+       'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+       'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+       'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+       'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$',
+       '%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=',
+       '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"'
+};
+const unsigned char dectab[256] = {
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+       91, 62, 90, 63, 64, 65, 66, 91, 67, 68, 69, 70, 71, 91, 72, 73,
+       52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 74, 75, 76, 77, 78, 79,
+       80,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+       15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 81, 91, 82, 83, 84,
+       85, 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, 86, 87, 88, 89, 91,
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91
+};
+
+void basE91_init(struct basE91 *b)
+{
+       b->queue = 0;
+       b->nbits = 0;
+       b->val = -1;
+}
+
+size_t basE91_encode(struct basE91 *b, const void *i, size_t len, void *o)
+{
+       const unsigned char *ib = i;
+       unsigned char *ob = o;
+       size_t n = 0;
+
+       while (len--) {
+               b->queue |= *ib++ << b->nbits;
+               b->nbits += 8;
+               if (b->nbits > 13) {    /* enough bits in queue */
+                       unsigned int val = b->queue & 8191;
+
+                       if (val > 88) {
+                               b->queue >>= 13;
+                               b->nbits -= 13;
+                       } else {        /* we can take 14 bits */
+                               val = b->queue & 16383;
+                               b->queue >>= 14;
+                               b->nbits -= 14;
+                       }
+                       ob[n++] = enctab[val % 91];
+                       ob[n++] = enctab[val / 91];
+               }
+       }
+
+       return n;
+}
+
+/* process remaining bits from bit queue; write up to 2 bytes */
+
+size_t basE91_encode_end(struct basE91 *b, void *o)
+{
+       unsigned char *ob = o;
+       size_t n = 0;
+
+       if (b->nbits) {
+               ob[n++] = enctab[b->queue % 91];
+               if (b->nbits > 7 || b->queue > 90)
+                       ob[n++] = enctab[b->queue / 91];
+       }
+       b->queue = 0;
+       b->nbits = 0;
+       b->val = -1;
+
+       return n;
+}
+
+/* return maximum length that an input of length i could encode to
+ * (this is a maximum, not a precise figure, because the actual
+ * size depends on the precise data */
+
+size_t basE91_encode_maxlen(size_t i /* must be < SIZE_T_MAX/8 */)
+{
+       size_t bits = i*8;
+       size_t pairs = bits / 13;
+       size_t leftover = bits % 13;
+       return 2*pairs + (leftover==0 ? 0 : leftover<=6 ? 1 : 2);
+}
+
+size_t basE91_decode(struct basE91 *b, const void *i, size_t len, void *o)
+{
+       const unsigned char *ib = i;
+       unsigned char *ob = o;
+       size_t n = 0;
+       unsigned int d;
+
+       while (len--) {
+               d = dectab[*ib++];
+               if (d == 91)
+                       continue;       /* ignore non-alphabet chars */
+               if (b->val == -1)
+                       b->val = d;     /* start next value */
+               else {
+                       b->val += d * 91;
+                       b->queue |= b->val << b->nbits;
+                       b->nbits += (b->val & 8191) > 88 ? 13 : 14;
+                       do {
+                               ob[n++] = b->queue;
+                               b->queue >>= 8;
+                               b->nbits -= 8;
+                       } while (b->nbits > 7);
+                       b->val = -1;    /* mark value complete */
+               }
+       }
+
+       return n;
+}
+
+/* process remaining bits; write at most 1 byte */
+
+size_t basE91_decode_end(struct basE91 *b, void *o)
+{
+       unsigned char *ob = o;
+       size_t n = 0;
+
+       if (b->val != -1)
+               ob[n++] = b->queue | b->val << b->nbits;
+       b->queue = 0;
+       b->nbits = 0;
+       b->val = -1;
+
+       return n;
+}
+
+/* return maximum length that an input of length i could decode to
+ * (this is a maximum, not a precise figure, because the actual
+ * size depends on the precise data */
+
+size_t basE91_decode_maxlen(size_t i /* must be < SIZE_T_MAX/7 */)
+{
+       size_t pairs = i / 2;
+       size_t bits = pairs * 14;
+       size_t bytes = bits / 8;
+       size_t leftover = i % 2;
+       return bytes + !!leftover;
+}
diff --git a/base91-c/base91.h b/base91-c/base91.h
new file mode 100644 (file)
index 0000000..b298017
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2000-2006 Joachim Henke
+ *
+ * For conditions of distribution and use, see copyright notice in base91.c
+ */
+
+#ifndef BASE91_H
+#define BASE91_H 1
+
+#include <stddef.h>
+
+struct basE91 {
+       unsigned long queue;
+       unsigned int nbits;
+       int val;
+};
+
+void basE91_init(struct basE91 *);
+
+size_t basE91_encode(struct basE91 *, const void *, size_t, void *);
+
+size_t basE91_encode_end(struct basE91 *, void *);
+
+size_t basE91_encode_maxlen(size_t /* must be < SIZE_T_MAX/8 */);
+
+size_t basE91_decode(struct basE91 *, const void *, size_t, void *);
+
+size_t basE91_decode_end(struct basE91 *, void *);
+
+size_t basE91_decode_maxlen(size_t /* must be < SIZE_T_MAX/7 */);
+
+#endif /* base91.h */
diff --git a/base91-c/cli.c b/base91-c/cli.c
new file mode 100644 (file)
index 0000000..7ca7f33
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * basE91 command line front-end
+ *
+ * Copyright (c) 2000-2006 Joachim Henke
+ * 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 Joachim Henke nor the names of his 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 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _WIN32
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+#include <getopt.h>
+#include "base91.h"
+
+#define FLG_D 1
+#define FLG_V 2
+#define FLG_VV 4
+
+static char status[32];
+static const char *progname;
+static char *ibuf, *obuf;
+static size_t ibuf_size, llen;
+static struct basE91 b91;
+
+static void stream_b91enc_p(void)
+{
+       size_t itotal = 0;
+       size_t ototal = 0;
+       size_t s;
+
+       while ((s = fread(ibuf, 1, ibuf_size, stdin)) > 0) {
+               itotal += s;
+               s = basE91_encode(&b91, ibuf, s, obuf);
+               ototal += s;
+               fwrite(obuf, 1, s, stdout);
+       }
+       s = basE91_encode_end(&b91, obuf);      /* empty bit queue */
+       ototal += s;
+       fwrite(obuf, 1, s, stdout);
+
+       sprintf(status, "\t%.2f%%\n", itotal ? (float) ototal / itotal * 100.0 : 1.0);
+}
+
+static void stream_b91enc_w(void)
+{
+       size_t l = llen;
+       size_t ltotal = 0;
+       size_t i, s;
+       char x;
+
+       while ((s = fread(ibuf, 1, ibuf_size, stdin)) > 0) {
+               s = basE91_encode(&b91, ibuf, s, obuf);
+               for (i = 0; l <= s; l += llen) {
+                       x = obuf[l];
+                       obuf[l] = '\0';
+                       puts(obuf + i);
+                       ++ltotal;
+                       obuf[l] = x;
+                       i = l;
+               }
+               fwrite(obuf + i, 1, s - i, stdout);
+               l -= s;
+       }
+       s = basE91_encode_end(&b91, obuf);
+       if (s || l < llen) {
+               obuf[s] = '\0';
+               if (s > l) {
+                       x = obuf[1];
+                       obuf[1] = '\0';
+                       puts(obuf);
+                       ++ltotal;
+                       obuf[0] = x;
+               }
+               puts(obuf);
+               ++ltotal;
+       }
+
+       sprintf(status, "\t%lu lines\n", (unsigned long) ltotal);
+}
+
+static void stream_b91dec(void)
+{
+       size_t s;
+
+       while ((s = fread(ibuf, 1, ibuf_size, stdin)) > 0) {
+               s = basE91_decode(&b91, ibuf, s, obuf);
+               fwrite(obuf, 1, s, stdout);
+       }
+       s = basE91_decode_end(&b91, obuf);      /* empty bit queue */
+       fwrite(obuf, 1, s, stdout);
+
+       sprintf(status, "done\n");
+}
+
+static int init_flags(const char *p)
+{
+       size_t l = strlen(p);
+
+       if (l > 5) {
+               progname = p + l - 6;
+               if (!strcmp(progname, "b91enc"))
+                       return 0;
+               if (!strcmp(progname, "b91dec"))
+                       return FLG_D;
+       }
+       llen = 76;
+       progname = "base91";
+
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       size_t buf_size = 65536;        /* buffer memory defaults to 64 KiB */
+       int flags = init_flags(*argv);
+       const char *ifile = "from standard input";
+       const char *ofile = NULL;
+       int opt;
+       struct option longopts[8] = {
+               {"decode", no_argument, NULL, 'd'},
+               {"output", required_argument, NULL, 'o'},
+               {"verbose", no_argument, NULL, 'v'},
+               {"wrap", required_argument, NULL, 'w'},
+               {"help", no_argument, NULL, 'h'},
+               {"version", no_argument, NULL, 'V'},
+               {NULL, 0, NULL, 0}
+       };
+
+       while ((opt = getopt_long(argc, argv, "dem:o:vw:hV", longopts, NULL)) != -1)
+               switch (opt) {
+               case 'd':
+                       flags |= FLG_D;
+                       break;
+               case 'e':
+                       flags &= ~FLG_D;
+                       break;
+               case 'm':
+                       {
+                               char *t;
+                               long l = strtol(optarg, &t, 0);
+
+                               if (t == optarg || strlen(t) > 1 || l < 0) {
+                                       fprintf(stderr, "invalid SIZE argument: `%s'\n", optarg);
+                                       return EXIT_FAILURE;
+                               }
+                               buf_size = l;
+                               switch (*t | 32) {
+                               case ' ':
+                               case 'b':
+                                       break;
+                               case 'k':
+                                       buf_size <<= 10;
+                                       break;
+                               case 'm':
+                                       buf_size <<= 20;
+                                       break;
+                               default:
+                                       fprintf(stderr, "invalid SIZE suffix: `%s'\n", t);
+                                       return EXIT_FAILURE;
+                               }
+                       }
+                       break;
+               case 'o':
+                       if (strcmp(optarg, "-"))
+                               ofile = optarg;
+                       break;
+               case 'v':
+                       flags |= (flags & FLG_V) ? FLG_VV : FLG_V;
+                       break;
+               case 'w':
+                       {
+                               char *t;
+                               long l = strtol(optarg, &t, 0);
+
+                               if (*t || l < 0) {
+                                       fprintf(stderr, "invalid number of columns: `%s'\n", optarg);
+                                       return EXIT_FAILURE;
+                               }
+                               llen = l;
+                       }
+                       break;
+               case 'h':
+                       printf("Usage: %s [OPTION]... [FILE]\n"
+                               "basE91 encode or decode FILE, or standard input, to standard output.\n", progname);
+                       puts("\n  -d, --decode\t\tdecode data\n"
+                               "  -m SIZE\t\tuse SIZE bytes of memory for buffers (suffixes b, K, M)\n"
+                               "  -o, --output=FILE\twrite to FILE instead of standard output\n"
+                               "  -v, --verbose\t\tverbose mode\n"
+                               "  -w, --wrap=COLS\twrap encoded lines after COLS characters (default 76)\n"
+                               "  --help\t\tdisplay this help and exit\n"
+                               "  --version\t\toutput version information and exit\n\n"
+                               "With no FILE, or when FILE is -, read standard input.");
+                       return EXIT_SUCCESS;
+               case 'V':
+                       printf("%s 0.6.0\nCopyright (c) 2000-2006 Joachim Henke\n", progname);
+                       return EXIT_SUCCESS;
+               default:
+                       fprintf(stderr, "Try `%s --help' for more information.\n", *argv);
+                       return EXIT_FAILURE;
+               }
+
+       if (flags & FLG_D) {
+               ibuf_size = (buf_size - 1) << 3;
+               if (ibuf_size < 15) {
+                       fputs("SIZE must be >= 3 for decoding\n", stderr);
+                       return EXIT_FAILURE;
+               }
+               ibuf_size /= 15;
+       } else {
+               ibuf_size = (buf_size - 2) << 4;
+               if (ibuf_size < 29) {
+                       fputs("SIZE must be >= 4 for encoding\n", stderr);
+                       return EXIT_FAILURE;
+               }
+               ibuf_size /= 29;
+       }
+
+       if (optind < argc && strcmp(argv[optind], "-")) {
+               ifile = argv[optind];
+               if (freopen(ifile, "r", stdin) != stdin) {
+                       perror(ifile);
+                       return EXIT_FAILURE;
+               }
+       }
+       if (ofile)
+               if (freopen(ofile, "w", stdout) != stdout) {
+                       perror(ofile);
+                       return EXIT_FAILURE;
+               }
+
+       if (flags & FLG_VV)
+               fprintf(stderr, "using %lu bytes for buffers; input buffer: %lu bytes\n", (unsigned long) buf_size, (unsigned long) ibuf_size);
+       obuf = malloc(buf_size);
+       if (!obuf) {
+               fputs("failed to allocate buffer memory\n", stderr);
+               return EXIT_FAILURE;
+       }
+
+       basE91_init(&b91);
+#ifdef _WIN32
+       _setmode(_fileno(stdin), _O_BINARY);
+#endif
+
+       if (flags & FLG_D) {
+#ifdef _WIN32
+               _setmode(_fileno(stdout), _O_BINARY);
+#endif
+               ibuf = obuf + 1;        /* create overlapping buffers to use memory efficiently */
+               if (flags & FLG_V)
+                       fprintf(stderr, "decoding %s ...", ifile);
+               stream_b91dec();
+       } else {
+               ibuf = obuf + buf_size - ibuf_size;     /* partial overlap */
+               if (flags & FLG_V)
+                       fprintf(stderr, "encoding %s ...", ifile);
+               if (llen)
+                       stream_b91enc_w();
+               else
+                       stream_b91enc_p();
+       }
+       free(obuf);
+
+       if (flags & FLG_V)
+               fputs(status, stderr);
+
+       return EXIT_SUCCESS;
+}
diff --git a/base91-c/lentest.c b/base91-c/lentest.c
new file mode 100644 (file)
index 0000000..3e43d70
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * basE91 length calculation test
+ *
+ * Copyright (c) 2019 Ian Jackson
+ *
+ * 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 Joachim Henke nor the names of his 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 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.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "base91.h"
+
+static size_t upto = (14*16 + 14 + 16)*2;
+
+static int do_test(int do_do, int fill, const char *what,
+       size_t f(struct basE91 *, const void *, size_t, void *),
+       size_t f_end(struct basE91 *, void *),
+       size_t f_maxlen(size_t)
+                                                                               )
+{
+       struct basE91 b;
+       size_t i, o, exp;
+       int bad = 0;
+       char ibuf[upto];
+       char obuf[upto*2+100]; /* in case we have bugs */
+
+       memset(ibuf,fill,upto);
+
+       if (!do_do) {
+               printf("%s: skipping\n",what);
+               return 0;
+       }
+
+       for (i=0; i<upto; i++) {
+               basE91_init(&b);
+               o = f(&b, ibuf, i, obuf);
+               o += f_end(&b, obuf+o);
+
+               exp = f_maxlen(i);
+               if (o == exp) continue;
+
+               bad = 1;
+               fprintf(stderr,"%s: i=%lu o=%lu expected=%lu\n",
+                                               what, (unsigned long)i, (unsigned long)o, (unsigned long)exp);
+       }
+       return bad;
+}
+
+int main(int argc, const char **argv) {
+       int do_encode=1, do_decode=1, bad=0;
+
+       if (argc>=2) {
+               do_encode = !!strchr(argv[1],'e');
+               do_decode = !!strchr(argv[1],'d');
+       }
+       if (argc>=3) {
+               upto = atoi(argv[2]);
+       }
+
+#define MAYBE_DO_TEST(ed, fill) \
+       (bad |= do_test(do_##ed, (fill), #ed, \
+                                                                       basE91_##ed, basE91_##ed##_end, basE91_##ed##_maxlen))
+       MAYBE_DO_TEST(encode, 0xff);
+       MAYBE_DO_TEST(decode, 'A');
+
+       if (bad) exit(8);
+       printf("ok\n");
+       exit(0);
+}
diff --git a/base91-c/test/Makefile b/base91-c/test/Makefile
new file mode 100644 (file)
index 0000000..2ce31df
--- /dev/null
@@ -0,0 +1,12 @@
+SHELL = /bin/sh
+
+.PHONY: all clean
+
+all:
+       ln -sf ../base91 b91enc
+       ln -sf ../base91 b91dec
+       ln -sf ../lentest .
+       $(SHELL) test.sh
+
+clean:
+       -rm -f b91??c *.b91 *.dat core
diff --git a/base91-c/test/test.sh b/base91-c/test/test.sh
new file mode 100644 (file)
index 0000000..09548f9
--- /dev/null
@@ -0,0 +1,92 @@
+fail_exit()
+{
+       echo 'FAILED!'
+       exit 1
+}
+
+T='--------------------------------------+'
+
+./b91enc --version || fail_exit
+echo -n '
+
+extracting test files...'
+echo '?/9f4iG0)z4jY;<$:B$AvWAAoB:CgALvDCeR+t<vs?JIdPSQ;L5lB3S' | ./b91dec | bunzip2 > bit0.dat && \
+echo '?/9f4iG0)z4jZgv|?AUYRAgA%AAAUA:y5L9LcoHE8HJI_yGIJ>T' | ./b91dec | bunzip2 > bit1.dat && \
+echo '71kS$5F/VZC0+Zw,c%df~>T1XkEvl{weS1NQ@@<EsB7?HV[3U(PhH}H)<LB9_E.4R)1I9W;Ce";rDlheie/
+X*Jh)bS+Jj]iu8V}b61nw32m:"$2@F)s(3LV,n5M?D=NZ9cM,9dV2t_eN#CVg4n@?Zx@FuNG2z|4iLzZvXbx*v_4M
+.ij4Cu=x~WzlpmRKXl3%kWqxx{wo38|Skh/q&;}D#~|Ng3$Gaw>3Gd*`P2p~<hXL_x?V:^0wcZ3x/LpEtP<]gaCm6
+O]D:}u%LONIl2iohu8h6ZXN+3ZS*w"r:8[U?B{?}mt@+<W24O"FA_#^(L$:Tg}#BYwqZc&uT_o,BSG4boOe]ij8fV
+8f/]aBW37mJIfH7uE=l)_y}F*^UA_L9,XPP<jSCN9y4ua|2jhD]E$}>)^4>yP{Y9MFo.Q2=Mmsk:"lhR%87Ey_GRP
+7hn_L73QVn>5CK0{6eHmBBtzP05[`PG3*"+n(bE$S";_b^gU5D87O,QjqW<sS}3&CyTDB~hH,oTOV/My7[vP&mg=W
+sDWjXHh2+q,pmh{AK4U!#n6@VfZFA!]p7E;_gWX_Q#!>3wVc(_T8_:O{8P$9~{n7"SiU;Ca{w_;HB6L&u2W8M>ScI
+aP#E.:novN/#Mk$STQeM^dSp5~JKkb3[t<MeaHgV_*vy=W#0B$3pH6/;q9]C<v=}2IewY6{PigoQ"b[:7+;!k8kv[
+!M@y}I~m%fm&x?S5]CDM&>5_*3q&V/Z7aVdFP<K[R"RkdOfN/.8>J#P&TR3#`W+;&GpWk9CHq%R|fV~(K:g6_Du~m
+j7S?;hnI$xFkGTDRJSJ#NNu_;(]ud#<HI7isnSR@,6ftg8D;$+[j8}mr7lrAijly3cxX7+l3[)@.c7Vk`#kEB,g,P
+>f3{1Q|;jH4l{Ql^mi]*jH>mCqmSW,Bx4^MBrnOtoa4gbM7m3S:OMQ4nt.un+)Zelc,Eoa|c!yN"3dV3b9YY2opW=
+VS&}g7RI#0g?^mT#@#;Q%kmT2gGX8.*H;^Dywb$(o(t;/_#j8Vi*djlobF!Xw)B2lLV_sio/`z1zu^+^m9D~At(<w
+RDd6[tY/gsu(CNd#j*do&Id|iCGCBG$FD4(JUlb"~O8E`."F<SdvvP;Xfn?V:uVP,I(XD|@)5KVNwx2k/`&vyXrnl
+Ots<a+&YHAL[Wyn,Zc*r3&.=8w~+PIs}.bioO`|D,52ku,daF"WA&nT)!"j&!EiNJ{y.%`Ba3R.f,#rF4qH07uekh
+qd"o]wvyXzrC_i#pC9+KWV5Nh},s^3|R0EV4g?#KJvp^V_.w$GAXf0,SU#=Z)ugj4k@T&A$/JVzgkwqJX9lx,YuFf
+1whB6~5[P^DGQN@}nhPX>UH|,6o=j6kT(=Cz8q6(FBOm~&+F*,IgYH;XtV+,ejB".y`K9!2VxGi?YeQs+q9Ses]Qt
+RzDX;Oyx]$Tcm;[GT4Auh}yN+%:}Q_xv5_B[/q30b!qFIn>w{^w`Qa99`=r0<Ew]RDY0qgDvOBKh?sKz=uxOY8Tv{
+c4;c)QQ3*ilu6I?W$}5M[K1UA29,xzD;<;%y*=b$X8[Z^Cb~QPagi;{[sEk(Ttm3.@Gjm%@ii*Cl{&>eo&uZFyq*`
+dg.h"YJiW)t5BaO;?liP7Y|)o|]9KwM`rZO1p#<WohA$KEPZU3rm"!_WNwMg[Yj{9MbgNFkygUc4rb*:k:z;)qV+g
+W]t76>}zYnX|.C(+P.RXy>T0XG^58"DO8Su/5MElg)J>eU,BjzB5}et|m+4>$GdR<(_[PUxc^o|c2L{w~mE@SZ.FH
+;(=m1zJIMyYbz8z0=gf*;)WDk:3@7^YN&i1|MVarqV)qe]x9kJ)OPi&F``3?|P:(Fg$1(P2{xf0yf$WBU!.YmwYIT
+F)^pdagK0(P~/96zGoD,QZlX]"`h/U`^ZWZeJ.c:XQ!WAz(Z|&2/E3)*V?vAb>?MUV5*T8i3u)apx`5<8J`bopXm>
+*J,^8m.Ldfyj~KR#mt*&Z3a]N9WUT+^c2*@07TM:ATg(D)YMiSR[RTll_7DB3.HhUzKL:N0.:%J,<(_$0Ab{``OaN
+43R:29/1$KXzs2%58B]G,h*r$Z/PqQqwh*sY.o(;Kkqw`1N^;JFk!;^?V+6/7irU+h._wN},b?.O.8t#:prfx9ZEE
+t2AWg56N]g6MF0Bs7X^07[RwvgwxT@4RLk#SXzXj74XT93xa=$c0Sgi6z{apd:[5sDmwG;&jKSGGR.=c9qS$u[S:y
+wM|buns:@)g|V@|QGD9wipn#u*HCFK7W}iD}f|E{h@PR@2+m%nN>u+"+>x&3iF+0`2GE03Gg;K`[.E[X4Swjchxf6
+LusH!p1)[}$SyFI"w|fztPfp*<~6Bu&1(.0XH68BX?a9mFcGoT>~qd7qtf)+v.o9(IAL7|jz32K"9d1Nh*1},xK<T
+VZa!xDsn66YUg~,En93Xg9u2c~?o~=,#$e]~KHG&tBM)bOvSN:0GHsZ|u$6523G(6wxlo_eqx!<CzIScE~pC+2;1W
+:G^D7,k+do4Wr+4j4|Q:vj]c?J+Wz:Lyn<+W)8]>nvFBHnD&1fv9+h,+nww+PRv7I2wU)B`nty%~eJ2OvRIa^k@T2
+*Y:8fa@pLgwFy,[Ea$di~YWC]4)j&3=B0nZn())A|p,70Y02g3ArYWjs+U' | ./b91enc -do rnd0.dat && \
+./b91enc --decode -o rnd1.dat rnd0.dat && echo ' OK' || fail_exit
+
+echo "
+basE91 encode files:
+
++-- best case --$T"
+./b91enc -m 5k -vv --output=bit0.b91 bit0.dat
+echo "+-- worst case -$T"
+./b91enc -m 3k -vv --output=bit1.b91 bit1.dat
+echo "+-- random data $T"
+./b91enc -m 96 -vv --output=rnd0.b91 rnd0.dat
+./b91enc -m 72 -vv --output=rnd1.b91 rnd1.dat
+
+echo '
+comparing check sums...'
+cksum rnd?.dat *.b91 | while read C S F
+do
+       echo -n "$F     "
+       case $F in
+               rnd0.dat) V='15559944992141';;
+               rnd1.dat) V='3514104192626';;
+               bit0.b91) V='33531953171198370';;
+               bit1.b91) V='5394938771290552';;
+               rnd0.b91) V='32051515602633';;
+               rnd1.b91) V='2018291165770';;
+       esac
+       test $V = "$C$S" && echo OK || exit 1
+done || fail_exit
+echo "
+basE91 decode and compare:
+
++-- best case --$T"
+fold -w 83 bit0.b91 | ./b91dec -vvm 2K | cmp bit0.dat && echo PASSED || fail_exit
+echo "+-- worst case -$T"
+fold -w 79 bit1.b91 | ./b91dec -vvm 1K | cmp bit1.dat && echo PASSED || fail_exit
+echo "+-- random data $T"
+fold -w 73 rnd0.b91 | ./b91dec -vvm 89 | cmp rnd0.dat && echo PASSED || fail_exit
+fold -w 71 rnd1.b91 | ./b91dec -vvm 73 | cmp rnd1.dat && echo PASSED || fail_exit
+
+echo "
+maxlength tests:"
+./lentest && echo PASSED || fail_exit
+
+echo '
+================
+all tests passed
+================
+'
diff --git a/base91-python/.gitignore b/base91-python/.gitignore
new file mode 100644 (file)
index 0000000..f24cd99
--- /dev/null
@@ -0,0 +1,27 @@
+*.py[co]
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+
+#Translations
+*.mo
+
+#Mr Developer
+.mr.developer.cfg
diff --git a/base91-python/README.md b/base91-python/README.md
new file mode 100644 (file)
index 0000000..580df98
--- /dev/null
@@ -0,0 +1,15 @@
+base91-python
+=============
+
+A python implementation of Base91 as described on http://base91.sourceforge.net/ , licenced under the New BSD License.
+
+Usage
+-----
+
+       import base91
+
+       base91.encode('test')                                           #result: 'fPNKd'
+       base91.encode(b'\xfe\x03\x00W\xa9\xbc')         #result: 'VLv(GdNE'
+
+       base91.decode('8D9Kc)=/2$WzeFui#G9Km+<{VT2u9MZil}[A') # result: 'May a moody baby doom a yam?\n'
+
diff --git a/base91-python/base91/__init__.py b/base91-python/base91/__init__.py
new file mode 100644 (file)
index 0000000..d654131
--- /dev/null
@@ -0,0 +1,94 @@
+# Base91 encode/decode for Python 2 and Python 3
+#
+# Copyright (c) 2012 Adrien Beraud
+# Copyright (c) 2015 Guillaume Jacquenot
+# 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 Adrien Beraud, Wisdom Vibes Pte. Ltd., 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 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.
+#
+import struct
+
+base91_alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+                   'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+                   'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+                   'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+                   '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$',
+                   '%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=',
+                   '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"']
+
+decode_table = dict((v, k) for k, v in enumerate(base91_alphabet))
+
+
+def decode(encoded_str):
+    ''' Decode Base91 string to a bytearray '''
+    v = -1
+    b = 0
+    n = 0
+    out = bytearray()
+    for strletter in encoded_str:
+        if not strletter in decode_table:
+            continue
+        c = decode_table[strletter]
+        if (v < 0):
+            v = c
+        else:
+            v += c * 91
+            b |= v << n
+            n += 13 if (v & 8191) > 88 else 14
+            while True:
+                out += struct.pack('B', b & 255)
+                b >>= 8
+                n -= 8
+                if not n > 7:
+                    break
+            v = -1
+    if v + 1:
+        out += struct.pack('B', (b | v << n) & 255)
+    return out
+
+
+def encode(bindata):
+    ''' Encode a bytearray to a Base91 string '''
+    b = 0
+    n = 0
+    out = ''
+    for count in range(len(bindata)):
+        byte = bindata[count:count + 1]
+        b |= struct.unpack('B', byte)[0] << n
+        n += 8
+        if n > 13:
+            v = b & 8191
+            if v > 88:
+                b >>= 13
+                n -= 13
+            else:
+                v = b & 16383
+                b >>= 14
+                n -= 14
+            out += base91_alphabet[v % 91] + base91_alphabet[v // 91]
+    if n:
+        out += base91_alphabet[b % 91]
+        if n > 7 or b > 90:
+            out += base91_alphabet[b // 91]
+    return out
diff --git a/base91-python/setup.py b/base91-python/setup.py
new file mode 100644 (file)
index 0000000..8fb8d22
--- /dev/null
@@ -0,0 +1,12 @@
+from setuptools import setup
+
+setup(
+    name='base91',
+    version='1.0.1',
+    packages=['base91'],
+    url='https://github.com/SunDwarf/base91-python',
+    license='New BSD',
+    author='',
+    author_email='',
+    description='Base91 encoding in Python'
+)
diff --git a/base91.py b/base91.py
new file mode 120000 (symlink)
index 0000000..f407d44
--- /dev/null
+++ b/base91.py
@@ -0,0 +1 @@
+base91-python/base91/__init__.py
\ No newline at end of file
diff --git a/base91s/Dir.sd.mk b/base91s/Dir.sd.mk
new file mode 100644 (file)
index 0000000..4b8f3fb
--- /dev/null
@@ -0,0 +1,20 @@
+
+&TARGETS += & base91s base91.o
+&OBJECTS += & base91.o cli.o
+
+&CFILES += & base91.c base91.h cli.c
+&CLEAN += $(&CFILES)
+
+&base91.c: &^base91.c.patch
+$(&CFILES): &/%: &~/base91-c/% &/Dir.mk
+       perl -pe <$< >$@.tmp \
+ 'next if m{^\#include}; s/basE91/base91s/g; s/base91\b/base91s/g'
+       patch <$(or $(filter %.patch,$^),/dev/null) $@.tmp
+       mv -f $@.tmp $@
+
+$(&OBJECTS): &base91.h
+
+&:local+global &LDFLAGS &LDLIBS
+
+&base91s: $(&OBJECTS)
+       $(CC) -o$@ $(&LDFLAGS) $^ $(&LDLIBS)
diff --git a/base91s/base91.c.patch b/base91s/base91.c.patch
new file mode 100644 (file)
index 0000000..1078d5a
--- /dev/null
@@ -0,0 +1,19 @@
+diff --git a/base91.c b/base91.c
+index 3d9d7ea..7a5958a 100644
+--- a/base91.c
++++ b/base91.c
+@@ -38,12 +38,12 @@ const unsigned char enctab[91] = {
+       'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$',
+       '%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=',
+-      '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"'
++      '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '-'
+ };
+ const unsigned char dectab[256] = {
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+       91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+-      91, 62, 90, 63, 64, 65, 66, 91, 67, 68, 69, 70, 71, 91, 72, 73,
++      91, 62, 91, 63, 64, 65, 66, 91, 67, 68, 69, 70, 71, 90, 72, 73,
+       52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 74, 75, 76, 77, 78, 79,
+       80,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+       15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 81, 91, 82, 83, 84,
diff --git a/comm-common.c b/comm-common.c
new file mode 100644 (file)
index 0000000..48041cd
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#include "secnet.h"
+#include "comm-common.h"
+
+struct comm_clientinfo *comm_clientinfo_ignore(void *state, dict_t *dict,
+                                              struct cloc cloc)
+{
+    return 0;
+}
+
+void comm_request_notify(void *commst, void *nst, comm_notify_fn *fn)
+{
+    struct commcommon *st=commst;
+    struct comm_notify_entry *n;
+    
+    NEW(n);
+    n->fn=fn;
+    n->state=nst;
+    LIST_INSERT_HEAD(&st->notify, n, entry);
+}
+
+void comm_release_notify(void *commst, void *nst, comm_notify_fn *fn)
+{
+    struct commcommon *st=commst;
+    struct comm_notify_entry *n, *t;
+
+    /* XXX untested */
+    LIST_FOREACH_SAFE(n, &st->notify, entry, t) {
+       if (n->state==nst && n->fn==fn) {
+          LIST_REMOVE(n, entry);
+          free(n);
+       }
+    }
+}
+
+bool_t comm_notify(struct commcommon *cc,
+                  struct buffer_if *buf, const struct comm_addr *ca)
+{
+    struct comm_notify_list *notify = &cc->notify;
+    struct comm_notify_entry *n;
+
+    priomsg_reset(&cc->why_unwanted);
+
+    LIST_FOREACH(n, notify, entry) {
+       if (n->fn(n->state, buf, ca, &cc->why_unwanted)) {
+           return True;
+       }
+    }
+    return False;
+}
+
+void comm_apply(struct commcommon *cc, void *st)
+{
+    assert(cc==st);
+    cc->cl.type=CL_COMM;
+    cc->cl.apply=NULL;
+    cc->cl.interface=&cc->ops;
+    cc->ops.st=cc;
+    cc->ops.request_notify=comm_request_notify;
+    cc->ops.release_notify=comm_release_notify;
+    LIST_INIT(&cc->notify);
+    cc->rbuf=NULL;
+    priomsg_new(&cc->why_unwanted, MAX_NAK_MSG);
+}
diff --git a/comm-common.h b/comm-common.h
new file mode 100644 (file)
index 0000000..45340e4
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#ifndef COMM_COMMON_H
+#define COMM_COMMON_H
+
+#include "secnet.h"
+#include "util.h"
+
+/*----- for all comms -----*/
+
+struct comm_notify_entry {
+    comm_notify_fn *fn;
+    void *state;
+    LIST_ENTRY(comm_notify_entry) entry;
+};
+LIST_HEAD(comm_notify_list, comm_notify_entry) notify;
+
+struct commcommon { /* must be first so that void* is comm_common* */
+    closure_t cl;
+    struct comm_if ops;
+    struct cloc loc;
+    struct comm_notify_list notify;
+    struct buffer_if *rbuf;
+    struct priomsg why_unwanted;
+};
+
+struct comm_clientinfo *comm_clientinfo_ignore(void *state, dict_t*,
+                                              struct cloc cloc);
+void comm_request_notify(void *commst, void *nst, comm_notify_fn *fn);
+void comm_release_notify(void *commst, void *nst, comm_notify_fn *fn);
+
+bool_t comm_notify(struct commcommon*, struct buffer_if *buf,
+                  const struct comm_addr *ca);
+  /* Either: returns True, with message delivered and buffer freed.
+   * Or: False, if no-one wanted it - buffer still allocd'd;
+   *     in that case, cc->why_unwanted has info
+   * Ie, roughly like comm_notify_fn. */
+
+void comm_apply(struct commcommon *cc, void *st);
+
+#define COMM_APPLY(st,cc,prefix,desc,loc)              \
+    NEW(st);                                           \
+    (cc)->loc=loc;                                     \
+    (cc)->cl.description=desc;                         \
+    (cc)->ops.clientinfo=comm_clientinfo_ignore;       \
+    (cc)->ops.sendmsg=prefix##sendmsg;                 \
+    (cc)->ops.addr_to_string=prefix##addr_to_string;   \
+    comm_apply((cc),(st))
+   /* void COMM_APPLY(SOMETHING *st, struct commcommon *FUNCTIONOF(st),
+    *                 prefix, "DESC", struct cloc loc);
+    *   // Expects in scope: prefix##sendmsg, prefix##addr_to_string.
+    */
+
+#define COMM_APPLY_STANDARD(st,cc,desc,args)                           \
+    item_t *item=list_elem(args,0);                                    \
+    if (!item || item->type!=t_dict) {                                 \
+       cfgfatal((cc)->loc,desc,"first argument must be a dictionary\n"); \
+    }                                                                  \
+    dict_t *d=item->data.dict;                                         \
+    (cc)->rbuf=find_cl_if(d,"buffer",CL_BUFFER,True,desc,(cc)->loc)
+    /* void COMM_APPLY_STANDARD(SOMETHING *st, struct commcommon *cc,
+     *                          const char *desc, list_t *args);
+     *   // Declares:
+     *   //    item_t *item = <undefined>;
+     *   //    dict_t *dict = <construction dictionary argument>;
+     */
+
+/*----- for udp-based comms -----*/
+
+#define UDP_MAX_SOCKETS 3 /* 2 ought to do really */
+
+#define MAX_AF MAX_RAW(AF_INET6,AF_INET)
+
+struct udpsock {
+    union iaddr addr;
+    int fd;
+    bool_t experienced[/*0=recv,1=send*/2][MAX_AF+1][/*success?*/2];
+};
+
+struct udpsocks {
+    int n_socks;
+    struct udpsock socks[UDP_MAX_SOCKETS];
+    /* private for udp_socks_* */
+    struct udpcommon *uc; /* link to parent, for cfg, notify list, etc. */
+    struct poll_interest *interest;
+    const char *desc;
+};
+
+struct udpcommon {
+    struct commcommon cc;
+    int port;
+    string_t authbind;
+    bool_t use_proxy;
+    union iaddr proxy;
+};
+
+bool_t udp_make_socket(struct udpcommon *uc, struct udpsock *us,
+                      int failmsgclass);
+  /* Caller should have filled in ->addr.  Fills in us->fd,
+     ->experienced; updates ->addr.  Logs any errors with lg_[v]perror. */
+bool_t udp_import_socket(struct udpcommon *uc, struct udpsock *us,
+                        int failmsgclass, int fd);
+  /* Like udp_make_socket, but caller provides fd.  fd is not closed
+     on error */
+
+void udp_destroy_socket(struct udpcommon *uc, struct udpsock *us);
+  /* Idempotent.  No errors are possible. */
+
+const char *af_name(int af);
+void udp_sock_experienced(struct log_if *lg, struct udpcommon *uc,
+                         struct udpsocks *socks, struct udpsock *us,
+                         const union iaddr *dest, int af /* 0 means any */,
+                         int r, int errnoval);
+
+void udp_socks_register(struct udpcommon *uc, struct udpsocks *socks,
+                       const char *desc);
+void udp_socks_deregister(struct udpcommon *uc, struct udpsocks *socks);
+void udp_socks_childpersist(struct udpcommon *uc, struct udpsocks *socks);
+
+#define UDP_APPLY_STANDARD(st,uc,desc)                                 \
+    (uc)->use_proxy=False;                                             \
+    (uc)->authbind=dict_read_string(d,"authbind",False,"udp",(uc)->cc.loc); \
+    (uc)->port=dict_read_number(d,"port",False,"udp",(uc)->cc.loc,0)
+    /* void UDP_APPLY_STANDARD(SOMETHING *st, struct udpcommon *uc,
+     *                         const char *desc);
+     *   // Expects in scope:  dict_t *d=...;   as from COMM_APPLY_STANDARD
+     */
+
+#endif /*COMM_COMMON_H*/
diff --git a/common.make.in b/common.make.in
new file mode 100644 (file)
index 0000000..7c073b1
--- /dev/null
@@ -0,0 +1,35 @@
+# common makefile settings for secnet
+#
+# This file is part of secnet.
+# See README for full list of copyright holders.
+#
+# secnet is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# 
+# secnet is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# version 3 along with secnet; if not, see
+# https://www.gnu.org/licenses/gpl.html.
+
+@SET_MAKE@
+
+topbuilddir:=@abs_top_builddir@
+src:=@top_srcdir@
+
+SHELL:=/bin/sh
+CC:=@CC@
+
+CFLAGS:=-Wall @WRITESTRINGS@ @CFLAGS@ -Werror \
+       -W -Wno-unused -Wno-unused-parameter \
+       -Wno-pointer-sign -Wstrict-prototypes -Wmissing-prototypes \
+       -Wmissing-declarations -Wnested-externs -Wredundant-decls \
+       -Wpointer-arith -Wformat=2 -Winit-self \
+       -Wswitch-enum -Wunused-variable -Wunused-function -Wbad-function-cast \
+       -Wno-strict-aliasing -fno-strict-aliasing \
+       -Wno-bool-operation -Wno-stringop-truncation
diff --git a/comprehensive-test b/comprehensive-test
new file mode 100755 (executable)
index 0000000..cf01c7b
--- /dev/null
@@ -0,0 +1,76 @@
+#!/bin/bash
+set -e
+set -o pipefail
+
+oot_rel=oot-rel.tmp~
+oot_abs=$(cd .. && pwd)/oot-comprehensive-test.tmp~
+
+nproc=$(nproc || echo 1)
+mflags=-j$nproc
+
+for arg in "$@"; do
+    case "$arg" in
+       --oot-abs=*) oot_abs=${arg%*=} ;;
+       *) echo >&2 "unknown arg/option $1"; exit 1;;
+    esac
+done
+
+case "${OLD_SECNET_DIR:?must be set, perhaps to the empty string}" in
+    ''|/*)
+        ;;
+    ../*)
+       OLD_SECNET_DIR="${PWD%/*}/${OLD_SECNET_DIR#../}"
+       echo >&2 "x OLD_SECNET_DIR=$OLD_SECNET_DIR"
+       ;;
+    *)
+       echo >&2 "relative non-.. OLD_SECNET_DIR $OLD_SECNET_DIR !";
+       exit 1
+       ;;
+esac
+
+x () { echo >&2 "x $*"; "$@"; }
+
+srcdir=$(pwd)
+
+build_and_test () {
+    cd "$srcdir"
+    x git clean -xdff
+    if [ "x$1" != x. ]; then
+       rm -rf "$1"
+       mkdir "$1"
+    fi
+    x ./autogen.sh
+    x cd "$1"
+    x "$srcdir/configure" CFLAGS='-O0 -g'
+    x make $mflags all check
+    for t in mtest/check stest/check; do
+       x make $mflags clean
+       x make $mflags $t
+    done
+    x make $mflags clean
+    if [ "x$1" != x. ]; then
+        find -type f
+     else
+        git-ls-files -o
+    fi | perl -ne '
+       s{^\./}{};
+       s{^}{/};
+        next if m{^/ct-files$};
+        next if m{^/autom4te\.cache/};
+        next if m{/Makefile$};
+       next if m{\.mk$};
+       next if m{^/common\.make$};
+       next if m{^/(?:config|\.makefiles)\.stamp$};
+       next if m{^/config\.(?:log|status|h)$};
+        warn "clean in '"$1"' missed $_";
+        $bad=1;
+        END { exit $bad; }
+    '
+    cd "$srcdir"
+}
+
+build_and_test .
+build_and_test "$oot_rel"
+build_and_test "$oot_abs"
+
+echo "----- $0 ok -----"
index ede9c49d55eb5c92221525e41d1b9fc936f05477..e3a74aab90fd481b73b0b8455db5eb17603054be 100644 (file)
@@ -1,4 +1,22 @@
 /* conffile.c - process the configuration file */
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 
 /* #define DUMP_PARSE_TREE */
 
@@ -75,7 +93,7 @@ static void dict_iadd(dict_t *dict, atom_t key, list_t *val)
     if (dict_ilookup_primitive(dict, key)) {
        fatal("duplicate key \"%s\" in dictionary",key);
     }
-    e=safe_malloc(sizeof(*e),"dict_add");
+    NEW(e);
     e->next=dict->entries;
     e->key=key;
     e->val=val;
@@ -89,7 +107,7 @@ static dict_t *dict_new(dict_t *parent)
 {
     dict_t *d;
 
-    d=safe_malloc(sizeof(*d),"dict_new");
+    NEW(d);
     d->parent=parent;
     d->search=NULL;
     d->entries=NULL;
@@ -100,7 +118,7 @@ static dict_t *dict_new(dict_t *parent)
 static struct p_node *node_copy(struct p_node *n)
 {
     struct p_node *r;
-    r=safe_malloc(sizeof(*r),"node_copy");
+    NEW(r);
     *r=*n;
     return r;
 }
@@ -250,7 +268,7 @@ static item_t *new_item(enum types type, struct cloc loc)
 {
     item_t *i;
 
-    i=safe_malloc(sizeof(*i),"new_item");
+    NEW(i);
     i->type=type;
     i->loc=loc;
     return i;
@@ -518,7 +536,7 @@ atom_t intern(cstring_t s)
 
     if (!i) {
        /* Did't find it; create a new one */
-       i=safe_malloc(sizeof(*i),"intern: alloc list entry");
+       NEW(i);
        i->a=safe_strdup(s,"intern: alloc string");
        i->next=atoms;
        atoms=i;
@@ -545,7 +563,7 @@ cstring_t *dict_keys(dict_t *dict)
 {
     atom_t *r, *j;
     struct entry *i;
-    r=safe_malloc(sizeof(*r)*(dict->size+1),"dict_keys");
+    NEW_ARY(r,dict->size+1);
     for (i=dict->entries, j=r; i; i=i->next, j++) {
        *j=i->key;
     }
@@ -561,10 +579,10 @@ list_t *list_new(void)
     return NULL;
 }
 
-int32_t list_length(list_t *a)
+int32_t list_length(const list_t *a)
 {
     int32_t l=0;
-    list_t *i;
+    const list_t *i;
     for (i=a; i; i=i->next) { assert(l < INT_MAX); l++; }
     return l;
 }
@@ -577,7 +595,7 @@ static list_t *list_copy(list_t *a)
     l=NULL;
     r=NULL;
     for (i=a; i; i=i->next) {
-       b=safe_malloc(sizeof(*b),"list_copy");
+       NEW(b);
        if (l) l->next=b; else r=b;
        l=b;
        b->item=i->item;
@@ -601,7 +619,7 @@ list_t *list_append(list_t *list, item_t *item)
 {
     list_t *l;
 
-    l=safe_malloc(sizeof(*l),"list_append");
+    NEW(l);
     l->item=item;
     l->next=NULL;
 
@@ -627,7 +645,7 @@ list_t *new_closure(closure_t *cl)
 void add_closure(dict_t *dict, cstring_t name, apply_fn apply)
 {
     closure_t *c;
-    c=safe_malloc(sizeof(*c),"add_closure");
+    NEW(c);
     c->description=name;
     c->type=CL_PURE;
     c->apply=apply;
@@ -637,19 +655,18 @@ void add_closure(dict_t *dict, cstring_t name, apply_fn apply)
 }
 
 void *find_cl_if(dict_t *dict, cstring_t name, uint32_t type,
-                bool_t fail_if_invalid, cstring_t desc, struct cloc loc)
+                bool_t required, cstring_t desc, struct cloc loc)
 {
     item_t *i;
     closure_t *cl;
 
-    i = dict_find_item(dict,name,fail_if_invalid,desc,loc);
+    i = dict_find_item(dict,name,required,desc,loc);
+    if (!i) return NULL;
     if (i->type!=t_closure) {
-       if (!fail_if_invalid) return NULL;
        cfgfatal(loc,desc,"\"%s\" must be a closure\n",name);
     }
     cl=i->data.closure;
     if (cl->type!=type) {
-       if (!fail_if_invalid) return NULL;
        cfgfatal(loc,desc,"\"%s\" is the wrong type of closure\n",name);
     }
     return cl->interface;
@@ -691,6 +708,31 @@ string_t dict_read_string(dict_t *dict, cstring_t key, bool_t required,
     return r;
 }
 
+const char **dict_read_string_array(dict_t *dict, cstring_t key,
+                                   bool_t required, cstring_t desc,
+                                   struct cloc loc, const char *const *def)
+{
+    list_t *l;
+    const char **ra, **rap;
+
+    l=dict_lookup(dict,key);
+    if (!l) {
+       if (!required) return (const char**)def;
+       cfgfatal(loc,desc,"required string list \"%s\" not found\n",key);
+    }
+
+    int32_t ll=list_length(l);
+    NEW_ARY(ra, ll+1);
+    for (rap=ra; l; l=l->next,rap++) {
+       item_t *it=l->item;
+       if (it->type!=t_string)
+           cfgfatal(it->loc,desc,"\"%s\" entry must be a string\n",key);
+       *rap=it->data.string;
+    }
+    *rap=0;
+    return ra;
+}
+
 uint32_t dict_read_number(dict_t *dict, cstring_t key, bool_t required,
                          cstring_t desc, struct cloc loc, uint32_t def)
 {
@@ -724,6 +766,21 @@ bool_t dict_read_bool(dict_t *dict, cstring_t key, bool_t required,
     return r;
 }
 
+dict_t *dict_read_dict(dict_t *dict, cstring_t key, bool_t required,
+                      cstring_t desc, struct cloc loc)
+{
+    item_t *i;
+    dict_t *r;
+
+    i=dict_find_item(dict,key,required,desc,loc);
+    if (!i) return NULL;
+    if (i->type!=t_dict) {
+       cfgfatal(loc,desc,"\"%s\" must be a dictionary\n",key);
+    }
+    r=i->data.dict;
+    return r;
+}
+
 uint32_t string_to_word(cstring_t s, struct cloc loc,
                        struct flagstr *f, cstring_t desc)
 {
index 7228c9e8fa6cd672ea5a3ac480d8328fc161cede..6b0259cdeccb58f07eaa4959ffb9e41b757aa033 100644 (file)
@@ -1,9 +1,28 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 /* the "incl" state is used for picking up the name of an include file */
 %x incl
 
 %option nounput
 %option noinput
 %option never-interactive
+%option noyywrap
 
 %{
 #include <assert.h>
@@ -42,7 +61,7 @@ static struct p_node *leafnode(uint32_t type)
 {
        struct p_node *r;
 
-       r=safe_malloc(sizeof(*r),"leafnode");
+       NEW(r);
        r->type=type;
        r->loc.file=config_file;
        r->loc.line=config_lineno;
@@ -151,4 +170,3 @@ include                     BEGIN(incl);
 
        /* Return all unclaimed single characters to the parser */
 .                      return *yytext;
-
index 744c80795748e643d8cecf361df5c8fb751e3a2d..c4834b1bf757092250d6c8392b824f5c85093826 100644 (file)
@@ -1,3 +1,22 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #ifndef conffile_h
 #define conffile_h
 
index 7837abe651365c12e460f4492db4f39da5f825d6..9f32cfdaf5c6ba53d4f37397449043351b97dc51 100644 (file)
@@ -103,7 +103,7 @@ static struct p_node *node(uint32_t type, struct p_node *l, struct p_node *r)
 {
        struct p_node *rv;
 
-       rv=safe_malloc(sizeof(*rv),"p_node");
+       NEW(rv);
        rv->type=type;
        rv->loc.file=config_file;
        rv->loc.line=config_lineno;
index 44d0ac7d4ba7dcc864248e7805321fb4f9dc2be8..c2127ab011c540f00d2d1399569fc7a6e9e9b11f 100644 (file)
@@ -1,3 +1,22 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #ifndef conffile_internal_h
 #define conffile_internal_h
 
index 74fea8367b3c2bafac0aa093ca10055c5b01a510..a99e83f1ce52faef56789141ec8041ea4957eb63 100644 (file)
@@ -1,4 +1,4 @@
-/* config.h.in.  Generated from configure.in by autoheader.  */
+/* config.h.in.  Generated from configure.ac by autoheader.  */
 
 
 #ifndef _CONFIG_H
@@ -8,35 +8,29 @@
 /* Define if building universal (internal helper macro) */
 #undef AC_APPLE_UNIVERSAL_BUILD
 
-/* Define to 1 if you have the <inttypes.h> header file. */
-#undef HAVE_INTTYPES_H
+/* Define to 1 to use IPv6 support in system and adns */
+#undef CONFIG_IPV6
+
+/* Define to 1 if you have the `fmemopen' function. */
+#undef HAVE_FMEMOPEN
+
+/* Define to 1 if you have the `funopen' function. */
+#undef HAVE_FUNOPEN
 
 /* Define to 1 if you have the `adns' library (-ladns). */
 #undef HAVE_LIBADNS
 
-/* Define to 1 if you have the `fl' library (-lfl). */
-#undef HAVE_LIBFL
-
 /* Define to 1 if you have the `gmp' library (-lgmp). */
 #undef HAVE_LIBGMP
 
 /* Define to 1 if you have the `gmp2' library (-lgmp2). */
 #undef HAVE_LIBGMP2
 
-/* Define to 1 if you have the `nsl' library (-lnsl). */
-#undef HAVE_LIBNSL
-
-/* Define to 1 if you have the `resolv' library (-lresolv). */
-#undef HAVE_LIBRESOLV
-
 /* Define to 1 if you have the `socket' library (-lsocket). */
 #undef HAVE_LIBSOCKET
 
-/* Define to 1 if you have the <linux/if.h> header file. */
-#undef HAVE_LINUX_IF_H
-
-/* Define to 1 if you have the <memory.h> header file. */
-#undef HAVE_MEMORY_H
+/* Define to 1 if you have the <linux/if_tun.h> header file. */
+#undef HAVE_LINUX_IF_TUN_H
 
 /* Define to 1 if you have the <net/if.h> header file. */
 #undef HAVE_NET_IF_H
 /* Define to 1 if you have the <net/route.h> header file. */
 #undef HAVE_NET_ROUTE_H
 
-/* Define to 1 if you have the <stdint.h> header file. */
-#undef HAVE_STDINT_H
-
-/* Define to 1 if you have the <stdlib.h> header file. */
-#undef HAVE_STDLIB_H
-
-/* Define to 1 if you have the <strings.h> header file. */
-#undef HAVE_STRINGS_H
-
-/* Define to 1 if you have the <string.h> header file. */
-#undef HAVE_STRING_H
-
 /* Define to 1 if you have the <stropts.h> header file. */
 #undef HAVE_STROPTS_H
 
 /* Define to 1 if you have the <sys/sockio.h> header file. */
 #undef HAVE_SYS_SOCKIO_H
 
-/* Define to 1 if you have the <sys/stat.h> header file. */
-#undef HAVE_SYS_STAT_H
-
-/* Define to 1 if you have the <sys/types.h> header file. */
-#undef HAVE_SYS_TYPES_H
-
-/* Define to 1 if you have the <unistd.h> header file. */
-#undef HAVE_UNISTD_H
-
 /* Define to the address where bug reports for this package should be sent. */
 #undef PACKAGE_BUGREPORT
 
 /* Define to the version of this package. */
 #undef PACKAGE_VERSION
 
-/* The size of `unsigned char', as computed by sizeof. */
-#undef SIZEOF_UNSIGNED_CHAR
-
-/* The size of `unsigned int', as computed by sizeof. */
-#undef SIZEOF_UNSIGNED_INT
-
-/* The size of `unsigned long', as computed by sizeof. */
-#undef SIZEOF_UNSIGNED_LONG
-
-/* The size of `unsigned long long', as computed by sizeof. */
-#undef SIZEOF_UNSIGNED_LONG_LONG
-
-/* The size of `unsigned short', as computed by sizeof. */
-#undef SIZEOF_UNSIGNED_SHORT
-
-/* Define to 1 if you have the ANSI C header files. */
-#undef STDC_HEADERS
-
 /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
    significant byte first (like Motorola and SPARC, unlike Intel). */
 #if defined AC_APPLE_UNIVERSAL_BUILD
 
 /* These used to be in config.h.bot, but are now in configure.in. */
 
-#ifdef HAVE_INTTYPES_H
-#include <inttypes.h>
-#else
-#ifdef HAVE_STDINT_H
-#include <stdint.h>
-#else
-#if SIZEOF_UNSIGNED_LONG_LONG==8
-typedef unsigned long long uint64_t;
-typedef long long int64_t;
-#elif SIZEOF_UNSIGNED_LONG==8
-typedef unsigned long uint64_t;
-typedef long int64_t;
-#else
-#error I do not know what to use for a uint64_t.
-#endif
-
-/* Give us an unsigned 32-bit data type. */
-#if SIZEOF_UNSIGNED_LONG==4
-typedef unsigned long uint32_t;
-typedef long int32_t;
-#elif SIZEOF_UNSIGNED_INT==4
-typedef unsigned int uint32_t;
-typedef int int32_t;
-#else
-#error I do not know what to use for a uint32_t.
-#endif
-
-/* An unsigned 16-bit data type. */
-#if SIZEOF_UNSIGNED_INT==2
-typedef unsigned int uint16_t;
-typedef int int16_t;
-#elif SIZEOF_UNSIGNED_SHORT==2
-typedef unsigned short uint16_t;
-typedef short int16_t;
-#else
-#error I do not know what to use for a uint16_t.
-#endif
-
-/* An unsigned 8-bit data type */
-#if SIZEOF_UNSIGNED_CHAR==1
-typedef unsigned char uint8_t;
-#else
-#error I do not know what to use for a uint8_t.
-#endif
-#endif
-#endif
-
 #ifdef __GNUC__
 #define NORETURN(_x) void _x __attribute__ ((noreturn))
 #define FORMAT(_a,_b,_c) __attribute__ ((format (_a,_b,_c)))
index 91dcc044a2f7cef84db290838016883b9aee9b8f..152c8ed24a97d24ddf74a60ffa670c0aa7d94133 100755 (executable)
--- a/configure
+++ b/configure
@@ -1,14 +1,12 @@
 #! /bin/sh
-# From configure.in Id: configure.in.
+# From configure.ac Id: configure.in.
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.67 for secnet 0.1.18+.
+# Generated by GNU Autoconf 2.69 for secnet 0.1.18+.
 #
 # Report bugs to <secnet@chiark.greenend.org.uk>.
 #
 #
-# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
-# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software
-# Foundation, Inc.
+# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
 #
 #
 # This configure script is free software; the Free Software Foundation
@@ -92,6 +90,7 @@ fi
 IFS=" ""       $as_nl"
 
 # Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
 case $0 in #((
   *[\\/]* ) as_myself=$0 ;;
   *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
@@ -136,6 +135,31 @@ export LANGUAGE
 # CDPATH.
 (unset CDPATH) >/dev/null 2>&1 && unset CDPATH
 
+# Use a proper internal environment variable to ensure we don't fall
+  # into an infinite loop, continuously re-executing ourselves.
+  if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then
+    _as_can_reexec=no; export _as_can_reexec;
+    # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+  *v*x* | *x*v* ) as_opts=-vx ;;
+  *v* ) as_opts=-v ;;
+  *x* ) as_opts=-x ;;
+  * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+as_fn_exit 255
+  fi
+  # We don't want this to propagate to other subprocesses.
+          { _as_can_reexec=; unset _as_can_reexec;}
 if test "x$CONFIG_SHELL" = x; then
   as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
   emulate sh
@@ -169,12 +193,12 @@ if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
 else
   exitcode=1; echo positional parameters were not saved.
 fi
-test x\$exitcode = x0 || exit 1"
+test x\$exitcode = x0 || exit 1
+test -x / || exit 1"
   as_suggested="  as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
   as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
   eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
-  test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
-test \$(( 1 + 1 )) = 2 || exit 1"
+  test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1"
   if (eval "$as_required") 2>/dev/null; then :
   as_have_required=yes
 else
@@ -214,14 +238,25 @@ IFS=$as_save_IFS
 
 
       if test "x$CONFIG_SHELL" != x; then :
-  # We cannot yet assume a decent shell, so we have to provide a
-       # neutralization value for shells without unset; and this also
-       # works around shells that cannot unset nonexistent variables.
-       BASH_ENV=/dev/null
-       ENV=/dev/null
-       (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
-       export CONFIG_SHELL
-       exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"}
+  export CONFIG_SHELL
+             # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+  *v*x* | *x*v* ) as_opts=-vx ;;
+  *v* ) as_opts=-v ;;
+  *x* ) as_opts=-x ;;
+  * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+exit 255
 fi
 
     if test x$as_have_required = xno; then :
@@ -324,6 +359,14 @@ $as_echo X"$as_dir" |
 
 
 } # as_fn_mkdir_p
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+  test -f "$1" && test -x "$1"
+} # as_fn_executable_p
 # as_fn_append VAR VALUE
 # ----------------------
 # Append the text in VALUE to the end of the definition contained in VAR. Take
@@ -445,6 +488,10 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits
   chmod +x "$as_me.lineno" ||
     { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
 
+  # If we had to re-execute with $CONFIG_SHELL, we're ensured to have
+  # already done that, so ensure we don't try to do so again and fall
+  # in an infinite loop.  This has already happened in practice.
+  _as_can_reexec=no; export _as_can_reexec
   # Don't try to exec as it changes $[0], causing all sort of problems
   # (the dirname of $[0] is not the place where we might find the
   # original and so on.  Autoconf is especially sensitive to this).
@@ -479,16 +526,16 @@ if (echo >conf$$.file) 2>/dev/null; then
     # ... but there are two gotchas:
     # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
     # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
-    # In both cases, we have to default to `cp -p'.
+    # In both cases, we have to default to `cp -pR'.
     ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
-      as_ln_s='cp -p'
+      as_ln_s='cp -pR'
   elif ln conf$$.file conf$$ 2>/dev/null; then
     as_ln_s=ln
   else
-    as_ln_s='cp -p'
+    as_ln_s='cp -pR'
   fi
 else
-  as_ln_s='cp -p'
+  as_ln_s='cp -pR'
 fi
 rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
 rmdir conf$$.dir 2>/dev/null
@@ -500,28 +547,8 @@ else
   as_mkdir_p=false
 fi
 
-if test -x / >/dev/null 2>&1; then
-  as_test_x='test -x'
-else
-  if ls -dL / >/dev/null 2>&1; then
-    as_ls_L_option=L
-  else
-    as_ls_L_option=
-  fi
-  as_test_x='
-    eval sh -c '\''
-      if test -d "$1"; then
-       test -d "$1/.";
-      else
-       case $1 in #(
-       -*)set "./$1";;
-       esac;
-       case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #((
-       ???[sx]*):;;*)false;;esac;fi
-    '\'' sh
-  '
-fi
-as_executable_p=$as_test_x
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
 
 # Sed expression to map a string onto a valid CPP name.
 as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
@@ -559,49 +586,10 @@ PACKAGE_BUGREPORT='secnet@chiark.greenend.org.uk'
 PACKAGE_URL=''
 
 ac_unique_file="secnet.c"
-# Factoring default headers for most tests.
-ac_includes_default="\
-#include <stdio.h>
-#ifdef HAVE_SYS_TYPES_H
-# include <sys/types.h>
-#endif
-#ifdef HAVE_SYS_STAT_H
-# include <sys/stat.h>
-#endif
-#ifdef STDC_HEADERS
-# include <stdlib.h>
-# include <stddef.h>
-#else
-# ifdef HAVE_STDLIB_H
-#  include <stdlib.h>
-# endif
-#endif
-#ifdef HAVE_STRING_H
-# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
-#  include <memory.h>
-# endif
-# include <string.h>
-#endif
-#ifdef HAVE_STRINGS_H
-# include <strings.h>
-#endif
-#ifdef HAVE_INTTYPES_H
-# include <inttypes.h>
-#endif
-#ifdef HAVE_STDINT_H
-# include <stdint.h>
-#endif
-#ifdef HAVE_UNISTD_H
-# include <unistd.h>
-#endif"
-
 ac_subst_vars='LTLIBOBJS
 LIBOBJS
 WRITESTRINGS
-EGREP
-GREP
 CPP
-RM
 INSTALL_DATA
 INSTALL_SCRIPT
 INSTALL_PROGRAM
@@ -633,6 +621,7 @@ infodir
 docdir
 oldincludedir
 includedir
+runstatedir
 localstatedir
 sharedstatedir
 sysconfdir
@@ -651,7 +640,8 @@ PACKAGE_VERSION
 PACKAGE_TARNAME
 PACKAGE_NAME
 PATH_SEPARATOR
-SHELL'
+SHELL
+_SUBDIRMK_MAKEFILES'
 ac_subst_files=''
 ac_user_opts='
 enable_option_checking
@@ -704,6 +694,7 @@ datadir='${datarootdir}'
 sysconfdir='${prefix}/etc'
 sharedstatedir='${prefix}/com'
 localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
 includedir='${prefix}/include'
 oldincludedir='/usr/include'
 docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -956,6 +947,15 @@ do
   | -silent | --silent | --silen | --sile | --sil)
     silent=yes ;;
 
+  -runstatedir | --runstatedir | --runstatedi | --runstated \
+  | --runstate | --runstat | --runsta | --runst | --runs \
+  | --run | --ru | --r)
+    ac_prev=runstatedir ;;
+  -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+  | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+  | --run=* | --ru=* | --r=*)
+    runstatedir=$ac_optarg ;;
+
   -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
     ac_prev=sbindir ;;
   -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1070,7 +1070,7 @@ Try \`$0 --help' for more information"
     $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
     expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
       $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
-    : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}
+    : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
     ;;
 
   esac
@@ -1093,7 +1093,7 @@ fi
 for ac_var in  exec_prefix prefix bindir sbindir libexecdir datarootdir \
                datadir sysconfdir sharedstatedir localstatedir includedir \
                oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
-               libdir localedir mandir
+               libdir localedir mandir runstatedir
 do
   eval ac_val=\$$ac_var
   # Remove trailing slashes.
@@ -1121,8 +1121,6 @@ target=$target_alias
 if test "x$host_alias" != x; then
   if test "x$build_alias" = x; then
     cross_compiling=maybe
-    $as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host.
-    If a cross compiler is detected then cross compile mode will be used" >&2
   elif test "x$build_alias" != "x$host_alias"; then
     cross_compiling=yes
   fi
@@ -1248,6 +1246,7 @@ Fine tuning of the installation directories:
   --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
   --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
   --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       modifiable per-process data [LOCALSTATEDIR/run]
   --libdir=DIR            object code libraries [EPREFIX/lib]
   --includedir=DIR        C header files [PREFIX/include]
   --oldincludedir=DIR     C header files for non-gcc [/usr/include]
@@ -1356,9 +1355,9 @@ test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
 secnet configure 0.1.18+
-generated by GNU Autoconf 2.67
+generated by GNU Autoconf 2.69
 
-Copyright (C) 2010 Free Software Foundation, Inc.
+Copyright (C) 2012 Free Software Foundation, Inc.
 This configure script is free software; the Free Software Foundation
 gives unlimited permission to copy, distribute and modify it.
 _ACEOF
@@ -1402,7 +1401,7 @@ sed 's/^/| /' conftest.$ac_ext >&5
 
        ac_retval=1
 fi
-  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
   as_fn_set_status $ac_retval
 
 } # ac_fn_c_try_compile
@@ -1439,53 +1438,11 @@ sed 's/^/| /' conftest.$ac_ext >&5
 
     ac_retval=1
 fi
-  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
   as_fn_set_status $ac_retval
 
 } # ac_fn_c_try_cpp
 
-# ac_fn_c_try_run LINENO
-# ----------------------
-# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
-# that executables *can* be run.
-ac_fn_c_try_run ()
-{
-  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  if { { ac_try="$ac_link"
-case "(($ac_try" in
-  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
-  *) ac_try_echo=$ac_try;;
-esac
-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
-  (eval "$ac_link") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
-  { { case "(($ac_try" in
-  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
-  *) ac_try_echo=$ac_try;;
-esac
-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
-  (eval "$ac_try") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; }; then :
-  ac_retval=0
-else
-  $as_echo "$as_me: program exited with status $ac_status" >&5
-       $as_echo "$as_me: failed program was:" >&5
-sed 's/^/| /' conftest.$ac_ext >&5
-
-       ac_retval=$ac_status
-fi
-  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
-  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
-  as_fn_set_status $ac_retval
-
-} # ac_fn_c_try_run
-
 # ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
 # -------------------------------------------------------
 # Tests whether HEADER exists, giving a warning if it cannot be compiled using
@@ -1494,10 +1451,10 @@ fi
 ac_fn_c_check_header_mongrel ()
 {
   as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  if eval "test \"\${$3+set}\"" = set; then :
+  if eval \${$3+:} false; then :
   { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
 $as_echo_n "checking for $2... " >&6; }
-if eval "test \"\${$3+set}\"" = set; then :
+if eval \${$3+:} false; then :
   $as_echo_n "(cached) " >&6
 fi
 eval ac_res=\$$3
@@ -1564,7 +1521,7 @@ $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
 esac
   { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
 $as_echo_n "checking for $2... " >&6; }
-if eval "test \"\${$3+set}\"" = set; then :
+if eval \${$3+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   eval "$3=\$ac_header_compiler"
@@ -1573,7 +1530,7 @@ eval ac_res=\$$3
               { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
 $as_echo "$ac_res" >&6; }
 fi
-  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
 
 } # ac_fn_c_check_header_mongrel
 
@@ -1586,7 +1543,7 @@ ac_fn_c_check_header_compile ()
   as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
   { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
 $as_echo_n "checking for $2... " >&6; }
-if eval "test \"\${$3+set}\"" = set; then :
+if eval \${$3+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 eval ac_res=\$$3
               { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
 $as_echo "$ac_res" >&6; }
-  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
 
 } # ac_fn_c_check_header_compile
 
-# ac_fn_c_compute_int LINENO EXPR VAR INCLUDES
-# --------------------------------------------
-# Tries to find the compile-time value of EXPR in a program that includes
-# INCLUDES, setting VAR accordingly. Returns whether the value could be
-# computed
-ac_fn_c_compute_int ()
+# ac_fn_c_try_run LINENO
+# ----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
+# that executables *can* be run.
+ac_fn_c_try_run ()
 {
   as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  if test "$cross_compiling" = yes; then
-    # Depending upon the size, compute the lo and hi bounds.
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$4
-int
-main ()
-{
-static int test_array [1 - 2 * !(($2) >= 0)];
-test_array [0] = 0
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_lo=0 ac_mid=0
-  while :; do
-    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$4
-int
-main ()
-{
-static int test_array [1 - 2 * !(($2) <= $ac_mid)];
-test_array [0] = 0
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_hi=$ac_mid; break
-else
-  as_fn_arith $ac_mid + 1 && ac_lo=$as_val
-                       if test $ac_lo -le $ac_mid; then
-                         ac_lo= ac_hi=
-                         break
-                       fi
-                       as_fn_arith 2 '*' $ac_mid + 1 && ac_mid=$as_val
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-  done
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$4
-int
-main ()
-{
-static int test_array [1 - 2 * !(($2) < 0)];
-test_array [0] = 0
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_hi=-1 ac_mid=-1
-  while :; do
-    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$4
-int
-main ()
-{
-static int test_array [1 - 2 * !(($2) >= $ac_mid)];
-test_array [0] = 0
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_lo=$ac_mid; break
-else
-  as_fn_arith '(' $ac_mid ')' - 1 && ac_hi=$as_val
-                       if test $ac_mid -le $ac_hi; then
-                         ac_lo= ac_hi=
-                         break
-                       fi
-                       as_fn_arith 2 '*' $ac_mid && ac_mid=$as_val
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-  done
-else
-  ac_lo= ac_hi=
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-# Binary search between lo and hi bounds.
-while test "x$ac_lo" != "x$ac_hi"; do
-  as_fn_arith '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo && ac_mid=$as_val
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$4
-int
-main ()
-{
-static int test_array [1 - 2 * !(($2) <= $ac_mid)];
-test_array [0] = 0
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_hi=$ac_mid
-else
-  as_fn_arith '(' $ac_mid ')' + 1 && ac_lo=$as_val
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-done
-case $ac_lo in #((
-?*) eval "$3=\$ac_lo"; ac_retval=0 ;;
-'') ac_retval=1 ;;
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
 esac
-  else
-    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$4
-static long int longval () { return $2; }
-static unsigned long int ulongval () { return $2; }
-#include <stdio.h>
-#include <stdlib.h>
-int
-main ()
-{
-
-  FILE *f = fopen ("conftest.val", "w");
-  if (! f)
-    return 1;
-  if (($2) < 0)
-    {
-      long int i = longval ();
-      if (i != ($2))
-       return 1;
-      fprintf (f, "%ld", i);
-    }
-  else
-    {
-      unsigned long int i = ulongval ();
-      if (i != ($2))
-       return 1;
-      fprintf (f, "%lu", i);
-    }
-  /* Do not output a trailing newline, as this causes \r\n confusion
-     on some platforms.  */
-  return ferror (f) || fclose (f) != 0;
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_run "$LINENO"; then :
-  echo >>conftest.val; read $3 <conftest.val; ac_retval=0
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then :
+  ac_retval=0
 else
-  ac_retval=1
-fi
-rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
-  conftest.$ac_objext conftest.beam conftest.$ac_ext
-rm -f conftest.val
+  $as_echo "$as_me: program exited with status $ac_status" >&5
+       $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
 
-  fi
-  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+       ac_retval=$ac_status
+fi
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
   as_fn_set_status $ac_retval
 
-} # ac_fn_c_compute_int
+} # ac_fn_c_try_run
 
 # ac_fn_c_try_link LINENO
 # -----------------------
@@ -1813,7 +1634,7 @@ $as_echo "$ac_try_echo"; } >&5
         test ! -s conftest.err
        } && test -s conftest$ac_exeext && {
         test "$cross_compiling" = yes ||
-        $as_test_x conftest$ac_exeext
+        test -x conftest$ac_exeext
        }; then :
   ac_retval=0
 else
   # interfere with the next link command; also delete a directory that is
   # left behind by Apple's compiler.  We do this before executing the actions.
   rm -rf conftest.dSYM conftest_ipa8_conftest.oo
-  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
   as_fn_set_status $ac_retval
 
 } # ac_fn_c_try_link
+
+# ac_fn_c_check_func LINENO FUNC VAR
+# ----------------------------------
+# Tests whether FUNC exists, setting the cache variable VAR accordingly
+ac_fn_c_check_func ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
+   For example, HP-UX 11i <limits.h> declares gettimeofday.  */
+#define $2 innocuous_$2
+
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char $2 (); below.
+    Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+    <limits.h> exists even on freestanding compilers.  */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $2
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $2 ();
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined __stub_$2 || defined __stub___$2
+choke me
+#endif
+
+int
+main ()
+{
+return $2 ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_func
+
+# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES
+# ---------------------------------------------
+# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR
+# accordingly.
+ac_fn_c_check_decl ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  as_decl_name=`echo $2|sed 's/ *(.*//'`
+  as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'`
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5
+$as_echo_n "checking whether $as_decl_name is declared... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+#ifndef $as_decl_name
+#ifdef __cplusplus
+  (void) $as_decl_use;
+#else
+  (void) $as_decl_name;
+#endif
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_decl
 cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
 It was created by secnet $as_me 0.1.18+, which was
-generated by GNU Autoconf 2.67.  Invocation command line was
+generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
 
@@ -2094,7 +2028,7 @@ $as_echo "$as_me: loading site script $ac_site_file" >&6;}
       || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "failed to load site script $ac_site_file
-See \`config.log' for more details" "$LINENO" 5 ; }
+See \`config.log' for more details" "$LINENO" 5; }
   fi
 done
 
@@ -2188,6 +2122,39 @@ ac_config_headers="$ac_config_headers config.h"
 
 
 
+  ac_config_files="$ac_config_files main.mk:main.mk.tmp Dir.mk:Dir.mk.tmp Final.mk:Final.mk.tmp"
+
+
+
+  _SUBDIRMK_MAKEFILES="$_SUBDIRMK_MAKEFILES subdirmk/regen.mk"
+  ac_config_files="$ac_config_files subdirmk/regen.mk:subdirmk/regen.mk.in"
+
+
+  _SUBDIRMK_MAKEFILES="$_SUBDIRMK_MAKEFILES subdirmk/usual.mk"
+  ac_config_files="$ac_config_files subdirmk/usual.mk:subdirmk/usual.mk.in"
+
+
+
+
+
+  subdirmk_subdirs="$subdirmk_subdirs 'test-example/'"
+  ac_config_files="$ac_config_files test-example/Dir.mk:test-example/Dir.mk.tmp"
+
+
+  subdirmk_subdirs="$subdirmk_subdirs 'mtest/'"
+  ac_config_files="$ac_config_files mtest/Dir.mk:mtest/Dir.mk.tmp"
+
+
+  subdirmk_subdirs="$subdirmk_subdirs 'stest/'"
+  ac_config_files="$ac_config_files stest/Dir.mk:stest/Dir.mk.tmp"
+
+
+  subdirmk_subdirs="$subdirmk_subdirs 'base91s/'"
+  ac_config_files="$ac_config_files base91s/Dir.mk:base91s/Dir.mk.tmp"
+
+
+
+
 
 
 ac_ext=c
@@ -2202,7 +2169,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 set dummy fink; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if test "${ac_cv_path_FINK+set}" = set; then :
+if ${ac_cv_path_FINK+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   case $FINK in
@@ -2216,7 +2183,7 @@ do
   IFS=$as_save_IFS
   test -z "$as_dir" && as_dir=.
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
     ac_cv_path_FINK="$as_dir/$ac_word$ac_exec_ext"
     $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
     break 2
@@ -2244,11 +2211,22 @@ if test "x$FINK" != x; then
   LDFLAGS="-L$finkdir/lib ${LDFLAGS}"
 fi
 
+# This is quite unpleasant.  It turns out that most header checking
+# macros call AC_INCLUDES_DEFAULT.  By default AC_INCLUDES_DEFAULT
+# implies AC_HEADER_STDC and a bunch of conditional includes.  But
+# these header checks are obsolete as the documentation for
+# AC_HEADER_STDC says.  Instead, define AC_INCLUDES_DEFAULT ourselves.
+# The list of headers below is the list from `(autoconf) Default
+# Includes' (filtered by hand for the modern ones rather than the
+# fallbacks).  We must include $1 because AC_INCLUDES_DEFAULT is
+# called with an argument giving the check-specific haders.
+
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
 $as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
 set x ${MAKE-make}
 ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
-if eval "test \"\${ac_cv_prog_make_${ac_make}_set+set}\"" = set; then :
+if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   cat >conftest.make <<\_ACEOF
@@ -2285,7 +2263,7 @@ if test -n "$ac_tool_prefix"; then
 set dummy ${ac_tool_prefix}gcc; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if test "${ac_cv_prog_CC+set}" = set; then :
+if ${ac_cv_prog_CC+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$CC"; then
@@ -2297,7 +2275,7 @@ do
   IFS=$as_save_IFS
   test -z "$as_dir" && as_dir=.
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
     ac_cv_prog_CC="${ac_tool_prefix}gcc"
     $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
     break 2
@@ -2325,7 +2303,7 @@ if test -z "$ac_cv_prog_CC"; then
 set dummy gcc; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if test "${ac_cv_prog_ac_ct_CC+set}" = set; then :
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$ac_ct_CC"; then
@@ -2337,7 +2315,7 @@ do
   IFS=$as_save_IFS
   test -z "$as_dir" && as_dir=.
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
     ac_cv_prog_ac_ct_CC="gcc"
     $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
     break 2
@@ -2378,7 +2356,7 @@ if test -z "$CC"; then
 set dummy ${ac_tool_prefix}cc; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if test "${ac_cv_prog_CC+set}" = set; then :
+if ${ac_cv_prog_CC+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$CC"; then
@@ -2390,7 +2368,7 @@ do
   IFS=$as_save_IFS
   test -z "$as_dir" && as_dir=.
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
     ac_cv_prog_CC="${ac_tool_prefix}cc"
     $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
     break 2
@@ -2418,7 +2396,7 @@ if test -z "$CC"; then
 set dummy cc; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if test "${ac_cv_prog_CC+set}" = set; then :
+if ${ac_cv_prog_CC+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$CC"; then
@@ -2431,7 +2409,7 @@ do
   IFS=$as_save_IFS
   test -z "$as_dir" && as_dir=.
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
     if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
        ac_prog_rejected=yes
        continue
@@ -2477,7 +2455,7 @@ if test -z "$CC"; then
 set dummy $ac_tool_prefix$ac_prog; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if test "${ac_cv_prog_CC+set}" = set; then :
+if ${ac_cv_prog_CC+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$CC"; then
@@ -2489,7 +2467,7 @@ do
   IFS=$as_save_IFS
   test -z "$as_dir" && as_dir=.
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
     ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
     $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
     break 2
@@ -2521,7 +2499,7 @@ do
 set dummy $ac_prog; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
 $as_echo_n "checking for $ac_word... " >&6; }
-if test "${ac_cv_prog_ac_ct_CC+set}" = set; then :
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   if test -n "$ac_ct_CC"; then
@@ -2533,7 +2511,7 @@ do
   IFS=$as_save_IFS
   test -z "$as_dir" && as_dir=.
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
     ac_cv_prog_ac_ct_CC="$ac_prog"
     $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
     break 2
@@ -2576,7 +2554,7 @@ fi
 test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "no acceptable C compiler found in \$PATH
-See \`config.log' for more details" "$LINENO" 5 ; }
+See \`config.log' for more details" "$LINENO" 5; }
 
 # Provide some information about the compiler.
 $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
@@ -2691,7 +2669,7 @@ sed 's/^/| /' conftest.$ac_ext >&5
 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error 77 "C compiler cannot create executables
-See \`config.log' for more details" "$LINENO" 5 ; }
+See \`config.log' for more details" "$LINENO" 5; }
 else
   { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 $as_echo "yes" >&6; }
@@ -2734,7 +2712,7 @@ else
   { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "cannot compute suffix of executables: cannot compile and link
-See \`config.log' for more details" "$LINENO" 5 ; }
+See \`config.log' for more details" "$LINENO" 5; }
 fi
 rm -f conftest conftest$ac_cv_exeext
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
@@ -2793,7 +2771,7 @@ $as_echo "$ac_try_echo"; } >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "cannot run C compiled programs.
 If you meant to cross compile, use \`--host'.
-See \`config.log' for more details" "$LINENO" 5 ; }
+See \`config.log' for more details" "$LINENO" 5; }
     fi
   fi
 fi
@@ -2804,7 +2782,7 @@ rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
 ac_clean_files=$ac_clean_files_save
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
 $as_echo_n "checking for suffix of object files... " >&6; }
-if test "${ac_cv_objext+set}" = set; then :
+if ${ac_cv_objext+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -2845,7 +2823,7 @@ sed 's/^/| /' conftest.$ac_ext >&5
 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "cannot compute suffix of object files: cannot compile
-See \`config.log' for more details" "$LINENO" 5 ; }
+See \`config.log' for more details" "$LINENO" 5; }
 fi
 rm -f conftest.$ac_cv_objext conftest.$ac_ext
 fi
@@ -2855,7 +2833,7 @@ OBJEXT=$ac_cv_objext
 ac_objext=$OBJEXT
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
 $as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
-if test "${ac_cv_c_compiler_gnu+set}" = set; then :
+if ${ac_cv_c_compiler_gnu+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -2892,7 +2870,7 @@ ac_test_CFLAGS=${CFLAGS+set}
 ac_save_CFLAGS=$CFLAGS
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
 $as_echo_n "checking whether $CC accepts -g... " >&6; }
-if test "${ac_cv_prog_cc_g+set}" = set; then :
+if ${ac_cv_prog_cc_g+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_save_c_werror_flag=$ac_c_werror_flag
@@ -2970,7 +2948,7 @@ else
 fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
 $as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
-if test "${ac_cv_prog_cc_c89+set}" = set; then :
+if ${ac_cv_prog_cc_c89+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_cv_prog_cc_c89=no
@@ -2979,8 +2957,7 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #include <stdarg.h>
 #include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
+struct stat;
 /* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
 struct buf { int x; };
 FILE * (*rcsopen) (struct buf *, struct stat *, int);
@@ -3111,7 +3088,7 @@ ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
 $as_echo_n "checking for a BSD-compatible install... " >&6; }
 if test -z "$INSTALL"; then
-if test "${ac_cv_path_install+set}" = set; then :
+if ${ac_cv_path_install+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
@@ -3131,7 +3108,7 @@ case $as_dir/ in #((
     # by default.
     for ac_prog in ginstall scoinst install; do
       for ac_exec_ext in '' $ac_executable_extensions; do
-       if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then
+       if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then
          if test $ac_prog = install &&
            grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
            # AIX install.  It has an incompatible calling convention.
@@ -3187,46 +3164,6 @@ test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
 
 test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
 
-# Extract the first word of "rm", so it can be a program name with args.
-set dummy rm; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if test "${ac_cv_path_RM+set}" = set; then :
-  $as_echo_n "(cached) " >&6
-else
-  case $RM in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_RM="$RM" # Let the user override the test with a path.
-  ;;
-  *)
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_exec_ext in '' $ac_executable_extensions; do
-  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
-    ac_cv_path_RM="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
-    break 2
-  fi
-done
-  done
-IFS=$as_save_IFS
-
-  ;;
-esac
-fi
-RM=$ac_cv_path_RM
-if test -n "$RM"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RM" >&5
-$as_echo "$RM" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-
 
 ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
@@ -3240,7 +3177,7 @@ if test -n "$CPP" && test -d "$CPP"; then
   CPP=
 fi
 if test -z "$CPP"; then
-  if test "${ac_cv_prog_CPP+set}" = set; then :
+  if ${ac_cv_prog_CPP+:} false; then :
   $as_echo_n "(cached) " >&6
 else
       # Double quotes because CPP needs to be expanded
@@ -3327,321 +3264,59 @@ if ac_fn_c_try_cpp "$LINENO"; then :
 
 else
   # Broken: fails on valid input.
-continue
-fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-
-  # OK, works on sane cases.  Now check whether nonexistent headers
-  # can be detected and how.
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#include <ac_nonexistent.h>
-_ACEOF
-if ac_fn_c_try_cpp "$LINENO"; then :
-  # Broken: success on invalid input.
-continue
-else
-  # Passes both tests.
-ac_preproc_ok=:
-break
-fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-
-done
-# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
-rm -f conftest.i conftest.err conftest.$ac_ext
-if $ac_preproc_ok; then :
-
-else
-  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
-See \`config.log' for more details" "$LINENO" 5 ; }
-fi
-
-ac_ext=c
-ac_cpp='$CPP $CPPFLAGS'
-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
-ac_compiler_gnu=$ac_cv_c_compiler_gnu
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
-$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
-if test "${ac_cv_path_GREP+set}" = set; then :
-  $as_echo_n "(cached) " >&6
-else
-  if test -z "$GREP"; then
-  ac_path_GREP_found=false
-  # Loop through the user's path and test for each of PROGNAME-LIST
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_prog in grep ggrep; do
-    for ac_exec_ext in '' $ac_executable_extensions; do
-      ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
-      { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue
-# Check for GNU ac_path_GREP and select it if it is found.
-  # Check for GNU $ac_path_GREP
-case `"$ac_path_GREP" --version 2>&1` in
-*GNU*)
-  ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
-*)
-  ac_count=0
-  $as_echo_n 0123456789 >"conftest.in"
-  while :
-  do
-    cat "conftest.in" "conftest.in" >"conftest.tmp"
-    mv "conftest.tmp" "conftest.in"
-    cp "conftest.in" "conftest.nl"
-    $as_echo 'GREP' >> "conftest.nl"
-    "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
-    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
-    as_fn_arith $ac_count + 1 && ac_count=$as_val
-    if test $ac_count -gt ${ac_path_GREP_max-0}; then
-      # Best one so far, save it but keep looking for a better one
-      ac_cv_path_GREP="$ac_path_GREP"
-      ac_path_GREP_max=$ac_count
-    fi
-    # 10*(2^10) chars as input seems more than enough
-    test $ac_count -gt 10 && break
-  done
-  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
-esac
-
-      $ac_path_GREP_found && break 3
-    done
-  done
-  done
-IFS=$as_save_IFS
-  if test -z "$ac_cv_path_GREP"; then
-    as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
-  fi
-else
-  ac_cv_path_GREP=$GREP
-fi
-
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
-$as_echo "$ac_cv_path_GREP" >&6; }
- GREP="$ac_cv_path_GREP"
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
-$as_echo_n "checking for egrep... " >&6; }
-if test "${ac_cv_path_EGREP+set}" = set; then :
-  $as_echo_n "(cached) " >&6
-else
-  if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
-   then ac_cv_path_EGREP="$GREP -E"
-   else
-     if test -z "$EGREP"; then
-  ac_path_EGREP_found=false
-  # Loop through the user's path and test for each of PROGNAME-LIST
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_prog in egrep; do
-    for ac_exec_ext in '' $ac_executable_extensions; do
-      ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
-      { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue
-# Check for GNU ac_path_EGREP and select it if it is found.
-  # Check for GNU $ac_path_EGREP
-case `"$ac_path_EGREP" --version 2>&1` in
-*GNU*)
-  ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
-*)
-  ac_count=0
-  $as_echo_n 0123456789 >"conftest.in"
-  while :
-  do
-    cat "conftest.in" "conftest.in" >"conftest.tmp"
-    mv "conftest.tmp" "conftest.in"
-    cp "conftest.in" "conftest.nl"
-    $as_echo 'EGREP' >> "conftest.nl"
-    "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
-    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
-    as_fn_arith $ac_count + 1 && ac_count=$as_val
-    if test $ac_count -gt ${ac_path_EGREP_max-0}; then
-      # Best one so far, save it but keep looking for a better one
-      ac_cv_path_EGREP="$ac_path_EGREP"
-      ac_path_EGREP_max=$ac_count
-    fi
-    # 10*(2^10) chars as input seems more than enough
-    test $ac_count -gt 10 && break
-  done
-  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
-esac
-
-      $ac_path_EGREP_found && break 3
-    done
-  done
-  done
-IFS=$as_save_IFS
-  if test -z "$ac_cv_path_EGREP"; then
-    as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
-  fi
-else
-  ac_cv_path_EGREP=$EGREP
-fi
-
-   fi
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
-$as_echo "$ac_cv_path_EGREP" >&6; }
- EGREP="$ac_cv_path_EGREP"
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
-$as_echo_n "checking for ANSI C header files... " >&6; }
-if test "${ac_cv_header_stdc+set}" = set; then :
-  $as_echo_n "(cached) " >&6
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-#include <float.h>
-
-int
-main ()
-{
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_cv_header_stdc=yes
-else
-  ac_cv_header_stdc=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-
-if test $ac_cv_header_stdc = yes; then
-  # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#include <string.h>
-
-_ACEOF
-if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
-  $EGREP "memchr" >/dev/null 2>&1; then :
-
-else
-  ac_cv_header_stdc=no
-fi
-rm -f conftest*
-
-fi
-
-if test $ac_cv_header_stdc = yes; then
-  # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#include <stdlib.h>
-
-_ACEOF
-if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
-  $EGREP "free" >/dev/null 2>&1; then :
-
-else
-  ac_cv_header_stdc=no
-fi
-rm -f conftest*
-
-fi
-
-if test $ac_cv_header_stdc = yes; then
-  # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
-  if test "$cross_compiling" = yes; then :
-  :
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#include <ctype.h>
-#include <stdlib.h>
-#if ((' ' & 0x0FF) == 0x020)
-# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
-# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
-#else
-# define ISLOWER(c) \
-                  (('a' <= (c) && (c) <= 'i') \
-                    || ('j' <= (c) && (c) <= 'r') \
-                    || ('s' <= (c) && (c) <= 'z'))
-# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
-#endif
-
-#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
-int
-main ()
-{
-  int i;
-  for (i = 0; i < 256; i++)
-    if (XOR (islower (i), ISLOWER (i))
-       || toupper (i) != TOUPPER (i))
-      return 2;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_run "$LINENO"; then :
-
-else
-  ac_cv_header_stdc=no
-fi
-rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
-  conftest.$ac_objext conftest.beam conftest.$ac_ext
-fi
-
-fi
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
-$as_echo "$ac_cv_header_stdc" >&6; }
-if test $ac_cv_header_stdc = yes; then
-
-$as_echo "#define STDC_HEADERS 1" >>confdefs.h
-
-fi
-
-# On IRIX 5.3, sys/types and inttypes.h are conflicting.
-for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
-                 inttypes.h stdint.h unistd.h
-do :
-  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
-ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
-"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  cat >>confdefs.h <<_ACEOF
-#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
-_ACEOF
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
 
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
 fi
+rm -f conftest.err conftest.i conftest.$ac_ext
 
 done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
 
-
-for ac_header in stdint.h inttypes.h
-do :
-  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
-ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  cat >>confdefs.h <<_ACEOF
-#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
-_ACEOF
-
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
 fi
 
-done
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
 
 for ac_header in net/if.h net/route.h
 do :
   as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
-ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "
+          # include <sys/types.h>
+          # include <sys/stat.h>
+          # include <stdlib.h>
+          # include <stddef.h>
+          # include <string.h>
+          # include <inttypes.h>
+          # include <stdint.h>
+          # include <unistd.h>
+
+"
 if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
   cat >>confdefs.h <<_ACEOF
 #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
@@ -3653,8 +3328,18 @@ done
 
 for ac_header in sys/socket.h
 do :
-  ac_fn_c_check_header_mongrel "$LINENO" "sys/socket.h" "ac_cv_header_sys_socket_h" "$ac_includes_default"
-if test "x$ac_cv_header_sys_socket_h" = x""yes; then :
+  ac_fn_c_check_header_mongrel "$LINENO" "sys/socket.h" "ac_cv_header_sys_socket_h" "
+          # include <sys/types.h>
+          # include <sys/stat.h>
+          # include <stdlib.h>
+          # include <stddef.h>
+          # include <string.h>
+          # include <inttypes.h>
+          # include <stdint.h>
+          # include <unistd.h>
+
+"
+if test "x$ac_cv_header_sys_socket_h" = xyes; then :
   cat >>confdefs.h <<_ACEOF
 #define HAVE_SYS_SOCKET_H 1
 _ACEOF
@@ -3663,16 +3348,25 @@ fi
 
 done
 
-for ac_header in linux/if.h
+for ac_header in linux/if_tun.h
 do :
-  ac_fn_c_check_header_compile "$LINENO" "linux/if.h" "ac_cv_header_linux_if_h" "#if HAVE_SYS_SOCKET_H
+  ac_fn_c_check_header_compile "$LINENO" "linux/if_tun.h" "ac_cv_header_linux_if_tun_h" "
+          # include <sys/types.h>
+          # include <sys/stat.h>
+          # include <stdlib.h>
+          # include <stddef.h>
+          # include <string.h>
+          # include <inttypes.h>
+          # include <stdint.h>
+          # include <unistd.h>
+#if HAVE_SYS_SOCKET_H
 # include <sys/socket.h>
 #endif
 
 "
-if test "x$ac_cv_header_linux_if_h" = x""yes; then :
+if test "x$ac_cv_header_linux_if_tun_h" = xyes; then :
   cat >>confdefs.h <<_ACEOF
-#define HAVE_LINUX_IF_H 1
+#define HAVE_LINUX_IF_TUN_H 1
 _ACEOF
 
 fi
@@ -3682,7 +3376,17 @@ done
 for ac_header in stropts.h sys/sockio.h net/if_tun.h
 do :
   as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
-ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "
+          # include <sys/types.h>
+          # include <sys/stat.h>
+          # include <stdlib.h>
+          # include <stddef.h>
+          # include <string.h>
+          # include <inttypes.h>
+          # include <stdint.h>
+          # include <unistd.h>
+
+"
 if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
   cat >>confdefs.h <<_ACEOF
 #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
@@ -3694,7 +3398,7 @@ done
 
  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5
 $as_echo_n "checking whether byte ordering is bigendian... " >&6; }
-if test "${ac_cv_c_bigendian+set}" = set; then :
+if ${ac_cv_c_bigendian+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_cv_c_bigendian=unknown
@@ -3869,7 +3573,17 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
 else
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
-$ac_includes_default
+
+          # include <sys/types.h>
+          # include <sys/stat.h>
+          # include <stdlib.h>
+          # include <stddef.h>
+          # include <string.h>
+          # include <inttypes.h>
+          # include <stdint.h>
+          # include <unistd.h>
+
+
 int
 main ()
 {
@@ -3913,174 +3627,9 @@ $as_echo "#define AC_APPLE_UNIVERSAL_BUILD 1" >>confdefs.h
      ;; #(
    *)
      as_fn_error $? "unknown endianness
- presetting ac_cv_c_bigendian=no (or yes) will help" "$LINENO" 5  ;;
+ presetting ac_cv_c_bigendian=no (or yes) will help" "$LINENO" 5 ;;
  esac
 
-# The cast to long int works around a bug in the HP C Compiler
-# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
-# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
-# This bug is HP SR number 8606223364.
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of unsigned long long" >&5
-$as_echo_n "checking size of unsigned long long... " >&6; }
-if test "${ac_cv_sizeof_unsigned_long_long+set}" = set; then :
-  $as_echo_n "(cached) " >&6
-else
-  if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (unsigned long long))" "ac_cv_sizeof_unsigned_long_long"        "$ac_includes_default"; then :
-
-else
-  if test "$ac_cv_type_unsigned_long_long" = yes; then
-     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error 77 "cannot compute sizeof (unsigned long long)
-See \`config.log' for more details" "$LINENO" 5 ; }
-   else
-     ac_cv_sizeof_unsigned_long_long=0
-   fi
-fi
-
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_unsigned_long_long" >&5
-$as_echo "$ac_cv_sizeof_unsigned_long_long" >&6; }
-
-
-
-cat >>confdefs.h <<_ACEOF
-#define SIZEOF_UNSIGNED_LONG_LONG $ac_cv_sizeof_unsigned_long_long
-_ACEOF
-
-
-# The cast to long int works around a bug in the HP C Compiler
-# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
-# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
-# This bug is HP SR number 8606223364.
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of unsigned long" >&5
-$as_echo_n "checking size of unsigned long... " >&6; }
-if test "${ac_cv_sizeof_unsigned_long+set}" = set; then :
-  $as_echo_n "(cached) " >&6
-else
-  if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (unsigned long))" "ac_cv_sizeof_unsigned_long"        "$ac_includes_default"; then :
-
-else
-  if test "$ac_cv_type_unsigned_long" = yes; then
-     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error 77 "cannot compute sizeof (unsigned long)
-See \`config.log' for more details" "$LINENO" 5 ; }
-   else
-     ac_cv_sizeof_unsigned_long=0
-   fi
-fi
-
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_unsigned_long" >&5
-$as_echo "$ac_cv_sizeof_unsigned_long" >&6; }
-
-
-
-cat >>confdefs.h <<_ACEOF
-#define SIZEOF_UNSIGNED_LONG $ac_cv_sizeof_unsigned_long
-_ACEOF
-
-
-# The cast to long int works around a bug in the HP C Compiler
-# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
-# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
-# This bug is HP SR number 8606223364.
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of unsigned int" >&5
-$as_echo_n "checking size of unsigned int... " >&6; }
-if test "${ac_cv_sizeof_unsigned_int+set}" = set; then :
-  $as_echo_n "(cached) " >&6
-else
-  if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (unsigned int))" "ac_cv_sizeof_unsigned_int"        "$ac_includes_default"; then :
-
-else
-  if test "$ac_cv_type_unsigned_int" = yes; then
-     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error 77 "cannot compute sizeof (unsigned int)
-See \`config.log' for more details" "$LINENO" 5 ; }
-   else
-     ac_cv_sizeof_unsigned_int=0
-   fi
-fi
-
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_unsigned_int" >&5
-$as_echo "$ac_cv_sizeof_unsigned_int" >&6; }
-
-
-
-cat >>confdefs.h <<_ACEOF
-#define SIZEOF_UNSIGNED_INT $ac_cv_sizeof_unsigned_int
-_ACEOF
-
-
-# The cast to long int works around a bug in the HP C Compiler
-# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
-# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
-# This bug is HP SR number 8606223364.
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of unsigned short" >&5
-$as_echo_n "checking size of unsigned short... " >&6; }
-if test "${ac_cv_sizeof_unsigned_short+set}" = set; then :
-  $as_echo_n "(cached) " >&6
-else
-  if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (unsigned short))" "ac_cv_sizeof_unsigned_short"        "$ac_includes_default"; then :
-
-else
-  if test "$ac_cv_type_unsigned_short" = yes; then
-     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error 77 "cannot compute sizeof (unsigned short)
-See \`config.log' for more details" "$LINENO" 5 ; }
-   else
-     ac_cv_sizeof_unsigned_short=0
-   fi
-fi
-
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_unsigned_short" >&5
-$as_echo "$ac_cv_sizeof_unsigned_short" >&6; }
-
-
-
-cat >>confdefs.h <<_ACEOF
-#define SIZEOF_UNSIGNED_SHORT $ac_cv_sizeof_unsigned_short
-_ACEOF
-
-
-# The cast to long int works around a bug in the HP C Compiler
-# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
-# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
-# This bug is HP SR number 8606223364.
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of unsigned char" >&5
-$as_echo_n "checking size of unsigned char... " >&6; }
-if test "${ac_cv_sizeof_unsigned_char+set}" = set; then :
-  $as_echo_n "(cached) " >&6
-else
-  if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (unsigned char))" "ac_cv_sizeof_unsigned_char"        "$ac_includes_default"; then :
-
-else
-  if test "$ac_cv_type_unsigned_char" = yes; then
-     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error 77 "cannot compute sizeof (unsigned char)
-See \`config.log' for more details" "$LINENO" 5 ; }
-   else
-     ac_cv_sizeof_unsigned_char=0
-   fi
-fi
-
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_unsigned_char" >&5
-$as_echo "$ac_cv_sizeof_unsigned_char" >&6; }
-
-
-
-cat >>confdefs.h <<_ACEOF
-#define SIZEOF_UNSIGNED_CHAR $ac_cv_sizeof_unsigned_char
-_ACEOF
-
-
 
     hard=
   if test -z "$hard"; then
@@ -4090,7 +3639,7 @@ _ACEOF
   fi
   { $as_echo "$as_me:${as_lineno-$LINENO}: checking $msg" >&5
 $as_echo_n "checking $msg... " >&6; }
-if test "${ac_cv_prog_cc_no_writeable_strings+set}" = set; then :
+if ${ac_cv_prog_cc_no_writeable_strings+:} false; then :
   $as_echo_n "(cached) " >&6
 else
 
@@ -4190,7 +3739,7 @@ fi
 
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for mpz_init_set_str in -lgmp" >&5
 $as_echo_n "checking for mpz_init_set_str in -lgmp... " >&6; }
-if test "${ac_cv_lib_gmp_mpz_init_set_str+set}" = set; then :
+if ${ac_cv_lib_gmp_mpz_init_set_str+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_check_lib_save_LIBS=$LIBS
@@ -4224,7 +3773,7 @@ LIBS=$ac_check_lib_save_LIBS
 fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gmp_mpz_init_set_str" >&5
 $as_echo "$ac_cv_lib_gmp_mpz_init_set_str" >&6; }
-if test "x$ac_cv_lib_gmp_mpz_init_set_str" = x""yes; then :
+if test "x$ac_cv_lib_gmp_mpz_init_set_str" = xyes; then :
   cat >>confdefs.h <<_ACEOF
 #define HAVE_LIBGMP 1
 _ACEOF
@@ -4235,7 +3784,7 @@ fi
 
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for mpz_init_set_str in -lgmp2" >&5
 $as_echo_n "checking for mpz_init_set_str in -lgmp2... " >&6; }
-if test "${ac_cv_lib_gmp2_mpz_init_set_str+set}" = set; then :
+if ${ac_cv_lib_gmp2_mpz_init_set_str+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_check_lib_save_LIBS=$LIBS
@@ -4269,7 +3818,7 @@ LIBS=$ac_check_lib_save_LIBS
 fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gmp2_mpz_init_set_str" >&5
 $as_echo "$ac_cv_lib_gmp2_mpz_init_set_str" >&6; }
-if test "x$ac_cv_lib_gmp2_mpz_init_set_str" = x""yes; then :
+if test "x$ac_cv_lib_gmp2_mpz_init_set_str" = xyes; then :
   cat >>confdefs.h <<_ACEOF
 #define HAVE_LIBGMP2 1
 _ACEOF
@@ -4280,7 +3829,7 @@ fi
 
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __gmpz_init_set_str in -lgmp" >&5
 $as_echo_n "checking for __gmpz_init_set_str in -lgmp... " >&6; }
-if test "${ac_cv_lib_gmp___gmpz_init_set_str+set}" = set; then :
+if ${ac_cv_lib_gmp___gmpz_init_set_str+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_check_lib_save_LIBS=$LIBS
@@ -4314,7 +3863,7 @@ LIBS=$ac_check_lib_save_LIBS
 fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gmp___gmpz_init_set_str" >&5
 $as_echo "$ac_cv_lib_gmp___gmpz_init_set_str" >&6; }
-if test "x$ac_cv_lib_gmp___gmpz_init_set_str" = x""yes; then :
+if test "x$ac_cv_lib_gmp___gmpz_init_set_str" = xyes; then :
   cat >>confdefs.h <<_ACEOF
 #define HAVE_LIBGMP 1
 _ACEOF
@@ -4323,65 +3872,34 @@ _ACEOF
 
 fi
 
-ac_fn_c_check_header_mongrel "$LINENO" "gmp.h" "ac_cv_header_gmp_h" "$ac_includes_default"
-if test "x$ac_cv_header_gmp_h" = x""yes; then :
+ac_fn_c_check_header_mongrel "$LINENO" "gmp.h" "ac_cv_header_gmp_h" "
+          # include <sys/types.h>
+          # include <sys/stat.h>
+          # include <stdlib.h>
+          # include <stddef.h>
+          # include <string.h>
+          # include <inttypes.h>
+          # include <stdint.h>
+          # include <unistd.h>
+
+"
+if test "x$ac_cv_header_gmp_h" = xyes; then :
 
 else
   as_fn_error $? "gmp.h not found" "$LINENO" 5
 fi
 
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for yywrap in -lfl" >&5
-$as_echo_n "checking for yywrap in -lfl... " >&6; }
-if test "${ac_cv_lib_fl_yywrap+set}" = set; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lfl  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
 
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char yywrap ();
-int
-main ()
-{
-return yywrap ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
-  ac_cv_lib_fl_yywrap=yes
-else
-  ac_cv_lib_fl_yywrap=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_fl_yywrap" >&5
-$as_echo "$ac_cv_lib_fl_yywrap" >&6; }
-if test "x$ac_cv_lib_fl_yywrap" = x""yes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBFL 1
-_ACEOF
 
-  LIBS="-lfl $LIBS"
+ ac_fn_c_check_func "$LINENO" "inet_ntoa" "ac_cv_func_inet_ntoa"
+if test "x$ac_cv_func_inet_ntoa" = xyes; then :
 
-fi
+else
 
-if test "$ac_cv_lib_fl_yywrap" != yes; then
-  as_fn_error $? "A compatible libfl is required" "$LINENO" 5
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for inet_ntoa in -lnsl" >&5
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for inet_ntoa in -lnsl" >&5
 $as_echo_n "checking for inet_ntoa in -lnsl... " >&6; }
-if test "${ac_cv_lib_nsl_inet_ntoa+set}" = set; then :
+if ${ac_cv_lib_nsl_inet_ntoa+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_check_lib_save_LIBS=$LIBS
@@ -4415,18 +3933,26 @@ LIBS=$ac_check_lib_save_LIBS
 fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_inet_ntoa" >&5
 $as_echo "$ac_cv_lib_nsl_inet_ntoa" >&6; }
-if test "x$ac_cv_lib_nsl_inet_ntoa" = x""yes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBNSL 1
-_ACEOF
+if test "x$ac_cv_lib_nsl_inet_ntoa" = xyes; then :
+
+  LIBS="-lnsl $LIBS";
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: inet_ntoa is in libnsl, urgh.  Must use -lnsl." >&5
+$as_echo "$as_me: WARNING: inet_ntoa is in libnsl, urgh.  Must use -lnsl." >&2;}
+
+else
 
-  LIBS="-lnsl $LIBS"
+    as_fn_error $? "cannot find library function inet_ntoa" "$LINENO" 5
 
 fi
 
+
+fi
+
+
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for socket in -lsocket" >&5
 $as_echo_n "checking for socket in -lsocket... " >&6; }
-if test "${ac_cv_lib_socket_socket+set}" = set; then :
+if ${ac_cv_lib_socket_socket+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_check_lib_save_LIBS=$LIBS
@@ -4460,7 +3986,7 @@ LIBS=$ac_check_lib_save_LIBS
 fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_socket" >&5
 $as_echo "$ac_cv_lib_socket_socket" >&6; }
-if test "x$ac_cv_lib_socket_socket" = x""yes; then :
+if test "x$ac_cv_lib_socket_socket" = xyes; then :
   cat >>confdefs.h <<_ACEOF
 #define HAVE_LIBSOCKET 1
 _ACEOF
@@ -4469,9 +3995,16 @@ _ACEOF
 
 fi
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for inet_aton in -lresolv" >&5
+
+
+ ac_fn_c_check_func "$LINENO" "inet_aton" "ac_cv_func_inet_aton"
+if test "x$ac_cv_func_inet_aton" = xyes; then :
+
+else
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for inet_aton in -lresolv" >&5
 $as_echo_n "checking for inet_aton in -lresolv... " >&6; }
-if test "${ac_cv_lib_resolv_inet_aton+set}" = set; then :
+if ${ac_cv_lib_resolv_inet_aton+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_check_lib_save_LIBS=$LIBS
@@ -4505,18 +4038,26 @@ LIBS=$ac_check_lib_save_LIBS
 fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_resolv_inet_aton" >&5
 $as_echo "$ac_cv_lib_resolv_inet_aton" >&6; }
-if test "x$ac_cv_lib_resolv_inet_aton" = x""yes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBRESOLV 1
-_ACEOF
+if test "x$ac_cv_lib_resolv_inet_aton" = xyes; then :
+
+  LIBS="-lresolv $LIBS";
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: inet_aton is in libresolv, urgh.  Must use -lresolv." >&5
+$as_echo "$as_me: WARNING: inet_aton is in libresolv, urgh.  Must use -lresolv." >&2;}
+
+else
+
+    as_fn_error $? "cannot find library function inet_aton" "$LINENO" 5
+
+fi
 
-  LIBS="-lresolv $LIBS"
 
 fi
 
+
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for adns_init in -ladns" >&5
 $as_echo_n "checking for adns_init in -ladns... " >&6; }
-if test "${ac_cv_lib_adns_adns_init+set}" = set; then :
+if ${ac_cv_lib_adns_adns_init+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_check_lib_save_LIBS=$LIBS
@@ -4550,7 +4091,7 @@ LIBS=$ac_check_lib_save_LIBS
 fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_adns_adns_init" >&5
 $as_echo "$ac_cv_lib_adns_adns_init" >&6; }
-if test "x$ac_cv_lib_adns_adns_init" = x""yes; then :
+if test "x$ac_cv_lib_adns_adns_init" = xyes; then :
   cat >>confdefs.h <<_ACEOF
 #define HAVE_LIBADNS 1
 _ACEOF
@@ -4559,8 +4100,18 @@ _ACEOF
 
 fi
 
-ac_fn_c_check_header_mongrel "$LINENO" "adns.h" "ac_cv_header_adns_h" "$ac_includes_default"
-if test "x$ac_cv_header_adns_h" = x""yes; then :
+ac_fn_c_check_header_mongrel "$LINENO" "adns.h" "ac_cv_header_adns_h" "
+          # include <sys/types.h>
+          # include <sys/stat.h>
+          # include <stdlib.h>
+          # include <stddef.h>
+          # include <string.h>
+          # include <inttypes.h>
+          # include <stdint.h>
+          # include <unistd.h>
+
+"
+if test "x$ac_cv_header_adns_h" = xyes; then :
 
 else
   as_fn_error $? "adns.h not found" "$LINENO" 5
@@ -4568,7 +4119,112 @@ fi
 
 
 
-ac_config_files="$ac_config_files Makefile"
+for ac_func in fmemopen funopen
+do :
+  as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
+if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+done
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking required gcc -std flag" >&5
+$as_echo_n "checking required gcc -std flag... " >&6; }
+if ${secnet_cv_gcc_std_flag+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+
+    secnet_cv_gcc_std_flag=""
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+void x(void) { for (int i=0; i<1; i++) { } }
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+
+        old_cflags="$CFLAGS"
+       CFLAGS="$CFLAGS -std=gnu11"
+       cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+void x(void) { for (int i=0; i<1; i++) { } }
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+           secnet_cv_gcc_std_flag=" -std=gnu11"
+
+else
+
+           { $as_echo "$as_me:${as_lineno-$LINENO}: result: failure!" >&5
+$as_echo "failure!" >&6; }
+           as_fn_error 1 "cannot get test program to compile" "$LINENO" 5
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+       CFLAGS="$old_cflags"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $secnet_cv_gcc_std_flag" >&5
+$as_echo "$secnet_cv_gcc_std_flag" >&6; }
+CFLAGS="$CFLAGS$secnet_cv_gcc_std_flag"
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: Checking requirements for IPv6 support..." >&5
+$as_echo "$as_me: Checking requirements for IPv6 support..." >&6;}
+enable_ipv6=true
+
+ac_fn_c_check_decl "$LINENO" "AF_INET6" "ac_cv_have_decl_AF_INET6" "
+          # include <sys/types.h>
+          # include <sys/stat.h>
+          # include <stdlib.h>
+          # include <stddef.h>
+          # include <string.h>
+          # include <inttypes.h>
+          # include <stdint.h>
+          # include <unistd.h>
+#include <netinet/in.h>
+"
+if test "x$ac_cv_have_decl_AF_INET6" = xyes; then :
+
+else
+  enable_ipv6=false
+fi
+
+ac_fn_c_check_func "$LINENO" "adns_addr2text" "ac_cv_func_adns_addr2text"
+if test "x$ac_cv_func_adns_addr2text" = xyes; then :
+
+else
+  enable_ipv6=false
+fi
+
+if $enable_ipv6; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling IPv6 support" >&5
+$as_echo "$as_me: Enabling IPv6 support" >&6;}
+
+$as_echo "#define CONFIG_IPV6 1" >>confdefs.h
+
+else
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Disabling IPv6 support" >&5
+$as_echo "$as_me: WARNING: Disabling IPv6 support" >&2;}
+fi
+
+
+
+  _SUBDIRMK_MAKEFILES="$_SUBDIRMK_MAKEFILES common.make"
+  ac_config_files="$ac_config_files common.make:common.make.in"
+
+
 
 ac_config_commands="$ac_config_commands default"
 
@@ -4636,10 +4292,21 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
      :end' >>confcache
 if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
   if test -w "$cache_file"; then
-    test "x$cache_file" != "x/dev/null" &&
+    if test "x$cache_file" != "x/dev/null"; then
       { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
 $as_echo "$as_me: updating cache $cache_file" >&6;}
-    cat confcache >$cache_file
+      if test ! -f "$cache_file" || test -h "$cache_file"; then
+       cat confcache >"$cache_file"
+      else
+        case $cache_file in #(
+        */* | ?:*)
+         mv -f confcache "$cache_file"$$ &&
+         mv -f "$cache_file"$$ "$cache_file" ;; #(
+        *)
+         mv -f confcache "$cache_file" ;;
+       esac
+      fi
+    fi
   else
     { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
@@ -4672,7 +4339,7 @@ LTLIBOBJS=$ac_ltlibobjs
 
 
 
-: ${CONFIG_STATUS=./config.status}
+: "${CONFIG_STATUS=./config.status}"
 ac_write_fail=0
 ac_clean_files_save=$ac_clean_files
 ac_clean_files="$ac_clean_files $CONFIG_STATUS"
@@ -4773,6 +4440,7 @@ fi
 IFS=" ""       $as_nl"
 
 # Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
 case $0 in #((
   *[\\/]* ) as_myself=$0 ;;
   *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
@@ -4968,16 +4636,16 @@ if (echo >conf$$.file) 2>/dev/null; then
     # ... but there are two gotchas:
     # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
     # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
-    # In both cases, we have to default to `cp -p'.
+    # In both cases, we have to default to `cp -pR'.
     ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
-      as_ln_s='cp -p'
+      as_ln_s='cp -pR'
   elif ln conf$$.file conf$$ 2>/dev/null; then
     as_ln_s=ln
   else
-    as_ln_s='cp -p'
+    as_ln_s='cp -pR'
   fi
 else
-  as_ln_s='cp -p'
+  as_ln_s='cp -pR'
 fi
 rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
 rmdir conf$$.dir 2>/dev/null
@@ -5037,28 +4705,16 @@ else
   as_mkdir_p=false
 fi
 
-if test -x / >/dev/null 2>&1; then
-  as_test_x='test -x'
-else
-  if ls -dL / >/dev/null 2>&1; then
-    as_ls_L_option=L
-  else
-    as_ls_L_option=
-  fi
-  as_test_x='
-    eval sh -c '\''
-      if test -d "$1"; then
-       test -d "$1/.";
-      else
-       case $1 in #(
-       -*)set "./$1";;
-       esac;
-       case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #((
-       ???[sx]*):;;*)false;;esac;fi
-    '\'' sh
-  '
-fi
-as_executable_p=$as_test_x
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+  test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
 
 # Sed expression to map a string onto a valid CPP name.
 as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
@@ -5080,7 +4736,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # values after options handling.
 ac_log="
 This file was extended by secnet $as_me 0.1.18+, which was
-generated by GNU Autoconf 2.67.  Invocation command line was
+generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
   CONFIG_HEADERS  = $CONFIG_HEADERS
@@ -5146,10 +4802,10 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
 secnet config.status 0.1.18+
-configured by $0, generated by GNU Autoconf 2.67,
+configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
-Copyright (C) 2010 Free Software Foundation, Inc.
+Copyright (C) 2012 Free Software Foundation, Inc.
 This config.status script is free software; the Free Software Foundation
 gives unlimited permission to copy, distribute and modify it."
 
@@ -5238,7 +4894,7 @@ fi
 _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 if \$ac_cs_recheck; then
-  set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+  set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
   shift
   \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
   CONFIG_SHELL='$SHELL'
@@ -5259,6 +4915,13 @@ _ASBOX
 
 _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+#
+# INIT-COMMANDS
+#
+
+     '$srcdir'/subdirmk/generate --srcdir='$srcdir' $subdirmk_subdirs
+
+
 _ACEOF
 
 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
@@ -5268,10 +4931,19 @@ for ac_config_target in $ac_config_targets
 do
   case $ac_config_target in
     "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
-    "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+    "main.mk") CONFIG_FILES="$CONFIG_FILES main.mk:main.mk.tmp" ;;
+    "Dir.mk") CONFIG_FILES="$CONFIG_FILES Dir.mk:Dir.mk.tmp" ;;
+    "Final.mk") CONFIG_FILES="$CONFIG_FILES Final.mk:Final.mk.tmp" ;;
+    "subdirmk/regen.mk") CONFIG_FILES="$CONFIG_FILES subdirmk/regen.mk:subdirmk/regen.mk.in" ;;
+    "subdirmk/usual.mk") CONFIG_FILES="$CONFIG_FILES subdirmk/usual.mk:subdirmk/usual.mk.in" ;;
+    "test-example/Dir.mk") CONFIG_FILES="$CONFIG_FILES test-example/Dir.mk:test-example/Dir.mk.tmp" ;;
+    "mtest/Dir.mk") CONFIG_FILES="$CONFIG_FILES mtest/Dir.mk:mtest/Dir.mk.tmp" ;;
+    "stest/Dir.mk") CONFIG_FILES="$CONFIG_FILES stest/Dir.mk:stest/Dir.mk.tmp" ;;
+    "base91s/Dir.mk") CONFIG_FILES="$CONFIG_FILES base91s/Dir.mk:base91s/Dir.mk.tmp" ;;
+    "common.make") CONFIG_FILES="$CONFIG_FILES common.make:common.make.in" ;;
     "default") CONFIG_COMMANDS="$CONFIG_COMMANDS default" ;;
 
-  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5 ;;
+  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
   esac
 done
 
@@ -5294,9 +4966,10 @@ fi
 # after its creation but before its name has been assigned to `$tmp'.
 $debug ||
 {
-  tmp=
+  tmp= ac_tmp=
   trap 'exit_status=$?
-  { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status
+  : "${ac_tmp:=$tmp}"
+  { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
 ' 0
   trap 'as_fn_exit 1' 1 2 13 15
 }
@@ -5304,12 +4977,13 @@ $debug ||
 
 {
   tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
-  test -n "$tmp" && test -d "$tmp"
+  test -d "$tmp"
 }  ||
 {
   tmp=./conf$$-$RANDOM
   (umask 077 && mkdir "$tmp")
 } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
 
 # Set up the scripts for CONFIG_FILES section.
 # No need to generate them if there are no CONFIG_FILES.
@@ -5331,7 +5005,7 @@ else
   ac_cs_awk_cr=$ac_cr
 fi
 
-echo 'BEGIN {' >"$tmp/subs1.awk" &&
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
 _ACEOF
 
 
@@ -5359,7 +5033,7 @@ done
 rm -f conf$$subs.sh
 
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
-cat >>"\$tmp/subs1.awk" <<\\_ACAWK &&
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
 _ACEOF
 sed -n '
 h
@@ -5407,7 +5081,7 @@ t delim
 rm -f conf$$subs.awk
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 _ACAWK
-cat >>"\$tmp/subs1.awk" <<_ACAWK &&
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
   for (key in S) S_is_set[key] = 1
   FS = "\a"
 
@@ -5439,7 +5113,7 @@ if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
   sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
 else
   cat
-fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
   || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
 _ACEOF
 
@@ -5473,7 +5147,7 @@ fi # test -n "$CONFIG_FILES"
 # No need to generate them if there are no CONFIG_HEADERS.
 # This happens for instance with `./config.status Makefile'.
 if test -n "$CONFIG_HEADERS"; then
-cat >"$tmp/defines.awk" <<\_ACAWK ||
+cat >"$ac_tmp/defines.awk" <<\_ACAWK ||
 BEGIN {
 _ACEOF
 
@@ -5485,8 +5159,8 @@ _ACEOF
 # handling of long lines.
 ac_delim='%!_!# '
 for ac_last_try in false false :; do
-  ac_t=`sed -n "/$ac_delim/p" confdefs.h`
-  if test -z "$ac_t"; then
+  ac_tt=`sed -n "/$ac_delim/p" confdefs.h`
+  if test -z "$ac_tt"; then
     break
   elif $ac_last_try; then
     as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5
@@ -5587,7 +5261,7 @@ do
   esac
   case $ac_mode$ac_tag in
   :[FHL]*:*);;
-  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5 ;;
+  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
   :[FH]-) ac_tag=-:-;;
   :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
   esac
@@ -5606,7 +5280,7 @@ do
     for ac_f
     do
       case $ac_f in
-      -) ac_f="$tmp/stdin";;
+      -) ac_f="$ac_tmp/stdin";;
       *) # Look for the file first in the build tree, then in the source tree
         # (if the path is not absolute).  The absolute path cannot be DOS-style,
         # because $ac_f cannot contain `:'.
@@ -5615,7 +5289,7 @@ do
           [\\/$]*) false;;
           *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
           esac ||
-          as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5 ;;
+          as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
       esac
       case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
       as_fn_append ac_file_inputs " '$ac_f'"
@@ -5641,8 +5315,8 @@ $as_echo "$as_me: creating $ac_file" >&6;}
     esac
 
     case $ac_tag in
-    *:-:* | *:-) cat >"$tmp/stdin" \
-      || as_fn_error $? "could not create $ac_file" "$LINENO" 5  ;;
+    *:-:* | *:-) cat >"$ac_tmp/stdin" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
     esac
     ;;
   esac
@@ -5772,21 +5446,22 @@ s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
 s&@INSTALL@&$ac_INSTALL&;t t
 $ac_datarootdir_hack
 "
-eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \
-  || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+  >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
 
 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
-  { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } &&
-  { ac_out=`sed -n '/^[         ]*datarootdir[  ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } &&
+  { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+  { ac_out=`sed -n '/^[         ]*datarootdir[  ]*:*=/p' \
+      "$ac_tmp/out"`; test -z "$ac_out"; } &&
   { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
 which seems to be undefined.  Please make sure it is defined" >&5
 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
 which seems to be undefined.  Please make sure it is defined" >&2;}
 
-  rm -f "$tmp/stdin"
+  rm -f "$ac_tmp/stdin"
   case $ac_file in
-  -) cat "$tmp/out" && rm -f "$tmp/out";;
-  *) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";;
+  -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+  *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
   esac \
   || as_fn_error $? "could not create $ac_file" "$LINENO" 5
  ;;
@@ -5797,20 +5472,20 @@ which seems to be undefined.  Please make sure it is defined" >&2;}
   if test x"$ac_file" != x-; then
     {
       $as_echo "/* $configure_input  */" \
-      && eval '$AWK -f "$tmp/defines.awk"' "$ac_file_inputs"
-    } >"$tmp/config.h" \
+      && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs"
+    } >"$ac_tmp/config.h" \
       || as_fn_error $? "could not create $ac_file" "$LINENO" 5
-    if diff "$ac_file" "$tmp/config.h" >/dev/null 2>&1; then
+    if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then
       { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
 $as_echo "$as_me: $ac_file is unchanged" >&6;}
     else
       rm -f "$ac_file"
-      mv "$tmp/config.h" "$ac_file" \
+      mv "$ac_tmp/config.h" "$ac_file" \
        || as_fn_error $? "could not create $ac_file" "$LINENO" 5
     fi
   else
     $as_echo "/* $configure_input  */" \
-      && eval '$AWK -f "$tmp/defines.awk"' "$ac_file_inputs" \
+      && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \
       || as_fn_error $? "could not create -" "$LINENO" 5
   fi
  ;;
@@ -5822,7 +5497,7 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
 
 
   case $ac_file$ac_mode in
-    "default":C) echo timestamp >stamp-h ;;
+    "default":C) echo timestamp >config.stamp ;;
 
   esac
 done # for ac_tag
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..471a6e7
--- /dev/null
@@ -0,0 +1,164 @@
+dnl Process this file with autoconf to produce a configure script.
+
+dnl This file is part of secnet.
+dnl See README for full list of copyright holders.
+dnl
+dnl secnet is free software; you can redistribute it and/or modify it
+dnl under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 3 of the License, or
+dnl (at your option) any later version.
+dnl 
+dnl secnet is distributed in the hope that it will be useful, but
+dnl WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+dnl General Public License for more details.
+dnl 
+dnl You should have received a copy of the GNU General Public License
+dnl version 3 along with secnet; if not, see
+dnl https://www.gnu.org/licenses/gpl.html.
+
+sinclude(ac_prog_cc_no_writeable_strings.m4)
+
+m4_include(subdirmk/subdirmk.ac)
+
+AC_INIT(secnet,0.1.18+,secnet@chiark.greenend.org.uk)
+AC_CONFIG_SRCDIR(secnet.c)
+AC_CONFIG_HEADER(config.h)
+
+SUBDIRMK_SUBDIRS([test-example mtest stest base91s])
+
+AC_PREREQ(2.50)
+AC_REVISION($Id: configure.in,v 1.4 2002/09/09 22:05:02 steve Exp $)
+
+AC_LANG_C
+
+# If fink is on the path then it is assumed we should use it.
+AC_PATH_PROG([FINK],[fink])
+if test "x$FINK" != x; then
+  finkdir=`echo $FINK|sed 's,/[[^/]]*/[[^/]]*$,,'`
+  CPPFLAGS="-I$finkdir/include ${CPPFLAGS}"
+  LDFLAGS="-L$finkdir/lib ${LDFLAGS}"
+fi
+
+# This is quite unpleasant.  It turns out that most header checking
+# macros call AC_INCLUDES_DEFAULT.  By default AC_INCLUDES_DEFAULT
+# implies AC_HEADER_STDC and a bunch of conditional includes.  But
+# these header checks are obsolete as the documentation for
+# AC_HEADER_STDC says.  Instead, define AC_INCLUDES_DEFAULT ourselves.
+# The list of headers below is the list from `(autoconf) Default
+# Includes' (filtered by hand for the modern ones rather than the
+# fallbacks).  We must include $1 because AC_INCLUDES_DEFAULT is
+# called with an argument giving the check-specific haders.
+m4_define([AC_INCLUDES_DEFAULT],[
+          # include <sys/types.h>
+          # include <sys/stat.h>
+          # include <stdlib.h>
+          # include <stddef.h>
+          # include <string.h>
+          # include <inttypes.h>
+          # include <stdint.h>
+          # include <unistd.h>
+$1
+])
+
+AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_CHECK_HEADERS([net/if.h net/route.h])
+AC_CHECK_HEADERS([sys/socket.h])
+AC_CHECK_HEADERS([linux/if_tun.h], [], [], 
+[#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+])
+AC_CHECK_HEADERS([stropts.h sys/sockio.h net/if_tun.h])
+AC_C_BIGENDIAN
+AC_PROG_CC_NO_WRITEABLE_STRINGS(WRITESTRINGS)
+
+AC_ARG_ENABLE(hacky-parallel,
+  [AS_HELP_STRING([--enable-hacky-parallel],
+                  [parallelise slow cryptography (default is no)])], [
+  case "$enableval" in
+  n|0|no) ;;
+  y|1|yes) CFLAGS="$CFLAGS -DHACKY_PARALLEL" ;;
+  *) ;;
+  esac
+])
+
+AC_DEFUN([REQUIRE_HEADER],[AC_CHECK_HEADER($1,,[AC_MSG_ERROR($1 not found)])])
+
+dnl the order in which libraries is checked is important
+dnl eg. adns on Solaris 2.5.1 depends on -lnsl and -lsocket
+AC_CHECK_LIB(gmp,mpz_init_set_str)
+AC_CHECK_LIB(gmp2,mpz_init_set_str)
+AC_CHECK_LIB(gmp,__gmpz_init_set_str)
+REQUIRE_HEADER([gmp.h])
+dnl Would love to barf if no gmp was found, but how to test? Requiring the header will do for now.
+SECNET_C_GETFUNC(inet_ntoa,nsl)
+AC_CHECK_LIB(socket,socket)
+SECNET_C_GETFUNC(inet_aton,resolv)
+AC_CHECK_LIB(adns,adns_init)
+REQUIRE_HEADER([adns.h])
+
+AC_CHECK_FUNCS([fmemopen funopen])
+
+dnl gcc 4.9.2 (jessie) requires -std=gnu11 to cope with for (int i=...
+dnl but we do not want to pass that everywhere because we don't want
+dnl to nail down the C dialect this way.  Why oh why oh why.
+m4_define([for_gcc_std],[
+void x(void) { for (int i=0; i<1; i++) { } }
+])
+AC_CACHE_CHECK([required gcc -std flag],[secnet_cv_gcc_std_flag],[
+    secnet_cv_gcc_std_flag=""
+    AC_COMPILE_IFELSE([AC_LANG_SOURCE(for_gcc_std)],[],[
+        old_cflags="$CFLAGS"
+       CFLAGS="$CFLAGS -std=gnu11"
+       AC_COMPILE_IFELSE([AC_LANG_SOURCE(for_gcc_std)],[
+           secnet_cv_gcc_std_flag=" -std=gnu11"
+       ],[
+           AC_MSG_RESULT([failure!])
+           AC_MSG_ERROR([cannot get test program to compile],1)
+       ])
+       CFLAGS="$old_cflags"
+    ])
+])
+CFLAGS="$CFLAGS$secnet_cv_gcc_std_flag"
+
+AC_MSG_NOTICE([Checking requirements for IPv6 support...])
+enable_ipv6=true
+m4_define(NO_IPV6,[enable_ipv6=false])
+AC_CHECK_DECL(AF_INET6,        [],[NO_IPV6],[#include <netinet/in.h>])
+AC_CHECK_FUNC(adns_addr2text,  [],[NO_IPV6])
+if $enable_ipv6; then
+    AC_MSG_NOTICE([Enabling IPv6 support])
+    AC_DEFINE(CONFIG_IPV6, 1,
+              [Define to 1 to use IPv6 support in system and adns])
+else
+    AC_MSG_WARN([Disabling IPv6 support])
+fi
+
+SUBDIRMK_MAKEFILES(common.make)
+
+AC_OUTPUT(,
+          echo timestamp >config.stamp)
+
+AH_TOP([
+#ifndef _CONFIG_H
+#define _CONFIG_H
+])
+
+AH_BOTTOM([
+/* -*- c -*- */
+
+/* These used to be in config.h.bot, but are now in configure.in. */
+
+#ifdef __GNUC__
+#define NORETURN(_x) void _x __attribute__ ((noreturn))
+#define FORMAT(_a,_b,_c) __attribute__ ((format (_a,_b,_c)))
+#else
+#define NORETURN(_x) _x
+#define FORMAT(_a,_b,_c)
+#endif
+
+#endif /* _CONFIG_H */
+])
diff --git a/configure.in b/configure.in
deleted file mode 100644 (file)
index 747d901..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-dnl Process this file with autoconf to produce a configure script.
-
-sinclude(ac_prog_cc_no_writeable_strings.m4)
-
-AC_INIT(secnet,0.1.18+,secnet@chiark.greenend.org.uk)
-AC_CONFIG_SRCDIR(secnet.c)
-AC_CONFIG_HEADER(config.h)
-
-AC_PREREQ(2.50)
-AC_REVISION($Id: configure.in,v 1.4 2002/09/09 22:05:02 steve Exp $)
-
-AC_LANG_C
-
-# If fink is on the path then it is assumed we should use it.
-AC_PATH_PROG([FINK],[fink])
-if test "x$FINK" != x; then
-  finkdir=`echo $FINK|sed 's,/[[^/]]*/[[^/]]*$,,'`
-  CPPFLAGS="-I$finkdir/include ${CPPFLAGS}"
-  LDFLAGS="-L$finkdir/lib ${LDFLAGS}"
-fi
-
-AC_PROG_MAKE_SET
-AC_PROG_CC
-AC_PROG_INSTALL
-AC_PATH_PROG(RM,rm)
-AC_STDC_HEADERS
-AC_CHECK_HEADERS([stdint.h inttypes.h])
-AC_CHECK_HEADERS([net/if.h net/route.h])
-AC_CHECK_HEADERS([sys/socket.h])
-AC_CHECK_HEADERS([linux/if.h], [], [], 
-[#if HAVE_SYS_SOCKET_H
-# include <sys/socket.h>
-#endif
-])
-AC_CHECK_HEADERS([stropts.h sys/sockio.h net/if_tun.h])
-AC_C_BIGENDIAN
-AC_CHECK_SIZEOF(unsigned long long)
-AC_CHECK_SIZEOF(unsigned long)
-AC_CHECK_SIZEOF(unsigned int)
-AC_CHECK_SIZEOF(unsigned short)
-AC_CHECK_SIZEOF(unsigned char)
-AC_PROG_CC_NO_WRITEABLE_STRINGS(WRITESTRINGS)
-
-AC_ARG_ENABLE(hacky-parallel,
-  [AS_HELP_STRING([--enable-hacky-parallel],
-                  [parallelise slow cryptography (default is no)])], [
-  case "$enableval" in
-  n|0|no) ;;
-  y|1|yes) CFLAGS="$CFLAGS -DHACKY_PARALLEL" ;;
-  *) ;;
-  esac
-])
-
-AC_DEFUN([REQUIRE_HEADER],[AC_CHECK_HEADER($1,,[AC_MSG_ERROR($1 not found)])])
-
-dnl the order in which libraries is checked is important
-dnl eg. adns on Solaris 2.5.1 depends on -lnsl and -lsocket
-AC_CHECK_LIB(gmp,mpz_init_set_str)
-AC_CHECK_LIB(gmp2,mpz_init_set_str)
-AC_CHECK_LIB(gmp,__gmpz_init_set_str)
-REQUIRE_HEADER([gmp.h])
-dnl Would love to barf if no gmp was found, but how to test? Requiring the header will do for now.
-AC_CHECK_LIB(fl,yywrap)
-if test "$ac_cv_lib_fl_yywrap" != yes; then
-  AC_MSG_ERROR([A compatible libfl is required])
-fi
-AC_CHECK_LIB(nsl,inet_ntoa)
-AC_CHECK_LIB(socket,socket)
-AC_CHECK_LIB(resolv,inet_aton)
-AC_CHECK_LIB(adns,adns_init)
-REQUIRE_HEADER([adns.h])
-
-AC_OUTPUT(Makefile,echo timestamp >stamp-h)
-
-AH_TOP([
-#ifndef _CONFIG_H
-#define _CONFIG_H
-])
-
-AH_BOTTOM([
-/* -*- c -*- */
-
-/* These used to be in config.h.bot, but are now in configure.in. */
-
-#ifdef HAVE_INTTYPES_H
-#include <inttypes.h>
-#else
-#ifdef HAVE_STDINT_H
-#include <stdint.h>
-#else
-#if SIZEOF_UNSIGNED_LONG_LONG==8
-typedef unsigned long long uint64_t;
-typedef long long int64_t;
-#elif SIZEOF_UNSIGNED_LONG==8
-typedef unsigned long uint64_t;
-typedef long int64_t;
-#else
-#error I do not know what to use for a uint64_t.
-#endif
-
-/* Give us an unsigned 32-bit data type. */
-#if SIZEOF_UNSIGNED_LONG==4
-typedef unsigned long uint32_t;
-typedef long int32_t;
-#elif SIZEOF_UNSIGNED_INT==4
-typedef unsigned int uint32_t;
-typedef int int32_t;
-#else
-#error I do not know what to use for a uint32_t.
-#endif
-
-/* An unsigned 16-bit data type. */
-#if SIZEOF_UNSIGNED_INT==2
-typedef unsigned int uint16_t;
-typedef int int16_t;
-#elif SIZEOF_UNSIGNED_SHORT==2
-typedef unsigned short uint16_t;
-typedef short int16_t;
-#else
-#error I do not know what to use for a uint16_t.
-#endif
-
-/* An unsigned 8-bit data type */
-#if SIZEOF_UNSIGNED_CHAR==1
-typedef unsigned char uint8_t;
-#else
-#error I do not know what to use for a uint8_t.
-#endif
-#endif
-#endif
-
-#ifdef __GNUC__
-#define NORETURN(_x) void _x __attribute__ ((noreturn))
-#define FORMAT(_a,_b,_c) __attribute__ ((format (_a,_b,_c)))
-#else
-#define NORETURN(_x) _x
-#define FORMAT(_a,_b,_c)
-#endif
-
-#endif /* _CONFIG_H */
-])
index 9f164bbfb52d2ef4be4160653c96213743aed89a..9ee83bd098eb5e5d36057234a58a02147826d411 100644 (file)
@@ -1,3 +1,467 @@
+secnet (0.6.1~) unstable; urgency=medium
+
+  * 
+
+ --
+
+secnet (0.6.0) unstable; urgency=medium
+
+  Bugfixes:
+  * mobile sites: Do not ever expire peer addresses.  In practice
+    this fixes transitions between IPv6-only and IPv4-only networks.
+  * make-secnet-sites: Tainted: Fix a lot of bad return values
+    (which would result in assertions rather than nice error messages).
+  * Fix hash algo confusion in mixed sha1/md5 configurations (see below).
+
+  Incompatible changes:
+  * site: Always advertise all capabilities, even in MSG1.  This is
+    incompatible with secnets earlier than 0.3.0 (September 2013), which
+    are all quite badly broken and should have been upgraded long ago.
+  * Drop support for using the same loaded rsa key with multiple different
+    hash algorithms (which was broken in 0.5.0).  Right now we support
+    only `sha1' and `md5' so everyone should be using `sha1'.
+    Installations which specified `md5' anywhere may need config changes.
+
+  Major new featureset (use of which is not adviseable yet):
+  * New facilities for negotiating about the signing keys to use for
+    authentication during key setup, and selecting and using the
+    appropriate keys.  (``key-cache'/`privcache' and `peer-keys').
+    Using these new facilities for keyrollover now is in principle
+    possible but rather complex.  Further machinery is planned;
+    for now, retain your existing config which should keep working.
+    In summary:
+       - secnet: new `privcache' closure;
+       - secnet: `key-cache' and `peer-keys' keys on site closures;
+       - secnet: new file format for peer public keysets;
+       - secnet: new `make-public' config operator;
+       - make-secnet-sites `pub', `pkg', `serial', etc. keywords;
+       - make-secnet-sites --filter, --pubkeys-*, --output-version.
+
+  More minor (logging) improvements:
+  * Make stderr line buffered and log to it by default.
+  * Do not log differently with --nodetach.
+  * New `prefix' option to `logfile' closure.
+  * Tidy and simplify some messages.
+
+  Supporting changes:
+  * Many substantial internal refactorings in secnet.
+  * Many substantial internal refactorings in make-secnet-sites.
+  * make-secnet-sites option parsing totally replaced.
+  * Imported subtrees for base91-c and base91-python.
+  * New portablity code, etc.: osdep.[ch], fmemopen reimplementation.
+  * Explicitly define oddly-rotated dh padding arrangement (write_mpbin).
+
+  Build system and packaging:
+  * Do not fail to build from git when HEAD refers to a packed ref.
+  * Update to subdirmk 0.3.
+  * Many makefile fixes (esp. to clean and cdeps).
+  * configure.ac: Drop or suppress some very obsolete checks.
+  * autogen.sh: Write a comment about need for autoheader.
+  * dir-locals: Provide python-indent-offset too.
+
+  Test suite bugfixes:
+  * stest: Use stderr, not tty, for logging.
+  * stest/udp-preload.c: Fix error handling of sun_prep.
+  * stest: Fix breakage if nproc is not installed.
+
+  Test suite improvements:
+  * New tests, including tests for new features.
+  * Existing tests (especially stest) generally made more thorough.
+  * New comprehensive-test and pretest-to-tested convenience scripts.
+  * Arrangements for testing with (user-provided) old secnet.
+  * parallel-test.*: scripts to help with parallelised bisection.
+  * stest: Print a lot more output about what we are doing.
+  * stest: Better support for cwd with longish pathname.
+  * stest: More flexibility, env var hooks, etc.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 16 Feb 2020 12:48:13 +0000
+
+secnet (0.5.1) unstable; urgency=medium
+
+  POTENTIALLY INCOMPATIBLE CHANGE.  Some security implications.
+
+  * make-secnet-sites: Prefix names when writing secnet sites.conf file.
+
+    make-secnet-sites must copy names (vpn, location and site names) from
+    the input sites file (which is not wholly trusted) to the secnet
+    config file.  Prior to this release, naming a location or site the
+    same as a secnet predefined name could generate a broken sites.conf
+    which secnet would reject.  (With the existing featureset,
+    malfunctions other than rejection, eg privilege escalation, are not
+    possible.)
+
+    make-secnet-sites now adds a prefix to these names when writing
+    sites.conf.  This will not affect configurations which use the
+    make-secnet-sites-provided `all-sites' key, as is usual.  Other
+    configurations will break unless the references in the static part of
+    the config are adjusted.
+
+    Previous behaviour can be restored with the --no-conf-key-prefix
+    option.  (Planned future enhancements to secnet are likely to make use
+    of that option, with untrusted input, dangerously insecure.)
+
+  other changes to make-secnet-sites:
+  * Fix argument parsing.  Fixes a regression affecting -P in 0.5.0,
+    and also fixes new facilities introduced in 0.5.0.
+  * Sort the properties on output (and adjust the test case expected
+    outputs).  Tests now pass on (at least) Python 2.7.13, 3.5.3, 3.7.5.
+  * Delete some unused code.
+
+  secnet:
+  * Change one idiom to avoid a warning from GCC9.  No functional change.
+
+  build system - MAJOR CHANGES:
+  * Fix out-of-tree builds.  (Broken in 0.5.0)
+  * Replace recursive make with use of the new subdirmk system.
+    This represents a fairly comprehensive overhaul of the makefiles.
+    Several bugs (esp. involving dependencies between files in different
+    directories) are fixed.
+  * Drop `make check' from `make all'.  (Otherwise there is no way
+    to ask for `all' without `check'.)
+  * Suppress two unhelpful new compiler warnings from GCC9.
+  * Release checklist update.
+
+  documentation:
+  * Credit Mark Wooding properly in CREDITS.
+  * Include DEVELOPER-CERTIFICATE.
+
+  tests:
+  * Locations now have different names to sites.
+  * Somewhat better debugging output from mtest.
+  * Do not run msgcode-test except with `make fullcheck'.
+  * Other minor bugfixes and improvments.
+  * stest: Suppress unhelpful -Wno-unused-result (needed for stretch).
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Fri, 22 Nov 2019 23:13:14 +0000
+
+secnet (0.5.0) unstable; urgency=medium
+
+  make-secnet-sites SECURITY FIX:
+  * Do not blindly trust inputs; instead, check the syntax for sanity.
+    Previous releases can be induced to run arbitrary code as the user
+    invoking secnet (which might be root), if a secnet sites.conf is used
+    that was generated from an untrustworthy sites file.
+  * The userv invocation mode of make-secnet-sites seems to have been safe
+    in itself, but it previously allowed hazardous data to be propagated
+    into the master sites file.  This is now prevented too.
+
+  make-secnet-sites overhaul work:
+  * make-secnet-sites is now in the common subset of Python2 and Python3.
+    The #! is python3 now, but it works with Python2.7 too.
+    It will probably *not* work with old versions of Python2.
+  * We no longer depend on the obsolete `ipaddr' library.  We use
+    `ipaddress' now.  And this is onlo a Recommends in the .deb.
+  * Ad-hoc argument parser been replaced with `argparse'.
+    There should be no change to existing working invocations.
+  * Bad address syntax error does not wrongly mention IPv6 scopes.
+  * Minor refactoring to support forthcoming work.  [Mark Wooding]
+
+  other bugfixes, improvements and changes to secnet itself:
+  * Better logging of why we are sending NAK messages.
+  * Correctly use the verified copy of the peer remote capabilities
+    from MSG3.  (Bug is not a vulnerability.)    [Mark Wooding]
+  * Significant internal rearrangements and refactorings, to support
+    forthcoming key management work.  [Mark Wooding and Ian Jackson]
+
+  build system etc.:
+  * Completely overhaul release checklist; drop dist target.
+  * Remove dependency on `libfl.a'.  [Mark Wooding]
+  * polypath.c: Fix missing include of <limits.h>.  [Mark Wooding]
+  * Add a Wireshark dissector `secnet-wireshark.lua'.  It is not
+    installed anywhere right now.  [Mark Wooding]
+
+  documentation:
+  * Improve documentation of capability negotiation in NOTES, secnet(8)
+    and magic.h.  [Mark Wooding]
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 24 Oct 2019 19:11:54 +0100
+
+secnet (0.4.5) unstable; urgency=medium
+
+  * INSTALL: Mention that rsa key generation might need ssh-keygen1.
+  * mobile: Fix negotiation bug with mixed old/new secnets and
+    simultaneous key setup attempts by each end.  [Mark Wooding]
+  * Makefile.in: Support installation from a `VPATH' build.  [Mark Wooding]
+  * Portability fixes for clang.  [Mark Wooding]
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 21 Sep 2019 12:04:31 +0100
+
+secnet (0.4.4) unstable; urgency=medium
+
+  Security fix:
+  * make-secnet-sites: Don't allow setting new VPN-level properties
+    when restricted.  This could allow denial of service by
+    users with delegated authorisation.  [Mark Wooding]
+
+  Bugfixes for poor network environments:
+  * polypath: cope properly with asymmetric routing, by correcting
+    the handling of late duplicated packets etc.   Protocol is now
+    incompatible with secnet prior to 0.3.0 when either end is mobile.
+  * Randomise key setup retry time.
+
+  Other bugfixes:
+  * rsa and cbcmac: Fix configuration error messages.  [Mark Wooding]
+  * Handle IPv4 addresses properly (ie, not foolishly byte-swapped),
+    when IPv6 is not available.  [Mark Wooding]
+  * Better logging (and less foolish debug), especially about whether
+    key is set up, and about crossed key setup attempts.
+  * Internal refactoring and fixes.  [Ian Jackson and Mark Wooding]
+
+  Build system and portability:
+  * configure: rerun autogen.sh with autoconf 2.69-10
+  * Avoid memset(0,0,0) wrt st->sharedsecret.  (Fixes compiler warning;
+    in theory might cause miscompilation.)  [Mark Wooding]
+
+  Documentation:
+  * README.make-secnet-sites: new documentation file.  [Mark Wooding]
+  * NOTES: Describe current allocation of capability bits.  [Mark Wooding]
+  * NOTES: tiny fix tot protocol description.
+  * secnet(8): Delete wrong information about dh groups.  [Mark Wooding]
+
+  Administrivia:
+  * Fix erroneous GPL3+ licence notices "version d or later" (!)
+  * .dir-locals.el: Settings for Python code.  [Mark Wooding]
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 08 Sep 2019 22:53:14 +0100
+
+secnet (0.4.3) unstable; urgency=low
+
+  Security improvement:
+  * Use `mpz_powm_sec' for modexps.
+
+  Enhancements:
+  * Implement comm-info and dedicated-interface-addr feature, for
+    benefit of hippotat.
+  * Implement `keepalive' site option, to try to keep link always up.
+
+  Build etc. fixes:
+  * #include <limits.h> (fixes the build on jessie).
+  * Tolerate building from a git checkout, but with git not installed.
+    (This can happen in chroots.)
+  * Turn off -Wsign-compare for bison output.
+  * Makefile.in: Fix `check-ipaddrset' rule to get reference from
+    $(srcdir).  (Makes out-of-tree builds work properly.)
+  * Release checklist fixes.
+  * Burn version numbers 0.4.1 and 0.4.2 due to errors in release prep.
+
+  Bugfixes:
+  * When printing messages about dropping IPv6, do not print anything
+    about ihl.  (Check the IP version field first!)
+  * When turning on debug, turn on verbose too.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 25 Nov 2017 13:36:41 +0000
+
+secnet (0.4.0) unstable; urgency=low
+
+  Debugging improvements:
+  * Packet-level debugging from site notes errors from transmit.
+  * Report when transport peers updated as a result of transmit.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 28 Feb 2015 15:03:00 +0000
+
+secnet (0.4.0~beta2) unstable; urgency=low
+
+  Polypath bugfixes:
+  * Ignore IPv6 Unique Local unicast addresses.
+  * Skip "tentative" IPv6 local addresses.
+  * Improve logging and debug output.
+
+  Portability fix:
+  * Build where size_t is not compatible with int.
+
+  Build system and packaging fixes:
+  * Makefile: support DESTDIR.
+  * debian/rules: set DESTDIR (not prefix).
+  * debian/rules: Support dpkg-buildflags.
+  * Install ipaddrset.py and secnet.8 with correct permissions.
+  * Fix check for <linux/if_tun.h> and git rid of our copy.
+  * Use -lresolv only if inet_aton is not found otherwise.
+  * Use -lnsl only if inet_ntoa is not found otherwise.
+  * debian/rules: Provide build-arch and build-indep targets.
+  * debian/rules: Do not run build for *-indep (!)
+  * Makefile.in: Putative dual (backport and not) release build process doc.
+
+  Copyright updates:
+  * Update to GPLv3.  Add missing copyright notices and credits.
+  * Get rid of old FSF street address; use URL instead.
+  * Remove obsolete LICENCE.txt (which was for snprintf reimplementation).
+  * Remove obsolete references to Cendio (for old ipaddr.py).
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 28 Dec 2014 17:14:10 +0000
+
+secnet (0.4.0~beta1) unstable; urgency=low
+
+  New features:
+  * Support transport over IPv6.  (We do not yet carry IPv6 in the private
+    network.)  IPv6 support depends on IPv6-capable adns (adns 1.5.x).
+  * New polypath comm, which can duplicate packets so as to send them via
+    multiple routes over the public network, for increased
+    reliability/performance (but increased cost).  Currently Linux-only
+    but should be fairly easy to port.
+  * Support multiple public addresses for peers.
+  * Discard previously-received packets (by default).
+
+  Logging improvements:
+  * Report (each first) transmission and reception success and failure.
+  * Log reason for DNS reolution failure.
+  * Log unexpected kinds of death from userv.
+  * Log authbind exit status as errno value (if appropriate).
+
+  Configuration adjustments:
+  * Adjust default number of mobile peer addresses to store when a peer
+    public address is also configured.
+  * Make specifying peer public port optional.  This avoids making special
+    arrangements to bind to a port for in mobile sites with no public
+    stable address.
+
+  Bugfixes:
+  * Hackypar children will die if they get a terminating signal.
+  * Fix signal dispositions inherited by secnet's child processes.
+  * Fix off-by-one error which prevented setting transport-peers-max to 5.
+
+  Test, build and internal improvements:
+  * Use conventional IP address handling library ipaddr.py.
+  * Provide a fuzzer for the slip decoder.
+  * Build system improvements.
+  * Many source code cleanups.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 26 Oct 2014 15:28:31 +0000
+
+secnet (0.3.4) unstable; urgency=low
+
+  SECURITY FIX:
+  * The previous security fix to buffer handling was entirely wrong.  This
+    one is better.  Thanks to Simon Tatham for the report and the patch.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Mon, 22 Sep 2014 16:16:11 +0100
+
+secnet (0.3.3) unstable; urgency=high
+
+  SECURITY FIXES:
+  * Pass correct size argument to recvfrom.  This is a serious security
+    problem which may be exploitable from outside the VPN.
+  * Fix a memory leak in some error logging.
+
+  Other related fixes:
+  * Two other latent bugs in buffer length handling found and fixed.
+  * Non-critical stylistic improvements to buffer length handling, to make
+    the code clearer and to assist audit.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Fri, 19 Sep 2014 23:50:45 +0100
+
+secnet (0.3.3~beta1) unstable; urgency=low
+
+  Installation compatibility fix:
+  * In make-secnet-sites, always use our own ipaddr.py even if the
+    incompatible modern ipaddr.py is installed (eg via python-ipaddr.deb).
+    (Future versions of secnet are going to need that Python module to be
+    installed.)
+
+  For links involving mobile sites:
+  * Use source of NAK packets as hint for peer transport address.
+  * When initiating rekey, make use of data transport peer addresses.
+
+  Build fix:
+  * Provide clean target in test-example/Makefile.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Fri, 19 Sep 2014 00:11:44 +0100
+
+secnet (0.3.2) unstable; urgency=low
+
+  * Release of 0.3.2.  No code changes since 0.3.1~beta1.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 26 Jun 2014 20:27:58 +0100
+
+secnet (0.3.2~beta1) unstable; urgency=low
+
+  For links involving mobile sites:
+  * SECURITY: Properly update peer address array when it is full.
+  * Do name-resolution on peer-initiated key setup too, when we are mobile
+    (and other name-resolution improvements).
+
+  Other minor improvements:
+  * Log peer addresses on key exchange timeout.
+  * When printing version (eg during startup), use value from git-describe
+    and thus include git commit id where applicable.
+  * Updates to release checklist in Makefile.in.
+  * Use C99 _Bool for bool_t.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Fri, 06 Jun 2014 01:17:54 +0100
+
+secnet (0.3.1) unstable; urgency=low
+
+  * Release of 0.3.1.  No code changes since 0.3.1~beta3.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 15 May 2014 01:08:30 +0100
+
+secnet (0.3.1~beta3) unstable; urgency=low
+
+  * Build fixes for non-i386 architectures and gcc 4.8.2.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 08 May 2014 19:53:43 +0100
+
+secnet (0.3.1~beta2) unstable; urgency=low
+
+  Fix relating to new fragmentation / ICMP functionality:
+  * Generate ICMP packets correctly in point-to-point configurations.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 03 May 2014 18:58:09 +0100
+
+secnet (0.3.1~beta1) unstable; urgency=low
+
+  Security fixes (vulnerabilities are to inside attackers only):
+  * SECURITY: Fixes to MTU and fragmentation handling.
+  * SECURITY: Correctly set "unused" ICMP header field.
+  * SECURITY: Fix IP length check not to crash on very short packets.
+
+  New feature:
+  * Make the inter-site MTU configurable, and negotiate it with the peer.
+
+  Bugfixes etc.:
+  * Fix netlink SEGV on clientless netlinks (i.e. configuration error).
+  * Fix formatting error in p-t-p startup message.
+  * Do not send ICMP errors in response to unknown incoming ICMP.
+  * Fix formatting error in secnet.8 manpage.
+  * Internal code rearrangements and improvements.
+
+  Packaging improvements:
+  * Updates to release checklist in Makefile.in.
+  * Additions to the test-example suite.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 01 May 2014 19:02:56 +0100
+
+secnet (0.3.0) unstable; urgency=low
+
+  * Release of 0.3.0.  No code changes since 0.3.0~beta3.
+  * Update release checklist.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 01 Sep 2013 20:27:48 +0100
+
+secnet (0.3.0~beta3) unstable; urgency=low
+
+  * New upstream version.
+   - Stability bugfix: properly initialise site's scratch buffer.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Mon, 05 Aug 2013 11:54:09 +0100
+
+secnet (0.3.0~beta2) unstable; urgency=low
+
+  * New upstream version.
+   - SECURITY FIX: RSA public modulus and exponent buffer overflow.
+   - SECURITY FIX: Use constant-time memcmp for message authentication.
+   - SECURITY FIX: Provide a new transform, eax-serpent, to replace cbcmac.
+   - SECURITY FIX: No longer send NAKs for NAKs, avoiding NAK storm.
+   - SECURITY FIX: Fix site name checking when site name A is prefix of B.
+   - SECURITY FIX: Safely reject too-short IP packets.
+   - Better robustness for mobile sites (proper user of NAKs, new PROD msg).
+   - Better robustness against SLIP decoding errors.
+   - Fix bugs which caused routes to sometimes not be advertised.
+   - Protocol capability negotiation mechanism.
+   - Improvements and fixes to protocol and usage documentation.
+   - Other bugfixes and code tidying up.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 25 Jul 2013 18:26:01 +0100
+
 secnet (0.3.0~beta1) unstable; urgency=low
 
   * New upstream version.
index eba2515692560296d9ee24a3b102d2f78c82138e..45dfe28f63dcbff16d68c2585c379f8d8f9662e1 100644 (file)
@@ -4,13 +4,14 @@ Priority: extra
 Maintainer: Ian Jackson <ijackson@chiark.greenend.org.uk>
 Uploaders: Stephen Early <steve@greenend.org.uk>,
            Richard Kettlewell <rjk@terraraq.org.uk>
-Build-Depends: debhelper (>= 5), libgmp3-dev, libadns1-dev, bison, flex
+Build-Depends: debhelper (>= 5), libgmp3-dev, libadns1-dev, bison, flex,
+               libbsd-dev, python3, tclx, tcl, libtcl-chiark-1
 Standards-Version: 3.0.1
 
 Package: secnet
 Architecture: any
 Depends: ${shlibs:Depends}, ${misc:Depends}
-Recommends: python
+Recommends: python3
 Description: VPN software for distributed networks
  secnet allows multiple private networks, each 'hidden' behind a single
  globally-routable IP address, to be bridged together.
index c760a78ba4d79bbbb1117d4b1d407f04d3c06d51..243dbd0a138cc769efd03f58cb2c24b9aa995ecb 100644 (file)
@@ -1,67 +1,33 @@
-This is the Debian GNU/Linux packaged version of secnet.
-
-The developers of this software are:
-  Stephen Early <steve@greenend.org.uk>
-
-
-                   GNU GENERAL PUBLIC LICENSE
-                      Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                           Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users.  This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it.  (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.)  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
-  To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have.  You must make sure that they, too, receive or can get the
-source code.  And you must show them these terms so they know their
-rights.
-
-  We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
-  Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software.  If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
-  Finally, any free program is threatened constantly by software
-patents.  We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary.  To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-On Debian systems, the complete text of the GNU General Public License
-can be found in `/usr/share/common-licenses/GPL'.
-
+This is secnet packages for Debian GNU/Linux systems and derivatives.
+
+secnet is
+  Copyright 1995-2003 Stephen Early <steve@greenend.org.uk>
+  Copyright 2002-2014 Ian Jackson <ijackson@chiark.greenend.org.uk>
+  Copyright 1991      Massachusetts Institute of Technology
+  Copyright 1998      Ross Anderson, Eli Biham, Lars Knudsen
+  Copyright 1993      Colin Plumb
+  Copyright 1998      James H. Brown, Steve Reid
+  Copyright 2000      Vincent Rijmen, Antoon Bosselaers, Paulo Barreto
+  Copyright 2001      Saul Kravitz
+  Copyright 2004      Fabrice Bellard
+  Copyright 2002      Guido Draheim
+  Copyright 2005-2010 Free Software Foundation, Inc.
+  Copyright 1995-2001 Jonathan Amery
+  Copyright 1995-2003 Peter Benie
+  Copyright 2011      Richard Kettlewell
+  Copyright 2012      Matthew Vernon
+  Copyright 2013      Mark Wooding
+  Copyright 1995-2013 Simon Tatham
+  Copyright 2012,2013 "Omnifarious" and "btel" on Stackoverflow
+
+The Debian package, additionally, is:
+  Copyright 2001      Joey Hess
+
+secnet is distributed under the terms of the GNU General Public
+License, version 3 or later.  A copy of this licence can be found on
+your Debian system in /usr/share/common-licenses/GPL-3.
+
+secnet is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
index da2a87196f12c66adbe6a87464092026d8a6b550..f94e7521ba9d088ec90493ed7deb8e192ae45da8 100755 (executable)
@@ -6,7 +6,35 @@
 # Uncomment this to turn on verbose mode.
 #export DH_VERBOSE=1
 
-build: build-stamp
+#  This file is Free Software.  It has been incorporated into, and
+#  extensively modified, for secnet.
+#
+#  Copyright 2001      Joey Hess
+#  Copyright 2011-2014 Ian Jackson
+#
+#  You may redistribute this file (and the other source files in the
+#  debian/ subdirectory) freely - the copyrightholders declare that
+#  they wish these files to be in the public domain.
+#
+#  You may redistribute secnet as a whole and/or modify it under the
+#  terms of the GNU General Public License as published by the Free
+#  Software Foundation; either version 3, or (at your option) any
+#  later version.
+#
+#  This software is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this software; if not, see
+#  https://www.gnu.org/licenses/gpl.html.
+
+export EXTRA_CFLAGS= $(shell dpkg-buildflags --get CPPFLAGS) \
+                    $(shell dpkg-buildflags --get CFLAGS)
+export EXTRA_LDFLAGS=$(shell dpkg-buildflags --get LDFLAGS)
+
+build build-arch: build-stamp
 build-stamp:
        dh_testdir
 
@@ -32,10 +60,10 @@ install: build
        dh_installdirs
 
        # Add here commands to install the package into debian/<packagename>
-       $(MAKE) prefix=`pwd`/debian/`dh_listpackages`/usr install
+       $(MAKE) DESTDIR=`pwd`/debian/`dh_listpackages` install
 
 # Build architecture-independent files here.
-binary-indep: build install
+build-indep binary-indep:
 # We have nothing to do by default.
 
 # Build architecture-dependent files here.
diff --git a/depend.sh b/depend.sh
deleted file mode 100755 (executable)
index 72fde4b..0000000
--- a/depend.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/sh
-
-# For more information see "Recursive Make Considered Harmful" at
-# http://www.canb.auug.org.au/~millerp/rmch/recu-make-cons-harm.html
-
-set -e
-set -u
-
-cutout="$1"
-shift
-
-# cutout may contain the character '.' which means a special thing to sed
-# Escape all '.'s (i.e. '..' -> '\.\.')
-cutout2="`echo ${cutout} | sed -e 's@\.@\\\.@g'`"
-
-# We don't bother depending on system header files (which have names
-# starting with '/'). We arrange for both the .o and the .d file to depend
-# on the appropriate header files. We're using VPATH, so we turn pathnames
-# of the form "${srcdir}/foo" into just "foo" (we expect srcdir to be
-# passed as our first command line argument)
-gcc -M -MG "$@" |
-sed -e 's@ /[^ ]*@@g' -e 's@^\(.*\)\.o:@\1.d \1.o:@' -e "s@${cutout2}/@@g"
diff --git a/dh.c b/dh.c
index c37b5386359294f856fb08eaf156a4c2ca586856..261209aafedcfaa7077143e43fa27b268a507d10 100644 (file)
--- a/dh.c
+++ b/dh.c
@@ -1,3 +1,32 @@
+/*
+ * dh.c
+ */
+/*
+ * This file is Free Software.  It was originally written for secnet.
+ *
+ * Copyright 1995-2003 Stephen Early
+ * Copyright 2002-2014 Ian Jackson
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later
+ * version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #include <stdio.h>
 #include <gmp.h>
 #include <limits.h>
@@ -23,7 +52,7 @@ static string_t dh_makepublic(void *sst, uint8_t *secret, int32_t secretlen)
 
     read_mpbin(&a, secret, secretlen);
 
-    mpz_powm(&b, &st->g, &a, &st->p);
+    mpz_powm_sec(&b, &st->g, &a, &st->p);
 
     r=write_mpstring(&b);
 
@@ -32,6 +61,18 @@ static string_t dh_makepublic(void *sst, uint8_t *secret, int32_t secretlen)
     return r;
 }
 
+static void write_mpbin_anomalous(MP_INT *a, uint8_t *buffer,
+                                 int32_t buflen)
+    /* If the BN is smaller than buflen, pads it *at the wrong end* */
+{
+    char *hb = write_mpstring(a);
+    int32_t len;
+    hex_decode(buffer, buflen, &len, hb, True);
+    if (len<buflen)
+       memset(buffer+len,0,buflen-len);
+    free(hb);
+}
+
 static dh_makeshared_fn dh_makeshared;
 static void dh_makeshared(void *sst, uint8_t *secret, int32_t secretlen,
                          cstring_t rempublic, uint8_t *sharedsecret,
@@ -47,9 +88,9 @@ static void dh_makeshared(void *sst, uint8_t *secret, int32_t secretlen,
     read_mpbin(&a, secret, secretlen);
     mpz_set_str(&b, rempublic, 16);
 
-    mpz_powm(&c, &b, &a, &st->p);
+    mpz_powm_sec(&c, &b, &a, &st->p);
 
-    write_mpbin(&c,sharedsecret,buflen);
+    write_mpbin_anomalous(&c,sharedsecret,buflen);
 
     mpz_clear(&a);
     mpz_clear(&b);
@@ -63,7 +104,7 @@ static list_t *dh_apply(closure_t *self, struct cloc loc, dict_t *context,
     string_t p,g;
     item_t *i;
 
-    st=safe_malloc(sizeof(*st),"dh_apply");
+    NEW(st);
     st->cl.description="dh";
     st->cl.type=CL_DH;
     st->cl.apply=NULL;
index d8ad027e0d2b14ab9cc1e5e77c442e77252c5eba..183855370f3053a1bc253d10e4eb0974df7b96fa 100644 (file)
@@ -4,19 +4,31 @@
 /*
  * This file is Free Software.  It was originally written for secnet.
  *
- * You may redistribute it and/or modify it under the terms of the GNU
- * General Public License as published by the Free Software
+ * Copyright 2013 Ian Jackson
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
  * Foundation; either version 2, or (at your option) any later
  * version.
  *
- * This program is distributed in the hope that it will be useful,
+ * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+/*
+ * The corresponding test vector file is eax-aes-test.vectors.  It was
+ * copied out of the AES (Rijndael) paper.  I don't believe it is a
+ * creative work that attracts copyright.  -iwj.
  */
 
 #include "eax-test.h"
index 9e8d951d20b7485b33b89c0ca57c7bb9df240eee..9c7133bffa89e119fcbfce6e3015b6f92258be47 100644 (file)
@@ -4,19 +4,34 @@
 /*
  * This file is Free Software.  It was originally written for secnet.
  *
- * You may redistribute it and/or modify it under the terms of the GNU
- * General Public License as published by the Free Software
+ * Copyright 2013 Ian Jackson
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
  * Foundation; either version 2, or (at your option) any later
  * version.
  *
- * This program is distributed in the hope that it will be useful,
+ * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+/*
+ * The corresponding test vector files are eax-serpent-test.vectors
+ * and eax-serpentbe-test.vectors.  eax-serpent-test.vectors was
+ * provided by Mark Wooding and eax-serpentbe-test.vectors was
+ * generated by this file (in its guise as eax-serpentbe-test).  I
+ * don't believe these test vecctors are creative works that attract
+ * copyright.  -iwj.
  */
 
 #include "eax-test.h"
index c29a51ff53cf8946607fccb7e9e36bc635d47f5e..ea63fdfcb77599245b3f9f6b72dabbff3944b8c9 100644 (file)
@@ -4,19 +4,27 @@
 /*
  * This file is Free Software.  It was originally written for secnet.
  *
- * You may redistribute it and/or modify it under the terms of the GNU
- * General Public License as published by the Free Software
+ * Copyright 2013 Ian Jackson
+ * Copyright 2013 Mark Wooding
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
  * Foundation; either version 2, or (at your option) any later
  * version.
  *
- * This program is distributed in the hope that it will be useful,
+ * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
  */
 
 /*
index 2fe5d964ce7e18b154ac05425e6dd5df6d9f0e8a..f6eb20113837ec2c1d840bfa8383477e796eec23 100644 (file)
@@ -3,20 +3,29 @@
  */
 /*
  * This file is Free Software.  It was originally written for secnet.
+ * See README for full list of copyright holders.
  *
- * You may redistribute it and/or modify it under the terms of the GNU
- * General Public License as published by the Free Software
+ * Copyright 2013 Ian Jackson
+ * Copyright 2013 Mark Wooding
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
  * Foundation; either version 2, or (at your option) any later
  * version.
  *
- * This program is distributed in the hope that it will be useful,
+ * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
  */
 
 
diff --git a/eax.c b/eax.c
index af5ccae16d977a8d523362f3a1e38b6a825568f1..a32baaca30bd4db0cb93fd0150f17c201b3be353 100644 (file)
--- a/eax.c
+++ b/eax.c
@@ -2,24 +2,29 @@
  * eax.c: implementation of the EAX authenticated encryption block cipher mode
  */
 /*
+ * This file is Free Software.  It was originally written for secnet.
+ *
  * Copyright 2013 Ian Jackson
  * Copyright 2013 Mark Wooding
  *
- * This file is Free Software.  It was originally written for secnet.
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
  *
- * You may redistribute it and/or modify it under the terms of the GNU
- * General Public License as published by the Free Software
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
  * Foundation; either version 2, or (at your option) any later
  * version.
  *
- * This program is distributed in the hope that it will be useful,
+ * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
  */
 
 /*
index ab40d05e95a92208a2c7dd409f4e9fe9ac64e7f0..f1d87581e6e7e891ab9bc151068964038c47ee80 100644 (file)
@@ -174,3 +174,5 @@ sites map(site,vpn/example/all-sites);
 
 # sites map(site,vpn/example/location1,vpn/example/location2);
 
+# This file is placed in the public domain (insofar as possible.)
+# Authors:  Stephen Early, Ian Jackson
index 31ef14576fab7897aa71d5522fe432ddca6ca628..25e41eadd149a42e14202400cf4e2d30de4e2429 100644 (file)
@@ -1,4 +1,22 @@
-/* Hacky parallelism; Ian Jackson */
+/* Hacky parallelism */
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 
 #define _GNU_SOURCE
 
@@ -24,90 +42,96 @@ typedef enum { hp_idle, hp_compute, hp_deferring, hp_fail } HPState;
 static HPState state;
 static pid_t child;
 
-static void checkchild(void) {
-  int r, status;
+static void checkchild(void)
+{
+    int r, status;
   
-  if (!child) return;
-
-  r= waitpid(child,&status,WNOHANG); if (!r) return;
-  if (r==-1) {
-    Message(M_ERR,"hacky_par: waitpid: %s\n",strerror(errno));
-    return;
-  }
-  child= 0;
+    if (!child) return;
+
+    r= waitpid(child,&status,WNOHANG); if (!r) return;
+    if (r==-1) {
+       Message(M_ERR,"hacky_par: waitpid: %s\n",strerror(errno));
+       return;
+    }
+    child= 0;
   
-  if (WIFSIGNALED(status)) {
-    Message(M_ERR,"hacky_par: signaled! %s\n",strsignal(WTERMSIG(status)));
-  } else if (!WIFEXITED(status)) {
-    Message(M_ERR,"hacky_par: unexpected status! %d\n", r);
-  }
+    if (WIFSIGNALED(status)) {
+       Message(M_ERR,"hacky_par: signaled! %s\n",strsignal(WTERMSIG(status)));
+    } else if (!WIFEXITED(status)) {
+       Message(M_ERR,"hacky_par: unexpected status! %d\n", r);
+    }
 }
 
-static HPState start(void) {
-  assert(!child);
+static HPState start(void)
+{
+    assert(!child);
 
-  child= fork();
-  if (child == -1) {
-    Message(M_ERR,"hacky_par: fork failed: %s\n",strerror(errno));
-    return hp_fail;
-  }
+    child= fork();
+    if (child == -1) {
+       Message(M_ERR,"hacky_par: fork failed: %s\n",strerror(errno));
+       return hp_fail;
+    }
 
-  if (!child) { /* we are the child */
-    return hp_compute;
-  }
+    if (!child) { /* we are the child */
+       afterfork();
+       return hp_compute;
+    }
 
-  Message(M_INFO,"hacky_par: started, punting\n");
-  return hp_deferring;
+    Message(M_INFO,"hacky_par: started, punting\n");
+    return hp_deferring;
 }
 
-int hacky_par_start_failnow(void) {
-  state= hp_idle;
-  checkchild();
-  if (child) {
-    state= hp_deferring;
-    Message(M_INFO,"hacky_par: busy, punting\n");
-    return 1;
-  }
-  return 0;
+int hacky_par_start_failnow(void)
+{
+    state= hp_idle;
+    checkchild();
+    if (child) {
+       state= hp_deferring;
+       Message(M_INFO,"hacky_par: busy, punting\n");
+       return 1;
+    }
+    return 0;
 }
 
-int hacky_par_mid_failnow(void) {
-  state= start();
-  return state != hp_compute;
+int hacky_par_mid_failnow(void)
+{
+    state= start();
+    return state != hp_compute;
 }
 
 bool_t (*packy_par_gen)(struct site *st);
 
 void hacky_par_end(int *ok,
                   int32_t retries, int32_t timeout,
-                  bool_t (*send_msg)(struct site *st), struct site *st) {
-  int i;
+                  bool_t (*send_msg)(struct site *st), struct site *st)
+{
+    int i;
   
-  switch (state) {
-  case hp_deferring:
-    assert(!*ok);
-    *ok= 1;
-    return;
-  case hp_fail:
-    assert(!*ok);
-    return;
-  case hp_idle:
-    return;
-  case hp_compute:
-    if (!ok) {
-      Message(M_ERR,"hacky_par: compute failed\n");
-      _exit(2);
-    }
-    Message(M_INFO,"hacky_par: got result, sending\n");
-    for (i=1; i<retries; i++) {
-        sleep((timeout + 999)/1000);
-       if (!send_msg(st)) {
-           Message(M_ERR,"hacky_par: retry failed\n");
-           _exit(1);
+    switch (state) {
+    case hp_deferring:
+       assert(!*ok);
+       *ok= 1;
+       return;
+    case hp_fail:
+       assert(!*ok);
+       return;
+    case hp_idle:
+       return;
+    case hp_compute:
+       if (!ok) {
+           Message(M_ERR,"hacky_par: compute failed\n");
+           _exit(2);
+       }
+       Message(M_INFO,"hacky_par: got result, sending\n");
+       for (i=1; i<retries; i++) {
+           sleep((timeout + 999)/1000);
+           if (!send_msg(st)) {
+               Message(M_ERR,"hacky_par: retry failed\n");
+               _exit(1);
+           }
        }
+       _exit(0);
     }
-    _exit(0);
-  }
 }
 
 #else /*!HACKY_PARALLEL*/
index f47493e9e3406567c97240faf12788d84115dd03..2f378d304f2d9aefe61d5ed92442456a31ee48be 100644 (file)
@@ -1,7 +1,24 @@
 /* Hacky parallelism
- * Ian Jackson
  * We fork, and return false !
  */
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 
 #ifndef hackympzpar_h
 #define hackympzpar_h
index 4139f727f6722f965b2adb1efb7d6c98875ffb79..3f4787c454e4d8cb7508626f43c02283c704a17a 100644 (file)
@@ -1,3 +1,27 @@
+/* This file is Free Software.  It was written for secnet.
+ *
+ * Authored 2013      Ian Jackson
+ *
+ * You may redistribute this file freely - the copyrightholders and
+ * authors declare that they wish these files to be in the public
+ * domain; or alternatively (at your option) that you may deal with
+ * them according to the `CC0 1.0 Universal' licence.
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #ifndef HEXDEBUG_H
 #define HEXDEBUG_H
 
index f8cda00eabbc3f6c5dac170c249a9cb8211572b0..d12af55329eca6bf66a7dc50004d34683271df69 100644 (file)
--- a/ipaddr.c
+++ b/ipaddr.c
@@ -1,5 +1,23 @@
 /* The 'ipset' data structure and related algorithms in this file were
    inspired by the 'ipaddr.py' library from Cendio Systems AB. */
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 
 #include "secnet.h"
 #include <limits.h>
@@ -7,6 +25,7 @@
 #include <stdio.h>
 #include <string.h>
 #include "ipaddr.h"
+#include "util.h"
 
 #define DEFAULT_ALLOC 2
 #define EXTEND_ALLOC_BY 4
 struct subnet_list *subnet_list_new(void)
 {
     struct subnet_list *r;
-    r=safe_malloc(sizeof(*r),"subnet_list_new:list");
+    NEW(r);
     r->entries=0;
     r->alloc=DEFAULT_ALLOC;
-    r->list=safe_malloc_ary(sizeof(*r->list),r->alloc,"subnet_list_new:data");
+    NEW_ARY(r->list,r->alloc);
     return r;
 }
 
@@ -29,18 +48,13 @@ void subnet_list_free(struct subnet_list *a)
 
 static void subnet_list_set_len(struct subnet_list *a, int32_t l)
 {
-    struct subnet *nd;
     int32_t na;
 
     if (l>a->alloc) {
-       assert(a->alloc < (int)(INT_MAX/sizeof(*nd))-EXTEND_ALLOC_BY);
+       assert(a->alloc < INT_MAX-EXTEND_ALLOC_BY);
        na=a->alloc+EXTEND_ALLOC_BY;
-       nd=realloc(a->list,sizeof(*nd)*na);
-       if (!nd) {
-           fatal_perror("subnet_list_set_len: realloc");
-       }
+       REALLOC_ARY(a->list,na);
        a->alloc=na;
-       a->list=nd;
     }
     a->entries=l;
 }
@@ -59,10 +73,10 @@ void subnet_list_append(struct subnet_list *a, uint32_t prefix, int len)
 struct ipset *ipset_new(void)
 {
     struct ipset *r;
-    r=safe_malloc(sizeof(*r),"ipset_new:set");
+    NEW(r);
     r->l=0;
     r->a=DEFAULT_ALLOC;
-    r->d=safe_malloc(sizeof(*r->d)*r->a,"ipset_new:data");
+    NEW_ARY(r->d,r->a);
     return r;
 }
 
@@ -114,18 +128,13 @@ struct ipset *ipset_from_subnet_list(struct subnet_list *l)
 
 static void ipset_set_len(struct ipset *a, int32_t l)
 {
-    struct iprange *nd;
     int32_t na;
 
     if (l>a->a) {
        assert(a->a < INT_MAX-EXTEND_ALLOC_BY);
        na=a->a+EXTEND_ALLOC_BY;
-       nd=realloc(a->d,sizeof(*nd)*na);
-       if (!nd) {
-           fatal_perror("ipset_set_len: realloc");
-       }
+       REALLOC_ARY(a->d,na);
        a->a=na;
-       a->d=nd;
     }
     a->l=l;
 }
@@ -136,7 +145,6 @@ static void ipset_append_range(struct ipset *a, struct iprange r)
     a->d[a->l-1]=r;
 }
 
-#define max(a,b) (a>b?a:b)
 struct ipset *ipset_union(struct ipset *a, struct ipset *b)
 {
     struct ipset *c;
@@ -161,7 +169,7 @@ struct ipset *ipset_union(struct ipset *a, struct ipset *b)
            ipset_append_range(c,r);
        else if (r.a <= c->d[c->l-1].b+1)
            /* Extends (or is consumed by) the last range */
-           c->d[c->l-1].b=max(c->d[c->l-1].b, r.b);
+           c->d[c->l-1].b=MAX(c->d[c->l-1].b, r.b);
        else
            ipset_append_range(c,r);
     }
@@ -323,13 +331,21 @@ struct subnet_list *ipset_to_subnet_list(struct ipset *is)
     return r;
 }
 
+#define IPADDR_BUFLEN 20
+
+static char *ipaddr_getbuf(void)
+{
+    SBUF_DEFINE(16, IPADDR_BUFLEN);
+    return SBUF;
+}
+
 /* The string buffer must be at least 16 bytes long */
 string_t ipaddr_to_string(uint32_t addr)
 {
     uint8_t a,b,c,d;
     string_t s;
 
-    s=safe_malloc(16,"ipaddr_to_string");
+    s=ipaddr_getbuf();
     a=addr>>24;
     b=addr>>16;
     c=addr>>8;
@@ -344,7 +360,7 @@ string_t subnet_to_string(struct subnet sn)
     uint8_t a,b,c,d;
     string_t s;
 
-    s=safe_malloc(19,"subnet_to_string");
+    s=ipaddr_getbuf();
     a=addr>>24;
     b=addr>>16;
     c=addr>>8;
@@ -404,7 +420,7 @@ static struct subnet string_item_to_subnet(item_t *i, cstring_t desc,
     return s;
 }
 
-uint32_t string_item_to_ipaddr(item_t *i, cstring_t desc)
+uint32_t string_item_to_ipaddr(const item_t *i, cstring_t desc)
 {
     uint32_t a, b, c, d;
     int match;
index 54f10d434367988a596f526c6fb1f267b75fe544..d8bc9fa5415b8747b677a5d66aae8999759c28e7 100644 (file)
--- a/ipaddr.h
+++ b/ipaddr.h
@@ -1,4 +1,22 @@
 /* Useful functions for dealing with collections of IP addresses */
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 
 #ifndef ipaddr_h
 #define ipaddr_h
@@ -53,6 +71,6 @@ extern string_t subnet_to_string(struct subnet sn);
 extern struct ipset *string_list_to_ipset(list_t *l,struct cloc loc,
                                          cstring_t module, cstring_t param);
                                          
-extern uint32_t string_item_to_ipaddr(item_t *i, cstring_t desc);
+extern uint32_t string_item_to_ipaddr(const item_t *i, cstring_t desc);
 
 #endif /* ipaddr_h */
diff --git a/ipaddr.py b/ipaddr.py
deleted file mode 100644 (file)
index 045b95d..0000000
--- a/ipaddr.py
+++ /dev/null
@@ -1,1244 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-# ipaddr.py -- handle IP addresses and set of IP addresses.
-# Copyright (C) 1996-2000 Cendio Systems AB
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-# 
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-# 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-"""IP address manipulation.
-
-This module is useful if you need to manipulate IP addresses or sets
-of IP addresses.
-
-The main classes are:
-
-    ipaddr   -- a single IP address.
-    netmask  -- a netmask.
-    network  -- an IP address/netmask combination.  It is often, but
-               not always, better to use the ip_set class instead.
-    ip_set   -- a set of IP addresses, that may or may not be adjacent.
-
-So, what can you do with this module?  As a simple example of the kind
-of things this module can do, this code computes the set of all IP
-addresses except 127.0.0.0/8 and prints it, expressed as a union of
-network/netmask pairs.
-
-    import ipaddr
-
-    s = ipaddr.ip_set()
-    s.add_network(ipaddr.network('127.0.0.0', '255.0.0.0',
-                                 ipaddr.DEMAND_FILTER))
-    for nw in s.complement().as_list_of_networks():
-       print nw.ip_str() + '/' + nw.mask.netmask_bits_str
-
-Errors are reported by raising an exception from the following
-exception hierarcy:
-
-Exception       # The standard Python base exception class.
- |
- +-- BadType    # Only raised if the programmer makes an error.
- +-- IpError    # Base class for errors that depend on the data.
-      |
-      +-- SetNotRepresentable
-      +-- BrokenIpAddress
-      |    |
-      |    +-- PartNegative
-      |    +-- PartOverflow
-      |
-      +-- BrokenNetmask
-      |    |
-      |    +-- NeedOneBit
-      |    +-- NeedMoreBits
-      |    +-- NeedLessBits
-      |
-      +-- BrokenNetwork
-           |
-           +-- EmptyIpAddress
-           +-- EmptyNetmask
-           +-- BrokenNetworkAddress
-           +-- NetworkAddressClash
-           +-- BroadcastAddressClash
-  
-BadType may be raised at any time if the programmer makes an error
-(such as passing a dictionary to a function that expects a string).
-SetNotRepresentable may be raised by ip_set.as_str_range().  All other
-exceptions are raised from the constructors and helper functions only.
-
-The following constants are present in this module:
-
-    DEMAND_NONE                See class network.
-    DEMAND_FILTER      See class network.
-    DEMAND_NETWORK     See class network.
-    DEMAND_INTERFACE   See class network.
-
-    hostmask           A netmask object with all 32 bits set.
-    complete_network   A network object representing all IP addresses.
-    complete_set       An ip_set object representing all IP addresses.
-    broadcast_network  A network object representing 255.255.255.255.
-    broadcast_set      An ip_set object representing 255.255.255.255.
-    
-The as_ipaddr function can be used when you have an object that you
-know are an ipaddr or network, and you want to get the ipaddr part.
-
-All the other functions in this module are internal helper functions,
-and they should not be used.
-
-The internal representation used for IP addresses is currently a long
-number.  That may change in the future, so where the internal
-representation is visible, you should do nothing with it except
-compare it to None.
-
-This module was developed by Cendio Systems AB for use in the Fuego
-Firewall.  Bug reports can be sent to Per Cederqvist <ceder@cendio.se>
-who is currently acting as maintainer for this module.
-
-Brief history:
-    1997-03-11      Module created, and used internally.
-    2000-03-09 1.0: First non-public beta release outside of Cendio Systems.
-    2000-03-17 1.1: First public release under the GNU GPL license.
-
-"""
-
-
-import copy
-import string
-import types
-
-# The error messages are marked with a call to this function, so that
-# they can easily be found and translated.
-def _(s):
-    return s
-
-# The exception hierarchy.
-class IpError(Exception):
-    """Base class for errors that are cause by errors in input data.
-    """
-    def __str__(self):
-       return self.format % self.args
-
-class SetNotRepresentable(IpError):
-    format = _("The set of IP addresses cannot be represented "
-              "as a single network range")
-
-class BrokenIpAddress(IpError):
-    format = _("Felaktigt IP-nummer")
-
-class PartNegative(BrokenIpAddress):
-    format = _("En komponent i IP-numret Ã¤r negativ")
-
-class PartOverflow(BrokenIpAddress):
-    format = _("En komponent i IP-numret Ã¤r större Ã¤n 255")
-
-class BrokenNetmask(IpError):
-    format = _("Felaktig nätmask")
-
-class NeedOneBit(BrokenNetmask):
-    format = _("Minst en bit mÃ¥ste vara ettställd")
-
-class NeedMoreBits(BrokenNetmask):
-    format = _("Minst %d bitar mÃ¥ste vara ettställda")
-
-class NeedLessBits(BrokenNetmask):
-    format = _("Högst %d bitar fÃ¥r vara ettställda")
-
-class BrokenNetwork(IpError):
-    """Base class for errors regarding network objects.
-    """
-
-class EmptyIpAddress(BrokenNetwork):
-    format = _("IP-nummer ej ifyllt")
-
-class EmptyNetmask(BrokenNetwork):
-    format = _("Nätmask ej ifylld")
-
-class BrokenNetworkAddress(BrokenNetwork):
-    format = _("Med denna nätmask Ã¤r %s ett otillÃ¥tet nätverksnummer; "
-              "menar du %s?")
-
-class NetworkAddressClash(BrokenNetwork):
-    format = _("Med denna nätmask krockar Fuegons adress med nätnumret")
-
-class BroadcastAddressClash(BrokenNetwork):
-    format = _("Med denna nätmask krockar Fuegons adress "
-              "med broadcastadressen")
-
-class BadType(Exception):
-    """An object of an unexpected type was passed to a function.
-    """
-    pass
-
-# These constants are used with netmasks and networks to specify what
-# the code expects.
-#
-#  DEMAND_NONE: netmask 0-32 (inclusive)
-#  DEMAND_FILTER: netmask 0-32, the host part must be all zeroes
-#  DEMAND_NETWORK: netmask 1-32, the host part must be all zeroes
-#  DEMAND_INTERFACE: netmask 1-30, the host part must *not* be all zeroes
-
-DEMAND_NONE = 1
-DEMAND_FILTER = 2
-DEMAND_NETWORK = 3
-DEMAND_INTERFACE = 4
-
-def bits_to_intrep(bits):
-    """Convert BITS to the internal representation.
-
-    BITS should be a number in the range 0-32 (inclusive).
-
-    """
-    return pow(2L, 32) - pow(2L, 32-bits)
-
-
-def intrep_with_bit_set(bit):
-    """Return an internal representation with bit BIT set.
-
-    BIT should be a number in the range 1-32, where bit 1 is the
-    leftmost.  Examples:
-
-      intrep_with_bit_set(1) --> the internal representation of 128.0.0.0
-      intrep_with_bit_set(32) --> the internal representation of 0.0.0.1
-    """
-    assert 0 < bit and bit <= 32
-
-    return pow(2L, 32-bit)
-
-
-__ONES = {0:0, 128:1, 192:2, 224:3,
-         240:4, 248:5, 252:6, 254:7}
-
-def tuple_to_bits(mask):
-    """Convert MASK to bits.
-
-    MASK should be a tuple of four integers in the range 0-255 (inclusive).
-
-    Raises BrokenNetmask if MASK is not a valid netmask.
-    """
-
-    if mask == None:
-       return None
-    else:
-       (a, b, c, d) = mask
-
-       if a == 255 and b == 255 and c == 255 and d == 255:
-           return 32
-
-       try:
-           if a == 255 and b == 255 and c == 255:
-               return 24 + __ONES[d]
-           elif a == 255 and b == 255  and d == 0:
-               return 16 + __ONES[c]
-           elif a == 255 and c == 0  and d == 0:
-               return 8 + __ONES[b]
-           elif b == 0 and c == 0  and d == 0:
-               return __ONES[a]
-       except KeyError:
-           pass
-
-       raise BrokenNetmask()
-
-
-def intrep_to_dotted_decimal(t):
-    """Convert T to dotted-decimal notation.
-
-    T should be the internal representation used py ipaddr.py.
-    """
-
-    return (str(int(t>>24)) + '.' + str(int((t>>16) & 255))
-           + '.' + str(int((t>>8) & 255)) + '.' + str(int(t & 255)))
-
-
-def as_ipaddr(nwip):
-    """Return the IP address object of NWIP.
-
-    NWIP may be an ipaddr object, which is returned unchanged,
-    or a network object, in which case the ipaddr part of it is
-    returned.
-    """
-
-    if isinstance(nwip, ipaddr):
-       return nwip
-    elif isinstance(nwip, network):
-       return nwip.ip
-    else:
-       raise BadType('Expected a network or ipaddr object', nwip)
-
-
-class ipaddr:
-    """Handle IP addresses.
-
-    Sample use:
-
-        ip1 = ipaddr('12.3.5.1')
-       ip2 = ipaddr([12, 3, 5, 1])
-       print ip1.ip_str()
-       >>> '12.3.5.1'
-       print ip1.intrep
-       >>> 201524481L
-       print ip2.ip_str()
-       >>> '12.3.5.1'
-       print ip2.intrep
-       >>> 201524481L
-
-    An ipaddr object can have two states: empty or good.
-    The status can be examined like this:
-
-       if ip.intrep == None:
-           handle_empty(m.user_input())
-       else:
-           handle_good(ip)
-
-    All other members should only be used in the good state.  The
-    value stored in the intrep member should only be compared against
-    None.  The type and value of it is an internal detail that may
-    change in the future.
-
-    """
-
-    def __init__(self, ip):
-       """Create an ipaddr from IP (a string, tuple or list).
-
-       The empty string or None may be given; it is handled as the
-       empty IP number.
-       """
-
-       if type(ip) == types.StringType:
-           self.__user_input = ip
-           ip = string.strip(ip)
-       else:
-           self.__user_input = None
-
-       # The empty IP number?
-
-       if ip == '' or ip == None:
-           self.__ip_str = ''
-           self.intrep = None
-           if ip == None:
-               self.__user_input = ''
-           return
-
-       if type(ip) == types.StringType:
-
-           # Convert a string.
-
-           try:
-               [a, b, c, d] = map(string.atoi, string.splitfields(ip, '.'))
-           except:
-               raise BrokenIpAddress()
-
-           if a < 0 or b < 0 or c < 0 or d < 0:
-               raise PartNegative()
-
-           if a > 255 or b > 255 or c > 255 or d > 255:
-               raise PartOverflow()
-
-           self.intrep = (long(a) << 24) + (b << 16) + (c << 8) + d
-
-       else:
-           assert type(ip) == types.LongType
-           self.intrep = ip
-
-       self.__ip_str = None
-
-    def ip_str(self):
-       if self.__ip_str == None:
-           self.__ip_str = intrep_to_dotted_decimal(self.intrep)
-       return self.__ip_str
-
-    def user_input(self):
-       if self.__user_input == None:
-           # This object was constructed from a tuple.  Generate a string.
-           self.__user_input = self.ip_str()
-       return self.__user_input
-
-    def compare(self, other):
-       """Compare this IP address with OTHER.
-
-       Returns -1, 0 or 1 if this IP address is less than, equal to,
-       or greater than OTHER (which should be an ipaddr object).
-       """
-       # FIXME: should we rename this __cmp__?  It needs to handle
-       # other types of the OTHER argument first.
-
-       if self.intrep == other.intrep:
-           return 0
-       if self.intrep < other.intrep:
-           return -1
-       else:
-           return 1
-
-    def __str__(self):
-        if self.intrep is None:
-            return "<ipaddr empty>"
-        else:
-            return "<ipaddr %s>" % self.ip_str()
-
-    def __repr__(self):
-       if self.intrep is None:
-           return "ipaddr.ipaddr('')"
-       else:
-           return "ipaddr.ipaddr('%s')" % self.ip_str()
-
-
-class netmask:
-    """Handle netmasks.
-
-    Sample use:
-
-       # Four ways to initialize a netmask.
-        nm1 = netmask('255.255.128.0', DEMAND_NONE)
-       nm2 = netmask([255, 255, 128, 0], DEMAND_NONE)
-       nm3 = netmask('17', DEMAND_NONE)
-       nm4 = netmask(17, DEMAND_NONE)
-       print nm1.netmask_str()
-       >>> '255.255.128.0'
-       print nm1.intrep
-       >>> (255, 255, 128, 0)
-       print nm1.netmask_bits
-       >>> 17
-       print nm1.netmask_bits_str
-       >>> '17'
-
-    A netmask can have two states: empty or good.  The state
-    can be examined like this:
-
-       if m.intrep == None:
-           handle_empty(m.user_input())
-       else:
-           handle_good(m)
-
-    All other members should be used only in the good state.
-
-    """
-
-    def __check_range(self, bits, minbits, maxbits):
-       if bits < minbits:
-           if minbits == 1:
-               raise NeedOneBit()
-           else:
-               raise NeedMoreBits(minbits)
-       elif bits > maxbits:
-           raise NeedLessBits(maxbits)
-
-
-    def __set_from_bits(self, bits, minbits, maxbits):
-       self.__check_range(bits, minbits, maxbits)
-       self.intrep = bits_to_intrep(bits)
-       self.netmask_bits = bits
-
-
-    def __set_from_tuple(self, tpl, minbits, maxbits):
-       bits = tuple_to_bits(tpl)
-       self.__check_range(bits, minbits, maxbits)
-       self.intrep = bits_to_intrep(bits)
-       self.netmask_bits = bits
-
-    DEMANDS = {DEMAND_NONE:(0,32),
-              DEMAND_FILTER:(0,32),
-              DEMAND_NETWORK:(1,32),
-              DEMAND_INTERFACE:(1,30)}
-
-    def __init__(self, mask, demand):
-       """Create a netmask from MASK (a string, tuple or number) and DEMAND.
-
-       The empty string or None may be given; it is handled as the
-       empty netmask.
-
-       See class network for a description of the DEMAND parameter.
-       """
-
-       (minbits, maxbits) = self.DEMANDS[demand]
-       self.demand = demand
-
-       if type(mask) == types.StringType:
-           self.__user_input = mask
-           mask = string.strip(mask)
-       else:
-           self.__user_input = None
-
-       if mask == '' or mask == None:
-
-           # Handle empty netmasks.
-
-           self.__netmask_str = ''
-           self.intrep = None
-           self.netmask_bits_str = ''
-           self.netmask_bits = None
-           if self.__user_input == None:
-               self.input = ''
-           return
-
-       # Decode the MASK argument and set self.netmask_bits
-       # and self.intrep.
-
-       if type(mask) == types.StringType:
-
-           # Is this a string containing a single number?
-           try:
-               bits = string.atoi(mask)
-           except (OverflowError, ValueError):
-               bits = None
-
-           if bits != None:
-
-               # This is a string containing a single number.
-
-               self.__set_from_bits(bits, minbits, maxbits)
-
-           else:
-
-               # Interpret the netmask as a dotted four-tuple.
-               try:
-                   [a, b, c, d] = map(string.atoi,
-                                      string.splitfields(mask, '.'))
-               except:
-                   raise BrokenNetmask()
-
-               self.__set_from_tuple((a, b, c, d), minbits, maxbits)
-
-       elif type(mask) == types.IntType:
-
-           # This is a number, representing the number of bits in the mask.
-
-           self.__set_from_bits(mask, minbits, maxbits)
-
-       else:
-
-           # This is a tuple or list.
-
-           if len(mask) != 4:
-               raise BadType('Wrong len of tuple/list')
-
-           (a, b, c, d) = (mask[0], mask[1], mask[2], mask[3])
-
-           self.__set_from_tuple((a, b, c, d), minbits, maxbits)
-
-       self.__netmask_str = None
-       self.netmask_bits_str = repr(self.netmask_bits)
-
-    def netmask_str(self):
-       if self.__netmask_str == None:
-           self.__netmask_str = intrep_to_dotted_decimal(self.intrep)
-       return self.__netmask_str
-
-    def user_input(self):
-       if self.__user_input == None:
-           # This object was constructed from a tuple or an integer.
-           self.__user_input = self.ip_str()
-       return self.__user_input
-
-    def __str__(self):
-        if self.intrep is None:
-            return "<netmask empty>"
-        else:
-            return "<netmask /%d>" % self.netmask_bits
-
-    def __repr__(self):
-        if self.intrep is None:
-            return "ipaddr.netmask('')"
-        else:
-            return "ipaddr.netmask(%d, %d)" % (self.netmask_bits, self.demand)
-
-
-hostmask = netmask(32, DEMAND_NONE)
-       
-
-class network:
-    """Designate a network or host.
-
-    The constructor takes three arguments: the IP number part, the
-    netmask part, and a demand parameter.  See class ipaddr and class
-    netmask for a description of the first two arguments.  The demand
-    argument can be one of the following constants:
-
-    DEMAND_NONE
-        No special demands.
-    DEMAND_FILTER
-        The host part must be all zeroes.
-    DEMAND_NETWORK
-        The netmask must be 1-32
-       The host part must be all zeroes.
-    DEMAND_INTERFACE
-        The netmask must be 1-30
-       The host part must *not* be all zeroes (the network address)
-        or all ones (the broadcast address).
-
-    The following members exist and are set by the constructor:
-
-      ip.user_input()          # a caching function
-      ip_str()                 # a caching function
-      ip.intrep
-      mask.user_input()                # a caching function
-      mask.netmask_str()       # a caching function
-      mask.intrep
-      mask.netmask_bits
-      mask.netmask_bits_str
-      network_str()            # a caching function
-      network_intrep
-      broadcast_str()          # a caching function
-      broadcast_intrep
-      host_part_str()          # a caching function
-      host_part_intrep
-
-    """
-
-    def __init__(self, ip, mask, demand):
-       self.ip = ipaddr(ip)
-       self.mask = netmask(mask, demand)
-
-       if self.ip.intrep == None:
-           raise EmptyIpAddress()
-
-       if self.mask.intrep == None:
-           raise EmptyNetmask()
-
-       self._precompute()
-
-    def _precompute(self):
-       self.__lower_str = None
-       self.__upper_str = None
-
-       self.network_intrep = self.ip.intrep & self.mask.intrep
-       self.broadcast_intrep = (self.network_intrep |
-                               (pow(2L, 32)-1-self.mask.intrep))
-       self.host_part_intrep = self.ip.intrep - self.network_intrep
-
-       self.__network_str = None
-       self.__broadcast_str = None
-       self.__host_part_str = None
-
-       demand = self.mask.demand
-
-       if demand == DEMAND_NONE:
-           pass
-       elif demand == DEMAND_FILTER or demand == DEMAND_NETWORK:
-           if self.host_part_intrep != 0L:
-               raise BrokenNetworkAddress(self.ip_str(), self.network_str())
-       elif demand == DEMAND_INTERFACE:
-           if self.host_part_intrep == 0L:
-               raise NetworkAddressClash()
-           elif self.broadcast_intrep == self.ip.intrep:
-               raise BroadcastAddressClash()
-       else:
-           raise BadType('Bad value for the demand parameter', demand)
-
-    def network_str(self):
-       if self.__network_str == None:
-           self.__network_str = intrep_to_dotted_decimal(self.network_intrep)
-       return self.__network_str
-
-    def broadcast_str(self):
-       if self.__broadcast_str == None:
-           self.__broadcast_str = intrep_to_dotted_decimal(
-               self.broadcast_intrep)
-       return self.__broadcast_str
-
-    def host_part_str(self):
-       if self.__host_part_str == None:
-           self.__host_part_str = intrep_to_dotted_decimal(
-               self.host_part_intrep)
-       return self.__host_part_str
-
-    def overlaps(self, other):
-       """Returns true if the network overlaps with OTHER.
-
-       OTHER must be a network object or an ipaddr object.  If it
-       is empty this method will always return false.
-
-       """
-
-       if self.network_intrep == None:
-           return 0
-
-       if isinstance(other, ipaddr):
-
-           if other.intrep == None:
-               return 0
-
-           return (self.mask.intrep & other.intrep) == self.network_intrep
-       else:
-           if other.network_intrep == None:
-               return 0
-
-           mask = self.mask.intrep & other.mask.intrep
-           return (mask & self.ip.intrep) == (mask & other.ip.intrep)
-
-    def intersection(self, other):
-       """Return the intersection of the network and OTHER.
-
-       The return value is a network object with DEMAND_FILTER.  If
-       the intersection is empty this method will return None.
-
-       OTHER must be a network object or an ipaddr object.  The
-       intersection will be empty if it is empty.
-       """
-
-       if self.network_intrep == None:
-           return None
-
-       if isinstance(other, ipaddr):
-
-           if other.intrep == None:
-               return None
-
-           prefix_mask = self.mask.intrep
-           short_net = self.network_intrep
-           long_ip = other.intrep
-           result = network(other.intrep, 32, DEMAND_FILTER)
-       else:
-           if other.network_intrep == None:
-               return None
-           
-           if self.mask.netmask_bits < other.mask.netmask_bits:
-               prefix_mask = self.mask.intrep
-               short_net = self.network_intrep
-               long_ip = other.network_intrep
-               result = network(other.network_intrep, other.mask.netmask_bits,
-                                DEMAND_FILTER)
-           else:
-               prefix_mask = other.mask.intrep
-               short_net = other.network_intrep
-               long_ip = self.network_intrep
-               result = network(self.network_intrep, self.mask.netmask_bits,
-                                DEMAND_FILTER)
-
-       if (long_ip & prefix_mask) != (short_net & prefix_mask):
-           return None
-
-       return result
-
-    def is_subset(self, nwip):
-       """Return true if NWIP is a subset of this network.
-
-       NWIP must be a network object or an ipaddr object.
-       """
-
-       if not self.overlaps(nwip):
-           return 0
-
-       if isinstance(nwip, ipaddr):
-           return 1
-
-       return nwip.mask.netmask_bits <= self.mask.netmask_bits
-
-    def is_same_set(self, nwip):
-       """Return true if NWIP contains the same set as this network.
-
-       NWIP must be a network object or an ipaddr object.
-       """
-
-       if isinstance(nwip, ipaddr):
-           return (self.mask.netmask_bits == 32
-                   and self.ip.intrep == nwip.intrep)
-       else:
-           return (self.mask.netmask_bits == nwip.mask.netmask_bits
-                   and self.network_intrep == nwip.network_intrep)
-
-    def subtract(self, nwip):
-       """Create a list of new network objects by subtracting NWIP from self.
-
-       The result consists of networks that together span all
-       IP addresses that are present in self, except those that are
-       present in NWIP.  (The result may be empty or contain several
-       disjoint network objects.)
-
-       Don't use this!  This method is slow.  The ip_set class can do
-       this kind of things in a more efficient way.
-       """
-
-       if not self.overlaps(nwip):
-           # No overlap at all, so NWIP cannot affect the result.
-           return [self]
-
-       if isinstance(nwip, ipaddr):
-           bits = 32
-           intrep = nwip.intrep
-       else:
-           assert isinstance(nwip, network)
-           bits = nwip.mask.netmask_bits
-           intrep = nwip.ip.intrep
-       nets = []
-       while bits > self.mask.netmask_bits:
-           nets.append(network(compute_neighbor(intrep, bits),
-                               bits, DEMAND_FILTER))
-           bits = bits - 1
-       return nets
-
-    def subtract_nwips(self, nwips):
-       """Create a list of new network objects by subtracting NWIPS.
-
-       The result consists of networks that together span all
-       IP addresses that are present in self, except those that are
-       present in NWIPS.  (The result may be empty or contain
-       several disjoint network objects.)  NWIPS should be a list
-       of network or ipaddr objects.
-
-       Don't use this!  This method is slow.  The ip_set class can do
-       this kind of things in a more efficient way.
-       """
-
-       subtracted = [self]
-       for s in nwips:
-           # precondition<A>: SUBTRACTED is a list of networks
-           tmp = []
-           for nw in subtracted:
-               tmp = tmp + nw.subtract(s)
-           subtracted = tmp
-           # postcondition: SUBTRACTED is a list of networks that
-           # spans all IP addresses that were present in
-           # precondition<A>, except those that are present in S.
-
-       return subtracted
-
-    def __compute_lower_upper(self):
-       if self.__lower_str != None:
-           return
-       assert self.network_intrep != None and self.broadcast_intrep != None
-
-       self.__lower_str = intrep_to_dotted_decimal(self.network_intrep + 1)
-       self.__upper_str = intrep_to_dotted_decimal(self.broadcast_intrep - 1)
-
-    def lower_host(self):
-       self.__compute_lower_upper()
-       return self.__lower_str
-
-    def upper_host(self):
-       self.__compute_lower_upper()
-       return self.__upper_str
-
-    def __repr__(self):
-       return _("{network %s/%d}") % (self.ip_str(), self.mask.netmask_bits)
-
-    def ip_str(self):
-       return self.ip.ip_str()
-
-
-class ip_set:
-    def __init__(self, nwip=None):
-       """Create an ip_set.
-
-       If the optional argument NWIP is supplied, the set is
-       initialized to it, otherwise the created set will be empty.
-       NWIP must be a network or ipaddr object.
-       """
-
-       # [[0L, 3L], [5L, 7L]] means 0.0.0.0/29 \ 0.0.0.4/32
-       self.__set = []
-
-       if nwip != None:
-           self.append(nwip)
-
-    def subtract_set(self, other):
-       """Remove all IP-numbers in OTHER from this.
-
-       OTHER should be an ip_set object.
-       """
-
-       self.subtract_list(other.__set)
-
-    def subtract_ips(self, ips):
-       """Remove all IP-numbers in IPS from this.
-
-       IPS should be a list of ipaddr objects.
-       """
-
-       for ip in ips:
-           self.subtract_list([[ip.intrep, ip.intrep]])
-
-    def subtract_list(self, other):
-       # Don't use this method directly, unless you are the test suite.
-       ix = 0
-       iy = 0
-       while ix < len(self.__set) and iy < len(other):
-           if self.__set[ix][1] < other[iy][0]:
-               # The entire range survived.
-               ix = ix + 1
-           elif self.__set[ix][0] > other[iy][1]:
-               # The entire other range is unused, so discard it.
-               iy = iy + 1
-           elif self.__set[ix][0] >= other[iy][0]:
-               if self.__set[ix][1] <= other[iy][1]:
-                   # The entire range is subtracted.
-                   del self.__set[ix]
-               else:
-                   # The start of the range is subtracted, but
-                   # the rest of the range may survive.  (As a matter
-                   # of fact, at least one number *will* survive,
-                   # since there should be a gap between other[iy][1]
-                   # and other[iy+1][0], but we don't use that fact.)
-                   self.__set[ix][0] = other[iy][1] + 1
-                   iy = iy + 1
-           else:
-               # The first part of the range survives.
-               end = self.__set[ix][1]
-               assert self.__set[ix][1] >= other[iy][0]
-               self.__set[ix][1] = other[iy][0] - 1
-               ix = ix + 1
-               if end > other[iy][1]:
-                   # The part that extends past the subtractor may survive.
-                   self.__set[ix:ix] = [[other[iy][1] + 1, end]]
-               # Retain the subtractor -- it may still kill some
-               # other range.
-
-    def add_set(self, other):
-       """Add all IP-numbers in OTHER to this.
-
-       OTHER should be an ip_set object.
-       """
-
-       self.add_list(other.__set)
-
-    def add_list(self, other):
-       # Don't use this method directly, unless you are the test suite.
-       ix = 0
-       iy = 0
-       res = []
-       while ix < len(self.__set) or iy < len(other):
-           # Remove the first range
-           if ix < len(self.__set):
-               if iy < len(other):
-                   if self.__set[ix][0] < other[iy][0]:
-                       rng = self.__set[ix]
-                       ix = ix + 1
-                   else:
-                       rng = other[iy]
-                       iy = iy + 1
-               else:
-                   rng = self.__set[ix]
-                   ix = ix + 1
-           else:
-               rng = other[iy]
-               iy = iy + 1
-
-           # Join this range to the list we already have collected.
-           if len(res) == 0:
-               # This is the first element.
-               res.append(rng)
-           elif rng[0] <= res[-1][1] + 1:
-               # This extends (or is consumed by) the last range.
-               res[-1][1] = max(res[-1][1], rng[1])
-           else:
-               # There is a gap between the previous range and this one.
-               res.append(rng)
-
-       self.__set = res
-
-    def append(self, nwip):
-       """Add NWIP to this.
-
-       NWIP should be a network object or ipaddr object.
-       """
-
-       if isinstance(nwip, network):
-           self.add_network(nwip)
-       else:
-           self.add_ipaddr(nwip)
-
-    def add_network(self, nw):
-       """Add NW to this.
-
-       NW should be a network object.
-       """
-       self.add_list([[nw.network_intrep, nw.broadcast_intrep]])
-
-    def add_range(self, lo_ip, hi_ip):
-       """Add the range of IP numbers specified by LO_IP and HI_IP to this.
-
-       LO_IP and HI_IP should be ipaddr objects.  They specify a
-       range of IP numbers.  Both LO_IP and HI_IP are included in the
-       range.
-       """
-
-       assert lo_ip.intrep != None
-       assert hi_ip.intrep != None
-       assert lo_ip.intrep <= hi_ip.intrep
-       self.add_list([[lo_ip.intrep, hi_ip.intrep]])
-
-    def add_ipaddr(self, ip):
-       """Add IP to this.
-
-       IP should be an ipaddr object.
-       """
-
-       assert ip.intrep != None
-       self.add_list([[ip.intrep, ip.intrep]])
-
-    def complement(self):
-       """Return everything not contained in this ip_set.
-
-       The return value is a new ip_set.  This is not modified.
-       """
-
-       pre = -1L
-       lst = []
-       for [lo, hi] in self.__set:
-           if lo != 0:
-               lst.append([pre+1, lo-1])
-           pre = hi
-       if pre < pow(2L, 32) - 1:
-           lst.append([pre+1, pow(2L, 32) - 1])
-       res = ip_set()
-       res.add_list(lst)
-       return res
-
-    def intersection(self, other):
-       """Return the intersection of this and OTHER.
-
-       The return value is a new ip_set.  This is not modified.
-       OTHER should be an ip_set, network or ipaddr object.
-       """
-
-       res = []
-       ix = 0
-       iy = 0
-       x = copy.deepcopy(self.__set)
-
-       if isinstance(other, ip_set):
-           y = copy.deepcopy(other.__set)
-       elif isinstance(other, network):
-           y = [[other.network_intrep, other.broadcast_intrep]]
-       elif isinstance(other, ipaddr):
-           y = [[other.intrep, other.intrep]]
-       else:
-           raise BadType('expected an ip_set, network or ipaddr argument')
-
-       while ix < len(x) and iy < len(y):
-           if x[ix][1] < y[iy][0]:
-               # The first entry on x doesn't overlap with anything on y.
-               ix = ix + 1
-           elif x[ix][0] > y[iy][1]:
-               # The first entry on y doesn't overlap with anything on x.
-               iy = iy + 1
-           else:
-               # Some overlap exists.
-
-               # Trim away any leading edges.
-               if x[ix][0] < y[iy][0]:
-                   # x starts before y
-                   x[ix][0] = y[iy][0]
-               elif x[ix][0] > y[iy][0]:
-                   # y starts before x
-                   y[iy][0] = x[ix][0]
-
-               # The ranges start at the same point (at least after
-               # the trimming).
-               if x[ix][1] == y[iy][1]:
-                   # The ranges are equal.
-                   res.append(x[ix])
-                   ix = ix + 1
-                   iy = iy + 1
-               elif x[ix][1] < y[iy][1]:
-                   # x is the smaller range
-                   res.append(x[ix])
-                   ix = ix + 1
-               else:
-                   # y is the smaller range
-                   res.append(y[iy])
-                   iy = iy + 1
-
-       result = ip_set()
-       result.add_list(res)
-       return result
-
-
-    def as_list_of_networks(self):
-       """Return this set as a list of networks.
-
-       The returned value is a list of network objects, that are
-       created with DEMAND_FILTER.  This method may be expensive, so
-       it should only be used when necessary.
-       """
-
-       bm = []
-       for [a, b] in self.__set:
-
-           lomask = 1L
-           lobit = 1L
-           himask = pow(2L, 32)-2
-           bits = 32
-           while a <= b:
-               if a & lomask != 0L:
-                   bm.append((bits, a))
-                   a = a + lobit
-               elif b & lomask != lomask:
-                   bm.append((bits, b & himask))
-                   b = b - lobit
-               else:
-                   lomask = (lomask << 1) | 1
-                   lobit = lobit << 1
-                   himask = himask ^ lobit
-                   bits = bits - 1
-                   assert(bits >= 0)
-       bm.sort()
-       res = []
-       for (mask, ip) in bm:
-           res.append(network(ip, mask, DEMAND_FILTER))
-       return res
-
-    def as_list_of_ranges(self):
-       """Return the set of IP addresses as a list of ranges.
-
-       Each range is a list of two long numbers.  Sample return
-       value: [[1L, 3L], [0x7f000001L, 0x7f000001L]], meaning
-       the set 0.0.0.1, 0.0.0.2, 0.0.0.3, 127.0.0.1.
-       """
-
-       # This method is currently very cheap, since this is the
-       # current internal representation.
-
-       return self.__set
-
-    def as_str_range(self):
-       """Return the set as a string, such as "1.2.3.4-1.2.3.8".
-
-       The returned value always has the form a.b.c.d-e.f.g.h.
-       Raises SetNotRepresentable if the set cannot be represented as a
-       single interval, or if it is the empty set.
-       """
-       if len(self.__set) != 1:
-           raise SetNotRepresentable()
-       return "%s-%s" % (intrep_to_dotted_decimal(self.__set[0][0]),
-                         intrep_to_dotted_decimal(self.__set[0][1]))
-
-    def contains(self, ip):
-       """Return true if IP is contained in the set.
-
-       IP should be an ipaddr object.  The empty ipaddr is never contained.
-       """
-
-       if ip.intrep == None:
-           return 0
-
-       for [lo, hi] in self.__set:
-           if lo <= ip.intrep <= hi:
-               return 1
-       return 0
-
-    def overlaps(self, nwip):
-       """Return true if NWIP overlaps the set of IP addresses.
-
-       NWIP may be an ipaddr, network or ip_set object.
-       """
-
-       if isinstance(nwip, ipaddr):
-           return self.contains(nwip)
-       elif isinstance(nwip, ip_set):
-           # This could be optimized -- we don't really need
-           # to compute the intersection.
-           return not self.intersection(nwip).is_empty()
-       elif isinstance(nwip, network):
-           wanted_low = nwip.network_intrep
-           wanted_high = nwip.broadcast_intrep
-           if wanted_low == None or wanted_high == None:
-               return 0
-           for [lo, hi] in self.__set:
-               if lo > wanted_high:
-                   # We are past the interresting interval.
-                   return 0
-               if lo >= wanted_low or hi >= wanted_low:
-                   return 1
-           return 0
-       else:
-           raise BadType('Expected an ipaddr, ip_set or network instance')
-
-    def is_empty(self):
-       """Return true if this ip_set is empty.
-       """
-
-       return len(self.__set) == 0
-
-    def any_ip(self):
-       """Return one of the IP addresses contained in ip_set.
-
-       This method may only be called if the set is non-empty.  You
-       can use the is_empty method to test for emptiness.
-
-       This picks an IP address from the set and returns it as an
-       ipaddr object.  Given the same set of IP addresses, this
-       method will always return the same IP address, but which IP
-       address it chooses is explicitly undocumented and may change
-       if the underlying implementation of ip_set ever changes.
-       """
-
-       assert not self.is_empty()
-       return ipaddr(self.__set[0][0])
-
-    def __str__(self):
-       res = []
-       for rng in self.__set:
-           if rng[0] == rng[1]:
-               res.append(intrep_to_dotted_decimal(rng[0]))
-           else:
-               res.append('%s-%s' % (intrep_to_dotted_decimal(rng[0]),
-                                     intrep_to_dotted_decimal(rng[1])))
-       return '<ipaddr.ip_set(%s)>' % string.join(res, ', ')
-
-complete_network = network(0L, 0, DEMAND_FILTER)
-complete_set = ip_set(complete_network)
-broadcast_network = network('255.255.255.255', 32, DEMAND_FILTER)
-broadcast_set = ip_set(broadcast_network)
-
-def compute_neighbor(intrep, bits):
-    xor_mask = intrep_with_bit_set(bits)
-    and_mask = bits_to_intrep(bits)
-    return (intrep ^ xor_mask) & and_mask
-
-
-if __name__ == '__main__':
-    # Test/demo code.  With no arguments, this will print a page
-    # of data that can be useful when trying to interpret an
-    # ipnumber/netmask pair.  With two arguments, it will print some
-    # information about the IP number and netmask that was entered.
-
-    import sys
-    if len(sys.argv) == 1:
-       print "Netmasks\n========"
-       for i in range(0, 17):
-           if i != 16:
-               print '%2d' % i,
-               print '%-13s' % netmask(i, DEMAND_NONE).netmask_str(),
-           else:
-               print ' ' * 16,
-           print i + 16, '%-16s' % netmask(i + 16, DEMAND_NONE).netmask_str()
-       print _("\n\nIP intervals\n============")
-       for i in range(9):
-           for j in range(0, 4):
-               print '%2d' % (8*j + i),
-           print '%3d' % (netmask(i, DEMAND_NONE).intrep >> 24),
-           x = 0
-           need_break = 0
-           if i < 8:
-               for j in range(0, 256, pow(2, 8-i)):
-                   if need_break:
-                       print
-                       print ' ' * 15,
-                       need_break = 0
-                   print '%3d-%-3d' % (j, j + pow(2, 8-i)-1),
-                   x = x + 1
-                   if x % 8 == 0:
-                       need_break = 1
-           else:
-               print '0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13...',
-           print
-       sys.exit(0)
-
-    if len(sys.argv) != 3:
-       sys.stderr.write(_("Usage: python ipaddr.py IP_ADDRESS NETMASK\n"))
-       sys.exit(1)
-    nw = network(sys.argv[1], sys.argv[2], DEMAND_NONE)
-    print nw
-    print "IP address:       ", nw.ip.ip_str()
-    print "Netmask:          ", nw.mask.netmask_str(),
-    print " (/" + nw.mask.netmask_bits_str + ")"
-    print "Network address:  ", nw.network_str()
-    print "Broadcast address:", nw.broadcast_str()
diff --git a/ipaddrset-test.expected b/ipaddrset-test.expected
new file mode 100644 (file)
index 0000000..103050d
--- /dev/null
@@ -0,0 +1,23 @@
+s = -
+2001:23:24::/48,172.18.45.0/24
+t = 172.18.45.192/28,172.31.80.8/32
+False
+False
+172.18.44.0/23
+False
+False
+172.18.45.6/32
+True
+False
+172.18.45.0/24
+True
+False
+a = ::/0,0.0.0.0/0
+True
+True
+^
+172.18.45.192/28
+172.18.45.192/28
+u
+2001:23:24::/48,172.18.45.0/24,172.31.80.8/32
+2001:23:24::/48,172.18.45.0/24,172.31.80.8/32
diff --git a/ipaddrset-test.py b/ipaddrset-test.py
new file mode 100755 (executable)
index 0000000..16258f0
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/python3
+
+# This file is Free Software.  It was originally written for secnet.
+#
+# Copyright 2014 Ian Jackson
+#
+# You may redistribute secnet as a whole and/or modify it under the
+# terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 3, or (at your option) any
+# later version.
+#
+# You may redistribute this fileand/or modify it under the terms of
+# the GNU General Public License as published by the Free Software
+# Foundation; either version 2, or (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this software; if not, see
+# https://www.gnu.org/licenses/gpl.html.
+#
+# The corresponding test vector file ise ipaddrset-test.expected.  I
+# don't believe it is a creative work that attracts copyright.  -iwj.
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import ipaddress
+from ipaddress import ip_network, ip_address
+
+import ipaddrset
+from ipaddrset import IPAddressSet
+
+v4a=ip_address('172.18.45.6')
+
+s=IPAddressSet()
+print('s =', s)
+s.append([ip_network('172.18.45.0/24')])
+s.append([ip_network('2001:23:24::/48')])
+print(s)
+
+t=IPAddressSet(map(ip_network,['172.31.80.8/32','172.18.45.192/28']))
+print('t =', t)
+print(t <= s)
+print(t == s)
+
+for n1s in ['172.18.44.0/23','172.18.45.6/32','172.18.45.0/24']:
+    n1=ip_network(n1s)
+    print(n1)
+    print(s.contains(n1))
+    print(t.contains(n1))
+
+n=s.networks()[0]
+
+a=ipaddrset.complete_set()
+print('a =', a)
+print(a >= s)
+print(a >= t)
+
+print('^')
+print(s.intersection(t))
+print(t.intersection(s))
+
+print('u')
+print(s.union(t))
+print(t.union(s))
diff --git a/ipaddrset.py b/ipaddrset.py
new file mode 100644 (file)
index 0000000..38d4571
--- /dev/null
@@ -0,0 +1,160 @@
+"""IP address set manipulation, built on top of ipaddress.py"""
+
+# This file is Free Software.  It was originally written for secnet.
+#
+# Copyright 2014 Ian Jackson
+#
+# You may redistribute secnet as a whole and/or modify it under the
+# terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 3, or (at your option) any
+# later version.
+#
+# You may redistribute this file and/or modify it under the terms of
+# the GNU General Public License as published by the Free Software
+# Foundation; either version 2, or (at your option) any later version.
+# Note however that this version of ipaddrset.py uses the Python
+# ipaddr library from Google, which is licenced only under the Apache
+# Licence, version 2.0, which is only compatible with the GNU GPL v3
+# (or perhaps later versions), and not with the GNU GPL v2.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this software; if not, see
+# https://www.gnu.org/licenses/gpl.html.
+
+import ipaddress
+
+_vsns = [6,4]
+
+class IPAddressSet:
+       "A set of IP addresses"
+
+       # constructors
+       def __init__(self,l=[]):
+               "New set contains each IP*Network in the sequence l"
+               self._v = {}
+               for v in _vsns:
+                       self._v[v] = [ ]
+               self.append(l)
+
+       # housekeeping and representation
+       def _compact(self):
+               for v in _vsns:
+                       self._v[v] = list(
+                               ipaddress.collapse_addresses(self._v[v]))
+       def __repr__(self):
+               return "IPAddressSet(%s)" % self.networks()
+       def str(self,comma=",",none="-"):
+               "Human-readable string with controllable delimiters"
+               if self:
+                       return comma.join(map(str, self.networks()))
+               else:
+                       return none
+       def __str__(self):
+               return self.str()
+
+       # mutators
+       def append(self,l):
+               "Appends each IP*Network in the sequence l to self"
+               self._append(l)
+               self._compact()
+
+       def _append(self,l):
+               "Appends each IP*Network in the sequence l to self"
+               for a in l:
+                       self._v[a.version].append(a)
+
+       # enquirers including standard comparisons
+       def __bool__(self):
+               for v in _vsns:
+                       if self._v[v]:
+                               return True
+               return False
+       __nonzero__=__bool__ # for python2
+
+       def __eq__(self,other):
+               for v in _vsns:
+                       if self._v[v] != other._v[v]:
+                               return False
+               return True
+       def __ne__(self,other): return not self.__eq__(other)
+       def __ge__(self,other):
+               """True iff self completely contains IPAddressSet other"""
+               for o in other:
+                       if not self._contains_net(o):
+                               return False
+               return True
+       def __le__(self,other): return other.__ge__(self)
+       def __gt__(self,other): return self!=other and other.__ge__(self)
+       def __lt__(self,other): return other.__gt__(self)
+
+       def __cmp__(self,other):
+               if self==other: return 0
+               if self>=other: return +1
+               if self<=other: return -1
+               return NotImplemented
+
+       def __iter__(self):
+               "Iterates over minimal list of distinct IPNetworks in this set"
+               for v in _vsns:
+                       for i in self._v[v]:
+                               yield i
+
+       def networks(self):
+               "Returns miminal list of distinct IPNetworks in this set"
+               return [i for i in self]
+
+       # set operations
+       def intersection(self,other):
+               "Returns the intersection; does not modify self"
+               r = IPAddressSet()
+               for v in _vsns:
+                       for i in self._v[v]:
+                               for j in other._v[v]:
+                                       if i.overlaps(j):
+                                               if i.prefixlen > j.prefixlen:
+                                                       r._append([i])
+                                               else:
+                                                       r._append([j])
+               return r
+       def union(self,other):
+               "Returns the union; does not modify self"
+               r = IPAddressSet()
+               r._append(self.networks())
+               r._append(other.networks())
+               r._compact()
+               return r
+
+       def _contains_net(self,n):
+               """True iff self completely contains IPNetwork n"""
+               for i in self:
+                       if i.overlaps(n) and n.prefixlen >= i.prefixlen:
+                               return True
+               return False
+
+       def contains(self,thing):
+               """Returns True iff self completely contains thing.
+                  thing may be an IPNetwork or an IPAddressSet"""
+               try:
+                       v = [thing.version]
+               except KeyError:
+                       v = None
+               if v:
+                       return self._contains_net(ipaddress.ip_network(thing))
+               else:
+                       return self.__ge__(thing)
+
+def complete_set():
+       "Returns a set containing all addresses"
+       s=IPAddressSet()
+       for v in _vsns:
+               if v==6: a=ipaddress.IPv6Address(0)
+               elif v==4: a=ipaddress.IPv4Address(0)
+               else: raise "internal error"
+               n=ipaddress.ip_network("%s/0" % a)
+               s.append([n])
+       return s
diff --git a/linux/if_tun.h b/linux/if_tun.h
deleted file mode 100644 (file)
index ec4e9db..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- *  Universal TUN/TAP device driver.
- *  Copyright (C) 1999-2000 Maxim Krasnyansky <max_mk@yahoo.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- *  GNU General Public License for more details.
- *
- *  $Id: if_tun.h,v 1.1 2001/11/21 14:54:03 steve Exp $
- */
-
-#ifndef __IF_TUN_H
-#define __IF_TUN_H
-
-/* Uncomment to enable debugging */
-/* #define TUN_DEBUG 1 */
-
-#ifdef __KERNEL__
-
-#ifdef TUN_DEBUG
-#define DBG  if(tun->debug)printk
-#define DBG1 if(debug==2)printk
-#else
-#define DBG( a... )
-#define DBG1( a... )
-#endif
-
-struct tun_struct {
-       char                    *name;
-       unsigned long           flags;
-       int                     attached;
-       uid_t                   owner;
-
-       wait_queue_head_t       read_wait;
-       struct sk_buff_head     readq;
-
-       struct net_device       dev;
-       struct net_device_stats stats;
-
-       struct fasync_struct    *fasync;
-
-#ifdef TUN_DEBUG       
-       int debug;
-#endif  
-};
-
-#ifndef MIN
-#define MIN(a,b) ( (a)<(b) ? (a):(b) ) 
-#endif
-
-#endif /* __KERNEL__ */
-
-/* Read queue size */
-#define TUN_READQ_SIZE 10
-
-/* TUN device flags */
-#define TUN_TUN_DEV    0x0001  
-#define TUN_TAP_DEV    0x0002
-#define TUN_TYPE_MASK   0x000f
-
-#define TUN_FASYNC     0x0010
-#define TUN_NOCHECKSUM 0x0020
-#define TUN_NO_PI      0x0040
-#define TUN_ONE_QUEUE  0x0080
-#define TUN_PERSIST    0x0100  
-
-/* Ioctl defines */
-#define TUNSETNOCSUM  _IOW('T', 200, int) 
-#define TUNSETDEBUG   _IOW('T', 201, int) 
-#define TUNSETIFF     _IOW('T', 202, int) 
-#define TUNSETPERSIST _IOW('T', 203, int) 
-#define TUNSETOWNER   _IOW('T', 204, int)
-
-/* TUNSETIFF ifr flags */
-#define IFF_TUN                0x0001
-#define IFF_TAP                0x0002
-#define IFF_NO_PI      0x1000
-#define IFF_ONE_QUEUE  0x2000
-
-struct tun_pi {
-       unsigned short flags;
-       unsigned short proto;
-};
-#define TUN_PKT_STRIP  0x0001
-
-#endif /* __IF_TUN_H */
diff --git a/log.c b/log.c
index d330113f0c1b8fe0255eab9ea60f87c37827686c..f937d2ec35d4500294d568d43da2e4258ed3512e 100644 (file)
--- a/log.c
+++ b/log.c
@@ -1,3 +1,21 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 #include "secnet.h"
 #include <stdio.h>
 #include <string.h>
@@ -14,8 +32,7 @@ bool_t secnet_is_daemon=False;
 uint32_t message_level=M_WARNING|M_ERR|M_SECURITY|M_FATAL;
 struct log_if *system_log=NULL;
 
-static void vMessageFallback(uint32_t class, const char *message, va_list args)
-    FORMAT(printf,2,0);
+FORMAT(printf,2,0)
 static void vMessageFallback(uint32_t class, const char *message, va_list args)
 {
     FILE *dest=stdout;
@@ -28,29 +45,11 @@ static void vMessageFallback(uint32_t class, const char *message, va_list args)
     }
 }
 
+FORMAT(printf,2,0)
 static void vMessage(uint32_t class, const char *message, va_list args)
 {
-#define MESSAGE_BUFLEN 1023
-    static char buff[MESSAGE_BUFLEN+1]={0,};
-    size_t bp;
-    char *nlp;
-
-    if (system_log) {
-       /* Messages go to the system log interface */
-       bp=strlen(buff);
-       assert(bp < MESSAGE_BUFLEN);
-       vsnprintf(buff+bp,MESSAGE_BUFLEN-bp,message,args);
-       buff[sizeof(buff)-2] = '\n';
-       buff[sizeof(buff)-1] = '\0';
-       /* Each line is sent separately */
-       while ((nlp=strchr(buff,'\n'))) {
-           *nlp=0;
-           slilog(system_log,class,"%s",buff);
-           memmove(buff,nlp+1,strlen(nlp+1)+1);
-       }
-    } else {
-       vMessageFallback(class,message,args);
-    }
+
+    vslilog_part(system_log, class, message, args);
 }  
 
 void Message(uint32_t class, const char *message, ...)
@@ -62,8 +61,7 @@ void Message(uint32_t class, const char *message, ...)
     va_end(ap);
 }
 
-static void MessageFallback(uint32_t class, const char *message, ...)
-    FORMAT(printf,2,3);
+FORMAT(printf,2,3)
 static void MessageFallback(uint32_t class, const char *message, ...)
 {
     va_list ap;
@@ -76,6 +74,7 @@ static void MessageFallback(uint32_t class, const char *message, ...)
 static NORETURN(vfatal(int status, bool_t perror, const char *message,
                       va_list args));
 
+FORMAT(printf,3,0)
 static void vfatal(int status, bool_t perror, const char *message,
                   va_list args)
 {
@@ -126,7 +125,8 @@ void fatal_perror_status(int status, const char *message, ...)
 }
 
 void vcfgfatal_maybefile(FILE *maybe_f /* or 0 */, struct cloc loc,
-                        cstring_t facility, const char *message, va_list args)
+                        cstring_t facility, const char *message, va_list args,
+                        const char *suffix)
 {
     enter_phase(PHASE_SHUTDOWN);
 
@@ -148,6 +148,7 @@ void vcfgfatal_maybefile(FILE *maybe_f /* or 0 */, struct cloc loc,
     }
     
     vMessage(M_FATAL,message,args);
+    Message(M_FATAL,"%s",suffix);
     exit(current_phase);
 }
 
@@ -157,7 +158,7 @@ void cfgfatal_maybefile(FILE *maybe_f, struct cloc loc, cstring_t facility,
     va_list args;
 
     va_start(args,message);
-    vcfgfatal_maybefile(maybe_f,loc,facility,message,args);
+    vcfgfatal_maybefile(maybe_f,loc,facility,message,args,0);
     va_end(args);
 }    
 
@@ -166,10 +167,16 @@ void cfgfatal(struct cloc loc, cstring_t facility, const char *message, ...)
     va_list args;
 
     va_start(args,message);
-    vcfgfatal_maybefile(0,loc,facility,message,args);
+    vcfgfatal_maybefile(0,loc,facility,message,args,"");
     va_end(args);
 }
 
+void cfgfile_log__vmsg(void *sst, int class, const char *message, va_list args)
+{
+    struct cfgfile_log *st=sst;
+    vcfgfatal_maybefile(0,st->loc,st->facility,message,args,"\n");
+}
+
 void cfgfile_postreadcheck(struct cloc loc, FILE *f)
 {
     assert(loc.file);
@@ -189,6 +196,7 @@ struct loglist {
     struct loglist *next;
 };
 
+FORMAT(printf, 3, 0)
 static void log_vmulti(void *sst, int class, const char *message, va_list args)
 {
     struct loglist *st=sst, *i;
@@ -203,15 +211,58 @@ static void log_vmulti(void *sst, int class, const char *message, va_list args)
     }
 }
 
-static void log_multi(void *st, int priority, const char *message, ...)
-    FORMAT(printf,3,4);
-static void log_multi(void *st, int priority, const char *message, ...)
+FORMAT(printf, 6, 0)
+void lg_vperror(struct log_if *lg, const char *desc, struct cloc *loc,
+               int class, int errnoval, const char *fmt, va_list al)
 {
-    va_list ap;
+    int status=current_phase;
+    int esave=errno;
 
-    va_start(ap,message);
-    log_vmulti(st,priority,message,ap);
-    va_end(ap);
+    if (!lg)
+       lg=system_log;
+
+    if (class & M_FATAL)
+       enter_phase(PHASE_SHUTDOWN);
+
+    slilog_part(lg,class,"%s",desc);
+    if (loc)
+       slilog_part(lg,class," (%s:%d)",loc->file,loc->line);
+    slilog_part(lg,class,": ");
+    vslilog_part(lg,class,fmt,al);
+    if (errnoval)
+       slilog_part(lg,class,": %s",strerror(errnoval));
+    slilog_part(lg,class,"\n");
+
+    if (class & M_FATAL)
+       exit(status);
+
+    errno=esave;
+}
+
+void lg_perror(struct log_if *lg, const char *desc, struct cloc *loc,
+              int class, int errnoval, const char *fmt, ...)
+{
+    va_list al;
+    va_start(al,fmt);
+    lg_vperror(lg,desc,loc,class,errnoval,fmt,al);
+    va_end(al);
+}
+
+void lg_exitstatus(struct log_if *lg, const char *desc, struct cloc *loc,
+                  int class, int status, const char *progname)
+{
+    if (!status)
+       lg_perror(lg,desc,loc,class,0,"%s exited",progname);
+    else if (WIFEXITED(status))
+       lg_perror(lg,desc,loc,class,0,"%s exited with error exit status %d",
+                 progname,WEXITSTATUS(status));
+    else if (WIFSIGNALED(status))
+       lg_perror(lg,desc,loc,class,0,"%s died due to fatal signal %s (%d)%s",
+                 progname,strsignal(WTERMSIG(status)),WTERMSIG(status),
+                 WCOREDUMP(status)?" (core dumped)":"");
+    else
+       lg_perror(lg,desc,loc,class,0,"%s died with unknown wait status %d",
+                 progname,status);
 }
 
 struct log_if *init_log(list_t *ll)
@@ -238,7 +289,7 @@ struct log_if *init_log(list_t *ll)
        if (cl->type!=CL_LOG) {
            cfgfatal(item->loc,"init_log","closure is not a logger");
        }
-       n=safe_malloc(sizeof(*n),"init_log");
+       NEW(n);
        n->l=cl->interface;
        n->next=l;
        l=n;
@@ -246,10 +297,10 @@ struct log_if *init_log(list_t *ll)
     if (!l) {
        fatal("init_log: no log");
     }
-    r=safe_malloc(sizeof(*r), "init_log");
+    NEW(r);
     r->st=l;
-    r->logfn=log_multi;
     r->vlogfn=log_vmulti;
+    r->buff[0]=0;
     return r;
 }
 
@@ -260,37 +311,44 @@ struct logfile {
     string_t logfile;
     uint32_t level;
     FILE *f;
+    const char *prefix;
+    bool_t forked;
 };
 
 static cstring_t months[]={
     "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
 
+FORMAT(printf, 3, 0)
 static void logfile_vlog(void *sst, int class, const char *message,
                         va_list args)
 {
     struct logfile *st=sst;
     time_t t;
     struct tm *tm;
+    char pidbuf[20];
 
-    if (secnet_is_daemon && st->f) {
-       if (class&st->level) {
-           t=time(NULL);
-           tm=localtime(&t);
-           fprintf(st->f,"%s %2d %02d:%02d:%02d ",
-                   months[tm->tm_mon],tm->tm_mday,tm->tm_hour,tm->tm_min,
-                   tm->tm_sec);
-           vfprintf(st->f,message,args);
-           fprintf(st->f,"\n");
-           fflush(st->f);
-       }
+    if (st->forked) {
+       pid_t us=getpid();
+       snprintf(pidbuf,sizeof(pidbuf),"[%ld] ",(long)us);
     } else {
-       vMessageFallback(class,message,args);
-       MessageFallback(class,"\n");
+       pidbuf[0]=0;
+    }
+
+    if (class&st->level) {
+       t=time(NULL);
+       tm=localtime(&t);
+       fprintf(st->f,"%s %2d %02d:%02d:%02d %s%s%s",
+               months[tm->tm_mon],tm->tm_mday,tm->tm_hour,tm->tm_min,
+               tm->tm_sec,
+               st->prefix, st->prefix[0] ? " " : "",
+               pidbuf);
+       vfprintf(st->f,message,args);
+       fprintf(st->f,"\n");
+       fflush(st->f);
     }
 }
 
-static void logfile_log(void *state, int class, const char *message, ...)
-    FORMAT(printf,3,4);
+FORMAT(printf,3,4)
 static void logfile_log(void *state, int class, const char *message, ...)
 {
     va_list ap;
@@ -304,6 +362,7 @@ static void logfile_hup_notify(void *sst, int signum)
 {
     struct logfile *st=sst;
     FILE *f;
+    if (!st->logfile) return;
     f=fopen(st->logfile,"a");
     if (!f) {
        logfile_log(st,M_FATAL,"received SIGHUP, but could not reopen "
@@ -320,7 +379,7 @@ static void logfile_phase_hook(void *sst, uint32_t new_phase)
     struct logfile *st=sst;
     FILE *f;
 
-    if (background) {
+    if (background && st->logfile) {
        f=fopen(st->logfile,"a");
        if (!f) fatal_perror("logfile (%s:%d): cannot open \"%s\"",
                             st->loc.file,st->loc.line,st->logfile);
@@ -329,6 +388,12 @@ static void logfile_phase_hook(void *sst, uint32_t new_phase)
     }
 }
 
+static void logfile_childpersist_hook(void *sst, uint32_t new_phase)
+{
+    struct logfile *st=sst;
+    st->forked=1;
+}
+
 static struct flagstr message_class_table[]={
     { "debug-config", M_DEBUG_CONFIG },
     { "debug-phase", M_DEBUG_PHASE },
@@ -346,6 +411,23 @@ static struct flagstr message_class_table[]={
     { NULL, 0 }
 };
 
+static void logfile_file_init(struct logfile *st, FILE *f, const char *desc)
+{
+    st->cl.description=desc;
+    st->cl.type=CL_LOG;
+    st->cl.apply=NULL;
+    st->cl.interface=&st->ops;
+    st->ops.st=st;
+    st->ops.vlogfn=logfile_vlog;
+    st->ops.buff[0]=0;
+    st->f=f;
+    st->logfile=0;
+    st->prefix="";
+    st->forked=0;
+    st->loc.file=0;
+    st->loc.line=-1;
+}
+
 static list_t *logfile_apply(closure_t *self, struct cloc loc, dict_t *context,
                             list_t *args)
 {
@@ -357,16 +439,9 @@ static list_t *logfile_apply(closure_t *self, struct cloc loc, dict_t *context,
        phase.  We should defer writing into the logfile until after we
        become a daemon. */
     
-    st=safe_malloc(sizeof(*st),"logfile_apply");
-    st->cl.description="logfile";
-    st->cl.type=CL_LOG;
-    st->cl.apply=NULL;
-    st->cl.interface=&st->ops;
-    st->ops.st=st;
-    st->ops.logfn=logfile_log;
-    st->ops.vlogfn=logfile_vlog;
+    NEW(st);
     st->loc=loc;
-    st->f=NULL;
+    logfile_file_init(st,stderr,"logfile");
 
     item=list_elem(args,0);
     if (!item || item->type!=t_dict) {
@@ -374,11 +449,14 @@ static list_t *logfile_apply(closure_t *self, struct cloc loc, dict_t *context,
     }
     dict=item->data.dict;
 
-    st->logfile=dict_read_string(dict,"filename",True,"logfile",loc);
+    st->logfile=dict_read_string(dict,"filename",False,"logfile",loc);
+    st->prefix=dict_read_string(dict,"prefix",False,"logfile",loc);
+    if (!st->prefix) st->prefix="";
     st->level=string_list_to_word(dict_lookup(dict,"class"),
                                       message_class_table,"logfile");
 
-    add_hook(PHASE_GETRESOURCES,logfile_phase_hook,st);
+    add_hook(PHASE_DAEMONIZE,logfile_phase_hook,st);
+    add_hook(PHASE_CHILDPERSIST,logfile_childpersist_hook,st);
 
     return new_closure(&st->cl);
 }
@@ -423,17 +501,6 @@ static void syslog_vlog(void *sst, int class, const char *message,
     }
 }
 
-static void syslog_log(void *sst, int priority, const char *message, ...)
-    FORMAT(printf,3,4);
-static void syslog_log(void *sst, int priority, const char *message, ...)
-{
-    va_list ap;
-
-    va_start(ap,message);
-    syslog_vlog(sst,priority,message,ap);
-    va_end(ap);
-}
-
 static struct flagstr syslog_facility_table[]={
 #ifdef LOG_AUTH
     { "auth", LOG_AUTH },
@@ -466,7 +533,9 @@ static void syslog_phase_hook(void *sst, uint32_t newphase)
     struct syslog *st=sst;
 
     if (background) {
-       openlog(st->ident,0,st->facility);
+       openlog(st->ident,
+               newphase==PHASE_CHILDPERSIST ? LOG_PID : 0,
+               st->facility);
        st->open=True;
     }
 }
@@ -479,14 +548,14 @@ static list_t *syslog_apply(closure_t *self, struct cloc loc, dict_t *context,
     item_t *item;
     string_t facstr;
 
-    st=safe_malloc(sizeof(*st),"syslog_apply");
+    NEW(st);
     st->cl.description="syslog";
     st->cl.type=CL_LOG;
     st->cl.apply=NULL;
     st->cl.interface=&st->ops;
     st->ops.st=st;
-    st->ops.logfn=syslog_log;
     st->ops.vlogfn=syslog_vlog;
+    st->ops.buff[0]=0;
 
     item=list_elem(args,0);
     if (!item || item->type!=t_dict)
@@ -498,7 +567,8 @@ static list_t *syslog_apply(closure_t *self, struct cloc loc, dict_t *context,
     st->facility=string_to_word(facstr,loc,
                                syslog_facility_table,"syslog");
     st->open=False;
-    add_hook(PHASE_GETRESOURCES,syslog_phase_hook,st);
+    add_hook(PHASE_DAEMONIZE,syslog_phase_hook,st);
+    add_hook(PHASE_CHILDPERSIST,syslog_phase_hook,st);
 
     return new_closure(&st->cl);
 }    
@@ -521,9 +591,11 @@ static int log_from_fd_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
 {
     struct fdlog *st=sst;
     if (!st->finished) {
-       *nfds_io=1;
+       BEFOREPOLL_WANT_FDS(1);
        fds[0].fd=st->fd;
        fds[0].events=POLLIN;
+    } else {
+       BEFOREPOLL_WANT_FDS(0);
     }
     return 0;
 }
@@ -560,6 +632,7 @@ static void log_from_fd_afterpoll(void *sst, struct pollfd *fds, int nfds)
                    i=-1;
                }
            }
+       } else if (errno==EINTR || iswouldblock(errno)) {
        } else {
            Message(M_WARNING,"log_from_fd: %s\n",strerror(errno));
            st->finished=True;
@@ -571,7 +644,7 @@ void log_from_fd(int fd, cstring_t prefix, struct log_if *log)
 {
     struct fdlog *st;
 
-    st=safe_malloc(sizeof(*st),"log_from_fd");
+    NEW(st);
     st->log=log;
     st->fd=fd;
     st->prefix=prefix;
@@ -579,12 +652,26 @@ void log_from_fd(int fd, cstring_t prefix, struct log_if *log)
     st->i=0;
     st->finished=False;
 
-    register_for_poll(st,log_from_fd_beforepoll,log_from_fd_afterpoll,1,
+    setnonblock(st->fd);
+
+    register_for_poll(st,log_from_fd_beforepoll,log_from_fd_afterpoll,
                      prefix);
 }
 
+static struct logfile startup_log;
+void log_early_init(void)
+{
+    logfile_file_init(&startup_log,stderr,"startup");
+    system_log=&startup_log.ops;;
+}
+
+/* for the benefit of main, really */
+void logfile_init_file(struct logfile *st, FILE *f);
+
 void log_module(dict_t *dict)
 {
+    setlinebuf(stderr);
+
     add_closure(dict,"logfile",logfile_apply);
     add_closure(dict,"syslog",syslog_apply);
 }
diff --git a/magic.h b/magic.h
index 598a79e1a681acffa661f886283b35f0785c0fff..15d84985299f79156344b0045b9f0600bd7dd42b 100644 (file)
--- a/magic.h
+++ b/magic.h
 /* Magic numbers used within secnet */
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 
 #ifndef magic_h
 #define magic_h
 
-#define LABEL_NAK     0x00000000
-#define LABEL_MSG0    0x00020200
-#define LABEL_MSG1    0x01010101
-#define LABEL_MSG2    0x02020202
-#define LABEL_MSG3    0x03030303
-#define LABEL_MSG3BIS 0x13030313
-#define LABEL_MSG4    0x04040404
-#define LABEL_MSG5    0x05050505
-#define LABEL_MSG6    0x06060606
-#define LABEL_MSG7    0x07070707
-#define LABEL_MSG8    0x08080808
-#define LABEL_MSG9    0x09090909
-#define LABEL_PROD    0x0a0a0a0a
+/* Encode a pair of 16 bit major and minor codes as a single 32-bit label.
+ * The encoding is strange for historical reasons.  Suppose that the nibbles
+ * of the major number are (from high to low) a, b, c, d, and the minor
+ * number has nibbles w, x, y, z.  (Here, a, b, c, d are variables, not hex
+ * digits.)  We scramble them to form a message label as follows.
+ *
+ *     0 d 0 d 0 d 0 d
+ *     0 0 0 a b c 0 0
+ *     z 0 0 0 0 0 z 0
+ *     w x y 0 0 0 0 0
+ *     ---------------
+ *     f g h i j k l m
+ *
+ * and calculate the nibbles f, g, ..., m of the message label (higher
+ * significance on the left) by XORing the columns.  It can be shown that
+ * this is invertible using linear algebra in GF(16), but but it's easier to
+ * notice that d = m, z = l, c = k XOR d, b = j, a = i XOR d, y = h,
+ * x = g XOR d, and w = f XOR z.
+ *
+ * Encoding in the forward direction, from a major/minor pair to a label, is
+ * (almost?) always done on constants, so its performance is fairly
+ * unimportant.  There is a compatibility constraint on the patterns produced
+ * with a = b = c = w = x = y = 0.  Subject to that, I wanted to find an
+ * invertible GF(16)-linear transformation which would let me recover the
+ * major and minor numbers with relatively little calculation.
+ */
+
+#define MSGCODE(major, minor)                                          \
+       ((((uint32_t)(major)&0x0000000fu) <<  0) ^                      \
+        (((uint32_t)(major)&0x0000000fu) <<  8) ^                      \
+        (((uint32_t)(major)&0x0000000fu) << 16) ^                      \
+        (((uint32_t)(major)&0x0000000fu) << 24) ^                      \
+        (((uint32_t)(major)&0x0000fff0u) <<  4) ^                      \
+        (((uint32_t)(minor)&0x0000000fu) <<  4) ^                      \
+        (((uint32_t)(minor)&0x0000000fu) << 28) ^                      \
+        (((uint32_t)(minor)&0x0000fff0u) << 16))
 
-/* uses of the 32-bit capability bitmap */
-#define CAPAB_EARLY           0x00000000 /* no Early flags yet (see NOTES) */
-#define CAPAB_TRANSFORM_MASK  0x0000ffff
-/* remaining 16 bits are unused */
+/* Extract major and minor codes from a 32-bit message label. */
+#define MSGMAJOR(label)                                                        \
+       ((((uint32_t)(label)&0x0000000fu) <<  0) ^                      \
+        (((uint32_t)(label)&0x0000000fu) <<  4) ^                      \
+        (((uint32_t)(label)&0x0000000fu) << 12) ^                      \
+        (((uint32_t)(label)&0x000fff00u) >>  4))
+#define MSGMINOR(label)                                                        \
+       ((((uint32_t)(label)&0x000000ffu) <<  8) ^                      \
+        (((uint32_t)(label)&0x000000f0u) >>  4) ^                      \
+        (((uint32_t)(label)&0xfff00000u) >> 16))
+
+#define LABEL_NAK      MSGCODE(     0, 0)
+#define LABEL_MSG0     MSGCODE(0x2020, 0) /* ! */
+#define LABEL_MSG1     MSGCODE(     1, 0)
+#define LABEL_MSG2     MSGCODE(     2, 0)
+#define LABEL_MSG3     MSGCODE(     3, 0)
+#define LABEL_MSG3BIS  MSGCODE(     3, 1)
+#define LABEL_MSG4     MSGCODE(     4, 0)
+#define LABEL_MSG5     MSGCODE(     5, 0)
+#define LABEL_MSG6     MSGCODE(     6, 0)
+#define LABEL_MSG7     MSGCODE(     7, 0)
+#define LABEL_MSG8     MSGCODE(     8, 0)
+#define LABEL_MSG9     MSGCODE(     9, 0)
+#define LABEL_PROD     MSGCODE(    10, 0)
 
 /*
- * The transform capability mask is a set of bits, one for each
- * transform supported.  The transform capability numbers are set in
- * the configuration (and should correspond between the two sites),
- * although there are sensible defaults.
+ * The capability mask is a set of bits, one for each optional feature
+ * supported.  The capability numbers for transforms are set in the
+ * configuration (and should correspond between the two sites), although
+ * there are sensible defaults.
  *
- * Advertising a nonzero transform capability mask promises that
- * the receiver understands LABEL_MSG3BIS messages, which
- * contain an additional byte specifying the transform capability
- * number actually chosen by the MSG3 sender.
+ * Advertising a nonzero capability mask promises that the receiver
+ * understands LABEL_MSG3BIS messages, which contain an additional byte
+ * specifying the transform capability number actually chosen by the MSG3
+ * sender.
  *
  * Aside from that, an empty bitmask is treated the same as
- *  1u<<CAPAB_TRANSFORMNUM_ANCIENT
+ *  1u<<CAPAB_BIT_ANCIENTTRANSFORM
  */
 
+/* uses of the 32-bit capability bitmap */
+#define CAPAB_TRANSFORM_MASK  0x0000ffff
+#define CAPAB_PRIORITY_MOBILE 0x80000000 /* mobile site has MSG1 priority */
+/* remaining bits are unused */
+
 /* bit indices, 0 is ls bit */
-#define CAPAB_TRANSFORMNUM_USER_MIN              0
-#define CAPAB_TRANSFORMNUM_USER_MAX              7
-#define CAPAB_TRANSFORMNUM_SERPENT256CBC         8
-#define CAPAB_TRANSFORMNUM_EAXSERPENT            9
-#define CAPAB_TRANSFORMNUM_MAX                  15
+#define CAPAB_BIT_USER_MIN              0
+#define CAPAB_BIT_USER_MAX              7
+#define CAPAB_BIT_SERPENT256CBC         8
+#define CAPAB_BIT_EAXSERPENT            9
+#define CAPAB_BIT_MAX                  15
 
-#define CAPAB_TRANSFORMNUM_ANCIENT CAPAB_TRANSFORMNUM_SERPENT256CBC
+#define CAPAB_BIT_ANCIENTTRANSFORM CAPAB_BIT_SERPENT256CBC
 
 #endif /* magic_h */
index 966bb77528e409cd3991e2c5bd5306d4c1c264d1..70fa7a4b9dd7483dc78b3612dd6e25b04fa65c60 100755 (executable)
@@ -1,19 +1,21 @@
-#! /usr/bin/env python
-# Copyright (C) 2001-2002 Stephen Early <steve@greenend.org.uk>
+#! /usr/bin/env python3
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
+# This file is part of secnet.
+# See README for full list of copyright holders.
+#
+# secnet is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
 # (at your option) any later version.
 # 
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
+# secnet is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
 # 
 # You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+# version 3 along with secnet; if not, see
+# https://www.gnu.org/licenses/gpl.html.
 
 """VPN sites file manipulation.
 
@@ -44,108 +46,613 @@ no-suppress-args
 cd ~/secnet/sites-test/
 execute ~/secnet/make-secnet-sites.py -u vpnheader groupfiles sites
 
-This program is part of secnet. It relies on the "ipaddr" library from
-Cendio Systems AB.
+This program is part of secnet.
 
 """
 
+from __future__ import print_function
+from __future__ import unicode_literals
+from builtins import int
+
 import string
 import time
 import sys
 import os
 import getopt
 import re
+import argparse
+import math
+
+import ipaddress
+
+# entry 0 is "near the executable", or maybe from PYTHONPATH=.,
+# which we don't want to preempt
+sys.path.insert(1,"/usr/local/share/secnet")
+sys.path.insert(1,"/usr/share/secnet")
+import ipaddrset
+import base91
 
-# The ipaddr library is installed as part of secnet
-sys.path.append("/usr/local/share/secnet")
-sys.path.append("/usr/share/secnet")
-import ipaddr
+from argparseactionnoyes import ActionNoYes
 
 VERSION="0.1.18"
 
+max_version = 2
+
+from sys import version_info
+if version_info.major == 2:  # for python2
+    import codecs
+    sys.stdin = codecs.getreader('utf-8')(sys.stdin)
+    sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
+    import io
+    open=lambda f,m='r': io.open(f,m,encoding='utf-8')
+
+max={'rsa_bits':8200,'name':33,'dh_bits':8200,'algname':127}
+
+def debugrepr(*args):
+       if debug_level > 0:
+               print(repr(args), file=sys.stderr)
+
+def base91s_encode(bindata):
+       return base91.encode(bindata).replace('"',"-")
+
+def base91s_decode(string):
+       return base91.decode(string.replace("-",'"'))
+
+class Tainted:
+       def __init__(self,s,tline=None,tfile=None):
+               self._s=s
+               self._ok=None
+               self._line=line if tline is None else tline
+               self._file=file if tfile is None else tfile
+       def __eq__(self,e):
+               return self._s==e
+       def __ne__(self,e):
+               # for Python2
+               return not self.__eq__(e)
+       def __str__(self):
+               raise RuntimeError('direct use of Tainted value')
+       def __repr__(self):
+               return 'Tainted(%s)' % repr(self._s)
+
+       def _bad(self,what,why):
+               assert(self._ok is not True)
+               self._ok=False
+               complain('bad parameter: %s: %s' % (what, why))
+               return False
+
+       def _max_ok(self,what,maxlen):
+               if len(self._s) > maxlen:
+                       return self._bad(what,'too long (max %d)' % maxlen)
+               return True
+
+       def _re_ok(self,bad,what,maxlen=None):
+               if maxlen is None: maxlen=max[what]
+               self._max_ok(what,maxlen)
+               if self._ok is False: return False
+               if bad.search(self._s):
+                       #print(repr(self), file=sys.stderr)
+                       return self._bad(what,'bad syntax')
+               return True
+
+       def _rtnval(self, is_ok, ifgood, ifbad=''):
+               if is_ok:
+                       assert(self._ok is not False)
+                       self._ok=True
+                       return ifgood
+               else:
+                       assert(self._ok is not True)
+                       self._ok=False
+                       return ifbad
+
+       def _rtn(self, is_ok, ifbad=''):
+               return self._rtnval(is_ok, self._s, ifbad)
+
+       def raw(self):
+               return self._s
+       def raw_mark_ok(self):
+               # caller promises to throw if syntax was dangeorus
+               return self._rtn(True)
+
+       def output(self):
+               if self._ok is False: return ''
+               if self._ok is True: return self._s
+               print('%s:%d: unchecked/unknown additional data "%s"' %
+                     (self._file,self._line,self._s),
+                     file=sys.stderr)
+               sys.exit(1)
+
+       bad_name=re.compile(r'^[^a-zA-Z]|[^-_0-9a-zA-Z]')
+       # secnet accepts _ at start of names, but we reserve that
+       bad_name_counter=0
+       def name(self,what='name'):
+               ok=self._re_ok(Tainted.bad_name,what)
+               return self._rtn(ok,
+                                '_line%d_%s' % (self._line, id(self)))
+
+       def keyword(self):
+               ok=self._s in keywords or self._s in levels
+               if not ok:
+                       complain('unknown keyword %s' % self._s)
+               return self._rtn(ok)
+
+       bad_hex=re.compile(r'[^0-9a-fA-F]')
+       def bignum_16(self,kind,what):
+               maxlen=(max[kind+'_bits']+3)/4
+               ok=self._re_ok(Tainted.bad_hex,what,maxlen)
+               return self._rtn(ok)
+
+       bad_num=re.compile(r'[^0-9]')
+       def bignum_10(self,kind,what):
+               maxlen=math.ceil(max[kind+'_bits'] / math.log10(2))
+               ok=self._re_ok(Tainted.bad_num,what,maxlen)
+               return self._rtn(ok)
+
+       def number(self,minn,maxx,what='number'):
+               # not for bignums
+               ok=self._re_ok(Tainted.bad_num,what,10)
+               if ok:
+                       v=int(self._s)
+                       if v<minn or v>maxx:
+                               ok=self._bad(what,'out of range %d..%d'
+                                            % (minn,maxx))
+               return self._rtnval(ok,v,minn)
+
+       def hexid(self,byteslen,what):
+               ok=self._re_ok(Tainted.bad_hex,what,byteslen*2)
+               if ok:
+                       if len(self._s) < byteslen*2:
+                               ok=self._bad(what,'too short')
+               return self._rtn(ok,ifbad='00'*byteslen)
+
+       bad_host=re.compile(r'[^-\][_.:0-9a-zA-Z]')
+       # We permit _ so we can refer to special non-host domains
+       # which have A and AAAA RRs.  This is a crude check and we may
+       # still produce config files with syntactically invalid
+       # domains or addresses, but that is OK.
+       def host(self):
+               ok=self._re_ok(Tainted.bad_host,'host/address',255)
+               return self._rtn(ok)
+
+       bad_email=re.compile(r'[^-._0-9a-z@!$%^&*=+~/]')
+       # ^ This does not accept all valid email addresses.  That's
+       # not really possible with this input syntax.  It accepts
+       # all ones that don't require quoting anywhere in email
+       # protocols (and also accepts some invalid ones).
+       def email(self):
+               ok=self._re_ok(Tainted.bad_email,'email address',1023)
+               return self._rtn(ok)
+
+       bad_groupname=re.compile(r'^[^_A-Za-z]|[^-+_0-9A-Za-z]')
+       def groupname(self):
+               ok=self._re_ok(Tainted.bad_groupname,'group name',64)
+               return self._rtn(ok)
+
+       bad_base91=re.compile(r'[^!-~]|[\'\"\\]')
+       def base91(self,what='base91'):
+               ok=self._re_ok(Tainted.bad_base91,what,4096)
+               return self._rtn(ok)
+
+class ArgActionLambda(argparse.Action):
+       def __init__(self, fn, **kwargs):
+               self.fn=fn
+               argparse.Action.__init__(self,**kwargs)
+       def __call__(self,ap,ns,values,option_string):
+               self.fn(values,ns,ap,option_string)
+
+class PkmBase():
+       def site_start(self,pubkeys_path):
+               self._pa=pubkeys_path
+               self._fs = FilterState()
+       def site_serial(self,serial): pass
+       def write_key(self,k): pass
+       def site_finish(self,confw): pass
+
+class PkmSingle(PkmBase):
+       opt = 'single'
+       help = 'write one public key per site to sites.conf'
+       def site_start(self,pubkeys_path):
+               PkmBase.site_start(self,pubkeys_path)
+               self._outk = []
+       def write_key(self,k):
+               if k.okforonlykey(output_version,self._fs):
+                       self._outk.append(k)
+       def site_finish(self,confw):
+               if len(self._outk) == 0:
+                       complain("site with no public key");
+               elif len(self._outk) != 1:
+                       debugrepr('outk ', self._outk)
+                       complain(
+ "site with multiple public keys, without --pubkeys-install (maybe --output-version=1 would help"
+                       )
+               else:
+                       confw.write("key %s;\n"%str(self._outk[0]))
+
+class PkmInstall(PkmBase):
+       opt = 'install'
+       help = 'install public keys in public key directory'
+       def site_start(self,pubkeys_path):
+               PkmBase.site_start(self,pubkeys_path)
+               self._pw=open(self._pa+'~tmp','w')
+       def site_serial(self,serial):
+               self._pw.write('serial %s\n' % serial)
+       def write_key(self,k):
+               wout=k.forpub(output_version,self._fs)
+               self._pw.write(' '.join(wout))
+               self._pw.write('\n')
+       def site_finish(self,confw):
+               self._pw.close()
+               os.rename(self._pa+'~tmp',self._pa+'~update')
+               PkmElide.site_finish(self,confw)
+
+class PkmElide(PkmBase):
+       opt = 'elide'
+       help = 'no public keys in sites.conf output nor in directory'
+       def site_finish(self,confw):
+               confw.write("peer-keys \"%s\";\n"%self._pa);
+
+class OpBase():
+       # Base case is reading a sites file from self.inputfilee.
+       # And writing a sites file to self.sitesfile.
+       def positional_args(self, av):
+               if len(av.arg)>3:
+                       print("Too many arguments")
+                       sys.exit(1)
+               (self.inputfile, self.outputfile) = (av.arg + [None]*2)[0:2]
+       def read_in(self):
+               if self.inputfile is None:
+                       self.inputlines = pfile("stdin",sys.stdin.readlines())
+               else:
+                       self.inputlines = pfilepath(self.inputfile)
+       def write_out(self):
+               if self.outputfile is None:
+                       f=sys.stdout
+               else:
+                       f=open(self.outputfile+"-tmp",'w')
+               f.write("# sites file autogenerated by make-secnet-sites\n")
+               self.write_out_heading(f)
+               f.write("# use make-secnet-sites to turn this file into a\n")
+               f.write("# valid /etc/secnet/sites.conf file\n\n")
+               self.write_out_contents(f)
+               f.write("# end of sites file\n")
+               if self.outputfile is not None:
+                       f.close()
+                       os.rename(self.outputfile+"-tmp",self.outputfile)
+
+class OpConf(OpBase):
+       opts = ['--conf']
+       help = 'sites.conf generation mode (default)'
+       def check_group(self,group,w): pass
+       def write_out(self):
+               if self.outputfile is None:
+                       of=sys.stdout
+               else:
+                       tmp_outputfile=self.outputfile+'~tmp~'
+                       of=open(tmp_outputfile,'w')
+               outputsites(of)
+               if self.outputfile is not None:
+                       os.rename(tmp_outputfile,self.outputfile)
+
+class OpFilter(OpBase):
+       opts = ['--filter']
+       help = 'sites file filtering mode'
+       def positional_arXgs(self, av):
+               if len(av.arg)!=1:
+                       print("Too many arguments")
+               (self.inputfile,) = (av.arg + [None])[0:1]
+               self.outputfile = None
+       def write_out_heading(self,f):
+               f.write("# --filter --output-version=%d\n"%output_version)
+       def write_out_contents(self,f):
+               for i in self.inputlines: f.write(i)
+
+class OpUserv(OpBase):
+       opts = ['--userv','-u']
+       help = 'userv service fragment update mode'
+       def positional_args(self, av):
+               if len(av.arg)!=4:
+                       print("Wrong number of arguments")
+                       sys.exit(1)
+               (self.header, self.groupfiledir,
+                self.outputfile, self.group) = av.arg
+               self.group = Tainted(self.group,0,'command line')
+               # untrusted argument from caller
+               if "USERV_USER" not in os.environ:
+                       print("Environment variable USERV_USER not found")
+                       sys.exit(1)
+               self.user=os.environ["USERV_USER"]
+               # Check that group is in USERV_GROUP
+               if "USERV_GROUP" not in os.environ:
+                       print("Environment variable USERV_GROUP not found")
+                       sys.exit(1)
+               ugs=os.environ["USERV_GROUP"]
+               ok=0
+               for i in ugs.split():
+                       if self.group==i: ok=1
+               if not ok:
+                       print("caller not in group %s"%group)
+                       sys.exit(1)
+       def check_group(self,group,w):
+               if group!=self.group: complain("Incorrect group!")
+               w[2].groupname()
+       def read_in(self):
+               self.headerinput=pfilepath(self.header,allow_include=True)
+               self.userinput=sys.stdin.readlines()
+               pfile("user input",self.userinput)
+       def write_out(self):
+               # Put the user's input into their group file, and
+               # rebuild the main sites file
+               f=open(self.groupfiledir+"/T"+self.group.groupname(),'w')
+               f.write("# Section submitted by user %s, %s\n"%
+                       (self.user,time.asctime(time.localtime(time.time()))))
+               f.write("# Checked by make-secnet-sites version %s\n\n"
+                       %VERSION)
+               for i in self.userinput: f.write(i)
+               f.write("\n")
+               f.close()
+               os.rename(self.groupfiledir+"/T"+self.group.groupname(),
+                         self.groupfiledir+"/R"+self.group.groupname())
+               OpBase.write_out(self)
+       def write_out_heading(self,f):
+               f.write("# generated %s, invoked by %s\n"%
+                       (time.asctime(time.localtime(time.time())),
+                        self.user))
+       def write_out_contents(self,f):
+               for i in self.headerinput: f.write(i)
+               files=os.listdir(self.groupfiledir)
+               for i in files:
+                       if i[0]=='R':
+                               j=open(self.groupfiledir+"/"+i)
+                               f.write(j.read())
+                               j.close()
+
+def parse_args():
+       global opmode
+       global prefix
+       global key_prefix
+       global debug_level
+       global output_version
+       global pubkeys_dir
+       global pubkeys_mode
+
+       ap = argparse.ArgumentParser(description='process secnet sites files')
+       def add_opmode(how):
+               ap.add_argument(*how().opts, action=ArgActionLambda,
+                       nargs=0,
+                       fn=(lambda v,ns,*x: setattr(ns,'opmode',how)),
+                       help=how().help)
+       add_opmode(OpConf)
+       add_opmode(OpFilter)
+       add_opmode(OpUserv)
+       ap.add_argument('--conf-key-prefix', action=ActionNoYes,
+                       default=True,
+                help='prefix conf file key names derived from sites data')
+       def add_pkm(how):
+               ap.add_argument('--pubkeys-'+how().opt, action=ArgActionLambda,
+                       nargs=0,
+                       fn=(lambda v,ns,*x: setattr(ns,'pkm',how)),
+                       help=how().help)
+       add_pkm(PkmInstall)
+       add_pkm(PkmSingle)
+       add_pkm(PkmElide)
+       ap.add_argument('--pubkeys-dir',  nargs=1,
+                       help='public key directory',
+                       default=['/var/lib/secnet/pubkeys'])
+       ap.add_argument('--output-version', nargs=1, type=int,
+                       help='sites file output version',
+                       default=[max_version])
+       ap.add_argument('--prefix', '-P', nargs=1,
+                       help='set prefix')
+       ap.add_argument('--debug', '-D', action='count', default=0)
+       ap.add_argument('arg',nargs=argparse.REMAINDER)
+       av = ap.parse_args()
+       debug_level = av.debug
+       debugrepr('av',av)
+       opmode = getattr(av,'opmode',OpConf)()
+       prefix = '' if av.prefix is None else av.prefix[0]
+       key_prefix = av.conf_key_prefix
+       output_version = av.output_version[0]
+       pubkeys_dir = av.pubkeys_dir[0]
+       pubkeys_mode = getattr(av,'pkm',PkmSingle)
+       opmode.positional_args(av)
+
+parse_args()
+
 # Classes describing possible datatypes in the configuration file
 
-class single_ipaddr:
+class basetype:
+       "Common protocol for configuration types."
+       def add(self,obj,w):
+               complain("%s %s already has property %s defined"%
+                       (obj.type,obj.name,w[0].raw()))
+       def forsites(self,version,copy,fs):
+               return copy
+
+class conflist:
+       "A list of some kind of configuration type."
+       def __init__(self,subtype,w):
+               self.subtype=subtype
+               self.list=[subtype(w)]
+       def add(self,obj,w):
+               self.list.append(self.subtype(w))
+       def __str__(self):
+               return ', '.join(map(str, self.list))
+       def forsites(self,version,copy,fs):
+               most_recent=self.list[len(self.list)-1]
+               return most_recent.forsites(version,copy,fs)
+def listof(subtype):
+       return lambda w: conflist(subtype, w)
+
+class single_ipaddr (basetype):
        "An IP address"
        def __init__(self,w):
-               self.addr=ipaddr.ipaddr(w[1])
+               self.addr=ipaddress.ip_address(w[1].raw_mark_ok())
        def __str__(self):
-               return '"%s"'%self.addr.ip_str()
+               return '"%s"'%self.addr
 
-class networks:
+class networks (basetype):
        "A set of IP addresses specified as a list of networks"
        def __init__(self,w):
-               self.set=ipaddr.ip_set()
+               self.set=ipaddrset.IPAddressSet()
                for i in w[1:]:
-                       x=string.split(i,"/")
-                       self.set.append(ipaddr.network(x[0],x[1],
-                               ipaddr.DEMAND_NETWORK))
+                       x=ipaddress.ip_network(i.raw_mark_ok(),strict=True)
+                       self.set.append([x])
        def __str__(self):
-               return string.join(map(lambda x:'"%s/%s"'%(x.ip_str(),
-                       x.mask.netmask_bits_str),
-                       self.set.as_list_of_networks()),",")
+               return ",".join(map((lambda n: '"%s"'%n), self.set.networks()))
 
-class dhgroup:
+class dhgroup (basetype):
        "A Diffie-Hellman group"
        def __init__(self,w):
-               self.mod=w[1]
-               self.gen=w[2]
+               self.mod=w[1].bignum_16('dh','dh mod')
+               self.gen=w[2].bignum_16('dh','dh gen')
        def __str__(self):
                return 'diffie-hellman("%s","%s")'%(self.mod,self.gen)
 
-class hash:
+class hash (basetype):
        "A choice of hash function"
        def __init__(self,w):
-               self.ht=w[1]
+               hname=w[1]
+               self.ht=hname.raw()
                if (self.ht!='md5' and self.ht!='sha1'):
                        complain("unknown hash type %s"%(self.ht))
+                       self.ht=None
+               else:
+                       hname.raw_mark_ok()
        def __str__(self):
                return '%s'%(self.ht)
 
-class email:
+class email (basetype):
        "An email address"
        def __init__(self,w):
-               self.addr=w[1]
+               self.addr=w[1].email()
        def __str__(self):
                return '<%s>'%(self.addr)
 
-class boolean:
+class boolean (basetype):
        "A boolean"
        def __init__(self,w):
-               if re.match('[TtYy1]',w[1]):
+               v=w[1]
+               if re.match('[TtYy1]',v.raw()):
                        self.b=True
-               elif re.match('[FfNn0]',w[1]):
+                       v.raw_mark_ok()
+               elif re.match('[FfNn0]',v.raw()):
                        self.b=False
+                       v.raw_mark_ok()
                else:
                        complain("invalid boolean value");
        def __str__(self):
                return ['False','True'][self.b]
 
-class num:
+class num (basetype):
        "A decimal number"
        def __init__(self,w):
-               self.n=string.atol(w[1])
+               self.n=w[1].number(0,0x7fffffff)
        def __str__(self):
                return '%d'%(self.n)
 
-class address:
+class serial (basetype):
+       def __init__(self,w):
+               self.i=w[1].hexid(4,'serial')
+       def __str__(self):
+               return self.i
+       def forsites(self,version,copy,fs):
+               if version < 2: return []
+               return copy
+
+class address (basetype):
        "A DNS name and UDP port number"
        def __init__(self,w):
-               self.adr=w[1]
-               self.port=string.atoi(w[2])
-               if (self.port<1 or self.port>65535):
-                       complain("invalid port number")
+               self.adr=w[1].host()
+               self.port=w[2].number(1,65536,'port')
        def __str__(self):
                return '"%s"; port %d'%(self.adr,self.port)
 
-class rsakey:
-       "An RSA public key"
+class inpub (basetype):
+       def forsites(self,version,xcopy,fs):
+               return self.forpub(version,fs)
+
+class pubkey (inpub):
+       "Some kind of publie key"
        def __init__(self,w):
-               self.l=string.atoi(w[1])
-               self.e=w[2]
-               self.n=w[3]
+               self.a=w[1].name('algname')
+               self.d=w[2].base91();
+       def __str__(self):
+               return 'make-public("%s","%s")'%(self.a,self.d)
+       def forpub(self,version,fs):
+               if version < 2: return []
+               return ['pub', self.a, self.d]
+       def okforonlykey(self,version,fs):
+               return len(self.forpub(version,fs)) != 0
+
+class rsakey (pubkey):
+       "An old-style RSA public key"
+       def __init__(self,w):
+               self.l=w[1].number(0,max['rsa_bits'],'rsa len')
+               self.e=w[2].bignum_10('rsa','rsa e')
+               self.n=w[3].bignum_10('rsa','rsa n')
+               if len(w) >= 5: w[4].email()
+               self.a='rsa1'
+               self.d=base91s_encode(('%d %s %s' %
+                                      (self.l,
+                                       self.e,
+                                       self.n)).encode('ascii'))
+               # ^ this allows us to use the pubkey.forsites()
+               # method for output in versions>=2
        def __str__(self):
                return 'rsa-public("%s","%s")'%(self.e,self.n)
+               # this specialisation means we can generate files
+               # compatible with old secnet executables
+       def forpub(self,version,fs):
+               if version < 2:
+                       if fs.pkg != '00000000': return []
+                       return ['pubkey', str(self.l), self.e, self.n]
+               return pubkey.forpub(self,version,fs)
+
+class rsakey_newfmt(rsakey):
+       "An old-style RSA public key in new-style sites format"
+       # This is its own class simply to have its own constructor.
+       def __init__(self,w):
+               self.a=w[1].name()
+               assert(self.a == 'rsa1')
+               self.d=w[2].base91()
+               try:
+                       w_inner=list(map(Tainted,
+                                       ['X-PUB-RSA1'] +
+                                       base91s_decode(self.d)
+                                       .decode('ascii')
+                                       .split(' ')))
+               except UnicodeDecodeError:
+                       complain('rsa1 key in new format has bad base91')
+               #print(repr(w_inner), file=sys.stderr)
+               rsakey.__init__(self,w_inner)
+
+class pubkey_group(inpub):
+       "Public key group introducer"
+       # appears in the site's list of keys mixed in with the keys
+       def __init__(self,w,fallback):
+               self.i=w[1].hexid(4,'pkg-id')
+               self.fallback=fallback
+       def forpub(self,version,fs):
+               fs.pkg=self.i
+               if version < 2: return []
+               return ['pkgf' if self.fallback else 'pkg', self.i]
+       def okforonlykey(self,version,fs):
+               self.forpub(version,fs)
+               return False
+       
+def somepubkey(w):
+       #print(repr(w), file=sys.stderr)
+       if w[0]=='pubkey':
+               return rsakey(w)
+       elif w[0]=='pub' and w[1]=='rsa1':
+               return rsakey_newfmt(w)
+       elif w[0]=='pub':
+               return pubkey(w)
+       elif w[0]=='pkg':
+               return pubkey_group(w,False)
+       elif w[0]=='pkgf':
+               return pubkey_group(w,True)
+       else:
+               assert(False)
 
 # Possible properties of configuration nodes
 keywords={
@@ -159,7 +666,11 @@ keywords={
  'renegotiate-time':(num,"Time after key setup to begin renegotiation (ms)"),
  'restrict-nets':(networks,"Allowable networks"),
  'networks':(networks,"Claimed networks"),
- 'pubkey':(rsakey,"RSA public site key"),
+ 'serial':(serial,"public key set serial"),
+ 'pkg':(listof(somepubkey),"start of public key group",'pub'),
+ 'pkgf':(listof(somepubkey),"start of fallback public key group",'pub'),
+ 'pub':(listof(somepubkey),"new style public site key"),
+ 'pubkey':(listof(somepubkey),"Old-style RSA public site key",'pub'),
  'peer':(single_ipaddr,"Tunnel peer IP address"),
  'address':(address,"External contact address and port"),
  'mobile':(boolean,"Site is mobile"),
@@ -189,7 +700,8 @@ class level:
        allow_properties={}
        require_properties={}
        def __init__(self,w):
-               self.name=w[1]
+               self.type=w[0].keyword()
+               self.name=w[1].name()
                self.properties={}
                self.children={}
        def indent(self,w,t):
@@ -197,17 +709,22 @@ class level:
        def prop_out(self,n):
                return self.allow_properties[n](n,str(self.properties[n]))
        def output_props(self,w,ind):
-               for i in self.properties.keys():
+               for i in sorted(self.properties.keys()):
                        if self.allow_properties[i]:
                                self.indent(w,ind)
                                w.write("%s"%self.prop_out(i))
-       def output_data(self,w,ind,np):
+       def kname(self):
+               return ((self.type[0].upper() if key_prefix else '')
+                       + self.name)
+       def output_data(self,w,path):
+               ind = 2*len(path)
                self.indent(w,ind)
-               w.write("%s {\n"%(self.name))
+               w.write("%s {\n"%(self.kname()))
                self.output_props(w,ind+2)
                if self.depth==1: w.write("\n");
-               for c in self.children.values():
-                       c.output_data(w,ind+2,np+self.name+"/")
+               for k in sorted(self.children.keys()):
+                       c=self.children[k]
+                       c.output_data(w,path+(c,))
                self.indent(w,ind)
                w.write("};\n")
 
@@ -222,17 +739,18 @@ class vpnlevel(level):
        }
        def __init__(self,w):
                level.__init__(self,w)
-       def output_vpnflat(self,w,ind,h):
+       def output_vpnflat(self,w,path):
                "Output flattened list of site names for this VPN"
+               ind=2*(len(path)+1)
                self.indent(w,ind)
-               w.write("%s {\n"%(self.name))
+               w.write("%s {\n"%(self.kname()))
                for i in self.children.keys():
-                       self.children[i].output_vpnflat(w,ind+2,
-                               h+"/"+self.name+"/"+i)
+                       self.children[i].output_vpnflat(w,path+(self,))
                w.write("\n")
                self.indent(w,ind+2)
                w.write("all-sites %s;\n"%
-                       string.join(self.children.keys(),','))
+                       ','.join(map(lambda i: i.kname(),
+                                    self.children.values())))
                self.indent(w,ind)
                w.write("};\n")
 
@@ -247,14 +765,20 @@ class locationlevel(level):
        }
        def __init__(self,w):
                level.__init__(self,w)
-               self.group=w[2]
-       def output_vpnflat(self,w,ind,h):
+               self.group=w[2].groupname()
+       def output_vpnflat(self,w,path):
+               ind=2*(len(path)+1)
                self.indent(w,ind)
-               # The "h=h,self=self" abomination below exists because
+               # The "path=path,self=self" abomination below exists because
                # Python didn't support nested_scopes until version 2.1
-               w.write("%s %s;\n"%(self.name,string.join(
-                       map(lambda x,h=h,self=self:
-                               h+"/"+x,self.children.keys()),',')))
+               #
+               #"/"+self.name+"/"+i
+               w.write("%s %s;\n"%(self.kname(),','.join(
+                       map(lambda x,path=path,self=self:
+                           '/'.join([prefix+"vpn-data"] + list(map(
+                                   lambda i: i.kname(),
+                                   path+(self,x)))),
+                           self.children.values()))))
 
 class sitelevel(level):
        "Site level (i.e. a leafnode) in the configuration hierarchy"
@@ -266,8 +790,11 @@ class sitelevel(level):
         'address':sp,
         'networks':None,
         'peer':None,
-        'pubkey':(lambda n,v:"key %s;\n"%v),
-        'address':(lambda n,v:"address %s;\n"%v),
+         'serial':None,
+        'pkg':None,
+        'pkgf':None,
+        'pub':None,
+        'pubkey':None,
         'mobile':sp,
        })
        require_properties={
@@ -276,15 +803,34 @@ class sitelevel(level):
         'networks':"Networks claimed by the site",
         'hash':"hash function",
         'peer':"Gateway address of the site",
-        'pubkey':"RSA public key of the site",
        }
+       def mangle_name(self):
+               return self.name.replace('/',',')
+       def pubkeys_path(self):
+               return pubkeys_dir + '/peer.' + self.mangle_name()
        def __init__(self,w):
                level.__init__(self,w)
-       def output_data(self,w,ind,np):
+       def output_data(self,w,path):
+               ind=2*len(path)
+               np='/'.join(map(lambda i: i.name, path))
                self.indent(w,ind)
-               w.write("%s {\n"%(self.name))
+               w.write("%s {\n"%(self.kname()))
                self.indent(w,ind+2)
-               w.write("name \"%s\";\n"%(np+self.name))
+               w.write("name \"%s\";\n"%(np,))
+               self.indent(w,ind+2)
+
+               pkm = pubkeys_mode()
+               debugrepr('pkm ',pkm)
+               pkm.site_start(self.pubkeys_path())
+               if 'serial' in self.properties:
+                       pkm.site_serial(self.properties['serial'])
+
+               for k in self.properties["pub"].list:
+                       debugrepr('pubkeys ', k)
+                       pkm.write_key(k)
+
+               pkm.site_finish(w)
+
                self.output_props(w,ind+2)
                self.indent(w,ind+2)
                w.write("link netlink {\n");
@@ -301,46 +847,62 @@ class sitelevel(level):
 # (depth,properties)
 levels={'vpn':vpnlevel, 'location':locationlevel, 'site':sitelevel}
 
-# Reserved vpn/location/site names
-reserved={'all-sites':None}
-reserved.update(keywords)
-reserved.update(levels)
-
 def complain(msg):
        "Complain about a particular input line"
-       global complaints
-       print ("%s line %d: "%(file,line))+msg
-       complaints=complaints+1
+       moan(("%s line %d: "%(file,line))+msg)
 def moan(msg):
        "Complain about something in general"
        global complaints
-       print msg;
+       print(msg);
+       if complaints is None: sys.exit(1)
        complaints=complaints+1
 
-root=level(['root','root'])   # All vpns are children of this node
+class UntaintedRoot():
+       def __init__(self,s): self._s=s
+       def name(self): return self._s
+       def keyword(self): return self._s
+
+root=level([UntaintedRoot(x) for x in ['root','root']])
+# All vpns are children of this node
 obstack=[root]
 allow_defs=0   # Level above which new definitions are permitted
-prefix=''
 
 def set_property(obj,w):
        "Set a property on a configuration node"
-       if obj.properties.has_key(w[0]):
-               complain("%s %s already has property %s defined"%
-                       (obj.type,obj.name,w[0]))
+       prop=w[0]
+       propname=prop.raw_mark_ok()
+       kw=keywords[propname]
+       if len(kw) >= 3: propname=kw[2] # for aliases
+       if propname in obj.properties:
+               obj.properties[propname].add(obj,w)
        else:
-               obj.properties[w[0]]=keywords[w[0]][0](w)
+               obj.properties[propname]=kw[0](w)
+       return obj.properties[propname]
 
-def pline(i,allow_include=False):
+class FilterState:
+       def __init__(self):
+               self.reset()
+       def reset(self):
+               # called when we enter a new node,
+               # in particular, at the start of each site
+               self.pkg = '00000000'
+
+def pline(il,filterstate,allow_include=False):
        "Process a configuration file line"
        global allow_defs, obstack, root
-       w=string.split(i.rstrip('\n'))
-       if len(w)==0: return [i]
+       w=il.rstrip('\n').split()
+       if len(w)==0: return ['']
+       w=list([Tainted(x) for x in w])
        keyword=w[0]
        current=obstack[len(obstack)-1]
+       copyout_core=lambda: ' '.join([ww.output() for ww in w])
+       indent='    '*len(obstack)
+       copyout=lambda: [indent + copyout_core() + '\n']
        if keyword=='end-definitions':
+               keyword.raw_mark_ok()
                allow_defs=sitelevel.depth
                obstack=[root]
-               return [i]
+               return copyout()
        if keyword=='include':
                if not allow_include:
                        complain("include not permitted here")
@@ -348,11 +910,12 @@ def pline(i,allow_include=False):
                if len(w) != 2:
                        complain("include requires one argument")
                        return []
-               newfile=os.path.join(os.path.dirname(file),w[1])
+               newfile=os.path.join(os.path.dirname(file),w[1].raw_mark_ok())
+               # ^ user of "include" is trusted so raw_mark_ok is good
                return pfilepath(newfile,allow_include=allow_include)
-       if levels.has_key(keyword):
+       if keyword.raw() in levels:
                # We may go up any number of levels, but only down by one
-               newdepth=levels[keyword].depth
+               newdepth=levels[keyword.raw_mark_ok()].depth
                currentdepth=len(obstack) # actually +1...
                if newdepth<=currentdepth:
                        obstack=obstack[:newdepth]
@@ -362,34 +925,41 @@ def pline(i,allow_include=False):
                # See if it's a new one (and whether that's permitted)
                # or an existing one
                current=obstack[len(obstack)-1]
-               if current.children.has_key(w[1]):
+               tname=w[1].name()
+               if tname in current.children:
                        # Not new
-                       current=current.children[w[1]]
-                       if service and group and current.depth==2:
-                               if group!=current.group:
-                                       complain("Incorrect group!")
+                       current=current.children[tname]
+                       if current.depth==2:
+                               opmode.check_group(current.group, w)
                else:
                        # New
                        # Ignore depth check for now
-                       nl=levels[keyword](w)
+                       nl=levels[keyword.raw()](w)
                        if nl.depth<allow_defs:
                                complain("New definitions not allowed at "
                                        "level %d"%nl.depth)
                                # we risk crashing if we continue
                                sys.exit(1)
-                       current.children[w[1]]=nl
+                       current.children[tname]=nl
                        current=nl
+               filterstate.reset()
                obstack.append(current)
-               return [i]
-       if current.allow_properties.has_key(keyword):
-               set_property(current,w)
-               return [i]
-       else:
+               return copyout()
+       if keyword.raw() not in current.allow_properties:
                complain("Property %s not allowed at %s level"%
-                       (keyword,current.type))
+                       (keyword.raw(),current.type))
+               return []
+       elif current.depth == vpnlevel.depth < allow_defs:
+               complain("Not allowed to set VPN properties here")
                return []
+       else:
+               prop=set_property(current,w)
+               out=[copyout_core()]
+               out=prop.forsites(output_version,out,filterstate)
+               if len(out)==0: return [indent + '#', copyout_core(), '\n']
+               return [indent + ' '.join(out) + '\n']
 
-       complain("unknown keyword '%s'"%(keyword))
+       complain("unknown keyword '%s'"%(keyword.raw()))
 
 def pfilepath(pathname,allow_include=False):
        f=open(pathname)
@@ -403,10 +973,11 @@ def pfile(name,lines,allow_include=False):
        file=name
        line=0
        outlines=[]
+       filterstate = FilterState()
        for i in lines:
                line=line+1
                if (i[0]=='#'): continue
-               outlines += pline(i,allow_include=allow_include)
+               outlines += pline(i,filterstate,allow_include=allow_include)
        return outlines
 
 def outputsites(w):
@@ -414,77 +985,29 @@ def outputsites(w):
        w.write("# secnet sites file autogenerated by make-secnet-sites "
                +"version %s\n"%VERSION)
        w.write("# %s\n"%time.asctime(time.localtime(time.time())))
-       w.write("# Command line: %s\n\n"%string.join(sys.argv))
+       w.write("# Command line: %s\n\n"%' '.join(sys.argv))
 
        # Raw VPN data section of file
        w.write(prefix+"vpn-data {\n")
        for i in root.children.values():
-               i.output_data(w,2,"")
+               i.output_data(w,(i,))
        w.write("};\n")
 
        # Per-VPN flattened lists
        w.write(prefix+"vpn {\n")
        for i in root.children.values():
-               i.output_vpnflat(w,2,prefix+"vpn-data")
+               i.output_vpnflat(w,())
        w.write("};\n")
 
        # Flattened list of sites
-       w.write(prefix+"all-sites %s;\n"%string.join(
-               map(lambda x:"%svpn/%s/all-sites"%(prefix,x),
-                       root.children.keys()),","))
-
-# Are we being invoked from userv?
-service=0
-# If we are, which group does the caller want to modify?
-group=None
+       w.write(prefix+"all-sites %s;\n"%",".join(
+               map(lambda x:"%svpn/%s/all-sites"%(prefix,x.kname()),
+                       root.children.values())))
 
 line=0
 file=None
 complaints=0
 
-if len(sys.argv)<2:
-       pfile("stdin",sys.stdin.readlines())
-       of=sys.stdout
-else:
-       if sys.argv[1]=='-u':
-               if len(sys.argv)!=6:
-                       print "Wrong number of arguments"
-                       sys.exit(1)
-               service=1
-               header=sys.argv[2]
-               groupfiledir=sys.argv[3]
-               sitesfile=sys.argv[4]
-               group=sys.argv[5]
-               if not os.environ.has_key("USERV_USER"):
-                       print "Environment variable USERV_USER not found"
-                       sys.exit(1)
-               user=os.environ["USERV_USER"]
-               # Check that group is in USERV_GROUP
-               if not os.environ.has_key("USERV_GROUP"):
-                       print "Environment variable USERV_GROUP not found"
-                       sys.exit(1)
-               ugs=os.environ["USERV_GROUP"]
-               ok=0
-               for i in string.split(ugs):
-                       if group==i: ok=1
-               if not ok:
-                       print "caller not in group %s"%group
-                       sys.exit(1)
-               headerinput=pfilepath(header,allow_include=True)
-               userinput=sys.stdin.readlines()
-               pfile("user input",userinput)
-       else:
-               if sys.argv[1]=='-P':
-                       prefix=sys.argv[2]
-                       sys.argv[1:3]=[]
-               if len(sys.argv)>3:
-                       print "Too many arguments"
-                       sys.exit(1)
-               pfilepath(sys.argv[1])
-               of=sys.stdout
-               if len(sys.argv)>2:
-                       of=open(sys.argv[2],'w')
-
 # Sanity check section
 # Delete nodes where leaf=0 that have no children
 
@@ -496,11 +1019,10 @@ def live(n):
        return 0
 def delempty(n):
        "Delete nodes that have no leafnode children"
-       for i in n.children.keys():
+       for i in list(n.children.keys()):
                delempty(n.children[i])
                if not live(n.children[i]):
                        del n.children[i]
-delempty(root)
 
 # Check that all constraints are met (as far as I can tell
 # restrict-nets/networks/peer are the only special cases)
@@ -509,67 +1031,37 @@ def checkconstraints(n,p,ra):
        new_p=p.copy()
        new_p.update(n.properties)
        for i in n.require_properties.keys():
-               if not new_p.has_key(i):
+               if i not in new_p:
                        moan("%s %s is missing property %s"%
                                (n.type,n.name,i))
        for i in new_p.keys():
-               if not n.allow_properties.has_key(i):
+               if i not in n.allow_properties:
                        moan("%s %s has forbidden property %s"%
                                (n.type,n.name,i))
        # Check address range restrictions
-       if n.properties.has_key("restrict-nets"):
+       if "restrict-nets" in n.properties:
                new_ra=ra.intersection(n.properties["restrict-nets"].set)
        else:
                new_ra=ra
-       if n.properties.has_key("networks"):
-               # I'd like to do this:
-               # n.properties["networks"].set.is_subset(new_ra)
-               # but there isn't an is_subset() method
-               # Instead we see if we intersect with the complement of new_ra
-               rac=new_ra.complement()
-               i=rac.intersection(n.properties["networks"].set)
-               if not i.is_empty():
+       if "networks" in n.properties:
+               if not n.properties["networks"].set <= new_ra:
                        moan("%s %s networks out of bounds"%(n.type,n.name))
-               if n.properties.has_key("peer"):
+               if "peer" in n.properties:
                        if not n.properties["networks"].set.contains(
                                n.properties["peer"].addr):
                                moan("%s %s peer not in networks"%(n.type,n.name))
        for i in n.children.keys():
                checkconstraints(n.children[i],new_p,new_ra)
 
-checkconstraints(root,{},ipaddr.complete_set)
+opmode.read_in()
+
+delempty(root)
+checkconstraints(root,{},ipaddrset.complete_set())
 
 if complaints>0:
-       if complaints==1: print "There was 1 problem."
-       else: print "There were %d problems."%(complaints)
+       if complaints==1: print("There was 1 problem.")
+       else: print("There were %d problems."%(complaints))
        sys.exit(1)
+complaints=None # arranges to crash if we complain later
 
-if service:
-       # Put the user's input into their group file, and rebuild the main
-       # sites file
-       f=open(groupfiledir+"/T"+group,'w')
-       f.write("# Section submitted by user %s, %s\n"%
-               (user,time.asctime(time.localtime(time.time()))))
-       f.write("# Checked by make-secnet-sites version %s\n\n"%VERSION)
-       for i in userinput: f.write(i)
-       f.write("\n")
-       f.close()
-       os.rename(groupfiledir+"/T"+group,groupfiledir+"/R"+group)
-       f=open(sitesfile+"-tmp",'w')
-       f.write("# sites file autogenerated by make-secnet-sites\n")
-       f.write("# generated %s, invoked by %s\n"%
-               (time.asctime(time.localtime(time.time())),user))
-       f.write("# use make-secnet-sites to turn this file into a\n")
-       f.write("# valid /etc/secnet/sites.conf file\n\n")
-       for i in headerinput: f.write(i)
-       files=os.listdir(groupfiledir)
-       for i in files:
-               if i[0]=='R':
-                       j=open(groupfiledir+"/"+i)
-                       f.write(j.read())
-                       j.close()
-       f.write("# end of sites file\n")
-       f.close()
-       os.rename(sitesfile+"-tmp",sitesfile)
-else:
-       outputsites(of)
+opmode.write_out()
diff --git a/md5.c b/md5.c
index 343ad8fc893cdd29bbc208c74b7e044f7499d857..ca9ddb1ad7069b4850e59e96949c1db32f62efbf 100644 (file)
--- a/md5.c
+++ b/md5.c
@@ -3,6 +3,7 @@
  * The algorithm is due to Ron Rivest.  This code was
  * written by Colin Plumb in 1993, no copyright is claimed.
  * This code is in the public domain; do with it what you wish.
+ * [I interpet this as a blanket permision -iwj.]
  *
  * Equivalent code is available from RSA Data Security, Inc.
  * This code has been tested against that, and is equivalent,
@@ -21,6 +22,7 @@
  */
 
 #include "secnet.h"
+#include "util.h"
 #include <string.h>            /* for memcpy() */
 #include "md5.h"
 
@@ -237,14 +239,11 @@ MD5Transform(uint32_t buf[4], uint32_t const in[16])
 
 #endif
 
-static void *md5_init(void)
+static void md5_init(void *sst)
 {
-    struct MD5Context *ctx;
+    struct MD5Context *ctx=sst;
 
-    ctx=safe_malloc(sizeof(*ctx),"md5_init");
     MD5Init(ctx);
-
-    return ctx;
 }
 
 static void md5_update(void *sst, const void *buf, int32_t len)
@@ -259,7 +258,6 @@ static void md5_final(void *sst, uint8_t *digest)
     struct MD5Context *ctx=sst;
 
     MD5Final(digest,ctx);
-    free(ctx);
 }
 
 struct md5 {
@@ -270,7 +268,6 @@ struct md5 {
 void md5_module(dict_t *dict)
 {
     struct md5 *st;
-    void *ctx;
     cstring_t testinput="12345\n";
     uint8_t expected[16]=
        {0xd5,0x77,0x27,0x3f,0xf8,0x85,0xc3,0xf8,
@@ -278,21 +275,20 @@ void md5_module(dict_t *dict)
     uint8_t digest[16];
     int i;
 
-    st=safe_malloc(sizeof(*st),"md5_module");
+    NEW(st);
     st->cl.description="md5";
     st->cl.type=CL_HASH;
     st->cl.apply=NULL;
     st->cl.interface=&st->ops;
-    st->ops.len=16;
+    st->ops.hlen=16;
+    st->ops.slen=sizeof(struct MD5Context);
     st->ops.init=md5_init;
     st->ops.update=md5_update;
     st->ops.final=md5_final;
 
     dict_add(dict,"md5",new_closure(&st->cl));
 
-    ctx=md5_init();
-    md5_update(ctx,testinput,strlen(testinput));
-    md5_final(ctx,digest);
+    hash_hash(&st->ops,testinput,strlen(testinput),digest);
     for (i=0; i<16; i++) {
        if (digest[i]!=expected[i]) {
            fatal("md5 module failed self-test");
diff --git a/md5.h b/md5.h
index 4dd562c472e9bcd50074531e7a2ee793f2a96f2a..b5441d45e862ae2773100dbb05a0b16697f1ca79 100644 (file)
--- a/md5.h
+++ b/md5.h
@@ -3,6 +3,7 @@
  * The algorithm is due to Ron Rivest.  This code was
  * written by Colin Plumb in 1993, no copyright is claimed.
  * This code is in the public domain; do with it what you wish.
+ * [I interpet this as a blanket permision -iwj.]
  *
  * Equivalent code is available from RSA Data Security, Inc.
  * This code has been tested against that, and is equivalent,
index 724ccbbd5b61dfef1881362e34708f77ef332882..38f1d0fdd1fce7e434543a36c6d49a6f193dfd8d 100644 (file)
--- a/modules.c
+++ b/modules.c
@@ -1,10 +1,31 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #include "secnet.h"
 
 void init_builtin_modules(dict_t *dict)
 {
+    pubkeys_init(dict);
     resolver_module(dict);
     random_module(dict);
     udp_module(dict);
+    polypath_module(dict);
     util_module(dict);
     site_module(dict);
     transform_eax_module(dict);
@@ -17,4 +38,10 @@ void init_builtin_modules(dict_t *dict)
     tun_module(dict);
     sha1_module(dict);
     log_module(dict);
+    privcache_module(dict);
 }
+
+const struct sigscheme_info sigschemes[]={
+    { "rsa1", 0x00, rsa1_loadpub, rsa1_loadpriv },
+    { 0 }
+};
diff --git a/msgcode-test.c b/msgcode-test.c
new file mode 100644 (file)
index 0000000..401bf6f
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * msgcode-test.c: check that the new message encoding is correct
+ */
+/*
+ * This file is Free Software.  It was originally written for secnet.
+ *
+ * Copyright 2017 Mark Wooding
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later
+ * version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "magic.h"
+
+#define OLD_LABEL_NAK     0x00000000
+#define OLD_LABEL_MSG0    0x00020200
+#define OLD_LABEL_MSG1    0x01010101
+#define OLD_LABEL_MSG2    0x02020202
+#define OLD_LABEL_MSG3    0x03030303
+#define OLD_LABEL_MSG3BIS 0x13030313
+#define OLD_LABEL_MSG4    0x04040404
+#define OLD_LABEL_MSG5    0x05050505
+#define OLD_LABEL_MSG6    0x06060606
+#define OLD_LABEL_MSG7    0x07070707
+#define OLD_LABEL_MSG8    0x08080808
+#define OLD_LABEL_MSG9    0x09090909
+#define OLD_LABEL_PROD    0x0a0a0a0a
+
+static void check_labels(const char *what, uint32_t new, uint32_t old)
+{
+    if (old != new) {
+       printf("mismatch for %s: %08"PRIx32" (new) /= %08"PRIx32" (old)\n",
+              what, new, old);
+       exit(2);
+    }
+}
+
+int main(void)
+{
+    unsigned i, j;
+    uint32_t m, r, s;
+
+#define CHECK(label) check_labels(#label, LABEL_##label, OLD_LABEL_##label)
+    CHECK(NAK);
+    CHECK(MSG0);
+    CHECK(MSG1);
+    CHECK(MSG2);
+    CHECK(MSG3);
+    CHECK(MSG3BIS);
+    CHECK(MSG4);
+    CHECK(MSG5);
+    CHECK(MSG6);
+    CHECK(MSG7);
+    CHECK(MSG8);
+    CHECK(MSG9);
+    CHECK(PROD);
+#undef CHECK
+    for (i = 0; i < 65536; i++) {
+       for (j = 0; j < 65536; j++) {
+           m = MSGCODE(i, j);
+           r = MSGMAJOR(m); s = MSGMINOR(m);
+           if (r != i || s != j) {
+               printf("roundtrip fail: %04x %04x -> %08"PRIx32" "
+                      "-> %08"PRIx32" %08"PRIx32"\n",
+                      i, j, m, r, s);
+               exit(2);
+           }
+       }
+    }
+
+    return (0);
+}
diff --git a/mtest/Dir.sd.mk b/mtest/Dir.sd.mk
new file mode 100644 (file)
index 0000000..bdb872f
--- /dev/null
@@ -0,0 +1,8 @@
+
+&DEPS += &~/make-secnet-sites
+&DEPS += &~/ipaddrset.py
+&DEPS += &^/common.tcl
+
+&:include test-common.sd.mk
+
+&check:: &check-real
diff --git a/mtest/Ginside.sites b/mtest/Ginside.sites
new file mode 100644 (file)
index 0000000..3348266
--- /dev/null
@@ -0,0 +1,8 @@
+vpn test-example
+location inside root
+site inside
+  networks 172.18.232.8/30
+  peer 172.18.232.9
+  address [127.0.0.1] 16910
+  mobile True
+  pubkey 1024 65537 130064631890186713927887504218626486455931306300999583387009075747001546036643522074275473238061323169592347601185592753550279410171535737146240085267000508853176463710554801101055212967131924064664249613912656320653505750073021702169423354903540699008756137338575553686987244488914481168225136440872431691669 inside@example.com
diff --git a/mtest/Goutside.sites b/mtest/Goutside.sites
new file mode 100644 (file)
index 0000000..c85f5f8
--- /dev/null
@@ -0,0 +1,7 @@
+vpn test-example
+location outside root
+site outside
+  networks 172.18.232.0/30
+  peer 172.18.232.1
+  address [::1] 16900
+  pubkey 1024 65537 129251483458784900555621175262818292872587807329014927540074484804119474262261383244074013537736576331652560727149001626325243856012659665194546933097292703586821422085819615124517093786704646988649444946154384037948502112302285511195679291084694375811092516151263088200304199780052361048758446082354317801941 outside@example.com
diff --git a/mtest/common.tcl b/mtest/common.tcl
new file mode 100644 (file)
index 0000000..b436a47
--- /dev/null
@@ -0,0 +1,42 @@
+source test-common.tcl
+
+proc mss-program {} {
+    global env
+    set l ./make-secnet-sites
+    if {![catch { set py $env(MTEST_PYTHON) }]} {
+       set l [concat $py $l]
+    }
+    return $l
+}
+
+proc run-mss-userv {user group args} {
+    eval [list prexec env USERV_USER=$user USERV_GROUP=$group] \
+        [mss-program] \
+        $args
+}
+
+proc run-mss {args} { eval [list prexec] [mss-program] $args }
+
+proc diff {a b seddery {sedderyb X}} {
+    if {![string compare $sedderyb X]} { set sedderyb $seddery }
+    puts "$a $b $seddery $sedderyb"
+    exec bash -c "
+       diff -u <( <$a $seddery  ) \\
+               <( <$b $sedderyb )
+    "
+}
+
+proc diff-output {expected got suffix} {
+    global seddery
+    global tmp
+    diff mtest/$expected$suffix $tmp/$got$suffix $seddery
+}
+
+file mkdir $tmp/groupfiles
+
+set env(PYTHONHASHSEED) 0
+set env(PYTHONBYTECODEBASE) 0
+
+set seddery { sed -n 's/^[ \t]*//; /^[^#]/p' }
+
+prefix_some_path PYTHONPATH .
diff --git a/mtest/delegations.sites b/mtest/delegations.sites
new file mode 100644 (file)
index 0000000..f6632e2
--- /dev/null
@@ -0,0 +1,5 @@
+location outside Goutside
+restrict-nets 172.18.232.0/29
+
+location inside Ginside
+restrict-nets 172.18.232.8/29
diff --git a/mtest/e-basic.conf b/mtest/e-basic.conf
new file mode 100644 (file)
index 0000000..4883dbc
--- /dev/null
@@ -0,0 +1,48 @@
+# secnet sites file autogenerated by make-secnet-sites version 0.1.18
+# Sat Dec  7 17:16:13 2019
+# Command line: ./make-secnet-sites --no-conf-key-prefix test-example/sites /home/ian/things/Fvpn/secnet/mtest/d-basic/out.conf
+
+vpn-data {
+  test-example {
+    # Contact email address: <devnull@example.com>
+    dh diffie-hellman("8db5f2c15ac96d9f3382d1ef4688fba14dc7908ae7dfd71a9cfe7f479a75d506dc53f159aeaf488bde073fe544bc91c099f101fcf60074f30c06e36263c03ca9e07931ce3fc235fe1171dc6d9316fb097bd4362891e2c36e234e7c16b038fd97b1f165c710e90537de66ee4f54001f5712b050d4e07de3fba07607b19b64f6c3","2");
+    hash sha1;
+    key-lifetime 72000000;
+    # restrict-nets "172.18.232.0/28"
+    setup-retries 5;
+    setup-timeout 2000;
+
+    in {
+      inside {
+        name "test-example/in/inside";
+        peer-keys "/var/lib/secnet/pubkeys/peer.inside";
+        address "[127.0.0.1]"; port 16910;
+        mobile True;
+        link netlink {
+          routes "172.18.232.8/29";
+          ptp-address "172.18.232.9";
+        };
+      };
+    };
+    out {
+      outside {
+        name "test-example/out/outside";
+        peer-keys "/var/lib/secnet/pubkeys/peer.outside";
+        address "[::1]"; port 16900;
+        link netlink {
+          routes "172.18.232.0/29";
+          ptp-address "172.18.232.1";
+        };
+      };
+    };
+  };
+};
+vpn {
+  test-example {
+    out vpn-data/test-example/out/outside;
+    in vpn-data/test-example/in/inside;
+
+    all-sites out,in;
+  };
+};
+all-sites vpn/test-example/all-sites;
diff --git a/mtest/e-filter.sites b/mtest/e-filter.sites
new file mode 100644 (file)
index 0000000..8339690
--- /dev/null
@@ -0,0 +1,32 @@
+# sites file autogenerated by make-secnet-sites
+# --filter --output-version=1
+# use make-secnet-sites to turn this file into a
+# valid /etc/secnet/sites.conf file
+
+    vpn test-example
+        contact devnull@example.com
+        dh 8db5f2c15ac96d9f3382d1ef4688fba14dc7908ae7dfd71a9cfe7f479a75d506dc53f159aeaf488bde073fe544bc91c099f101fcf60074f30c06e36263c03ca9e07931ce3fc235fe1171dc6d9316fb097bd4362891e2c36e234e7c16b038fd97b1f165c710e90537de66ee4f54001f5712b050d4e07de3fba07607b19b64f6c3 2
+        hash sha1
+        key-lifetime 72000000
+        restrict-nets 172.18.232.0/28
+        setup-timeout 2000
+        setup-retries 5
+        location out root
+            site outside
+                networks 172.18.232.0/29
+                peer 172.18.232.1
+                address [::1] 16900
+                #serial 5dcfe8e9
+                #pkg 5dc36a47
+                #pub rsa1 zt1E8Wddh26=^XgSyt2x2i)G7I5=Z[*T:4(aml1Xs1U<lN$S?+<,[h.GAJ::*NLU0tB.%kkMQJ4+y6$SH)YbKm-i$JC.WCbRgw_,UoKugJ7=R[7RJ)QbKmOuh2G?bvlTF2QbWo<Gh26=%NgS37;aBk*X819=aCASSz9E;mGSrID.pNFTF)WxUo9X@J5+Z[lTY%*EmlVds1E?KChSs!J.vn~R@Jn/66$SJ){x:mmMAJX<uC%SowCFVoUu[29=C+$S@+R.AkiMgJX<EkbR6t*E:m)Gx26+xNgS#7p.Vo9X[2V*pN8R=4CFxnVdrIm/LvFT$7<,vnCS%2W<q6FTA2{x2iBjAJm/tN8Re%+xBkwoG3s@|jAS;4dECkGS81H?9NmT5t*EMmGu<1<:@YASE2gbml5XQJ,(q6FTm!Dy]h6o81D.WCmT3t`E3i$G[2.(mCBSH)mxcjSu81E.tNcR6texllddgJX<1N*TjwVEBkbdgJ=:5NcRe%(a4iuM%2E?mCFT:4>xvnCS81>:tN$Ss!9E;mSuAJq@)YAS=4CF^heMR2=:_@7RVz>x2iooQJq@^jKUB29ECk5X7ID?N[*Tu!Yb:msMQJ9=EkgS37ux;m]i<1::rvKURztEbjkMQJD?OCmTb%tE;miM;Is@KC7RJ)gbWoiM$JD?iCFTQz`E4iVB$Jq@PvlT[+gb2i0o@JG?eChSpwQbllESQJ::xN+Tb%1ElluMs16+26FT-1;aLm[zAJm/26{Q[+),;m+GR2S*&6{Q-1_,N
+                #pub unknown-algo TPwJh>A
+                #pkgf 00000000
+                pubkey 1024 65537 129251483458784900555621175262818292872587807329014927540074484804119474262261383244074013537736576331652560727149001626325243856012659665194546933097292703586821422085819615124517093786704646988649444946154384037948502112302285511195679291084694375811092516151263088200304199780052361048758446082354317801941
+                location in root
+            site inside
+                networks 172.18.232.8/29
+                peer 172.18.232.9
+                address [127.0.0.1] 16910
+                mobile True
+                pubkey 1024 65537 130064631890186713927887504218626486455931306300999583387009075747001546036643522074275473238061323169592347601185592753550279410171535737146240085267000508853176463710554801101055212967131924064664249613912656320653505750073021702169423354903540699008756137338575553686987244488914481168225136440872431691669
+# end of sites file
diff --git a/mtest/e-userv.sites b/mtest/e-userv.sites
new file mode 100644 (file)
index 0000000..e9108a6
--- /dev/null
@@ -0,0 +1,35 @@
+# sites file autogenerated by make-secnet-sites
+# generated Sun Oct 20 13:21:06 2019, invoked by Uuser
+# use make-secnet-sites to turn this file into a
+# valid /etc/secnet/sites.conf file
+
+vpn test-example
+contact header@example.com
+dh 8db5f2c15ac96d9f3382d1ef4688fba14dc7908ae7dfd71a9cfe7f479a75d506dc53f159aeaf488bde073fe544bc91c099f101fcf60074f30c06e36263c03ca9e07931ce3fc235fe1171dc6d9316fb097bd4362891e2c36e234e7c16b038fd97b1f165c710e90537de66ee4f54001f5712b050d4e07de3fba07607b19b64f6c3 2
+hash sha1
+key-lifetime 72000000
+restrict-nets 192.168.0.0/16 10.0.0.0/8 172.16.0.0/12
+setup-timeout 2000
+setup-retries 5
+
+location outside Goutside
+restrict-nets 172.18.232.0/29
+
+location inside Ginside
+restrict-nets 172.18.232.8/29
+
+end-definitions
+
+# Section submitted by user Uuser, Sun Oct 20 13:21:06 2019
+# Checked by make-secnet-sites version 0.1.18
+
+vpn test-example
+location inside root
+site inside
+  networks 172.18.232.8/30
+  peer 172.18.232.9
+  address [127.0.0.1] 16910
+  mobile True
+  pubkey 1024 65537 130064631890186713927887504218626486455931306300999583387009075747001546036643522074275473238061323169592347601185592753550279410171535737146240085267000508853176463710554801101055212967131924064664249613912656320653505750073021702169423354903540699008756137338575553686987244488914481168225136440872431691669 inside@example.com
+
+# end of sites file
diff --git a/mtest/header.sites b/mtest/header.sites
new file mode 100644 (file)
index 0000000..f4b3481
--- /dev/null
@@ -0,0 +1,13 @@
+vpn test-example
+contact header@example.com
+dh 8db5f2c15ac96d9f3382d1ef4688fba14dc7908ae7dfd71a9cfe7f479a75d506dc53f159aeaf488bde073fe544bc91c099f101fcf60074f30c06e36263c03ca9e07931ce3fc235fe1171dc6d9316fb097bd4362891e2c36e234e7c16b038fd97b1f165c710e90537de66ee4f54001f5712b050d4e07de3fba07607b19b64f6c3 2
+hash sha1
+key-lifetime 72000000
+restrict-nets 192.168.0.0/16 10.0.0.0/8 172.16.0.0/12
+setup-timeout 2000
+setup-retries 5
+
+include delegations.sites
+
+end-definitions
+
diff --git a/mtest/t-basic b/mtest/t-basic
new file mode 100755 (executable)
index 0000000..34a4ad0
--- /dev/null
@@ -0,0 +1,8 @@
+#! /usr/bin/tclsh
+
+source mtest/common.tcl
+
+run-mss --no-conf-key-prefix --pubkeys-elide test-example/sites $tmp/out.conf
+
+set seddery { sed -n 's/^[ \t]*//; /^[^#]/p' }
+diff  mtest/e-basic.conf $tmp/out.conf $seddery
diff --git a/mtest/t-filter b/mtest/t-filter
new file mode 100755 (executable)
index 0000000..78d8fb1
--- /dev/null
@@ -0,0 +1,7 @@
+#! /usr/bin/tclsh
+
+source mtest/common.tcl
+
+run-mss --filter --output-version=1 test-example/sites $tmp/out.sites
+
+diff  mtest/e-filter.sites $tmp/out.sites $seddery
diff --git a/mtest/t-prefix b/mtest/t-prefix
new file mode 100755 (executable)
index 0000000..d53b3cb
--- /dev/null
@@ -0,0 +1,9 @@
+#! /usr/bin/tclsh
+
+source mtest/common.tcl
+
+run-mss -Ppprefix --no-conf-key-prefix --pubkeys-elide test-example/sites $tmp/out.conf
+
+diff  mtest/e-basic.conf $tmp/out.conf \
+    "sed -e 's/vpn/pprefixvpn/g; s/^all-sites/pprefix&/' | $seddery" \
+    $seddery
diff --git a/mtest/t-userv b/mtest/t-userv
new file mode 100755 (executable)
index 0000000..5d159bd
--- /dev/null
@@ -0,0 +1,37 @@
+#! /usr/bin/tclsh
+
+source mtest/common.tcl
+
+#----- success test -----
+
+set good [list Uuser Ginside -u \
+          mtest/header.sites $tmp/groupfiles $tmp/out.sites Ginside \
+         < mtest/Ginside.sites]
+
+eval run-mss-userv $good
+
+diff-output e-userv out .sites
+
+#----- argument parser does not look for args beyond header -----
+
+set env(LC_MESSAGES) C
+
+set try [lreplace $good 4 4 --misparse-test]
+
+if {![catch {
+    eval run-mss-userv $try
+} emsg]} {
+    error "should have failed"
+} else {
+    switch -glob $emsg {
+       {*unrecognized arguments: --misparse-test*} {
+           error "misparsed!"
+       }
+       {*No such file or directory: '--misparse-test/TGinside'*} {
+       }
+       * {
+           error "huh ? $emsg"
+       }
+    }
+}
+
index bc707579c60ea3d1fe8474198edc0f1df4e6de13..7add6d7dc1638800a45aa180b9e758ac3938a1bd 100644 (file)
--- a/netlink.c
+++ b/netlink.c
@@ -1,5 +1,24 @@
 /* User-kernel network link */
 
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 /* See RFCs 791, 792, 1123 and 1812 */
 
 /* The netlink device is actually a router.  Tunnels are unnumbered
@@ -106,6 +125,12 @@ their use.
 #include "netlink.h"
 #include "process.h"
 
+#ifdef NETLINK_DEBUG
+#define MDEBUG(...) Message(M_DEBUG, __VA_ARGS__)
+#else /* !NETLINK_DEBUG */
+#define MDEBUG(...) ((void)0)
+#endif /* !NETLINK_DEBUG */
+
 #define ICMP_TYPE_ECHO_REPLY             0
 
 #define ICMP_TYPE_UNREACHABLE            3
@@ -120,7 +145,7 @@ their use.
 #define ICMP_CODE_TTL_EXCEEDED           0
 
 /* Generic IP checksum routine */
-static inline uint16_t ip_csum(uint8_t *iph,int32_t count)
+static inline uint16_t ip_csum(const uint8_t *iph,int32_t count)
 {
     register uint32_t sum=0;
 
@@ -144,7 +169,7 @@ static inline uint16_t ip_csum(uint8_t *iph,int32_t count)
  *      By Jorge Cwik <jorge@laser.satlink.net>, adapted for linux by
  *      Arnt Gulbrandsen.
  */
-static inline uint16_t ip_fast_csum(uint8_t *iph, int32_t ihl) {
+static inline uint16_t ip_fast_csum(const uint8_t *iph, int32_t ihl) {
     uint32_t sum;
 
     __asm__ __volatile__(
@@ -174,7 +199,7 @@ static inline uint16_t ip_fast_csum(uint8_t *iph, int32_t ihl) {
     return sum;
 }
 #else
-static inline uint16_t ip_fast_csum(uint8_t *iph, int32_t ihl)
+static inline uint16_t ip_fast_csum(const uint8_t *iph, int32_t ihl)
 {
     assert(ihl < INT_MAX/4);
     return ip_csum(iph,ihl*4);
@@ -192,7 +217,11 @@ struct iphdr {
     uint8_t    tos;
     uint16_t   tot_len;
     uint16_t   id;
-    uint16_t   frag_off;
+    uint16_t   frag;
+#define IPHDR_FRAG_OFF  ((uint16_t)0x1fff)
+#define IPHDR_FRAG_MORE ((uint16_t)0x2000)
+#define IPHDR_FRAG_DONT ((uint16_t)0x4000)
+/*                 reserved        0x8000 */
     uint8_t    ttl;
     uint8_t    protocol;
     uint16_t   check;
@@ -206,7 +235,7 @@ struct icmphdr {
     uint8_t type;
     uint8_t code;
     uint16_t check;
-    union {
+    union icmpinfofield {
        uint32_t unused;
        struct {
            uint8_t pointer;
@@ -218,9 +247,29 @@ struct icmphdr {
            uint16_t id;
            uint16_t seq;
        } echo;
+       struct {
+           uint16_t unused;
+           uint16_t mtu;
+       } fragneeded;
     } d;
 };
+
+static const union icmpinfofield icmp_noinfo;
     
+static void netlink_client_deliver(struct netlink *st,
+                                  struct netlink_client *client,
+                                  uint32_t source, uint32_t dest,
+                                  struct buffer_if *buf);
+static void netlink_host_deliver(struct netlink *st,
+                                struct netlink_client *sender,
+                                uint32_t source, uint32_t dest,
+                                struct buffer_if *buf);
+
+static const char *sender_name(struct netlink_client *sender /* or NULL */)
+{
+    return sender?sender->name:"(local)";
+}
+
 static void netlink_packet_deliver(struct netlink *st,
                                   struct netlink_client *client,
                                   struct buffer_if *buf);
@@ -233,7 +282,8 @@ static void netlink_packet_deliver(struct netlink *st,
    settable.
    */
 static struct icmphdr *netlink_icmp_tmpl(struct netlink *st,
-                                        uint32_t dest,uint16_t len)
+                                        uint32_t source, uint32_t dest,
+                                        uint16_t len)
 {
     struct icmphdr *h;
 
@@ -246,10 +296,10 @@ static struct icmphdr *netlink_icmp_tmpl(struct netlink *st,
     h->iph.tos=0;
     h->iph.tot_len=htons(len+(h->iph.ihl*4)+8);
     h->iph.id=0;
-    h->iph.frag_off=0;
+    h->iph.frag=0;
     h->iph.ttl=255; /* XXX should be configurable */
     h->iph.protocol=1;
-    h->iph.saddr=htonl(st->secnet_address);
+    h->iph.saddr=htonl(source);
     h->iph.daddr=htonl(dest);
     h->iph.check=0;
     h->iph.check=ip_fast_csum((uint8_t *)&h->iph,h->iph.ihl);
@@ -293,18 +343,27 @@ static bool_t netlink_icmp_may_reply(struct buffer_if *buf)
     struct icmphdr *icmph;
     uint32_t source;
 
+    if (buf->size < (int)sizeof(struct icmphdr)) return False;
     iph=(struct iphdr *)buf->start;
     icmph=(struct icmphdr *)buf->start;
     if (iph->protocol==1) {
        switch(icmph->type) {
-       case 3: /* Destination unreachable */
-       case 11: /* Time Exceeded */
-       case 12: /* Parameter Problem */
+           /* Based on http://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml#icmp-parameters-types
+            * as retrieved Thu, 20 Mar 2014 00:16:44 +0000.
+            * Deprecated, reserved, unassigned and experimental
+            * options are treated as not safe to reply to.
+            */
+       case 0: /* Echo Reply */
+       case 8: /* Echo */
+       case 13: /* Timestamp */
+       case 14: /* Timestamp Reply */
+           return True;
+       default:
            return False;
        }
     }
     /* How do we spot broadcast destination addresses? */
-    if (ntohs(iph->frag_off)&0x1fff) return False; /* Non-initial fragment */
+    if (ntohs(iph->frag)&IPHDR_FRAG_OFF) return False;
     source=ntohl(iph->saddr);
     if (source==0) return False;
     if ((source&0xff000000)==0x7f000000) return False;
@@ -338,6 +397,7 @@ static bool_t netlink_icmp_may_reply(struct buffer_if *buf)
    */
 static uint16_t netlink_icmp_reply_len(struct buffer_if *buf)
 {
+    if (buf->size < (int)sizeof(struct iphdr)) return 0;
     struct iphdr *iph=(struct iphdr *)buf->start;
     uint16_t hlen,plen;
 
@@ -345,26 +405,60 @@ static uint16_t netlink_icmp_reply_len(struct buffer_if *buf)
     /* We include the first 8 bytes of the packet data, provided they exist */
     hlen+=8;
     plen=ntohs(iph->tot_len);
-    return (hlen>plen?plen:hlen);
+    return MIN(hlen,plen);
 }
 
 /* client indicates where the packet we're constructing a response to
    comes from. NULL indicates the host. */
-static void netlink_icmp_simple(struct netlink *st, struct buffer_if *buf,
-                               struct netlink_client *client,
-                               uint8_t type, uint8_t code)
+static void netlink_icmp_simple(struct netlink *st,
+                               struct netlink_client *origsender,
+                               struct buffer_if *buf,
+                               uint8_t type, uint8_t code,
+                               union icmpinfofield info)
 {
-    struct iphdr *iph=(struct iphdr *)buf->start;
     struct icmphdr *h;
     uint16_t len;
 
     if (netlink_icmp_may_reply(buf)) {
+       struct iphdr *iph=(struct iphdr *)buf->start;
+
+       uint32_t icmpdest = ntohl(iph->saddr);
+       uint32_t icmpsource;
+       const char *icmpsourcedebugprefix;
+       if (!st->ptp) {
+           icmpsource=st->secnet_address;
+           icmpsourcedebugprefix="";
+       } else if (origsender) {
+           /* was from peer, send reply as if from host */
+           icmpsource=st->local_address;
+           icmpsourcedebugprefix="L!";
+       } else {
+           /* was from host, send reply as if from peer */
+           icmpsource=st->secnet_address; /* actually, peer address */
+           icmpsourcedebugprefix="P!";
+       }
+       MDEBUG("%s: generating ICMP re %s[%s]->[%s]:"
+              " from %s%s type=%u code=%u\n",
+              st->name, sender_name(origsender),
+              ipaddr_to_string(ntohl(iph->saddr)),
+              ipaddr_to_string(ntohl(iph->daddr)),
+              icmpsourcedebugprefix,
+              ipaddr_to_string(icmpsource),
+              type, code);
+
        len=netlink_icmp_reply_len(buf);
-       h=netlink_icmp_tmpl(st,ntohl(iph->saddr),len);
-       h->type=type; h->code=code;
-       memcpy(buf_append(&st->icmp,len),buf->start,len);
+       h=netlink_icmp_tmpl(st,icmpsource,icmpdest,len);
+       h->type=type; h->code=code; h->d=info;
+       BUF_ADD_BYTES(append,&st->icmp,buf->start,len);
        netlink_icmp_csum(h);
-       netlink_packet_deliver(st,NULL,&st->icmp);
+
+       if (!st->ptp) {
+           netlink_packet_deliver(st,NULL,&st->icmp);
+       } else if (origsender) {
+           netlink_client_deliver(st,origsender,icmpsource,icmpdest,&st->icmp);
+       } else {
+           netlink_host_deliver(st,NULL,icmpsource,icmpdest,&st->icmp);
+       }
        BUF_ASSERT_FREE(&st->icmp);
     }
 }
@@ -389,11 +483,12 @@ static bool_t netlink_check(struct netlink *st, struct buffer_if *buf,
        return False;                                   \
     }while(0)
 
+    if (buf->size < (int)sizeof(struct iphdr)) BAD("len %"PRIu32"",buf->size);
     struct iphdr *iph=(struct iphdr *)buf->start;
     int32_t len;
 
-    if (iph->ihl < 5) BAD("ihl %u",iph->ihl);
     if (iph->version != 4) BAD("version %u",iph->version);
+    if (iph->ihl < 5) BAD("ihl %u",iph->ihl);
     if (buf->size < iph->ihl*4) BAD("size %"PRId32"<%u*4",buf->size,iph->ihl);
     if (ip_fast_csum((uint8_t *)iph, iph->ihl)!=0) BAD("csum");
     len=ntohs(iph->tot_len);
@@ -406,13 +501,194 @@ static bool_t netlink_check(struct netlink *st, struct buffer_if *buf,
 #undef BAD
 }
 
-/* Deliver a packet. "client" is the _origin_ of the packet, not its
-   destination, and is NULL for packets from the host and packets
+static const char *fragment_filter_header(uint8_t *base, long *hlp)
+{
+    const int fixedhl = sizeof(struct iphdr);
+    long hl = *hlp;
+    const uint8_t *ipend = base + hl;
+    uint8_t *op = base + fixedhl;
+    const uint8_t *ip = op;
+
+    while (ip < ipend) {
+       uint8_t opt = ip[0];
+       int remain = ipend - ip;
+       if (opt == 0x00) /* End of Options List */ break;
+       if (opt == 0x01) /* No Operation */ continue;
+       if (remain < 2) return "IPv4 options truncated at length";
+       int optlen = ip[1];
+       if (remain < optlen) return "IPv4 options truncated in option";
+       if (opt & 0x80) /* copy */ {
+           memmove(op, ip, optlen);
+           op += optlen;
+       }
+       ip += optlen;
+    }
+    while ((hl = (op - base)) & 0x3)
+       *op++ = 0x00 /* End of Option List */;
+    ((struct iphdr*)base)->ihl = hl >> 2;
+    *hlp = hl;
+
+    return 0;
+}
+
+/* Fragment or send ICMP Fragmentation Needed */
+static void netlink_maybe_fragment(struct netlink *st,
+                                  struct netlink_client *sender,
+                                  netlink_deliver_fn *deliver,
+                                  void *deliver_dst,
+                                  const char *delivery_name,
+                                  int32_t mtu,
+                                  uint32_t source, uint32_t dest,
+                                  struct buffer_if *buf)
+{
+    struct iphdr *iph=(struct iphdr*)buf->start;
+    long hl = iph->ihl*4;
+    const char *ssource = ipaddr_to_string(source);
+
+    if (buf->size <= mtu) {
+       deliver(deliver_dst, buf);
+       return;
+    }
+
+    MDEBUG("%s: fragmenting %s->%s org.size=%"PRId32"\n",
+          st->name, ssource, delivery_name, buf->size);
+
+#define BADFRAG(m, ...)                                        \
+       Message(M_WARNING,                              \
+               "%s: fragmenting packet from source %s" \
+               " for transmission via %s: " m "\n",    \
+               st->name, ssource, delivery_name,       \
+               ## __VA_ARGS__);
+
+    unsigned orig_frag = ntohs(iph->frag);
+
+    if (orig_frag&IPHDR_FRAG_DONT) {
+       union icmpinfofield info =
+           { .fragneeded = { .unused = 0, .mtu = htons(mtu) } };
+       netlink_icmp_simple(st,sender,buf,
+                           ICMP_TYPE_UNREACHABLE,
+                           ICMP_CODE_FRAGMENTATION_REQUIRED,
+                           info);
+       BUF_FREE(buf);
+       return;
+    }
+    if (mtu < hl + 8) {
+       BADFRAG("mtu %"PRId32" too small", mtu);
+       BUF_FREE(buf);
+       return;
+    }
+
+    /* we (ab)use the icmp buffer to stash the original packet */
+    struct buffer_if *orig = &st->icmp;
+    BUF_ALLOC(orig,"netlink_client_deliver fragment orig");
+    buffer_copy(orig,buf);
+    BUF_FREE(buf);
+
+    const uint8_t *startindata = orig->start + hl;
+    const uint8_t *indata =      startindata;
+    const uint8_t *endindata =   orig->start + orig->size;
+    _Bool filtered = 0;
+
+    for (;;) {
+       /* compute our fragment offset */
+       long dataoffset = indata - startindata
+           + (orig_frag & IPHDR_FRAG_OFF)*8;
+       assert(!(dataoffset & 7));
+       if (dataoffset > IPHDR_FRAG_OFF*8) {
+           BADFRAG("ultimate fragment offset out of range");
+           break;
+       }
+
+       BUF_ALLOC(buf,"netlink_client_deliver fragment frag");
+       buffer_init(buf,calculate_max_start_pad());
+
+       /* copy header (possibly filtered); will adjust in a bit */
+       struct iphdr *fragh = buf_append(buf, hl);
+       memcpy(fragh, orig->start, hl);
+
+       /* decide how much payload to copy and copy it */
+       long avail = mtu - hl;
+       long remain = endindata - indata;
+       long use = avail < remain ? (avail & ~(long)7) : remain;
+       BUF_ADD_BYTES(append, buf, indata, use);
+       indata += use;
+
+       _Bool last_frag = indata >= endindata;
+
+       /* adjust the header */
+       fragh->tot_len = htons(buf->size);
+       fragh->frag =
+           htons((orig_frag & ~IPHDR_FRAG_OFF) |
+                 (last_frag ? 0 : IPHDR_FRAG_MORE) |
+                 (dataoffset >> 3));
+       fragh->check = 0;
+       fragh->check = ip_fast_csum((const void*)fragh, fragh->ihl);
+
+       /* actually send it */
+       deliver(deliver_dst, buf);
+       if (last_frag)
+           break;
+
+       /* after copying the header for the first frag,
+        * we filter the header for the remaining frags */
+       if (!filtered++) {
+           const char *bad = fragment_filter_header(orig->start, &hl);
+           if (bad) { BADFRAG("%s", bad); break; }
+       }
+    }
+
+    BUF_FREE(orig);
+
+#undef BADFRAG
+}
+
+/* Deliver a packet _to_ client; used after we have decided
+ * what to do with it (and just to check that the client has
+ * actually registered a delivery function with us). */
+static void netlink_client_deliver(struct netlink *st,
+                                  struct netlink_client *client,
+                                  uint32_t source, uint32_t dest,
+                                  struct buffer_if *buf)
+{
+    if (!client->deliver) {
+       string_t s,d;
+       s=ipaddr_to_string(source);
+       d=ipaddr_to_string(dest);
+       Message(M_ERR,"%s: dropping %s->%s, client not registered\n",
+               st->name,s,d);
+       BUF_FREE(buf);
+       return;
+    }
+    netlink_maybe_fragment(st,NULL, client->deliver,client->dst,client->name,
+                          client->mtu, source,dest,buf);
+    client->outcount++;
+}
+
+/* Deliver a packet to the host; used after we have decided that that
+ * is what to do with it. */
+static void netlink_host_deliver(struct netlink *st,
+                                struct netlink_client *sender,
+                                uint32_t source, uint32_t dest,
+                                struct buffer_if *buf)
+{
+    netlink_maybe_fragment(st,sender, st->deliver_to_host,st->dst,"(host)",
+                          st->mtu, source,dest,buf);
+    st->outcount++;
+}
+
+/* Deliver a packet. "sender"==NULL for packets from the host and packets
    generated internally in secnet.  */
 static void netlink_packet_deliver(struct netlink *st,
-                                  struct netlink_client *client,
+                                  struct netlink_client *sender,
                                   struct buffer_if *buf)
 {
+    if (buf->size < (int)sizeof(struct iphdr)) {
+       Message(M_ERR,"%s: trying to deliver a too-short packet"
+               " from %s!\n",st->name, sender_name(sender));
+       BUF_FREE(buf);
+       return;
+    }
+
     struct iphdr *iph=(struct iphdr *)buf->start;
     uint32_t dest=ntohl(iph->daddr);
     uint32_t source=ntohl(iph->saddr);
@@ -430,9 +706,9 @@ static void netlink_packet_deliver(struct netlink *st,
        return;
     }
     
-    /* Packets from the host (client==NULL) may always be routed.  Packets
+    /* Packets from the host (sender==NULL) may always be routed.  Packets
        from clients with the allow_route option will also be routed. */
-    if (!client || (client && (client->options & OPT_ALLOWROUTE)))
+    if (!sender || (sender && (sender->options & OPT_ALLOWROUTE)))
        allow_route=True;
 
     /* If !allow_route, we check the routing table anyway, and if
@@ -478,8 +754,7 @@ static void netlink_packet_deliver(struct netlink *st,
        /* The packet's not going down a tunnel.  It might (ought to)
           be for the host.   */
        if (ipset_contains_addr(st->networks,dest)) {
-           st->deliver_to_host(st->dst,buf);
-           st->outcount++;
+           netlink_host_deliver(st,sender,source,dest,buf);
            BUF_ASSERT_FREE(buf);
        } else {
            string_t s,d;
@@ -487,9 +762,8 @@ static void netlink_packet_deliver(struct netlink *st,
            d=ipaddr_to_string(dest);
            Message(M_DEBUG,"%s: don't know where to deliver packet "
                    "(s=%s, d=%s)\n", st->name, s, d);
-           free(s); free(d);
-           netlink_icmp_simple(st,buf,client,ICMP_TYPE_UNREACHABLE,
-                               ICMP_CODE_NET_UNREACHABLE);
+           netlink_icmp_simple(st,sender,buf,ICMP_TYPE_UNREACHABLE,
+                               ICMP_CODE_NET_UNREACHABLE, icmp_noinfo);
            BUF_FREE(buf);
        }
     } else {
@@ -503,22 +777,21 @@ static void netlink_packet_deliver(struct netlink *st,
               with destination network administratively prohibited */
            Message(M_NOTICE,"%s: denied forwarding for packet (s=%s, d=%s)\n",
                    st->name,s,d);
-           free(s); free(d);
                    
-           netlink_icmp_simple(st,buf,client,ICMP_TYPE_UNREACHABLE,
-                               ICMP_CODE_NET_PROHIBITED);
+           netlink_icmp_simple(st,sender,buf,ICMP_TYPE_UNREACHABLE,
+                               ICMP_CODE_NET_PROHIBITED, icmp_noinfo);
            BUF_FREE(buf);
        } else {
            if (best_quality>0) {
-               /* XXX Fragment if required */
-               st->routes[best_match]->deliver(
-                   st->routes[best_match]->dst, buf);
-               st->routes[best_match]->outcount++;
+               netlink_client_deliver(st,st->routes[best_match],
+                                      source,dest,buf);
                BUF_ASSERT_FREE(buf);
            } else {
                /* Generate ICMP destination unreachable */
-               netlink_icmp_simple(st,buf,client,ICMP_TYPE_UNREACHABLE,
-                                   ICMP_CODE_NET_UNREACHABLE); /* client==NULL */
+               netlink_icmp_simple(st,sender,buf,
+                                   ICMP_TYPE_UNREACHABLE,
+                                   ICMP_CODE_NET_UNREACHABLE,
+                                   icmp_noinfo);
                BUF_FREE(buf);
            }
        }
@@ -527,9 +800,10 @@ static void netlink_packet_deliver(struct netlink *st,
 }
 
 static void netlink_packet_forward(struct netlink *st, 
-                                  struct netlink_client *client,
+                                  struct netlink_client *sender,
                                   struct buffer_if *buf)
 {
+    if (buf->size < (int)sizeof(struct iphdr)) return;
     struct iphdr *iph=(struct iphdr *)buf->start;
     
     BUF_ASSERT_USED(buf);
@@ -537,8 +811,8 @@ static void netlink_packet_forward(struct netlink *st,
     /* Packet has already been checked */
     if (iph->ttl<=1) {
        /* Generate ICMP time exceeded */
-       netlink_icmp_simple(st,buf,client,ICMP_TYPE_TIME_EXCEEDED,
-                           ICMP_CODE_TTL_EXCEEDED);
+       netlink_icmp_simple(st,sender,buf,ICMP_TYPE_TIME_EXCEEDED,
+                           ICMP_CODE_TTL_EXCEEDED,icmp_noinfo);
        BUF_FREE(buf);
        return;
     }
@@ -546,24 +820,33 @@ static void netlink_packet_forward(struct netlink *st,
     iph->check=0;
     iph->check=ip_fast_csum((uint8_t *)iph,iph->ihl);
 
-    netlink_packet_deliver(st,client,buf);
+    netlink_packet_deliver(st,sender,buf);
     BUF_ASSERT_FREE(buf);
 }
 
 /* Deal with packets addressed explicitly to us */
 static void netlink_packet_local(struct netlink *st,
-                                struct netlink_client *client,
+                                struct netlink_client *sender,
                                 struct buffer_if *buf)
 {
     struct icmphdr *h;
 
     st->localcount++;
 
+    if (buf->size < (int)sizeof(struct icmphdr)) {
+       Message(M_WARNING,"%s: short packet addressed to secnet; "
+               "ignoring it\n",st->name);
+       BUF_FREE(buf);
+       return;
+    }
     h=(struct icmphdr *)buf->start;
 
-    if ((ntohs(h->iph.frag_off)&0xbfff)!=0) {
-       Message(M_WARNING,"%s: fragmented packet addressed to secnet; "
-               "ignoring it\n",st->name);
+    unsigned fraginfo = ntohs(h->iph.frag);
+    if ((fraginfo&(IPHDR_FRAG_OFF|IPHDR_FRAG_MORE))!=0) {
+       if (!(fraginfo & IPHDR_FRAG_OFF))
+           /* report only for first fragment */
+           Message(M_WARNING,"%s: fragmented packet addressed to secnet; "
+                   "ignoring it\n",st->name);
        BUF_FREE(buf);
        return;
     }
@@ -586,8 +869,8 @@ static void netlink_packet_local(struct netlink *st,
        Message(M_WARNING,"%s: unknown incoming ICMP\n",st->name);
     } else {
        /* Send ICMP protocol unreachable */
-       netlink_icmp_simple(st,buf,client,ICMP_TYPE_UNREACHABLE,
-                           ICMP_CODE_PROTOCOL_UNREACHABLE);
+       netlink_icmp_simple(st,sender,buf,ICMP_TYPE_UNREACHABLE,
+                           ICMP_CODE_PROTOCOL_UNREACHABLE,icmp_noinfo);
        BUF_FREE(buf);
        return;
     }
@@ -597,13 +880,13 @@ static void netlink_packet_local(struct netlink *st,
 
 /* If cid==NULL packet is from host, otherwise cid specifies which tunnel 
    it came from. */
-static void netlink_incoming(struct netlink *st, struct netlink_client *client,
+static void netlink_incoming(struct netlink *st, struct netlink_client *sender,
                             struct buffer_if *buf)
 {
     uint32_t source,dest;
     struct iphdr *iph;
     char errmsgbuf[50];
-    const char *sourcedesc=client?client->name:"host";
+    const char *sourcedesc=sender?sender->name:"host";
 
     BUF_ASSERT_USED(buf);
 
@@ -614,6 +897,7 @@ static void netlink_incoming(struct netlink *st, struct netlink_client *client,
        BUF_FREE(buf);
        return;
     }
+    assert(buf->size >= (int)sizeof(struct iphdr));
     iph=(struct iphdr *)buf->start;
 
     source=ntohl(iph->saddr);
@@ -622,16 +906,15 @@ static void netlink_incoming(struct netlink *st, struct netlink_client *client,
     /* Check source. If we don't like the source, there's no point
        generating ICMP because we won't know how to get it to the
        source of the packet. */
-    if (client) {
+    if (sender) {
        /* Check that the packet source is appropriate for the tunnel
           it came down */
-       if (!ipset_contains_addr(client->networks,source)) {
+       if (!ipset_contains_addr(sender->networks,source)) {
            string_t s,d;
            s=ipaddr_to_string(source);
            d=ipaddr_to_string(dest);
            Message(M_WARNING,"%s: packet from tunnel %s with bad "
-                   "source address (s=%s,d=%s)\n",st->name,client->name,s,d);
-           free(s); free(d);
+                   "source address (s=%s,d=%s)\n",st->name,sender->name,s,d);
            BUF_FREE(buf);
            return;
        }
@@ -645,7 +928,6 @@ static void netlink_incoming(struct netlink *st, struct netlink_client *client,
            d=ipaddr_to_string(dest);
            Message(M_WARNING,"%s: outgoing packet with bad source address "
                    "(s=%s,d=%s)\n",st->name,s,d);
-           free(s); free(d);
            BUF_FREE(buf);
            return;
        }
@@ -657,10 +939,10 @@ static void netlink_incoming(struct netlink *st, struct netlink_client *client,
        where it came from.  It's up to external software to check
        address validity and generate ICMP, etc. */
     if (st->ptp) {
-       if (client) {
-           st->deliver_to_host(st->dst,buf);
+       if (sender) {
+           netlink_host_deliver(st,sender,source,dest,buf);
        } else {
-           st->clients->deliver(st->clients->dst,buf);
+           netlink_client_deliver(st,st->clients,source,dest,buf);
        }
        BUF_ASSERT_FREE(buf);
        return;
@@ -669,11 +951,11 @@ static void netlink_incoming(struct netlink *st, struct netlink_client *client,
     /* st->secnet_address needs checking before matching destination
        addresses */
     if (dest==st->secnet_address) {
-       netlink_packet_local(st,client,buf);
+       netlink_packet_local(st,sender,buf);
        BUF_ASSERT_FREE(buf);
        return;
     }
-    netlink_packet_forward(st,client,buf);
+    netlink_packet_forward(st,sender,buf);
     BUF_ASSERT_FREE(buf);
 }
 
@@ -713,7 +995,6 @@ static void netlink_output_subnets(struct netlink *st, uint32_t loglevel,
     for (i=0; i<snets->entries; i++) {
        net=subnet_to_string(snets->list[i]);
        Message(loglevel,"%s ",net);
-       free(net);
     }
 }
 
@@ -726,9 +1007,8 @@ static void netlink_dump_routes(struct netlink *st, bool_t requested)
     if (requested) c=M_WARNING;
     if (st->ptp) {
        net=ipaddr_to_string(st->secnet_address);
-       Message(c,"%s: point-to-point (remote end is %s); routes:\n",
+       Message(c,"%s: point-to-point (remote end is %s); routes: ",
                st->name, net);
-       free(net);
        netlink_output_subnets(st,c,st->clients->subnets);
        Message(c,"\n");
     } else {
@@ -749,11 +1029,9 @@ static void netlink_dump_routes(struct netlink *st, bool_t requested)
        net=ipaddr_to_string(st->secnet_address);
        Message(c,"%s/32 -> netlink \"%s\" (use %d)\n",
                net,st->name,st->localcount);
-       free(net);
        for (i=0; i<st->subnets->entries; i++) {
            net=subnet_to_string(st->subnets->list[i]);
            Message(c,"%s ",net);
-           free(net);
        }
        if (i>0)
            Message(c,"-> host (use %d)\n",st->outcount);
@@ -780,8 +1058,7 @@ static void netlink_phase_hook(void *sst, uint32_t new_phase)
     /* All the networks serviced by the various tunnels should now
      * have been registered.  We build a routing table by sorting the
      * clients by priority.  */
-    st->routes=safe_malloc_ary(sizeof(*st->routes),st->n_clients,
-                              "netlink_phase_hook");
+    NEW_ARY(st->routes,st->n_clients);
     /* Fill the table */
     i=0;
     for (c=st->clients; c; c=c->next) {
@@ -810,12 +1087,16 @@ static void netlink_inst_set_mtu(void *sst, int32_t new_mtu)
 }
 
 static void netlink_inst_reg(void *sst, netlink_deliver_fn *deliver, 
-                            void *dst)
+                            void *dst, uint32_t *localmtu_r)
 {
     struct netlink_client *c=sst;
+    struct netlink *st=c->nst;
 
     c->deliver=deliver;
     c->dst=dst;
+
+    if (localmtu_r)
+       *localmtu_r=st->mtu;
 }
 
 static struct flagstr netlink_option_table[]={
@@ -875,7 +1156,7 @@ static closure_t *netlink_inst_create(struct netlink *st,
        return NULL;
     }
 
-    c=safe_malloc(sizeof(*c),"netlink_inst_create");
+    NEW(c);
     c->cl.description=name;
     c->cl.type=CL_NETLINK;
     c->cl.apply=NULL;
@@ -968,6 +1249,8 @@ netlink_deliver_fn *netlink_init(struct netlink *st,
        st->remote_networks=ipset_complement(empty);
        ipset_free(empty);
     }
+    st->local_address=string_item_to_ipaddr(
+       dict_find_item(dict,"local-address", True, "netlink", loc),"netlink");
 
     sa=dict_find_item(dict,"secnet-address",False,"netlink",loc);
     ptpa=dict_find_item(dict,"ptp-address",False,"netlink",loc);
@@ -991,7 +1274,7 @@ netlink_deliver_fn *netlink_init(struct netlink *st,
        though, and will make the route dump look complicated... */
     st->subnets=ipset_to_subnet_list(st->networks);
     st->mtu=dict_read_number(dict, "mtu", False, "netlink", loc, DEFAULT_MTU);
-    buffer_new(&st->icmp,ICMP_BUFSIZE);
+    buffer_new(&st->icmp,MAX(ICMP_BUFSIZE,st->mtu));
     st->outcount=0;
     st->localcount=0;
 
@@ -1042,7 +1325,7 @@ static list_t *null_apply(closure_t *self, struct cloc loc, dict_t *context,
     item_t *item;
     dict_t *dict;
 
-    st=safe_malloc(sizeof(*st),"null_apply");
+    NEW(st);
 
     item=list_elem(args,0);
     if (!item || item->type!=t_dict)
index 7c427161eae531e5d26238dd97f5544a0b4c8b0f..93b556d477249c42bc1eec65ac64db275522957c 100644 (file)
--- a/netlink.h
+++ b/netlink.h
@@ -1,3 +1,22 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #ifndef netlink_h
 #define netlink_h
 
@@ -48,6 +67,7 @@ struct netlink {
     struct ipset *networks; /* Local networks */
     struct subnet_list *subnets; /* Same as networks, for display */
     struct ipset *remote_networks; /* Allowable remote networks */
+    uint32_t local_address; /* host interface address */
     uint32_t secnet_address; /* our own address, or the address of the
                                other end of a point-to-point link */
     bool_t ptp;
diff --git a/osdep.c b/osdep.c
new file mode 100644 (file)
index 0000000..2407698
--- /dev/null
+++ b/osdep.c
@@ -0,0 +1,65 @@
+/*
+ * osdep.c
+ * - portability routines
+ */
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#include "config.h"
+#include "osdep.h"
+#include "secnet.h"
+#include "util.h"
+
+#ifndef HAVE_FMEMOPEN
+# ifdef HAVE_FUNOPEN
+
+struct fmemopen_state {
+    const char *bufp;
+    size_t remain;
+};
+
+static int fmemopen_readfn(void *sst, char *out, int sz)
+{
+    struct fmemopen_state *st=sst;
+    assert(sz>=0);
+    int now=MIN((size_t)sz,st->remain);
+    memcpy(out,st->bufp,now);
+    st->remain-=now;
+    return now;
+}
+static int fmemopen_close(void *sst) { free(sst); return 0; }
+
+FILE *fmemopen(void *buf, size_t size, const char *mode)
+{
+    /* this is just a fake plastic imitation */
+    assert(!strcmp(mode,"r"));
+    struct fmemopen_state *st;
+    NEW(st);
+    st->bufp=buf;
+    st->remain=size;
+    FILE *f=funopen(st,fmemopen_readfn,0,0,fmemopen_close);
+    if (!f) free(st);
+    return f;
+}
+
+# else /* HAVE_FUNOPEN */
+#  error no fmemopen, no funopen, cannot proceed
+# endif
+
+#endif /* HAVE_FMEMOPEN */
diff --git a/osdep.h b/osdep.h
new file mode 100644 (file)
index 0000000..46c532b
--- /dev/null
+++ b/osdep.h
@@ -0,0 +1,35 @@
+/*
+ * osdep.c
+ * - portability routines
+ */
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#ifndef osdep_h
+#define osdep_h
+
+#include "config.h"
+
+#include <stdio.h>
+
+#ifndef HAVE_FMEMOPEN
+extern FILE *fmemopen(void *buf, size_t size, const char *mode);
+#endif
+
+#endif /* osdep_h */
diff --git a/parallel-test.make b/parallel-test.make
new file mode 100644 (file)
index 0000000..17c190a
--- /dev/null
@@ -0,0 +1,12 @@
+
+# usage
+#  ../parallel-bisect.sh
+
+DIRS := $(wildcard d.*)
+
+TARGETS := $(addsuffix /done, $(DIRS))
+
+all: $(TARGETS)
+
+%/done:
+       set -e; SECNET_TEST_BUILDDIR=$(PWD)/$* ./stest/t-nonnego-oo
diff --git a/parallel-test.sh b/parallel-test.sh
new file mode 100755 (executable)
index 0000000..ccd6ab3
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+# usage
+#  ../parallel-bisect.sh
+#
+# There should be subdirectories d.N for N=1..20
+# which are build trees of the current secnet.
+
+set -ex
+cd d.1
+make -j4 clean
+make -j4 stest/d-nonnego-oo/ok
+cd ..
+for f in d.*; do
+    ln d.1/secnet $f/secnet.new
+    rm $f/secnet
+    mv $f/secnet.new $f/secnet
+done
+here=$(git rev-parse HEAD)
+us=${0%/*}
+log=$us/at-$here.log
+>$log
+for x in `seq 1 ${1-500}`; do
+    echo $x
+    echo >>$log $x
+    make -j -f $us/parallel-test.make >$us/dump/at-$here.log 2>&1
+    echo >>$log "$x ok"
+done
+echo ok
diff --git a/polypath-interface-monitor-linux b/polypath-interface-monitor-linux
new file mode 100755 (executable)
index 0000000..3ecae59
--- /dev/null
@@ -0,0 +1,122 @@
+#!/usr/bin/perl -w
+
+# This file is part of secnet.
+# See README for full list of copyright holders.
+#
+# secnet is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# 
+# secnet is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# version 3 along with secnet; if not, see
+# https://www.gnu.org/licenses/gpl.html.
+
+use strict;
+use IO::Handle;
+
+my $us = $0;
+$us =~ s{.*/}{};
+
+open DEBUG, ">/dev/null" or die $!;
+
+if (@ARGV && $ARGV[0] eq '-D') {
+    shift @ARGV;
+    open DEBUG, ">&STDERR" or die $!;
+}
+
+die "$us: no arguments permitted\n" if @ARGV;
+
+our ($monh,$monchild);
+
+our %reported;
+#  no entry: not reported, does not exist
+#  /ry+/: reported, entry exists
+# during processing only:
+#  /r/: reported, may not still exist
+#  /y+/: not reported, entry exists
+
+sub killmonitor () {
+    return unless $monchild;
+    kill 9, $monchild
+       or warn "$us: cannot kill monitor child [$monchild]: $!\n";
+    $monchild=undef;
+    close $monh;
+}
+
+END { killmonitor(); }
+
+my $restart;
+
+for (;;) {
+    my $o;
+    eval {
+       if (!$monh) {
+           killmonitor();
+           $monh = new IO::File;
+           $monchild = open $monh, "-|", qw(ip -o monitor addr)
+               or die "spawn monitor: $!\n";
+           sleep(1) if $restart++;
+       } else {
+           my $discard;
+           my $got = sysread $monh, $discard, 4096;
+           die "read monitor: $!\n" unless defined $got;
+           die "monitor failed\n" unless $got;
+       }
+       $_='r' foreach values %reported;
+       print DEBUG "#########################################\n";
+       foreach my $ip (qw(4 6)) {
+           print DEBUG "###### $ip:\n";
+           my $addrh = new IO::File;
+           open $addrh, "-|", qw(ip -o), "-$ip", qw(addr show)
+               or die "spawn addr $ip show: $!\n";
+           my $afstr = $ip==4 ? 'inet' : $ip==6 ? 'inet6' : die;
+           while (<$addrh>) {
+               print DEBUG "#$_";
+               if (m{^\d+\:\s*(\S+)\s+$afstr\s+([0-9a-z.:]+)(?:/\d+)?\s}) {
+                   my $rhs=$'; #';
+                   my $outline = "$ip $1 $2";
+                   # "ip -o addr show" has a ridiculous output format.  In
+                   # particular, it mixes output keywords which introduce
+                   # values with ones which don't, and there seems to be
+                   # no way to tell without knowing all the possible
+                   # keywords.  We hope that before the \ there is nothing
+                   # which contains arbitrary text (specifically, which
+                   # might be `tentative' other than to specify IPv6
+                   # tentativeness).  We have to do this for IPv6 only
+                   # because in the IPv4 output, the interface name
+                   # appears here!
+                   next if $ip==6 && $rhs=~m{[^\\]* tentative\s};
+                   $reported{$outline} .= "y";
+               } else {
+                   chomp;
+                   warn "unexpected output from addr $ip show: $_\n";
+               }
+           }
+           my $r = close $addrh;
+           die "addr $ip show failed $!\n" unless $r;
+           $o = '';
+       }
+       foreach my $k (keys %reported) {
+           local $_ = $reported{$k};
+           if (m/^r$/) {
+               $o .= "-$k\n";
+               delete $reported{$k};
+           } elsif (m/^y/) {
+               $o .= "+$k\n";
+           }
+       }
+    };
+    if ($@) {
+       print STDERR "$us: $@";
+       sleep 5;
+       next;
+    }
+    print $o or die $!;
+    STDOUT->flush or die $!;
+}
diff --git a/polypath.c b/polypath.c
new file mode 100644 (file)
index 0000000..64284a3
--- /dev/null
@@ -0,0 +1,938 @@
+/* polypath
+ * send/receive module for secnet
+ * for multi-route setups */
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#include "secnet.h"
+#include "util.h"
+#include "unaligned.h"
+#include "comm-common.h"
+
+#include <adns.h>
+#include <ctype.h>
+#include <limits.h>
+
+#ifdef CONFIG_IPV6
+
+static comm_sendmsg_fn polypath_sendmsg;
+
+struct interf {
+    char *name; /* from malloc */
+    struct udpsocks socks;
+    bool_t experienced_xmit_noaf[MAX_AF+1];
+    LIST_ENTRY(interf) entry;
+};
+
+struct polypath {
+    struct udpcommon uc;
+    int max_interfs;
+    const char *const *ifname_pats;
+    const char *const *monitor_command;
+    bool_t permit_loopback;
+    LIST_HEAD(interf_list, interf) interfs_general;
+    struct interf_list interfs_dedicated;
+    struct buffer_if lbuf;
+    int monitor_fd;
+    pid_t monitor_pid;
+    int privsep_incoming_fd;
+    int privsep_ipcsock_fd;
+};
+
+struct comm_clientinfo {
+    union iaddr dedicated; /* might be AF_UNSPEC */
+};
+
+static void polypath_phase_shutdown(void *sst, uint32_t newphase);
+
+#define LG 0, st->uc.cc.cl.description, &st->uc.cc.loc
+
+static const char *const default_loopback_ifname_pats[] = {
+    "!lo", 0
+};
+static const char *const default_ifname_pats[] = {
+    "!tun*","!tap*","!sl*","!userv*", "@hippo*", "*", 0
+};
+
+static const char *const default_monitor_command[] = {
+#if __linux__
+    DATAROOTDIR "/secnet/" "polypath-interface-monitor-linux", 0
+#else
+    0
+#endif
+};
+
+static const char *polypath_addr_to_string(void *commst,
+                                          const struct comm_addr *ca)
+{
+    static char sbuf[100];
+
+    snprintf(sbuf, sizeof(sbuf), "polypath:%s",
+            iaddr_to_string(&ca->ia));
+    return sbuf;
+}
+
+static bool_t ifname_search_pats(struct polypath *st, struct cloc loc,
+                                const char *ifname, char *want_io,
+                                const char *const *pats) {
+    /* Returns True iff we found a list entry, in which case *want_io
+     * is set to the sense of that entry.  Otherwise *want_io is set
+     * to the sense of the last entry, or unchanged if there were no pats. */
+    if (!pats)
+       return False;
+    const char *const *pati;
+    for (pati=pats; *pati; pati++) {
+       const char *pat=*pati;
+       if (*pat=='!' || *pat=='+' || *pat=='@') { *want_io=*pat; pat++; }
+       else if (*pat=='*' || isalnum((unsigned char)*pat)) { *want_io='+'; }
+       else cfgfatal(loc,"polypath","invalid interface name pattern `%s'",pat);
+       int match=fnmatch(pat,ifname,0);
+       if (match==0) return True;
+       if (match!=FNM_NOMATCH)
+           cfgfatal(loc,"polypath","fnmatch failed! (pattern `%s')",pat);
+    }
+    return False;
+}
+
+static char ifname_wanted(struct polypath *st, struct cloc loc,
+                         const char *ifname) {
+    char want='!'; /* pretend an empty cfg ends with !<doesn'tmatch> */
+    if (ifname_search_pats(st,loc,ifname,&want, st->ifname_pats))
+       return want;
+    if (want!='!') /* last pattern was positive, do not search default */
+       return '!';
+    if (!st->permit_loopback &&
+       ifname_search_pats(st,loc,ifname,&want, default_loopback_ifname_pats))
+       return want;
+    if (ifname_search_pats(st,loc,ifname,&want, default_ifname_pats))
+       return want;
+    abort();
+}
+
+static struct comm_clientinfo *polypath_clientinfo(void *state,
+                                    dict_t *dict, struct cloc cloc) {
+    struct comm_clientinfo *clientinfo;
+
+    NEW(clientinfo);
+    FILLZERO(*clientinfo);
+    clientinfo->dedicated.sa.sa_family=AF_UNSPEC;
+
+    item_t *item = dict_find_item(dict,"dedicated-interface-addr",
+                                 False,"polypath",cloc);
+    if (item) string_item_to_iaddr(item,0,&clientinfo->dedicated,
+                                  "polypath");
+    return clientinfo;
+}
+    
+static int polypath_beforepoll(void *state, struct pollfd *fds, int *nfds_io,
+                              int *timeout_io)
+{
+    struct polypath *st=state;
+    BEFOREPOLL_WANT_FDS(1);
+    fds[0].fd=st->monitor_fd;
+    fds[0].events=POLLIN;
+    return 0;
+}
+
+static inline bool_t matches32(uint32_t word, uint32_t prefix, int prefixlen)
+{
+    assert(prefixlen>0);
+    assert(prefixlen<=32);
+    uint32_t mask = ~(((uint32_t)1 << (32-prefixlen)) - 1);
+    assert(!(prefix & ~mask));
+    return (word & mask) == prefix;
+}
+
+/* These macros expect
+ *    bad_fn_type *const bad;
+ *    void *badctx;
+ * and
+ *   out:
+ */
+#define BAD(m)     do{ bad(st,badctx,M_WARNING,m,0);  goto out; }while(0)
+#define BADE(m,ev) do{ bad(st,badctx,M_WARNING,m,ev); goto out; }while(0)
+typedef void bad_fn_type(struct polypath *st, void *badctx,
+                        int mclass, const char* m, int ev);
+
+typedef void polypath_ppml_callback_type(struct polypath *st,
+          bad_fn_type *bad, void *badctx,
+          bool_t add, char want,
+          const char *ifname, const char *ifaddr,
+          const union iaddr *ia, int fd /* -1 if none yet */);
+
+struct ppml_bad_ctx {
+    const char *orgl;
+    char *undospace;
+};
+
+static void ppml_bad(struct polypath *st, void *badctx,
+                    int mclass, const char *m, int ev)
+{
+    struct ppml_bad_ctx *bc=badctx;
+    if (bc->undospace)
+       *(bc->undospace)=' ';
+    lg_perror(LG,mclass,ev,
+             "error processing polypath state change: %s"
+             " (while processing `%s')",
+             m,bc->orgl);
+}
+
+static void polypath_process_monitor_line(struct polypath *st, char *orgl,
+                                      polypath_ppml_callback_type *callback)
+    /* always calls callback with fd==-1 */
+{
+    struct udpcommon *uc=&st->uc;
+    char *l=orgl;
+    bad_fn_type (*const bad)=ppml_bad;
+    struct ppml_bad_ctx badctx[1]={{
+           .orgl=orgl,
+           .undospace=0
+       }};
+
+    bool_t add;
+    int c=*l++;
+    if (c=='+') add=True;
+    else if (c=='-') add=False;
+    else BAD("bad +/-");
+
+    int proto;
+    c=*l++;
+    if (c=='4') proto=AF_INET;
+    else if (c=='6') proto=AF_INET6;
+    else BAD("bad proto");
+
+    char *space=strchr(l,' ');
+    if (!space) BAD("no first space");
+    const char *ifname=space+1;
+
+    space=strchr(ifname,' ');
+    if (!space) BAD("no second space");
+    const char *ifaddr=space+1;
+    *space=0;
+    badctx->undospace=space;
+
+    union iaddr ia;
+    FILLZERO(ia);
+    socklen_t salen=sizeof(ia);
+    int r=adns_text2addr(ifaddr,uc->port, adns_qf_addrlit_ipv4_quadonly,
+                        &ia.sa, &salen);
+    assert(r!=ENOSPC);
+    if (r) BADE("adns_text2addr",r);
+    if (ia.sa.sa_family!=proto) BAD("address family mismatch");
+
+#define DONT(m) do{                                                    \
+       if (add)                                                        \
+           lg_perror(LG,M_INFO,0,"ignoring %s [%s]: %s",ifname,ifaddr,m); \
+       goto out;                                                       \
+    }while(0)
+
+    char want=ifname_wanted(st,st->uc.cc.loc,ifname);
+    if (want=='!') DONT("unwanted interface name");
+
+    switch (ia.sa.sa_family) {
+    case AF_INET6: {
+       const struct in6_addr *i6=&ia.sin6.sin6_addr;
+#define DONTKIND(X,m) \
+       if (IN6_IS_ADDR_##X(i6)) DONT("IPv6 address is " m)
+       DONTKIND(UNSPECIFIED, "unspecified");
+       DONTKIND(MULTICAST  , "multicast"  );
+       DONTKIND(LINKLOCAL  , "link local" );
+       DONTKIND(SITELOCAL  , "site local" );
+       DONTKIND(V4MAPPED   , "v4-mapped"  );
+       if (!st->permit_loopback)
+           DONTKIND(LOOPBACK   , "loopback"   );
+#undef DONTKIND
+#define DONTMASK(w7x,w6x,prefixlen,m)                                  \
+       if (matches32(get_uint32(i6->s6_addr),                          \
+                      ((uint32_t)0x##w7x << 16) | (uint32_t)0x##w6x,   \
+                      prefixlen))                                      \
+           DONT("IPv6 address is " m)
+        DONTMASK( 100,   0,  8, "Discard-Only (RFC6666)");
+       DONTMASK(2001,   0, 23, "in IETF protocol block (RFC2928)");
+       DONTMASK(fc00,   0,  7, "Uniqe Local unicast (RFC4193)");
+#undef DONTMASK
+       break;
+    }
+    case AF_INET: {
+       const uint32_t i4=htonl(ia.sin.sin_addr.s_addr);
+       if (i4==INADDR_ANY) DONT("IPv4 address is any/unspecified");
+       if (i4==INADDR_BROADCAST) DONT("IPv4 address is all hosts broadcast");
+#define DONTMASK(b3,b2,b1,b0,prefixlen,m) do{                          \
+           const uint8_t prefixbytes[4] = { (b3),(b2),(b1),(b0) };     \
+           if (matches32(i4,get_uint32(prefixbytes),prefixlen))        \
+               DONT("IPv4 address is " m);                             \
+       }while(0)
+       DONTMASK(169,254,0,0, 16, "link local");
+       DONTMASK(224,  0,0,0,  4, "multicast");
+       DONTMASK(192,  0,0,0, 24, "in IETF protocol block (RFC6890)");
+       DONTMASK(240,  0,0,0,  4, "in reserved addressing block (RFC1112)");
+       if (!st->permit_loopback)
+           DONTMASK(127,  0,0,0,  8, "loopback");
+#undef DONTMASK
+       break;
+    }
+    default:
+       abort();
+    }
+
+#undef DONT
+
+    /* OK, process it */
+    callback(st, bad,badctx, add,want, ifname,ifaddr,&ia,-1);
+
+ out:;
+}
+
+static void dump_pria(struct polypath *st, struct interf_list *interfs,
+                     const char *ifname, char want)
+{
+#ifdef POLYPATH_DEBUG
+    struct interf *interf;
+    if (ifname)
+       lg_perror(LG,M_DEBUG,0, "polypath record ifaddr `%s' (%c)",
+                 ifname, want);
+    LIST_FOREACH(interf, interfs, entry) {
+       lg_perror(LG,M_DEBUG,0, "  polypath interface `%s', nsocks=%d",
+                 interf->name, interf->socks.n_socks);
+       int i;
+       for (i=0; i<interf->socks.n_socks; i++) {
+           struct udpsock *us=&interf->socks.socks[i];
+           lg_perror(LG,M_DEBUG,0, "    polypath sock fd=%d addr=%s",
+                     us->fd, iaddr_to_string(&us->addr));
+       }
+    }
+#endif
+}
+
+static bool_t polypath_make_socket(struct polypath *st,
+                                  bad_fn_type *bad, void *badctx,
+                                  struct udpsock *us, const char *ifname)
+    /* on error exit has called bad; might leave us->fd as -1 */
+{
+    assert(us->fd==-1);
+
+    bool_t ok=udp_make_socket(&st->uc,us,M_WARNING);
+    if (!ok) BAD("unable to set up socket");
+    int r=setsockopt(us->fd,SOL_SOCKET,SO_BINDTODEVICE,
+                    ifname,strlen(ifname)+1);
+    if (r) BADE("setsockopt(,,SO_BINDTODEVICE,)",errno);
+    return True;
+
+ out:
+    return False;
+}
+
+static void polypath_record_ifaddr(struct polypath *st,
+                                  bad_fn_type *bad, void *badctx,
+                                  bool_t add, char want,
+                                  const char *ifname,
+                                  const char *ifaddr,
+                                  const union iaddr *ia, int fd)
+{
+    struct udpcommon *uc=&st->uc;
+    struct interf *interf=0;
+    int max_interfs;
+    struct udpsock *us=0;
+
+    struct interf_list *interfs;
+    switch (want) {
+    case '+': interfs=&st->interfs_general; max_interfs=st->max_interfs; break;
+    case '@': interfs=&st->interfs_dedicated; max_interfs=INT_MAX; break;
+    default:   fatal("polypath: got bad want (%#x, %s)", want, ifname);
+    }
+
+    dump_pria(st,interfs,ifname,want);
+
+    int n_ifs=0;
+    LIST_FOREACH(interf,interfs,entry) {
+       if (!strcmp(interf->name,ifname))
+           goto found_interf;
+       n_ifs++;
+    }
+    /* not found */
+    if (n_ifs==max_interfs) BAD("too many interfaces");
+    interf=malloc(sizeof(*interf));
+    if (!interf) BADE("malloc for new interface",errno);
+    interf->name=0;
+    interf->socks.n_socks=0;
+    FILLZERO(interf->experienced_xmit_noaf);
+    LIST_INSERT_HEAD(interfs,interf,entry);
+    interf->name=strdup(ifname);
+    udp_socks_register(&st->uc,&interf->socks,interf->name);
+    if (!interf->name) BADE("strdup interface name",errno);
+ found_interf:
+
+    if (add) {
+       if (interf->socks.n_socks == UDP_MAX_SOCKETS)
+           BAD("too many addresses on this interface");
+       struct udpsock *us=&interf->socks.socks[interf->socks.n_socks];
+       us->fd=-1;
+       COPY_OBJ(us->addr,*ia);
+       if (fd<0) {
+           bool_t ok=polypath_make_socket(st,bad,badctx, us,ifname);
+           if (!ok) goto out;
+       } else {
+           bool_t ok=udp_import_socket(uc,us,M_WARNING,fd);
+           if (!ok) goto out;
+           fd=-1;
+       }
+       interf->socks.n_socks++;
+       lg_perror(LG,M_INFO,0,"using %s %s",ifname,
+                 iaddr_to_string(&us->addr));
+       us=0; /* do not destroy this socket during `out' */
+    } else {
+       int i;
+       for (i=0; i<interf->socks.n_socks; i++)
+           if (iaddr_equal(&interf->socks.socks[i].addr,ia,True))
+               goto address_remove_found;
+       bad(st,badctx,M_DEBUG,"address to remove not found",0);
+       goto out;
+    address_remove_found:
+       lg_perror(LG,M_INFO,0,"removed %s %s",ifname,
+                 iaddr_to_string(&interf->socks.socks[i].addr));
+       udp_destroy_socket(&st->uc,&interf->socks.socks[i]);
+       interf->socks.socks[i]=
+           interf->socks.socks[--interf->socks.n_socks];
+    }
+
+ out:
+    if (us)
+       udp_destroy_socket(uc,us);
+    if (fd>=0)
+       close(fd);
+    if (interf && !interf->socks.n_socks) {
+       udp_socks_deregister(&st->uc,&interf->socks);
+       LIST_REMOVE(interf,entry);
+       free(interf->name);
+       free(interf);
+    }
+
+    dump_pria(st,interfs,0,0);
+}
+
+static void subproc_problem(struct polypath *st,
+                           enum async_linebuf_result alr, const char *emsg)
+{
+    int status;
+    assert(st->monitor_pid);
+
+    pid_t gotpid=waitpid(st->monitor_pid,&status,WNOHANG);
+    if (gotpid==st->monitor_pid) {
+       st->monitor_pid=0;
+       lg_exitstatus(LG,M_FATAL,status,"interface monitor");
+    } else if (gotpid<0)
+       lg_perror(LG,M_ERR,errno,"unable to reap interface monitor");
+    else
+       assert(gotpid==0);
+
+    if (alr==async_linebuf_eof)
+       lg_perror(LG,M_FATAL,0,"unexpected EOF from interface monitor");
+    else
+       lg_perror(LG,M_FATAL,0,"bad output from interface monitor: %s",emsg);
+    assert(!"not reached");
+}
+
+/* Used in non-privsep case, and in privsep child */
+static void afterpoll_monitor(struct polypath *st, struct pollfd *fd,
+                             polypath_ppml_callback_type *callback)
+{
+    enum async_linebuf_result alr;
+    const char *emsg;
+    
+    while ((alr=async_linebuf_read(fd,&st->lbuf,&emsg)) == async_linebuf_ok)
+       polypath_process_monitor_line(st,st->lbuf.base,callback);
+
+    if (alr==async_linebuf_nothing)
+       return;
+
+    subproc_problem(st,alr,emsg);
+}
+
+/* Used in non-privsep case only - glue for secnet main loop */
+static void polypath_afterpoll_monitor(void *state, struct pollfd *fds,
+                                      int nfds)
+{
+    struct polypath *st=state;
+    if (nfds<1) return;
+    afterpoll_monitor(st,fds,polypath_record_ifaddr);
+}
+
+/* Actual udp packet sending work */
+
+static void polypath_sendmsg_interf(struct polypath *st,
+                                   struct interf *interf,
+                                   struct buffer_if *buf,
+                                   const struct comm_addr *dest,
+                                   const union iaddr *dedicated,
+                                   bool_t *allreasonable)
+{
+    int i;
+    int af=dest->ia.sa.sa_family;
+    bool_t wanted=False, attempted=False, reasonable=False;
+
+    for (i=0; i<interf->socks.n_socks; i++) {
+       struct udpsock *us=&interf->socks.socks[i];
+       if (dedicated && !iaddr_equal(dedicated, &us->addr, True))
+           continue;
+       wanted=True;
+       if (af != us->addr.sa.sa_family)
+           continue;
+       attempted=True;
+       int r=sendto(us->fd,buf->start,buf->size,
+                    0,&dest->ia.sa,iaddr_socklen(&dest->ia));
+       udp_sock_experienced(0,&st->uc,&interf->socks,us,
+                            &dest->ia,af, r,errno);
+       if (r>=0) {
+           reasonable=True;
+           break;
+       }
+       if (!(errno==EAFNOSUPPORT || errno==ENETUNREACH))
+           reasonable=True;
+       lg_perror(LG,M_DEBUG,errno,"%s [%s] xmit %"PRIu32" bytes to %s",
+                 interf->name,iaddr_to_string(&us->addr),
+                 buf->size,iaddr_to_string(&dest->ia));
+    }
+    if (!wanted)
+       return;
+
+    if (!attempted)
+       if (!interf->experienced_xmit_noaf[af]++)
+           lg_perror(LG,M_WARNING,0,
+                     "%s has no suitable address to transmit %s",
+                     interf->name, af_name(af));
+
+    *allreasonable &= reasonable;
+}
+
+static bool_t polypath_sendmsg(void *commst, struct buffer_if *buf,
+                         const struct comm_addr *dest,
+                         struct comm_clientinfo *clientinfo)
+{
+    struct polypath *st=commst;
+    struct interf *interf;
+    bool_t allreasonable=True;
+
+    LIST_FOREACH(interf,&st->interfs_general,entry) {
+       polypath_sendmsg_interf(st,interf,buf,dest,
+                               0, &allreasonable);
+    }
+    if (clientinfo && clientinfo->dedicated.sa.sa_family != AF_UNSPEC) {
+       LIST_FOREACH(interf,&st->interfs_dedicated,entry) {
+           polypath_sendmsg_interf(st,interf,buf,dest,
+                                   &clientinfo->dedicated, &allreasonable);
+       }
+    }
+    return allreasonable;
+}
+
+/* Non-privsep: called in (sole) child.  Privsep: in grandchild. */
+static void child_monitor(struct polypath *st, int childfd)
+{
+    dup2(childfd,1);
+    execvp(st->monitor_command[0],(char**)st->monitor_command);
+    fprintf(stderr,"secnet: cannot execute %s: %s\n",
+           st->monitor_command[0], strerror(errno));
+    exit(-1);
+}
+
+/* General utility function. */
+static void start_subproc(struct polypath *st, void (*make_fdpair)(int[2]),
+                         void (*child)(struct polypath *st, int childfd),
+                         const char *desc)
+{
+    int pfds[2];
+
+    assert(!st->monitor_pid);
+    assert(st->monitor_fd<0);
+
+    make_fdpair(pfds);
+
+    pid_t pid=fork();
+    if (!pid) {
+       afterfork();
+       close(pfds[0]);
+       child(st,pfds[1]);
+       abort();
+    }
+    if (pid<0)
+       fatal_perror("%s: failed to fork for interface monitoring",
+                    st->uc.cc.cl.description);
+
+    close(pfds[1]);
+    st->monitor_pid=pid;
+    st->monitor_fd=pfds[0];
+    setnonblock(st->monitor_fd);
+
+    lg_perror(LG,M_NOTICE,0, "%s: spawning %s [pid %ld]",
+             st->uc.cc.cl.description, desc, (long)st->monitor_pid);
+}
+
+/* Non-privsep only: glue for forking the monitor, from the main loop */
+static void polypath_phase_startmonitor(void *sst, uint32_t newphase)
+{
+    struct polypath *st=sst;
+    start_subproc(st,pipe_cloexec,child_monitor,
+                 "interface monitor (no privsep)");
+    register_for_poll(st,polypath_beforepoll,
+                     polypath_afterpoll_monitor,"polypath");
+}
+
+/*----- Privsep-only: -----*/
+
+/*
+ * We use two subprocesses, a child and a grandchild.  These are
+ * forked before secnet drops privilege.
+ *
+ * The grandchild is the same interface monitor helper script as used
+ * in the non-privsep case.  But its lines are read by the child
+ * instead of by the main secnet.  The child is responsible for
+ * creating the actual socket (binding it, etc.).  Each socket is
+ * passed to secnet proper via fd passing, along with a data message
+ * describing the interface name and address.  The child does not
+ * retain a list of current interfaces and addresses - it trusts the
+ * interface monitor to get that right.  secnet proper still maintains
+ * that data structure.
+ *
+ * The result is that much of the non-privsep code can be reused, but
+ * just plumbed together differently.
+ *
+ * The child does not retain the socket after passing it on.
+ * Interface removals are handled similarly but without any fd.
+ *
+ * The result is that the configuration's limits on which interfaces
+ * and ports secnet may use are enforced by the privileged child.
+ */
+
+struct privsep_mdata {
+    bool_t add;
+    char ifname[100];
+    union iaddr ia;
+    char want; /* `+' or `@' */
+};
+
+static void papp_bad(struct polypath *st, void *badctx,
+                    int mclass, const char *m, int ev)
+{
+    const struct privsep_mdata *mdata=(const void*)st->lbuf.start;
+    const char *addr_str=badctx;
+
+    lg_perror(LG,mclass,ev,
+             "error processing polypath address change %s %s [%s]: %s",
+             mdata->add ? "+" : "-",
+             mdata->ifname, addr_str, m);
+}
+
+static void polypath_afterpoll_privsep(void *state, struct pollfd *fds,
+                                      int nfds)
+/* In secnet proper; receives messages from child. */
+{
+    struct polypath *st=state;
+
+    if (nfds<1) return;
+
+    int revents=fds[0].revents;
+
+    const char *badbit=pollbadbit(revents);
+    if (badbit) subproc_problem(st,async_linebuf_broken,badbit);
+
+    if (!(revents & POLLIN)) return;
+
+    for (;;) {
+       if (st->lbuf.size==sizeof(struct privsep_mdata)) {
+           const struct privsep_mdata *mdata=(const void*)st->lbuf.start;
+           if (mdata->add && st->privsep_incoming_fd<0)
+               fatal("polypath (privsep): got add message data but no fd");
+           if (!mdata->add && st->privsep_incoming_fd>=0)
+               fatal("polypath (privsep): got remove message data with fd");
+           if (!memchr(mdata->ifname,0,sizeof(mdata->ifname)))
+               fatal("polypath (privsep): got ifname with no terminating nul");
+           int af=mdata->ia.sa.sa_family;
+           if (!(af==AF_INET6 || af==AF_INET))
+               fatal("polypath (privsep): got message data but bad AF %d",af);
+           const char *addr_str=iaddr_to_string(&mdata->ia);
+           polypath_record_ifaddr(st,papp_bad,(void*)addr_str,
+                                  mdata->add,mdata->want,
+                                  mdata->ifname,addr_str,
+                                  &mdata->ia, st->privsep_incoming_fd);
+           st->privsep_incoming_fd=-1;
+           st->lbuf.size=0;
+       }
+       struct msghdr msg;
+       int fd;
+       size_t cmsgdatalen=sizeof(fd);
+       char cmsg_control_buf[CMSG_SPACE(cmsgdatalen)];
+       struct iovec iov;
+
+       FILLZERO(msg);
+       msg.msg_iov=&iov;
+       msg.msg_iovlen=1;
+
+       iov.iov_base=st->lbuf.start+st->lbuf.size;
+       iov.iov_len=sizeof(struct privsep_mdata)-st->lbuf.size;
+
+       if (st->privsep_incoming_fd<0) {
+           msg.msg_control=cmsg_control_buf;
+           msg.msg_controllen=sizeof(cmsg_control_buf);
+       }
+
+       ssize_t got=recvmsg(st->monitor_fd,&msg,0);
+       if (got<0) {
+           if (errno==EINTR) continue;
+           if (iswouldblock(errno)) break;
+           fatal_perror("polypath (privsep): recvmsg failed");
+       }
+       if (got==0)
+           subproc_problem(st,async_linebuf_eof,0);
+
+       st->lbuf.size+=got;
+
+       if (msg.msg_controllen) {
+           size_t cmsgdatalen=sizeof(st->privsep_incoming_fd);
+           struct cmsghdr *h=CMSG_FIRSTHDR(&msg);
+           if (!(st->privsep_incoming_fd==-1 &&
+                 h &&
+                 h->cmsg_level==SOL_SOCKET &&
+                 h->cmsg_type==SCM_RIGHTS &&
+                 h->cmsg_len==CMSG_LEN(cmsgdatalen) &&
+                 !CMSG_NXTHDR(&msg,h)))
+               subproc_problem(st,async_linebuf_broken,"bad cmsg");
+           memcpy(&st->privsep_incoming_fd,CMSG_DATA(h),cmsgdatalen);
+           assert(st->privsep_incoming_fd>=0);
+       }
+
+    }
+}
+
+static void privsep_handle_ifaddr(struct polypath *st,
+                                  bad_fn_type *bad, void *badctx,
+                                  bool_t add, char want,
+                                  const char *ifname,
+                                  const char *ifaddr,
+                                  const union iaddr *ia, int fd_dummy)
+/* In child: handles discovered wanted interfaces, making sockets
+   and sending them to secnet proper. */
+{
+    struct msghdr msg;
+    struct iovec iov;
+    struct udpsock us={ .fd=-1 };
+    size_t cmsgdatalen=sizeof(us.fd);
+    char cmsg_control_buf[CMSG_SPACE(cmsgdatalen)];
+
+    assert(fd_dummy==-1);
+
+    struct privsep_mdata mdata;
+    FILLZERO(mdata);
+    mdata.add=add;
+
+    size_t l=strlen(ifname);
+    if (l>=sizeof(mdata.ifname)) BAD("interface name too long");
+    strcpy(mdata.ifname,ifname);
+    mdata.want=want;
+
+    COPY_OBJ(mdata.ia,*ia);
+
+    iov.iov_base=&mdata;
+    iov.iov_len =sizeof(mdata);
+
+    FILLZERO(msg);
+    msg.msg_iov=&iov;
+    msg.msg_iovlen=1;
+
+    if (add) {
+       COPY_OBJ(us.addr,*ia);
+       bool_t ok=polypath_make_socket(st,bad,badctx,&us,ifname);
+       if (!ok) goto out;
+
+       msg.msg_control=cmsg_control_buf;
+       msg.msg_controllen=sizeof(cmsg_control_buf);
+
+       struct cmsghdr *h=CMSG_FIRSTHDR(&msg);
+       h->cmsg_level=SOL_SOCKET;
+       h->cmsg_type =SCM_RIGHTS;
+       h->cmsg_len  =CMSG_LEN(cmsgdatalen);
+       memcpy(CMSG_DATA(h),&us.fd,cmsgdatalen);
+    }
+
+    while (iov.iov_len) {
+       ssize_t got=sendmsg(st->privsep_ipcsock_fd,&msg,0);
+       if (got<0) {
+           if (errno!=EINTR) fatal_perror("polypath privsep sendmsg");
+           got=0;
+       } else {
+           assert(got>0);
+           assert((size_t)got<=iov.iov_len);
+       }
+       iov.iov_base=(char*)iov.iov_base+got;
+       iov.iov_len-=got;
+       msg.msg_control=0;
+       msg.msg_controllen=0;
+    }
+
+ out:
+    if (us.fd>=0) close(us.fd);
+}
+
+static void child_privsep(struct polypath *st, int ipcsockfd)
+/* Privsep child main loop. */
+{
+    struct pollfd fds[2];
+
+    enter_phase(PHASE_CHILDPERSIST);
+
+    st->privsep_ipcsock_fd=ipcsockfd;
+    start_subproc(st,pipe_cloexec,child_monitor,
+                 "interface monitor (grandchild)");
+    for (;;) {
+       int nfds=1;
+       int r=polypath_beforepoll(st,fds,&nfds,0);
+       assert(nfds==1);
+       assert(!r);
+
+       fds[1].fd=st->privsep_ipcsock_fd;
+       fds[1].events=POLLIN;
+
+       r=poll(fds,ARRAY_SIZE(fds),-1);
+
+       if (r<0) {
+           if (errno==EINTR) continue;
+           fatal_perror("polypath privsep poll");
+       }
+       if (fds[1].revents) {
+           if (fds[1].revents & (POLLHUP|POLLIN)) {
+               polypath_phase_shutdown(st,PHASE_SHUTDOWN);
+               exit(0);
+           }
+           fatal("polypath privsep poll parent socket revents=%#x",
+                 fds[1].revents);
+       }
+       if (fds[0].revents & POLLNVAL)
+           fatal("polypath privsep poll child socket POLLNVAL");
+       afterpoll_monitor(st,fds,privsep_handle_ifaddr);
+    }
+}
+
+static void privsep_socketpair(int *fd)
+{
+    int r=socketpair(AF_UNIX,SOCK_STREAM,0,fd);
+    if (r) fatal_perror("socketpair(AF_UNIX,SOCK_STREAM,,)");
+    setcloexec(fd[0]);
+    setcloexec(fd[1]);
+}
+
+static void polypath_phase_startprivsep(void *sst, uint32_t newphase)
+{
+    struct polypath *st=sst;
+
+    if (!will_droppriv()) {
+       add_hook(PHASE_RUN,          polypath_phase_startmonitor,st);
+       return;
+    }
+
+    start_subproc(st,privsep_socketpair,child_privsep,
+                 "socket generator (privsep interface handler)");
+
+    BUF_FREE(&st->lbuf);
+    buffer_destroy(&st->lbuf);
+    buffer_new(&st->lbuf,sizeof(struct privsep_mdata));
+    BUF_ALLOC(&st->lbuf,"polypath mdata buf");
+    st->privsep_incoming_fd=-1;
+
+    register_for_poll(st,polypath_beforepoll,
+                     polypath_afterpoll_privsep,"polypath");
+}
+
+static void polypath_phase_shutdown(void *sst, uint32_t newphase)
+{
+    struct polypath *st=sst;
+    if (st->monitor_pid) {
+       assert(st->monitor_pid>0);
+       kill(st->monitor_pid,SIGTERM);
+    }
+}
+
+static void polypath_phase_childpersist(void *sst, uint32_t newphase)
+{
+    struct polypath *st=sst;
+    struct interf *interf;
+
+    LIST_FOREACH(interf,&st->interfs_general,entry)
+       udp_socks_childpersist(&st->uc,&interf->socks);
+    LIST_FOREACH(interf,&st->interfs_dedicated,entry)
+       udp_socks_childpersist(&st->uc,&interf->socks);
+}
+
+#undef BAD
+#undef BADE
+
+/*----- generic closure and module setup -----*/
+
+static list_t *polypath_apply(closure_t *self, struct cloc loc,
+                             dict_t *context, list_t *args)
+{
+    struct polypath *st;
+
+    COMM_APPLY(st,&st->uc.cc,polypath_,"polypath",loc);
+    COMM_APPLY_STANDARD(st,&st->uc.cc,"polypath",args);
+    UDP_APPLY_STANDARD(st,&st->uc,"polypath");
+
+    st->uc.cc.ops.clientinfo = polypath_clientinfo;
+
+    struct udpcommon *uc=&st->uc;
+    struct commcommon *cc=&uc->cc;
+
+    st->max_interfs=dict_read_number(d,"max-interfaces",False,"polypath",loc,3);
+
+    st->ifname_pats=dict_read_string_array(d,"interfaces",False,"polypath",
+                                          cc->loc,0);
+    st->permit_loopback=0; /* ifname_wanted reads this */
+    ifname_wanted(st,st->uc.cc.loc," "); /* try to check each pattern */
+
+    st->monitor_command=dict_read_string_array(d,"monitor-command",False,
+                               "polypath",cc->loc, default_monitor_command);
+    if (!st->monitor_command[0])
+       cfgfatal(loc,"polypath","no polypath interface monitor-command"
+                " (polypath unsupported on this platform?)\n");
+
+    st->permit_loopback=dict_read_bool(d,"permit-loopback",False,
+                                      "polypath",cc->loc,False);
+
+    LIST_INIT(&st->interfs_general);
+    LIST_INIT(&st->interfs_dedicated);
+    buffer_new(&st->lbuf,ADNS_ADDR2TEXT_BUFLEN+100);
+    BUF_ALLOC(&st->lbuf,"polypath lbuf");
+
+    st->monitor_fd=-1;
+    st->monitor_pid=0;
+
+    add_hook(PHASE_GETRESOURCES, polypath_phase_startprivsep,st);
+
+    add_hook(PHASE_SHUTDOWN,    polypath_phase_shutdown,    st);
+    add_hook(PHASE_CHILDPERSIST,polypath_phase_childpersist,st);
+
+    return new_closure(&cc->cl);
+}
+
+#endif /* CONFIG_IPV6 */
+
+void polypath_module(dict_t *dict)
+{
+#ifdef CONFIG_IPV6
+    add_closure(dict,"polypath",polypath_apply);
+#endif /* CONFIG_IPV6 */
+}
diff --git a/pretest-to-tested b/pretest-to-tested
new file mode 100755 (executable)
index 0000000..fea6e97
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# best to run this in a git-worktree
+# example runes in main tree:
+#  git-branch -f pretest && git-branch -f tested `git-merge-base HEAD tested` && git-checkout wip
+
+stl=''
+for subtree in base91-c subdirmk base91-python; do
+    st=$(git-subtree split -P $subtree pretest)
+    stl+=" ^$st"
+done
+
+set -e
+while true; do
+    next=$(git-rev-list --reverse $stl tested..pretest | head -n1)
+    if [ "x$next" = x ]; then break; fi
+    git checkout "$next"
+    ./comprehensive-test
+    git push . HEAD:tested
+done
diff --git a/privcache.c b/privcache.c
new file mode 100644 (file)
index 0000000..d790c30
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#include "secnet.h"
+#include "util.h"
+
+#define DEFAULT_SIZE 5
+#define DEFAULT_MAXPRIV_BYTES 4095
+
+struct ent {
+    struct sigkeyid id;
+    struct sigprivkey_if *sigpriv; /* 0 means none such */
+};
+
+struct privcache {
+    closure_t cl;
+    struct privcache_if ops;
+    int used, alloc;
+    struct pathprefix_template path;
+    struct ent *ents;
+    struct buffer_if databuf;
+};
+
+static bool_t uncached_load_file(
+                          const struct sigscheme_info *scheme,
+                          const char *path,
+                          struct buffer_if *databuf,
+                          struct sigprivkey_if **sigpriv_r,
+                          closure_t **closure_r,
+                          struct log_if *log);
+
+static struct sigprivkey_if *uncached_get(struct privcache *st,
+                          const struct sigkeyid *id, struct log_if *log)
+{
+    sprintf(st->path.write_here, SIGKEYID_PR_FMT, SIGKEYID_PR_VAL(id));
+
+    const char *path=st->path.buffer;
+    const struct sigscheme_info *scheme;
+    for (scheme=sigschemes;
+        scheme->name;
+        scheme++)
+       if (scheme->algid == id->b[GRPIDSZ])
+           goto found;
+
+    slilog(log,M_ERR,"private key file %s not loaded (unknown algid)",
+          path);
+    return 0;
+
+ found:;
+    struct sigprivkey_if *sigpriv;
+    closure_t *cl;
+    bool_t ok=uncached_load_file(scheme,
+                             path,
+                             &st->databuf,
+                             &sigpriv,
+                             &cl,
+                             log);
+    return ok ? sigpriv : 0;
+}
+
+static bool_t uncached_load_file(
+                          const struct sigscheme_info *scheme,
+                          const char *path,
+                          struct buffer_if *databuf,
+                          struct sigprivkey_if **sigpriv_r,
+                          closure_t **closure_r,
+                          struct log_if *log)
+{
+    bool_t ok=False;
+    FILE *f=0;
+    struct sigprivkey_if *sigpriv=0;
+
+    f=fopen(path,"rb");
+    if (!f) {
+       if (errno == ENOENT) {
+           slilog(log,M_DEBUG,"private key %s not found",
+                  path);
+       } else {
+           slilog(log,M_ERR,"failed to open private key file %s",
+                  path);
+       }
+       goto error_out;
+    }
+
+    setbuf(f,0);
+    buffer_init(databuf,0);
+    ssize_t got=fread(databuf->base,1,databuf->alloclen,f);
+    if (ferror(f)) {
+       slilog(log,M_ERR,"failed to read private-key file %s",
+              path);
+       goto error_out;
+    }
+    if (!feof(f)) {
+       slilog(log,M_ERR,"private key file %s longer than max %d",
+              path, (int)databuf->alloclen);
+       goto error_out;
+    }
+    fclose(f); f=0;
+
+    databuf->start=databuf->base;
+    databuf->size=got;
+    struct cloc loc = { .file=path, .line=0 };
+    ok=scheme->loadpriv(scheme, databuf, &sigpriv, closure_r, log, loc);
+    if (!ok) goto error_out; /* loadpriv will have logged */
+
+    *sigpriv_r=sigpriv;
+
+  out:
+    if (f) fclose(f);
+    return ok;
+
+ error_out:
+    if (sigpriv) sigpriv->dispose(sigpriv->st);
+    ok=False;
+    goto out;
+}
+
+static struct sigprivkey_if *privcache_lookup(void *sst,
+                                             const struct sigkeyid *id,
+                                             struct log_if *log) {
+    struct privcache *st=sst;
+    int was;
+    struct ent result;
+
+    for (was=0; was<st->used; was++) {
+       if (sigkeyid_equal(id, &st->ents[was].id)) {
+           result = st->ents[was];
+           goto found;
+       }
+    }
+
+    if (st->used < st->alloc) {
+       was=st->used;
+       st->used++;
+    } else {
+       was=st->used-1;
+       if (st->ents[was].sigpriv) {
+           st->ents[was].sigpriv->dispose(st->ents[was].sigpriv->st);
+       }
+    }
+
+    COPY_OBJ(result.id, *id);
+    result.sigpriv=uncached_get(st,id,log);
+
+ found:
+    memmove(&st->ents[1], &st->ents[0], sizeof(st->ents[0]) * was);
+    st->ents[0]=result;
+    return result.sigpriv;
+}
+
+static list_t *privcache_apply(closure_t *self, struct cloc loc,
+                              dict_t *context, list_t *args)
+{
+    struct privcache *st;
+    item_t *item;
+    dict_t *dict;
+
+    NEW(st);
+    st->cl.description="privcache";
+    st->cl.type=CL_PRIVCACHE;
+    st->cl.apply=NULL;
+    st->cl.interface=&st->ops;
+    st->ops.st=st;
+    st->ops.lookup=privcache_lookup;
+    st->ents=0;
+    st->path.buffer=0;
+    st->used=st->alloc=0;
+
+    item=list_elem(args,0);
+    if (!item || item->type!=t_dict)
+       cfgfatal(loc,"privcache","parameter must be a dictionary\n");
+    
+    dict=item->data.dict;
+
+    st->alloc=dict_read_number(dict,"privcache-size",False,"privcache",loc,
+                              DEFAULT_SIZE);
+    NEW_ARY(st->ents,st->alloc);
+    st->used=0;
+
+    int32_t buflen=dict_read_number(dict,"privkey-max",False,"privcache",loc,
+                                   DEFAULT_MAXPRIV_BYTES);
+    buffer_new(&st->databuf,buflen+1);
+
+    const char *path=dict_read_string(dict,"privkeys",True,"privcache",loc);
+    pathprefix_template_init(&st->path,path,KEYIDSZ*2);
+
+    return new_closure(&st->cl);
+}
+
+static list_t *loadprivate_apply(closure_t *self, struct cloc loc,
+                              dict_t *context, list_t *args)
+{
+    CL_GET_STR_ARG(0,algname,"algorithm name");
+    CL_GET_STR_ARG(1,path,"private key path");
+
+    const struct sigscheme_info *sch=sigscheme_lookup(algname);
+    if (!sch) cfgfatal(algname_i->loc,"load-private",
+                      "unknown algorithm `%s'",algname);
+
+    struct buffer_if databuf;
+    buffer_new(&databuf,DEFAULT_MAXPRIV_BYTES);
+    BUF_ALLOC(&databuf,"load-private data buf");
+
+    struct cfgfile_log log;
+    cfgfile_log_init(&log,loc,"load-private");
+
+    struct sigprivkey_if *sigpriv;
+    closure_t *cl;
+    bool_t ok=
+       uncached_load_file(sch,path,&databuf,&sigpriv,&cl,&log.log);
+    if (!ok) cfgfatal(loc,"load-private","private key loading failed");
+
+    BUF_FREE(&databuf);
+    buffer_destroy(&databuf);
+    return new_closure(cl);
+}
+
+void privcache_module(dict_t *dict)
+{
+    add_closure(dict,"priv-cache",privcache_apply);
+    add_closure(dict,"load-private",loadprivate_apply);
+}
index a9ff3d92648557d7692d9da97fe864c41227a5fe..3444308dfe6afbd85ea2e00780971c1aa2f95afa 100644 (file)
--- a/process.c
+++ b/process.c
@@ -1,3 +1,22 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #define _GNU_SOURCE
 #include "secnet.h"
 #include <unistd.h>
@@ -35,8 +54,6 @@ static struct signotify *sigs=NULL;
 
 static int spw,spr; /* file descriptors for signal notification pipe */
 
-static void set_default_signals(void);
-
 /* Long-lived subprocesses can only be started once we've started
    signal processing so that we can catch SIGCHLD for them and report
    their exit status using the callback function.  We block SIGCHLD
@@ -47,7 +64,7 @@ pid_t makesubproc(process_entry_fn *entry, process_callback_fn *cb,
     struct child *c;
     pid_t p;
 
-    c=safe_malloc(sizeof(*c),"makesubproc");
+    NEW(c);
     c->desc=desc;
     c->cb=cb;
     c->cst=cst;
@@ -58,8 +75,7 @@ pid_t makesubproc(process_entry_fn *entry, process_callback_fn *cb,
     p=fork();
     if (p==0) {
        /* Child process */
-       set_default_signals();
-       sigprocmask(SIG_SETMASK,&emptyset,NULL);
+       afterfork();
        entry(est);
        abort();
     } else if (p==-1) {
@@ -95,7 +111,7 @@ static void sigchld_handler(void *st, int signum)
        if (rv==i->pid) {
            i->finished=True;
            
-           nw=safe_malloc(sizeof(*nw),"sigchld_handler");
+           NEW(nw);
            nw->pid=i->pid;
            nw->cb=i->cb;
            nw->cst=i->cst;
@@ -143,17 +159,8 @@ int sys_cmd(const char *path, const char *arg, ...)
            fatal("sys_cmd: waitpid for %s returned wrong process ID!",
                  path);
        if (rv) {
-           /* If the command failed reporting its exit status */
-           if (WIFEXITED(rv))
-               Message(M_ERR, "sys_cmd(%s,%s,...) exited with status %d\n",
-                       path, arg, WEXITSTATUS(rv));
-           else if(WIFSIGNALED(rv))
-               Message(M_ERR, "sys_cmd(%s,%s,...) exited with signal %d (%s)%s\n",
-                       path, arg, WTERMSIG(rv), strsignal(WTERMSIG(rv)),
-                       WCOREDUMP(rv) ? " - core dumped" : "");
-           else
-               Message(M_ERR, "sys_cmd(%s,%s,...) exited with wstat %#x\n",
-                       path, arg, rv);
+           /* If the command failed report its exit status */
+           lg_exitstatus(0,"sys_cmd",0,M_ERR,rv,path);
        }
     } else if (c==0) {
        char *args[100];
@@ -164,6 +171,7 @@ int sys_cmd(const char *path, const char *arg, ...)
           if the execvp() fails this seems somewhat pointless, and
           increases the chance of the child process failing before it
           gets to exec(). */
+       afterfork();
        va_start(ap,arg);
        args[0]=(char *)arg; /* program name */
        i=1;
@@ -183,11 +191,7 @@ static beforepoll_fn signal_beforepoll;
 static int signal_beforepoll(void *st, struct pollfd *fds, int *nfds_io,
                             int *timeout_io)
 {
-    if (*nfds_io<1) {
-       *nfds_io=1;
-       return ERANGE;
-    }
-    *nfds_io=1;
+    BEFOREPOLL_WANT_FDS(1);
     fds[0].fd=spr;
     fds[0].events=POLLIN;
     return 0;
@@ -227,12 +231,16 @@ static void signal_afterpoll(void *st, struct pollfd *fds, int nfds)
     }
 }
 
-static void set_default_signals(void)
+void afterfork(void)
 {
     struct signotify *n;
     sigset_t done;
     struct sigaction sa;
 
+    clear_phase_hooks(PHASE_SHUTDOWN);
+    /* Prevents calls to fatal() etc. in the child from running off
+       and doing a lot of unhelpful things */
+
     sigemptyset(&done);
     for (n=sigs; n; n=n->next)
        if (!sigismember(&done,n->signum)) {
@@ -242,6 +250,19 @@ static void set_default_signals(void)
            sa.sa_flags=0;
            sigaction(n->signum,&sa,NULL);
        }
+
+    sigemptyset(&emptyset);
+    sigprocmask(SIG_SETMASK,&emptyset,NULL);
+}
+
+void childpersist_closefd_hook(void *fd_vp, uint32_t newphase)
+{
+    int *fd_p=fd_vp;
+    int fd=*fd_p;
+    if (fd<0) return;
+    *fd_p=-1;
+    setnonblock(fd); /* in case close() might block */
+    close(fd); /* discard errors - we don't care, in the child */
 }
 
 static void signal_handler(int signum)
@@ -289,7 +310,7 @@ void request_signal_notification(int signum, signal_notify_fn *notify,
     struct signotify *s;
     sigset_t old;
 
-    s=safe_malloc(sizeof(*s),"request_signal_notification");
+    NEW(s);
     s->signum=signum;
     s->notify=notify;
     s->cst=cst;
@@ -310,16 +331,13 @@ void start_signal_handling(void)
     sigemptyset(&registered);
     sigemptyset(&pending);
 
-    if (pipe(p)!=0) {
-       fatal_perror("start_signal_handling: pipe");
-    }
+    pipe_cloexec(p);
     spw=p[1];
     spr=p[0];
-    if (fcntl(spw, F_SETFL, fcntl(spw, F_GETFL)|O_NONBLOCK)==-1) {
-       fatal_perror("start_signal_handling: fcntl(O_NONBLOCK)");
-    }
+    setnonblock(spw);
+    setnonblock(spr);
 
-    register_for_poll(NULL,signal_beforepoll,signal_afterpoll,1,"signal");
+    register_for_poll(NULL,signal_beforepoll,signal_afterpoll,"signal");
     signal_handling=True;
 
     /* Register signal handlers for all the signals we're interested in */
index 1fb0e07ca0f48b60312744b4491d4b761c68ac9b..99681b0eaeea4b4771a383799b8f951b863c021a 100644 (file)
--- a/process.h
+++ b/process.h
@@ -1,3 +1,22 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #ifndef process_h
 #define process_h
 
diff --git a/pubkeys.c b/pubkeys.c
new file mode 100644 (file)
index 0000000..6dc741b
--- /dev/null
+++ b/pubkeys.c
@@ -0,0 +1,88 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#include "util.h"
+#include "base91s/base91.h"
+#include "pubkeys.h"
+#include "pubkeys.yy.h"
+
+void keyset_dispose(struct peer_keyset **ks_io)
+{
+    struct peer_keyset *ks=*ks_io;
+    if (!ks) return;
+    *ks_io=0;
+    ks->refcount--;
+    assert(ks->refcount>=0);
+    if (ks->refcount) return;
+    for (int ki=0; ki<ks->nkeys; ki++) {
+       struct sigpubkey_if *pk=ks->keys[ki].pubkey;
+       pk->dispose(pk->st);
+    }
+    free(ks);
+}
+
+const struct sigscheme_info *sigscheme_lookup(const char *name)
+{
+    const struct sigscheme_info *scheme;
+    for (scheme=sigschemes; scheme->name; scheme++)
+       if (!strcmp(name,scheme->name))
+           return scheme;
+    return 0;
+}
+
+static list_t *makepublic_apply(closure_t *self, struct cloc loc,
+                               dict_t *context, list_t *args)
+{
+    CL_GET_STR_ARG(0,algname,"algorithm name");
+    CL_GET_STR_ARG(1,b91d,"base91s-encoded public key");
+
+    const struct sigscheme_info *sch=sigscheme_lookup(algname);
+    if (!sch) cfgfatal(algname_i->loc,"make-public",
+                      "unknown algorithm `%s'",algname);
+
+    size_t b91l=strlen(b91d);
+    if (b91l > INT_MAX/4) cfgfatal(algname_i->loc,"make-public",
+                                     "base91s data unreasonably long");
+
+    struct buffer_if buf;
+    buffer_new(&buf,base91s_decode_maxlen(b91l));
+    BUF_ALLOC(&buf,"make-public data buf");
+    assert(buf.start == buf.base);
+    struct base91s b91;
+    base91s_init(&b91);
+    buf.size= base91s_decode(&b91,b91d,b91l,buf.start);
+    buf.size += base91s_decode_end(&b91,buf.start+buf.size);
+    assert(buf.size <= buf.alloclen);
+
+    struct cfgfile_log log;
+    cfgfile_log_init(&log,loc,"make-public");
+
+    struct sigpubkey_if *pubkey;
+    closure_t *cl;
+    bool_t ok=sch->loadpub(sch,&buf,&pubkey,&cl,&log.log,loc);
+    if (!ok) cfgfatal(loc,"make-public","public key loading failed");
+
+    BUF_FREE(&buf);
+    buffer_destroy(&buf);
+    return new_closure(cl);
+}
+
+void pubkeys_init(dict_t *dict) {
+    add_closure(dict,"make-public",makepublic_apply);
+}
diff --git a/pubkeys.fl.pl b/pubkeys.fl.pl
new file mode 100755 (executable)
index 0000000..da1e4d4
--- /dev/null
@@ -0,0 +1,330 @@
+#!/usr/bin/perl -w
+# -*- C -*-
+#
+# secnet - pubkeys.fl.pl
+#
+# This file is part of secnet.
+# See README for full list of copyright holders.
+#
+# secnet is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# 
+# secnet is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# version 3 along with secnet; if not, see
+# https://www.gnu.org/licenses/gpl.html.
+
+# We process __DATA__ of this file first through the perl code,
+# and then through flex.  We do it like this because directives
+# with positional arguments are otherwise rather tedious to specify
+# in flex.  Of course we could have used bison too but this seems
+# better overall.
+
+use strict;
+
+our $do = '';
+our $co = '';
+our $kw;
+our $kwid;
+our @next_kw;
+our $in_s;
+our $data_off;
+
+our %subst = (GRPIDSZ => 4, SERIALSZ => 4);
+
+our $last_lno = -1;
+sub lineno (;$$) {
+    my ($always, $delta) = @_;
+    my $o = '';
+    $delta //= 0;
+    if ($always || $. != $last_lno+1) {
+       $o .= sprintf "#line %d \"%s\"\n", $delta+$data_off+$., $0;
+    }
+    $last_lno = $.;
+    $o;
+}
+
+while (<DATA>) {
+       last if m/^\%\%\s*$/;
+       if (m/^!SUBSTCHECKS\s*$/) {
+               foreach (keys %subst) {
+                       $do .= <<END
+#if $_ != $subst{$_}
+# error $_ value disagrees between pubkeys.fl.pl and C headers
+#endif
+END
+               }
+               next;
+       }
+       $do .= lineno();
+       $do .= $_;
+}
+
+sub inst ($) {
+       $do .= "%x $_[0]\n";
+       "<$_[0]>";
+}
+
+while (<DATA>) {
+    s#\{!2(\w+)\}# '{'.(2 * ($subst{$1}//die "$1 ?")).'}' #ge;
+    if (m/^!(KEYWORD|KWALIAS) ([-0-9a-z]+)(\s*\{.*\})?$/) {
+       my $kwt=$2;
+       if ($1 eq 'KEYWORD') {
+           die if $kw;
+           $kw = $kwt;
+       } else {
+           die if @next_kw;
+           die unless $kw;
+       }
+       my $xact = $3 // '';
+       $kwid = $kw; $kwid =~ y/-/_/;
+       $in_s = "HK_${kwid}";
+       $co .= "{L}$kwt { BEGIN($in_s); $xact }\n";
+       next;
+    }
+    if (m/^!ARG (\w+) (\S.*\S) \{\s*$/) {
+       die unless $kw;
+       die if @next_kw;
+       $co .= inst("$in_s")."{S} { BEGIN(D_${kwid}_$1); }\n";
+       $co .= inst("D_${kwid}_$1")."$2 {\n";
+       $in_s = "HA_${kwid}_$1";
+       $co .= "\tBEGIN($in_s);\n";
+       @next_kw = ($kw);
+       $co .= lineno(1,1);
+       next;
+    }
+    if (m/^!\}\s*$/) {
+       die unless @next_kw;
+       $co .= lineno(1,0);
+       $co .= "}\n";
+       $kw = shift @next_kw;
+       next;
+    }
+    if (m/^!FINAL \{\s*$/) {
+       die unless $kw;
+       die if @next_kw;
+       $co .= inst("FIN_$kwid")."\\n { BEGIN(0); c->loc.line++; }\n";
+       $co .= inst("$in_s")."{L}/\\n {\n";
+       $co .= "\tBEGIN(FIN_$kwid);\n";
+       $co .= lineno(1,1);
+       @next_kw = (undef);
+       next;
+    }
+    if (m/^!/) {
+       die;
+    }
+    $co .= $_;
+    if (m/^\%\%\s*$/) {
+       $co .= lineno(1,1);
+    }
+}
+
+print $do, "%%\n", $co or die $!;
+
+BEGIN { $data_off = __LINE__ + 1; }
+__DATA__
+
+L      [ \t]*
+S      [ \t]+
+BASE91S        []-~!#-&(-[]+
+%x SKIPNL
+%x SYNTAXERR
+
+%option yylineno
+%option noyywrap
+%option batch
+%option 8bit
+%option nodefault
+%option never-interactive
+
+%option prefix="pkyy"
+
+%option warn
+
+%{
+
+#include "secnet.h"
+#include "pubkeys.h"
+#include "util.h"
+#include "unaligned.h"
+#include "base91s/base91.h"
+
+!SUBSTCHECKS
+
+struct pubkeyset_context {
+    /* filled in during setup: */
+    struct cloc loc; /* line is runtime */
+    struct log_if *log;
+    struct buffer_if *data_buf;
+    struct peer_keyset *building;
+    /* runtime: */
+    bool_t had_serial;
+    bool_t fallback_skip;
+    const struct sigscheme_info *scheme;
+    uint8_t grpid[GRPIDSZ];
+    serialt serial;
+};
+
+static struct pubkeyset_context c[1];
+
+#define LI (c->log)
+#define HEX2BIN(v,l) ({                                                        \
+       int32_t outlen;                                                 \
+       bool_t ok=hex_decode((v), ((l)), &outlen, yytext, False);       \
+       assert(ok);                                                     \
+       assert(outlen==((l)));                                          \
+    })
+#define HEX2BIN_ARRAY(v) HEX2BIN((v),sizeof((v)))
+#define DOSKIPQ ({                                     \
+        BEGIN(SKIPNL);                                 \
+        break;                                         \
+    })
+#define DOSKIP(m) ({                                   \
+        slilog(LI,M_INFO,"%s:%d: " m, c->loc.file, c->loc.line);       \
+        DOSKIPQ;                                       \
+    })
+#define FAIL(m) do{                                    \
+       slilog(LI,M_ERR,"%s:%d: " m, c->loc.file, c->loc.line); \
+       return -1;                                      \
+    }while(0)
+
+%}
+
+%%
+
+!KEYWORD pkg  { c->fallback_skip=0; }
+!KWALIAS pkgf { c->fallback_skip=!!c->building->nkeys; }
+!ARG id [0-9a-f]{!2GRPIDSZ} {
+    HEX2BIN_ARRAY(c->grpid);
+!}
+!FINAL {
+!}
+!KEYWORD pub
+!ARG algo [-0-9a-z]+ {
+    if (c->fallback_skip) DOSKIP("fallback not needed");
+    c->scheme = sigscheme_lookup(yytext);
+    if (!c->scheme) DOSKIP("unknown pk algorithm");
+!}
+!ARG data {BASE91S} {
+    /* baseE91 and thus base91s can sometimes store 14 bits per
+     * character pair, so the max decode ratio is 14/16. */
+    size_t maxl = base91s_decode_maxlen(yyleng);
+    buffer_init(c->data_buf,0);
+    if (buf_remaining_space(c->data_buf) < maxl) DOSKIP("pk data too long");
+    struct base91s b91;
+    base91s_init(&b91);
+    size_t l = base91s_decode(&b91, yytext, yyleng, c->data_buf->start);
+    l += base91s_decode_end(&b91, c->data_buf->start + l);
+    assert(l <= maxl);
+    buf_append(c->data_buf,l);
+!}
+!FINAL {
+    if (c->building->nkeys >= MAX_SIG_KEYS) DOSKIP("too many public keys");
+    struct sigpubkey_if *pubkey;
+    closure_t *cl;
+    bool_t ok=c->scheme->loadpub(c->scheme,c->data_buf,
+                                &pubkey,&cl,c->log,c->loc);
+    if (!ok) break;
+    struct peer_pubkey *fill=&c->building->keys[c->building->nkeys];
+    memcpy(fill->id.b,c->grpid,GRPIDSZ);
+    assert(ALGIDSZ==1); /* otherwise need htons or htonl or something */
+    fill->id.b[GRPIDSZ]=c->scheme->algid;
+    fill->pubkey=pubkey;
+    c->building->nkeys++;
+!}
+
+!KEYWORD serial
+!ARG id [0-9a-f]{!2SERIALSZ} {
+    if (c->had_serial) FAIL("`serial' repeated");
+    c->had_serial = 1;
+    uint8_t sb[SERIALSZ];
+    HEX2BIN_ARRAY(sb);
+    c->serial=get_uint32(sb);
+!}
+!FINAL {
+!}
+
+{L}[-0-9a-z]+ {
+    DOSKIP("unknown directive");
+}
+
+{L}\# {
+    BEGIN(SKIPNL);
+}
+{L}\n {
+    c->loc.line++;
+}
+
+<SKIPNL>.*\n {
+    c->loc.line++;
+    BEGIN(0);
+}
+
+<INITIAL><<EOF>>       { return 0; }
+
+<*>. {
+    yymore();
+    BEGIN(SYNTAXERR);
+}
+<SYNTAXERR>.* {
+    slilog(LI,M_DEBUG,"pubkeys syntax error at `%s'", yytext);
+    FAIL("syntax error");
+}
+<*>\n { FAIL("syntax error - unexpected newline"); }
+<<EOF>> { FAIL("syntax error - unexpected eof"); }
+
+%%
+
+extern struct peer_keyset *
+keyset_load(const char *path, struct buffer_if *data_buf,
+           struct log_if *log, int logcl_enoent) {
+    assert(!c->building);
+    c->log=log;
+    c->loc.file=path;
+    pkyyin = fopen(path, "r");
+    if (!pkyyin) {
+       slilog(LI,
+              errno==ENOENT ? logcl_enoent : M_ERR,
+              "%scould not open keyset file %s: %s",
+              logcl_enoent==M_DEBUG && errno==ENOENT ? "expectedly " : "",
+              path,strerror(errno));
+       goto err;
+    }
+
+    pkyyrestart(pkyyin);
+    BEGIN(0);
+    c->data_buf=data_buf;
+    NEW(c->building);
+    c->building->nkeys=0;
+    c->building->refcount=1;
+    c->fallback_skip=0;
+    c->had_serial=0;
+    c->loc.line=1;
+    FILLZERO(c->grpid);
+    FILLZERO(c->serial);
+    int r=pkyylex();
+    if (r) goto err_bad;
+
+    if (!c->building->nkeys) {
+       slilog(LI,M_ERR,"no useable keys in %s",path);
+       goto err_bad;
+    }
+    fclose(pkyyin);
+    struct peer_keyset *built=c->building;
+    c->building=0;
+    return built;
+
+ err_bad:
+    errno=EBADMSG;
+ err:
+    if (c->building) { free(c->building); c->building=0; }
+    if (pkyyin) { fclose(pkyyin); pkyyin=0; }
+    return 0;
+}
+
diff --git a/pubkeys.h b/pubkeys.h
new file mode 100644 (file)
index 0000000..a630d77
--- /dev/null
+++ b/pubkeys.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#ifndef pubkeys_h
+#define pubkeys_h
+
+#include "secnet.h"
+
+/*----- shared with site.c -----*/
+
+struct peer_pubkey {
+    struct sigkeyid id;
+    struct sigpubkey_if *pubkey;
+};
+
+struct peer_keyset {
+    int refcount;
+    serialt serial;
+    int nkeys;
+    struct peer_pubkey keys[MAX_SIG_KEYS];
+};
+
+extern struct peer_keyset *
+keyset_load(const char *path, struct buffer_if *data_buf,
+           struct log_if *log, int logcl_enoent);
+
+extern void keyset_dispose(struct peer_keyset **ks);
+
+static inline struct peer_keyset *keyset_dup(struct peer_keyset *in) {
+    in->refcount++;
+    return in;
+}
+
+extern bool_t
+pubkey_want(struct peer_keyset *building /* refcount and serial undef */,
+           struct sigkeyid *id, const struct sigscheme_info *scheme);
+
+#endif /* pubkeys_h */
index 39a9cb07c78f57183358059b21069743dd6b35e1..323fffd692e4677732edef90fe5d5a4106261872 100644 (file)
--- a/random.c
+++ b/random.c
@@ -1,3 +1,22 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #include "secnet.h"
 #include <stdio.h>
 #include <fcntl.h>
@@ -14,7 +33,7 @@ struct rgen_data {
 };
 
 static random_fn random_generate;
-static bool_t random_generate(void *data, int32_t bytes, uint8_t *buff)
+static void random_generate(void *data, int32_t bytes, uint8_t *buff)
 {
     struct rgen_data *st=data;
     int r;
@@ -22,26 +41,10 @@ static bool_t random_generate(void *data, int32_t bytes, uint8_t *buff)
     r= read(st->fd,buff,bytes);
 
     assert(r == bytes);
-    /* This is totally crap error checking, but AFAICT many callers of
-     * this function do not check the return value.  This is a minimal
-     * change to make the code not fail silently-but-insecurely.
-     *
-     * A proper fix requires either:
-     *  - Declare all random number generation failures as fatal
-     *    errors, and make this return void, and fix all callers,
-     *    and make this call some appropriate function if it fails.
-     *  - Make this have proper error checking (and reporting!)
-     *    and make all callers check the error (and report!);
-     *    this will be tricky, I think, because you have to report
-     *    the errno somewhere.
-     *
-     * There's also the issue that this is only one possible
-     * implementation of a random number source; others may not rely
-     * on reading from a file descriptor, and may not produce
-     * appropriate settings of errno.
+    /* This is totally crap error checking, but callers of
+     * this function do not check the return value and dealing
+     * with failure of this everywhere would be very inconvenient.
      */
-
-    return True;
 }
 
 static list_t *random_apply(closure_t *self, struct cloc loc,
@@ -51,7 +54,7 @@ static list_t *random_apply(closure_t *self, struct cloc loc,
     item_t *arg1, *arg2;
     string_t filename=NULL;
 
-    st=safe_malloc(sizeof(*st),"random_apply");
+    NEW(st);
 
     st->cl.description="randomsource";
     st->cl.type=CL_RANDOMSRC;
index 033ddc113aa37466015b8124016ba00d062e81fa..3a6ad5a91d680a5dc5faf5f5614d83b42dc3da7f 100644 (file)
@@ -1,7 +1,26 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 /* Name resolution using adns */
 
 #include <errno.h>
 #include "secnet.h"
+#include "util.h"
 #ifndef HAVE_LIBADNS
 #error secnet requires ADNS version 1.0 or above
 #endif
@@ -19,39 +38,78 @@ struct adns {
 
 struct query {
     void *cst;
+    const char *name;
+    int port;
+    struct comm_if *comm;
     resolve_answer_fn *answer;
     adns_query query;
 };
 
 static resolve_request_fn resolve_request;
 static bool_t resolve_request(void *sst, cstring_t name,
+                             int port, struct comm_if *comm,
                              resolve_answer_fn *cb, void *cst)
 {
     struct adns *st=sst;
     struct query *q;
     int rv;
-    const int maxlitlen=50;
-
+    const int maxlitlen=
+#ifdef CONFIG_IPV6
+       ADNS_ADDR2TEXT_BUFLEN*2
+#else
+       50
+#endif
+       ;
     ssize_t l=strlen(name);
     if (name[0]=='[' && l<maxlitlen && l>2 && name[l-1]==']') {
        char trimmed[maxlitlen+1];
        memcpy(trimmed,name+1,l-2);
        trimmed[l-2]=0;
-       struct in_addr ia;
-       if (inet_aton(trimmed,&ia))
-           cb(cst,&ia);
+       struct comm_addr ca;
+       ca.comm=comm;
+       ca.ix=-1;
+#ifdef CONFIG_IPV6
+       socklen_t salen=sizeof(ca.ia);
+       rv=adns_text2addr(trimmed, port, adns_qf_addrlit_ipv4_quadonly,
+                         &ca.ia.sa, &salen);
+       assert(rv!=ENOSPC);
+       if (rv) {
+           char msg[250];
+           snprintf(msg,sizeof(msg),"invalid address literal: %s",
+                    strerror(rv));
+           msg[sizeof(msg)-1]=0;
+           cb(cst,0,0,0,name,msg);
+       } else {
+           cb(cst,&ca,1,1,name,0);
+       }
+#else
+       ca.ia.sin.sin_family=AF_INET;
+       ca.ia.sin.sin_port=htons(port);
+       if (inet_aton(trimmed,&ca.ia.sin.sin_addr))
+           cb(cst,&ca,1,1,name,0);
        else
-           cb(cst,0);
+           cb(cst,0,0,0,name,"invalid IP address");
+#endif
        return True;
     }
 
-    q=safe_malloc(sizeof *q,"resolve_request");
+    NEW(q);
     q->cst=cst;
+    q->comm=comm;
+    q->port=port;
+    q->name=name;
     q->answer=cb;
 
-    rv=adns_submit(st->ast, name, adns_r_a, 0, q, &q->query);
+    rv=adns_submit(st->ast, name, adns_r_addr, 0, q, &q->query);
+    if (rv) {
+        Message(M_WARNING,
+               "resolver: failed to submit lookup for %s: %s",name,
+               adns_strerror(rv));
+       free(q);
+       return False;
+    }
 
-    return rv==0;
+    return True;
 }
 
 static int resolver_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
@@ -78,11 +136,42 @@ static void resolver_afterpoll(void *sst, struct pollfd *fds, int nfds)
        if (rv==0) {
            q=qp;
            if (ans->status!=adns_s_ok) {
-               q->answer(q->cst,NULL); /* Failure */
+               q->answer(q->cst,NULL,0,0,q->name,adns_strerror(ans->status));
                free(q);
                free(ans);
            } else {
-               q->answer(q->cst,ans->rrs.inaddr);
+               int rslot, wslot, total;
+               int ca_len=MIN(ans->nrrs,MAX_PEER_ADDRS);
+               struct comm_addr ca_buf[ca_len];
+               for (rslot=0, wslot=0, total=0;
+                    rslot<ans->nrrs;
+                    rslot++) {
+                   total++;
+                   if (!(wslot<ca_len)) continue;
+                   adns_rr_addr *ra=&ans->rrs.addr[rslot];
+                   struct comm_addr *ca=&ca_buf[wslot];
+                   ca->comm=q->comm;
+                   ca->ix=-1;
+                   assert(ra->len <= (int)sizeof(ca->ia));
+                   memcpy(&ca->ia,&ra->addr,ra->len);
+                   switch (ra->addr.sa.sa_family) {
+                   case AF_INET:
+                       assert(ra->len == sizeof(ca->ia.sin));
+                       ca->ia.sin.sin_port=htons(q->port);
+                       break;
+#ifdef CONFIG_IPV6
+                   case AF_INET6:
+                       assert(ra->len == sizeof(ca->ia.sin6));
+                       ca->ia.sin6.sin6_port=htons(q->port);
+                       break;
+#endif /*CONFIG_IPV6*/
+                   default:
+                       /* silently skip unexpected AFs from adns */
+                       continue;
+                   }
+                   wslot++;
+               }
+               q->answer(q->cst,ca_buf,wslot,total,q->name,0);
                free(q);
                free(ans);
            }
@@ -105,7 +194,7 @@ static list_t *adnsresolver_apply(closure_t *self, struct cloc loc,
     item_t *i;
     string_t conf;
 
-    st=safe_malloc(sizeof(*st),"adnsresolver_apply");
+    NEW(st);
     st->cl.description="adns";
     st->cl.type=CL_RESOLVER;
     st->cl.apply=NULL;
@@ -132,7 +221,7 @@ static list_t *adnsresolver_apply(closure_t *self, struct cloc loc,
     }
 
     register_for_poll(st, resolver_beforepoll, resolver_afterpoll,
-                     ADNS_POLLFDS_RECOMMENDED+5,"resolver");
+                     "resolver");
 
     return new_closure(&st->cl);
 }
diff --git a/rsa.c b/rsa.c
index f7dd69db6f69115e03c94779a2529468b642619f..6a89b21156242eba81ac7dfa78b4904106e1b2aa 100644 (file)
--- a/rsa.c
+++ b/rsa.c
-/* This file is part of secnet, and is distributed under the terms of
-   the GNU General Public License version 2 or later.
+/*
+ * rsa.c: implementation of RSA with PKCS#1 padding
+ */
+/*
+ * This file is Free Software.  It was originally written for secnet.
+ *
+ * Copyright 1995-2003 Stephen Early
+ * Copyright 2002-2014 Ian Jackson
+ * Copyright 2001      Simon Tatham
+ * Copyright 2013      Mark Wooding
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later
+ * version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 
-   Copyright (C) 1995-2002 Stephen Early
-   Copyright (C) 2001 Simon Tatham
-   Copyright (C) 2002 Ian Jackson
-   */
 
 #include <stdio.h>
 #include <string.h>
 #include <gmp.h>
 #include "secnet.h"
 #include "util.h"
+#include "unaligned.h"
 
 #define AUTHFILE_ID_STRING "SSH PRIVATE KEY FILE FORMAT 1.1\n"
 
 #define mpp(s,n) do { char *p = mpz_get_str(NULL,16,n); printf("%s 0x%sL\n", s, p); free(p); } while (0)
 
+struct rsacommon {
+    uint8_t *hashbuf;
+};
+
+#define FREE(b)                ({ free((b)); (b)=0; })
+
+struct load_ctx {
+    void (*verror)(struct load_ctx *l, struct cloc loc,
+                  FILE *maybe_f,
+                  const char *message, va_list args);
+    bool_t (*postreadcheck)(struct load_ctx *l, FILE *f);
+    const char *what;
+    dict_t *deprdict; /* used only to look up hash */
+    struct cloc loc;
+    union {
+       struct {
+           struct log_if *log;
+       } tryload;
+    } u;
+};
+
+static void load_err(struct load_ctx *l,
+                    const struct cloc *maybe_loc, FILE *maybe_f,
+                    const char *fmt, ...)
+{
+    va_list al;
+    va_start(al,fmt);
+    l->verror(l, maybe_loc ? *maybe_loc : l->loc, maybe_f,fmt,al);
+    va_end(al);
+}
+
+FORMAT(printf,4,0)
+static void verror_tryload(struct load_ctx *l, struct cloc loc,
+                          FILE *maybe_f,
+                          const char *message, va_list args)
+{
+    int class=M_ERR;
+    slilog_part(l->u.tryload.log,class,"%s: ",l->what);
+    vslilog(l->u.tryload.log,class,message,args);
+}
+
+static void verror_cfgfatal(struct load_ctx *l, struct cloc loc,
+                           FILE *maybe_f,
+                           const char *message, va_list args)
+{
+    vcfgfatal_maybefile(maybe_f,l->loc,l->what,message,args,"");
+}
+
 struct rsapriv {
     closure_t cl;
-    struct rsaprivkey_if ops;
+    struct sigprivkey_if ops;
     struct cloc loc;
+    struct rsacommon common;
     MP_INT n;
     MP_INT p, dp;
     MP_INT q, dq;
     MP_INT w;
 };
+
+#define RSAPUB_BNS(each)                       \
+    each(0,e,"public exponent")                        \
+    each(1,n,"modulus")
+
+#define RSAPUB_LOADCORE_PASSBN(ix,en,what) \
+    en##s, en##_loc,
+
+#define RSAPUB_INIT_ST_BN( ix,en,what) mpz_init (&st->en);
+#define RSAPUB_CLEAR_ST_BN(ix,en,what) mpz_clear(&st->en);
+
 struct rsapub {
     closure_t cl;
-    struct rsapubkey_if ops;
+    struct sigpubkey_if ops;
     struct cloc loc;
+    struct rsacommon common;
     MP_INT e;
     MP_INT n;
 };
@@ -42,6 +128,24 @@ struct rsapub {
 
 static const char *hexchars="0123456789abcdef";
 
+static void rsa_sethash(struct load_ctx *l,
+                       struct rsacommon *c,
+                       const struct hash_if **in_ops)
+{
+    struct hash_if *hash=0;
+    if (l->deprdict)
+       hash=find_cl_if(l->deprdict,"hash",CL_HASH,False,"site",l->loc);
+    if (!hash)
+       hash=sha1_hash_if;
+    c->hashbuf=safe_malloc(hash->hlen, "generate_msg");
+    *in_ops=hash;
+}
+
+static void rsacommon_dispose(struct rsacommon *c)
+{
+    free(c->hashbuf);
+}
+
 static void emsa_pkcs1(MP_INT *n, MP_INT *m,
                       const uint8_t *data, int32_t datalen)
 {
@@ -69,7 +173,7 @@ static void emsa_pkcs1(MP_INT *n, MP_INT *m,
     msize=mpz_sizeinbase(n, 16);
 
     if (datalen*2+6>=msize) {
-       fatal("rsa_sign: message too big");
+       fatal("rsa: message too big");
     }
 
     strcpy(buff,"0001");
@@ -90,17 +194,20 @@ static void emsa_pkcs1(MP_INT *n, MP_INT *m,
     mpz_set_str(m, buff, 16);
 }
 
-static string_t rsa_sign(void *sst, uint8_t *data, int32_t datalen)
+static bool_t rsa_sign(void *sst, uint8_t *data, int32_t datalen,
+                      struct buffer_if *msg)
 {
     struct rsapriv *st=sst;
     MP_INT a, b, u, v, tmp, tmp2;
-    string_t signature;
+    string_t signature = 0;
+    bool_t ok;
 
     mpz_init(&a);
     mpz_init(&b);
 
+    hash_hash(st->ops.hash,data,datalen,st->common.hashbuf);
     /* Construct the message representative. */
-    emsa_pkcs1(&st->n, &a, data, datalen);
+    emsa_pkcs1(&st->n, &a, st->common.hashbuf, st->ops.hash->hlen);
 
     /*
      * Produce an RSA signature (a^d mod n) using the Chinese
@@ -124,8 +231,8 @@ static string_t rsa_sign(void *sst, uint8_t *data, int32_t datalen)
     mpz_init(&u);
     mpz_init(&v);
 
-    mpz_powm(&u, &a, &st->dp, &st->p);
-    mpz_powm(&v, &a, &st->dq, &st->q);
+    mpz_powm_sec(&u, &a, &st->dp, &st->p);
+    mpz_powm_sec(&v, &a, &st->dq, &st->q);
     mpz_sub(&tmp, &u, &v);
     mpz_mul(&tmp2, &tmp, &st->w);
     mpz_add(&tmp, &tmp2, &v);
@@ -138,14 +245,45 @@ static string_t rsa_sign(void *sst, uint8_t *data, int32_t datalen)
 
     signature=write_mpstring(&b);
 
+    uint8_t *op = buf_append(msg,2);
+    if (!op) { ok=False; goto out; }
+    size_t l = strlen(signature);
+    assert(l < 65536);
+    put_uint16(op, l);
+    op = buf_append(msg,l);
+    if (!op) { ok=False; goto out; }
+    memcpy(op, signature, l);
+
+    ok = True;
+
+ out:
+    free(signature);
     mpz_clear(&b);
     mpz_clear(&a);
-    return signature;
+    return ok;
 }
 
-static rsa_checksig_fn rsa_sig_check;
+static bool_t rsa_sig_unpick(void *sst, struct buffer_if *msg,
+                            struct alg_msg_data *sig)
+{
+    uint8_t *lp = buf_unprepend(msg, 2);
+    if (!lp) return False;
+    sig->len = get_uint16(lp);
+    sig->start = buf_unprepend(msg, sig->len);
+    if (!sig->start) return False;
+
+    /* In `rsa_sig_check' below, we assume that we can write a nul
+     * terminator following the signature.  Make sure there's enough space.
+     */
+    if (msg->start >= msg->base + msg->alloclen)
+       return False;
+
+    return True;
+}
+
+static sig_checksig_fn rsa_sig_check;
 static bool_t rsa_sig_check(void *sst, uint8_t *data, int32_t datalen,
-                           cstring_t signature)
+                           const struct alg_msg_data *sig)
 {
     struct rsapub *st=sst;
     MP_INT a, b, c;
@@ -155,9 +293,14 @@ static bool_t rsa_sig_check(void *sst, uint8_t *data, int32_t datalen,
     mpz_init(&b);
     mpz_init(&c);
 
-    emsa_pkcs1(&st->n, &a, data, datalen);
+    hash_hash(st->ops.hash,data,datalen,st->common.hashbuf);
+    emsa_pkcs1(&st->n, &a, st->common.hashbuf, st->ops.hash->hlen);
 
-    mpz_set_str(&b, signature, 16);
+    /* Terminate signature with a '0' - already checked that this will fit */
+    int save = sig->start[sig->len];
+    sig->start[sig->len] = 0;
+    mpz_set_str(&b, sig->start, 16);
+    sig->start[sig->len] = save;
 
     mpz_powm(&c, &b, &st->e, &st->n);
 
@@ -170,259 +313,332 @@ static bool_t rsa_sig_check(void *sst, uint8_t *data, int32_t datalen,
     return ok;
 }
 
-static list_t *rsapub_apply(closure_t *self, struct cloc loc, dict_t *context,
-                           list_t *args)
+static void rsapub_dispose(void *sst) {
+    struct rsapub *st=sst;
+
+    if (!st) return;
+    RSAPUB_BNS(RSAPUB_CLEAR_ST_BN)
+    rsacommon_dispose(&st->common);
+    free(st);
+}
+
+#define RSAPUB_LOADCORE_DEFBN(ix,en,what) \
+    const char *en##s, struct cloc en##_loc,
+
+#define LDPUBFATAL(lc,...) ({                  \
+       load_err(l,(lc),0,__VA_ARGS__); \
+       goto error_out;                         \
+    })
+
+static struct rsapub *rsa_loadpub_core(RSAPUB_BNS(RSAPUB_LOADCORE_DEFBN)
+                                      struct load_ctx *l)
 {
     struct rsapub *st;
-    item_t *i;
-    string_t e,n;
 
-    st=safe_malloc(sizeof(*st),"rsapub_apply");
+    NEW(st);
     st->cl.description="rsapub";
-    st->cl.type=CL_RSAPUBKEY;
+    st->cl.type=CL_SIGPUBKEY;
     st->cl.apply=NULL;
     st->cl.interface=&st->ops;
     st->ops.st=st;
+    st->common.hashbuf=NULL;
+    st->ops.unpick=rsa_sig_unpick;
     st->ops.check=rsa_sig_check;
-    st->loc=loc;
-
-    i=list_elem(args,0);
-    if (i) {
-       if (i->type!=t_string) {
-           cfgfatal(i->loc,"rsa-public","first argument must be a string\n");
-       }
-       e=i->data.string;
-       if (mpz_init_set_str(&st->e,e,10)!=0) {
-           cfgfatal(i->loc,"rsa-public","encryption key \"%s\" is not a "
-                    "decimal number string\n",e);
-       }
-    } else {
-       cfgfatal(loc,"rsa-public","you must provide an encryption key\n");
-    }
-    if (mpz_sizeinbase(&st->e, 256) > RSA_MAX_MODBYTES) {
-       cfgfatal(loc, "rsa-public", "implausibly large public exponent\n");
-    }
-    
-    i=list_elem(args,1);
-    if (i) {
-       if (i->type!=t_string) {
-           cfgfatal(i->loc,"rsa-public","second argument must be a string\n");
-       }
-       n=i->data.string;
-       if (mpz_init_set_str(&st->n,n,10)!=0) {
-           cfgfatal(i->loc,"rsa-public","modulus \"%s\" is not a decimal "
-                    "number string\n",n);
-       }
-    } else {
-       cfgfatal(loc,"rsa-public","you must provide a modulus\n");
-    }
-    if (mpz_sizeinbase(&st->n, 256) > RSA_MAX_MODBYTES) {
-       cfgfatal(loc, "rsa-public", "implausibly large modulus\n");
+    st->ops.hash=0;
+    st->ops.dispose=rsapub_dispose;
+    st->loc=l->loc;
+    RSAPUB_BNS(RSAPUB_INIT_ST_BN)
+
+#define RSAPUB_LOADCORE_GETBN(ix,en,what)                              \
+    if (mpz_init_set_str(&st->en,en##s,10)!=0) {                       \
+       LDPUBFATAL(&en##_loc, what " \"%s\" is not a "                  \
+                "decimal number string",en##s);                        \
+    }                                                                  \
+    if (mpz_sizeinbase(&st->en, 256) > RSA_MAX_MODBYTES) {             \
+       LDPUBFATAL(&en##_loc, "implausibly large " what);               \
     }
+
+    RSAPUB_BNS(RSAPUB_LOADCORE_GETBN)
+
+    rsa_sethash(l,&st->common,&st->ops.hash);
+
+    return st;
+
+ error_out:
+    rsapub_dispose(st);
+    return 0;
+}
+
+static list_t *rsapub_apply(closure_t *self, struct cloc loc, dict_t *context,
+                           list_t *args)
+{
+    struct load_ctx l[1];
+    l->verror=verror_cfgfatal;
+    l->postreadcheck=0;
+    l->what="rsa-public";
+    l->deprdict=context;
+    l->loc=loc;
+
+#define RSAPUB_APPLY_GETBN(ix,en,what)                         \
+    item_t *en##i;                                             \
+    const char *en##s;                                         \
+    en##i=list_elem(args,ix);                                  \
+    if (!en##i)                                                        \
+        cfgfatal(loc,"rsa-public",                             \
+                 "you must provide an encryption key\n");      \
+    struct cloc en##_loc=en##i->loc;                           \
+    if (en##i->type!=t_string)                                 \
+       cfgfatal(en##_loc,"rsa-public",                         \
+                "first argument must be a string\n");          \
+    en##s=en##i->data.string;
+
+    RSAPUB_BNS(RSAPUB_APPLY_GETBN)
+
+    struct rsapub *st=rsa_loadpub_core(RSAPUB_BNS(RSAPUB_LOADCORE_PASSBN)
+                                      l);
+
     return new_closure(&st->cl);
 }
 
-static uint32_t keyfile_get_int(struct cloc loc, FILE *f)
+bool_t rsa1_loadpub(const struct sigscheme_info *algo,
+                   struct buffer_if *pubkeydata,
+                   struct sigpubkey_if **sigpub_r,
+                   closure_t **closure_r,
+                   struct log_if *log, struct cloc loc)
+{
+    struct rsapub *st=0;
+
+    struct load_ctx l[1];
+    l->verror=verror_tryload;
+    l->postreadcheck=0;
+    l->what="rsa1_loadpub";
+    l->deprdict=0;
+    l->loc=loc;
+    l->u.tryload.log=log;
+
+    char *nul=buf_append(pubkeydata,1);
+    if (!nul) LDPUBFATAL(0,"rsa1 public key data too long for extra nul");
+    *nul=0;
+
+    const char *delim=" \t\n";
+    char *saveptr;
+    /*unused*/ strtok_r(pubkeydata->start,delim,&saveptr);
+
+#define RSAPUB_TRYLOAD_GETBN(ix,en,what)                               \
+    struct cloc en##_loc=loc;                                          \
+    const char *en##s=strtok_r(0,delim,&saveptr);                      \
+    if (!en##s) LDPUBFATAL(0,"end of pubkey data looking for " what);
+
+    RSAPUB_BNS(RSAPUB_TRYLOAD_GETBN);
+
+    st=rsa_loadpub_core(RSAPUB_BNS(RSAPUB_LOADCORE_PASSBN) l);
+    if (!st) goto error_out;
+
+    *sigpub_r=&st->ops;
+    *closure_r=&st->cl;
+    return True;
+
+ error_out:
+    rsapub_dispose(st);
+    return False;
+}
+
+#define LDFATAL(...)      ({ load_err(l,0,0,__VA_ARGS__); goto error_out; })
+#define LDFATAL_FILE(...) ({ load_err(l,0,f,__VA_ARGS__); goto error_out; })
+#define KEYFILE_GET(is)   ({                                   \
+       uint##is##_t keyfile_get_tmp=keyfile_get_##is(l,f);     \
+       if (!l->postreadcheck(l,f)) goto error_out;             \
+       keyfile_get_tmp;                                        \
+    })
+
+static uint32_t keyfile_get_32(struct load_ctx *l, FILE *f)
 {
     uint32_t r;
     r=fgetc(f)<<24;
     r|=fgetc(f)<<16;
     r|=fgetc(f)<<8;
     r|=fgetc(f);
-    cfgfile_postreadcheck(loc,f);
     return r;
 }
 
-static uint16_t keyfile_get_short(struct cloc loc, FILE *f)
+static uint16_t keyfile_get_16(struct load_ctx *l, FILE *f)
 {
     uint16_t r;
     r=fgetc(f)<<8;
     r|=fgetc(f);
-    cfgfile_postreadcheck(loc,f);
     return r;
 }
 
-static list_t *rsapriv_apply(closure_t *self, struct cloc loc, dict_t *context,
-                            list_t *args)
+static void rsapriv_dispose(void *sst)
 {
-    struct rsapriv *st;
-    FILE *f;
-    cstring_t filename;
-    item_t *i;
+    struct rsapriv *st=sst;
+    mpz_clear(&st->n);
+    mpz_clear(&st->p); mpz_clear(&st->dp);
+    mpz_clear(&st->q); mpz_clear(&st->dq);
+    mpz_clear(&st->w);
+    rsacommon_dispose(&st->common);
+    free(st);
+}
+
+static struct rsapriv *rsa_loadpriv_core(struct load_ctx *l,
+                                        FILE *f, struct cloc loc,
+                                        bool_t do_validity_check)
+{
+    struct rsapriv *st=0;
     long length;
-    uint8_t *b, *c;
+    uint8_t *b=0, *c=0;
     int cipher_type;
     MP_INT e,d,iqmp,tmp,tmp2,tmp3;
     bool_t valid;
 
-    st=safe_malloc(sizeof(*st),"rsapriv_apply");
+    mpz_init(&e);
+    mpz_init(&d);
+    mpz_init(&iqmp);
+    mpz_init(&tmp);
+    mpz_init(&tmp2);
+    mpz_init(&tmp3);
+
+    NEW(st);
     st->cl.description="rsapriv";
-    st->cl.type=CL_RSAPRIVKEY;
+    st->cl.type=CL_SIGPRIVKEY;
     st->cl.apply=NULL;
     st->cl.interface=&st->ops;
     st->ops.st=st;
+    st->common.hashbuf=NULL;
     st->ops.sign=rsa_sign;
+    st->ops.hash=0;
+    st->ops.dispose=rsapriv_dispose;
     st->loc=loc;
+    mpz_init(&st->n);
+    mpz_init(&st->q);
+    mpz_init(&st->p);
+    mpz_init(&st->dp);
+    mpz_init(&st->dq);
+    mpz_init(&st->w);
 
-    /* Argument is filename pointing to SSH1 private key file */
-    i=list_elem(args,0);
-    if (i) {
-       if (i->type!=t_string) {
-           cfgfatal(i->loc,"rsa-public","first argument must be a string\n");
-       }
-       filename=i->data.string;
-    } else {
-       filename=NULL; /* Make compiler happy */
-       cfgfatal(loc,"rsa-private","you must provide a filename\n");
-    }
-
-    f=fopen(filename,"rb");
     if (!f) {
-       if (just_check_config) {
-           Message(M_WARNING,"rsa-private (%s:%d): cannot open keyfile "
-                   "\"%s\"; assuming it's valid while we check the "
-                   "rest of the configuration\n",loc.file,loc.line,filename);
-           goto assume_valid;
-       } else {
-           fatal_perror("rsa-private (%s:%d): cannot open file \"%s\"",
-                        loc.file,loc.line,filename);
-       }
+       assert(just_check_config);
+       goto assume_valid;
     }
 
     /* Check that the ID string is correct */
     length=strlen(AUTHFILE_ID_STRING)+1;
     b=safe_malloc(length,"rsapriv_apply");
     if (fread(b,length,1,f)!=1 || memcmp(b,AUTHFILE_ID_STRING,length)!=0) {
-       cfgfatal_maybefile(f,loc,"rsa-private","failed to read magic ID"
-                          " string from SSH1 private keyfile \"%s\"\n",
-                          filename);
+       LDFATAL_FILE("failed to read magic ID"
+                    " string from SSH1 private keyfile\n");
     }
-    free(b);
+    FREE(b);
 
     cipher_type=fgetc(f);
-    keyfile_get_int(loc,f); /* "Reserved data" */
+    KEYFILE_GET(32); /* "Reserved data" */
     if (cipher_type != 0) {
-       cfgfatal(loc,"rsa-private","we don't support encrypted keyfiles\n");
+       LDFATAL("we don't support encrypted keyfiles\n");
     }
 
     /* Read the public key */
-    keyfile_get_int(loc,f); /* Not sure what this is */
-    length=(keyfile_get_short(loc,f)+7)/8;
+    KEYFILE_GET(32); /* Not sure what this is */
+    length=(KEYFILE_GET(16)+7)/8;
     if (length>RSA_MAX_MODBYTES) {
-       cfgfatal(loc,"rsa-private","implausible length %ld for modulus\n",
+       LDFATAL("implausible length %ld for modulus\n",
                 length);
     }
     b=safe_malloc(length,"rsapriv_apply");
     if (fread(b,length,1,f) != 1) {
-       cfgfatal_maybefile(f,loc,"rsa-private","error reading modulus\n");
+       LDFATAL_FILE("error reading modulus\n");
     }
-    mpz_init(&st->n);
     read_mpbin(&st->n,b,length);
-    free(b);
-    length=(keyfile_get_short(loc,f)+7)/8;
+    FREE(b);
+    length=(KEYFILE_GET(16)+7)/8;
     if (length>RSA_MAX_MODBYTES) {
-       cfgfatal(loc,"rsa-private","implausible length %ld for e\n",length);
+       LDFATAL("implausible length %ld for e\n",length);
     }
     b=safe_malloc(length,"rsapriv_apply");
     if (fread(b,length,1,f)!=1) {
-       cfgfatal_maybefile(f,loc,"rsa-private","error reading e\n");
+       LDFATAL_FILE("error reading e\n");
     }
-    mpz_init(&e);
     read_mpbin(&e,b,length);
-    free(b);
+    FREE(b);
     
-    length=keyfile_get_int(loc,f);
+    length=KEYFILE_GET(32);
     if (length>1024) {
-       cfgfatal(loc,"rsa-private","implausibly long (%ld) key comment\n",
+       LDFATAL("implausibly long (%ld) key comment\n",
                 length);
     }
     c=safe_malloc(length+1,"rsapriv_apply");
     if (fread(c,length,1,f)!=1) {
-       cfgfatal_maybefile(f,loc,"rsa-private","error reading key comment\n");
+       LDFATAL_FILE("error reading key comment\n");
     }
     c[length]=0;
 
     /* Check that the next two pairs of characters are identical - the
        keyfile is not encrypted, so they should be */
 
-    if (keyfile_get_short(loc,f) != keyfile_get_short(loc,f)) {
-       cfgfatal(loc,"rsa-private","corrupt keyfile\n");
+    if (KEYFILE_GET(16) != KEYFILE_GET(16)) {
+       LDFATAL("corrupt keyfile\n");
     }
 
     /* Read d */
-    length=(keyfile_get_short(loc,f)+7)/8;
+    length=(KEYFILE_GET(16)+7)/8;
     if (length>RSA_MAX_MODBYTES) {
-       cfgfatal(loc,"rsa-private","implausibly long (%ld) decryption key\n",
+       LDFATAL("implausibly long (%ld) decryption key\n",
                 length);
     }
     b=safe_malloc(length,"rsapriv_apply");
     if (fread(b,length,1,f)!=1) {
-       cfgfatal_maybefile(f,loc,"rsa-private",
-                          "error reading decryption key\n");
+       LDFATAL_FILE("error reading decryption key\n");
     }
-    mpz_init(&d);
     read_mpbin(&d,b,length);
-    free(b);
+    FREE(b);
     /* Read iqmp (inverse of q mod p) */
-    length=(keyfile_get_short(loc,f)+7)/8;
+    length=(KEYFILE_GET(16)+7)/8;
     if (length>RSA_MAX_MODBYTES) {
-       cfgfatal(loc,"rsa-private","implausibly long (%ld)"
+       LDFATAL("implausibly long (%ld)"
                 " iqmp auxiliary value\n", length);
     }
     b=safe_malloc(length,"rsapriv_apply");
     if (fread(b,length,1,f)!=1) {
-       cfgfatal_maybefile(f,loc,"rsa-private",
-                          "error reading decryption key\n");
+       LDFATAL_FILE("error reading decryption key\n");
     }
-    mpz_init(&iqmp);
     read_mpbin(&iqmp,b,length);
-    free(b);
+    FREE(b);
     /* Read q (the smaller of the two primes) */
-    length=(keyfile_get_short(loc,f)+7)/8;
+    length=(KEYFILE_GET(16)+7)/8;
     if (length>RSA_MAX_MODBYTES) {
-       cfgfatal(loc,"rsa-private","implausibly long (%ld) q value\n",
+       LDFATAL("implausibly long (%ld) q value\n",
                 length);
     }
     b=safe_malloc(length,"rsapriv_apply");
     if (fread(b,length,1,f)!=1) {
-       cfgfatal_maybefile(f,loc,"rsa-private",
-                          "error reading q value\n");
+       LDFATAL_FILE("error reading q value\n");
     }
-    mpz_init(&st->q);
     read_mpbin(&st->q,b,length);
-    free(b);
+    FREE(b);
     /* Read p (the larger of the two primes) */
-    length=(keyfile_get_short(loc,f)+7)/8;
+    length=(KEYFILE_GET(16)+7)/8;
     if (length>RSA_MAX_MODBYTES) {
-       cfgfatal(loc,"rsa-private","implausibly long (%ld) p value\n",
+       LDFATAL("implausibly long (%ld) p value\n",
                 length);
     }
     b=safe_malloc(length,"rsapriv_apply");
     if (fread(b,length,1,f)!=1) {
-       cfgfatal_maybefile(f,loc,"rsa-private",
-                          "error reading p value\n");
+       LDFATAL_FILE("error reading p value\n");
     }
-    mpz_init(&st->p);
     read_mpbin(&st->p,b,length);
-    free(b);
+    FREE(b);
     
-    if (fclose(f)!=0) {
-       fatal_perror("rsa-private (%s:%d): fclose",loc.file,loc.line);
+    if (ferror(f)) {
+       fatal_perror("rsa-private (%s:%d): ferror",loc.file,loc.line);
     }
 
+    rsa_sethash(l,&st->common,&st->ops.hash);
+
     /*
      * Now verify the validity of the key, and set up the auxiliary
      * values for fast CRT signing.
      */
     valid=False;
-    i=list_elem(args,1);
-    mpz_init(&tmp);
-    mpz_init(&tmp2);
-    mpz_init(&tmp3);
-    if (i && i->type==t_bool && i->data.bool==False) {
-       Message(M_INFO,"rsa-private (%s:%d): skipping RSA key validity "
-               "check\n",loc.file,loc.line);
-    } else {
+    if (do_validity_check) {
        /* Verify that p*q is equal to n. */
        mpz_mul(&tmp, &st->p, &st->q);
        if (mpz_cmp(&tmp, &st->n) != 0)
@@ -462,9 +678,6 @@ static list_t *rsapriv_apply(closure_t *self, struct cloc loc, dict_t *context,
      *   dq == d mod (q-1)      similarly mod q
      *   w == iqmp * q          so that w == 0 mod q, and w == 1 mod p
      */
-    mpz_init(&st->dp);
-    mpz_init(&st->dq);
-    mpz_init(&st->w);
     mpz_sub_ui(&tmp, &st->p, 1);
     mpz_mod(&st->dp, &d, &tmp);
     mpz_sub_ui(&tmp, &st->q, 1);
@@ -473,19 +686,129 @@ static list_t *rsapriv_apply(closure_t *self, struct cloc loc, dict_t *context,
     
 done_checks:
     if (!valid) {
-       cfgfatal(loc,"rsa-private","file \"%s\" does not contain a "
-                "valid RSA key!\n",filename);
+       LDFATAL("file does not contain a "
+                "valid RSA key!\n");
     }
+
+assume_valid:
+out:
     mpz_clear(&tmp);
     mpz_clear(&tmp2);
     mpz_clear(&tmp3);
 
-    free(c);
+    FREE(b);
+    FREE(c);
     mpz_clear(&e);
     mpz_clear(&d);
     mpz_clear(&iqmp);
 
-assume_valid:
+    return st;
+
+error_out:
+    if (st) rsapriv_dispose(st);
+    st=0;
+    goto out;
+}
+
+static bool_t postreadcheck_tryload(struct load_ctx *l, FILE *f)
+{
+    assert(!ferror(f));
+    if (feof(f)) { load_err(l,0,0,"eof mid-integer"); return False; }
+    return True;
+}
+
+bool_t rsa1_loadpriv(const struct sigscheme_info *algo,
+                    struct buffer_if *privkeydata,
+                    struct sigprivkey_if **sigpriv_r,
+                    closure_t **closure_r,
+                    struct log_if *log, struct cloc loc)
+{
+    FILE *f=0;
+    struct rsapriv *st=0;
+
+    f=fmemopen(privkeydata->start,privkeydata->size,"r");
+    if (!f) {
+       slilog(log,M_ERR,"failed to fmemopen private key file\n");
+       goto error_out;
+    }
+
+    struct load_ctx l[1];
+    l->what="rsa1priv load";
+    l->verror=verror_tryload;
+    l->postreadcheck=postreadcheck_tryload;
+    l->deprdict=0;
+    l->loc=loc;
+    l->u.tryload.log=log;
+
+    st=rsa_loadpriv_core(l,f,loc,False);
+    if (!st) goto error_out;
+    goto out;
+
+ error_out:
+    FREE(st);
+ out:
+    if (f) fclose(f);
+    if (!st) return False;
+    *sigpriv_r=&st->ops;
+    *closure_r=&st->cl;
+    return True;
+}
+
+static bool_t postreadcheck_apply(struct load_ctx *l, FILE *f)
+{
+    cfgfile_postreadcheck(l->loc,f);
+    return True;
+}
+
+static list_t *rsapriv_apply(closure_t *self, struct cloc loc, dict_t *context,
+                            list_t *args)
+{
+    struct rsapriv *st;
+    item_t *i;
+    cstring_t filename;
+    FILE *f;
+    struct load_ctx l[1];
+
+    l->what="rsa-private";
+    l->verror=verror_cfgfatal;
+    l->postreadcheck=postreadcheck_apply;
+    l->deprdict=context;
+    l->loc=loc;
+
+    /* Argument is filename pointing to SSH1 private key file */
+    i=list_elem(args,0);
+    if (i) {
+       if (i->type!=t_string) {
+           cfgfatal(i->loc,"rsa-private","first argument must be a string\n");
+       }
+       filename=i->data.string;
+    } else {
+       filename=NULL; /* Make compiler happy */
+       cfgfatal(i->loc,"rsa-private","you must provide a filename\n");
+    }
+
+    f=fopen(filename,"rb");
+    if (!f) {
+       if (just_check_config) {
+           Message(M_WARNING,"rsa-private (%s:%d): cannot open keyfile "
+                   "\"%s\"; assuming it's valid while we check the "
+                   "rest of the configuration\n",loc.file,loc.line,filename);
+       } else {
+           fatal_perror("rsa-private (%s:%d): cannot open file \"%s\"",
+                        loc.file,loc.line,filename);
+       }
+    }
+
+    bool_t do_validity_check=True;
+    i=list_elem(args,1);
+    if (i && i->type==t_bool && i->data.bool==False) {
+       Message(M_INFO,"rsa-private (%s:%d): skipping RSA key validity "
+               "check\n",loc.file,loc.line);
+       do_validity_check=False;
+    }
+
+    st=rsa_loadpriv_core(l,f,loc,do_validity_check);
+    fclose(f);
     return new_closure(&st->cl);
 }
 
diff --git a/secnet-wireshark.lua b/secnet-wireshark.lua
new file mode 100644 (file)
index 0000000..62739bc
--- /dev/null
@@ -0,0 +1,883 @@
+--- -*-lua-*-
+---
+--- This file is part of secnet.
+--- See README for full list of copyright holders.
+---
+--- secnet is free software; you can redistribute it and/or modify it
+--- under the terms of the GNU General Public License as published by
+--- the Free Software Foundation; either version d of the License, or
+--- (at your option) any later version.
+---
+--- secnet is distributed in the hope that it will be useful, but
+--- WITHOUT ANY WARRANTY; without even the implied warranty of
+--- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+--- General Public License for more details.
+---
+--- You should have received a copy of the GNU General Public License
+--- version 3 along with secnet; if not, see
+--- https://www.gnu.org/licenses/gpl.html.
+
+local secnet = Proto("secnet", "Secnet VPN")
+
+-----------------------------------------------------------------------------
+--- Session tracking.
+---
+--- This is the hardest part of the dissector.
+
+-- Timelines.  A timeline associates pieces of information with times T.
+
+local function tl_new()
+  -- Return a fresh shiny timeline.
+
+  return { }
+end
+
+local function tl__find(tl, t)
+  -- Find and return the earliest association in TL not earlier than T.  If
+  -- there is no such entry, return nil.
+
+  local lo = 1
+  local hi = #tl + 1
+
+  -- Plain old binary search.  The active interval is half-open, [lo, hi).
+  while true do
+    local w = hi - lo
+    if w == 0 then return nil end
+    local mid = lo + math.floor(w/2)
+    local tv = tl[mid]
+    if tv.t > t then hi = mid
+    elseif tv.t == t or w == 1 then return tv
+    else lo = mid
+    end
+  end
+end
+
+local function tl_find(tl, t)
+  -- Find and return the state of the timeline at time T, i.e., the earliest
+  -- value in TL not earlier than T.  If there is no such entry, return nil.
+
+  local tv = tl__find(tl, t)
+  if tv == nil then return nil else return tv.v end
+end
+
+local function tl_add(tl, t, v)
+  -- Associate the value V with time T in TL.
+
+  local tv = tl__find(tl, t)
+  if tv ~= nil and tv.t == t then
+    tv.v = v
+  else
+    -- Append the new item.  If necessary, sort the vector; we expect that
+    -- we'll see everything in the right order, so this won't be a problem.
+    local n = #tl
+    tl[n + 1] = { t = t, v = v }
+    if n > 0 and tl[n].t > t then
+      table.sort(tl, function (tv0, tv1) return tv0.t < tv1.t end)
+    end
+  end
+end
+
+local function dump_timeline(tl, cvt)
+  -- Dump a timeline TL, using the function CVT to convert each value to a
+  -- string.
+
+  for _, tv in ipairs(tl) do print("\t" .. tv.t .. ": " .. cvt(tv.v)) end
+end
+
+local function get_timeline_create(map, index)
+  -- If MAP[INDEX] exists, return it; otherwise set MAP[INDEX] to a fresh
+  -- timeline and return that.
+
+  local tl = map[index]
+  if tl == nil then tl = tl_new(); map[index] = tl end
+  return tl
+end
+
+local function lookup_timeline(map, index, t)
+  -- If it exists, MAP[INDEX] should be a timeline; find its state at time T.
+  -- Return nil if there's nothing there, or T is too early.
+
+  local tl = map[index]
+  if tl == nil then return nil
+  else return tl_find(tl, t)
+  end
+end
+
+-- The `SITEMAP' maps site names to little structures.
+--
+--   * `algs' is a map from peer site names to a timeline of structures
+--     described below.
+--
+--   * `index' is a map from site indices to a timeline of names, reflecting
+--     that, at some time T, this site thought that some index I referred to
+--     a peer site P.
+--
+-- The `algs' map contains the following slots, populated during .
+--
+--   * `xform' is a timeline of transform names.
+local SITEMAP = { }
+
+-- The `ADDRMAP' maps (IPv4 or IPv6) socket addresses in the form
+-- `[ADDR]:PORT' to a timeline of site names, populated based on claims made
+-- by senders about themselves.  The `GUESSMAP' is similar, but populated
+-- based on assertions about recipients.
+local ADDRMAP = { }
+local GUESSMAP = { }
+
+local function snd_sockname(st)
+  -- Return the sender's socket name as a thing which can be used as a table
+  -- index.
+
+  local pinfo = st.pinfo
+  return string.format("[%s]:%d", pinfo.net_src, pinfo.src_port)
+end
+
+local function rcv_sockname(st)
+  -- Return the recipient's socket name as a thing which can be used as a
+  -- table index.
+
+  local pinfo = st.pinfo
+  return string.format("[%s]:%d", pinfo.net_dst, pinfo.dst_port)
+end
+
+local function get_site_create(name)
+  -- If NAME refers to a known site, then return its information structure;
+  -- otherwise create a new one and return that.
+
+  local site = SITEMAP[name]
+  if site == nil then
+    site = { algs = { }, index = { } }
+    SITEMAP[name] = site
+  end
+  return site
+end
+
+local function notice_site_name(map, st, sock, name)
+  -- Record in MAP that the packet described in the state ST tells us that,
+  -- at that time, the site NAME appeared to be at address SOCK.
+
+  tl_add(get_timeline_create(map, sock), st.pinfo.rel_ts, name)
+end
+
+local function dump_algs(algs)
+  -- Dump the algorithms selection ALGS from a site structure.
+
+  return "xform=" .. algs.transform
+end
+
+local function dump_str(str) return str end
+
+local function dump_addrmap(what, map)
+  -- Dump MAP, which is an address map like `ADDRMAP' or `GUESSMAP'; WHAT is
+  -- a string describing which map it is.
+
+  print(what .. "...")
+  for addr, tl in pairs(map) do
+    print("  " .. addr)
+    dump_timeline(tl, dump_str)
+  end
+end
+
+local function dump_tracking_state()
+  -- Dump the entire tracking state to standard output.
+
+  dump_addrmap("Address map", ADDRMAP)
+  dump_addrmap("Guess map", GUESSMAP)
+  print("Site map...")
+  for name, site in pairs(SITEMAP) do
+    print("  " .. name)
+    print("    algs...")
+    for peer, tl in pairs(site.algs) do
+      print("      " .. peer)
+      dump_timeline(tl, dump_algs)
+    end
+    print("    index...")
+    for ix, tl in pairs(site.index) do
+      print("      " .. ix)
+      dump_timeline(tl, dump_str)
+    end
+  end
+end
+
+local function notice_sndname(st, name)
+  -- Record that sender of the packet described by state ST is called NAME.
+
+  st.sndname = name
+  notice_site_name(ADDRMAP, st, snd_sockname(st), name)
+end
+
+local function notice_rcvname(st, name)
+  -- Record that the sender of the packet described by ST thought that its
+  -- recipient was called NAME.
+
+  st.rcvname = name
+  notice_site_name(GUESSMAP, st, rcv_sockname(st), name)
+  if st.sndname ~= nil then
+    local site = get_site_create(st.sndname)
+    tl_add(get_timeline_create(site.index, st.sndix), st.pinfo.rel_ts, name)
+  end
+end
+
+-- Tables describing the kinds of algorithms which can be selected.
+local CAPTAB = {
+  [8] = { name = "serpent256cbc", kind = "transform",
+         desc = "Deprecated Serpent256-CBC transform" },
+  [9] = { name = "eaxserpent", kind = "transform",
+         desc = "Serpent256-EAX transform" },
+  [31] = { name = "mobile-priority", kind = "early",
+          desc = "Mobile site takes priority in case of MSG1 crossing" }
+}
+
+local function get_algname(kind, cap, dflt)
+  -- Fetch an algorithm of the given KIND, given its capability number CAP;
+  -- if CAP is nil, then return DFLT instead.
+
+  local name
+  if cap == nil then
+    name = dflt
+  else
+    local info = CAPTAB[cap]
+    if info ~= nil and info.kind == kind then name = info.name
+    else name = string.format("Unknown %s #%d", kind, cap)
+    end
+  end
+  return name
+end
+
+local function notice_alg_selection(st)
+  -- Record the algorithm selections declared in the packet described by ST.
+
+  local transform = get_algname("transform", st.transform, "serpent256cbc")
+  local site = get_site_create(st.sndname)
+  local peer = get_site_create(st.rcvname)
+  local now = st.pinfo.rel_ts
+  local algs = { transform = transform }
+  tl_add(get_timeline_create(site.algs, st.rcvname), now, algs)
+  tl_add(get_timeline_create(peer.algs, st.sndname), now, algs)
+end
+
+-----------------------------------------------------------------------------
+--- Protocol dissection primitives.
+
+local PF = { } -- The table of protocol fields, filled in later.
+local F = { } -- A table of field values, also filled in later.
+
+local function msgcode(major, minor)
+  -- Construct a Secnet message number according to the complicated rules.
+
+  local majlo = bit.band(major, 0x000f)
+  local majhi = bit.band(major, 0xfff0)
+  local minlo = bit.band(minor, 0x000f)
+  local minhi = bit.band(minor, 0xfff0)
+  return bit.bxor(bit.lshift(majlo,  0),
+                 bit.lshift(majlo,  8),
+                 bit.lshift(majlo, 16),
+                 bit.lshift(majlo, 24),
+                 bit.lshift(majhi,  4),
+                 bit.lshift(minlo,  4),
+                 bit.lshift(minlo, 28),
+                 bit.lshift(minhi, 16))
+end
+
+local function msgmajor(label)
+  -- Return the major message number from a LABEL.
+
+  local lo = bit.band(label, 0x000f)
+  local hi = bit.band(bit.rshift(label, 4), 0xfff0)
+  return bit.bxor(lo, bit.lshift(lo, 4), bit.lshift(lo, 12), hi)
+end
+
+local function msgminor(label)
+  -- Return the minor message number from a LABEL.
+
+  return bit.bxor(bit.lshift(bit.band(label, 0x00ff), 8),
+                 bit.band(bit.rshift(label, 4), 0x000f),
+                 bit.band(bit.rshift(label, 16), 0xfff0))
+end
+
+-- Main message-number table.
+local M = { NAK                = msgcode(     0, 0),
+           MSG0        = msgcode(0x2020, 0), -- !
+           MSG1        = msgcode(     1, 0),
+           MSG2        = msgcode(     2, 0),
+           MSG3        = msgcode(     3, 0),
+           MSG3BIS     = msgcode(     3, 1),
+           MSG4        = msgcode(     4, 0),
+           MSG5        = msgcode(     5, 0),
+           MSG6        = msgcode(     6, 0),
+           MSG7        = msgcode(     7, 0),
+           MSG8        = msgcode(     8, 0),
+           MSG9        = msgcode(     9, 0),
+           PROD        = msgcode(    10, 0)}
+
+-- The `dissect_*' functions follow a common protocol.  They parse a thing
+-- from a packet buffer BUF, of size SZ, starting from POS, and store
+-- interesting things in a given TREE; when they're done, they return the
+-- updated index where the next interesting thing might be, and maybe store
+-- interesting things in the state ST.  As a result, it's usually a simple
+-- matter to parse a packet by invoking the appropriate primitive dissectors
+-- in the right order.
+
+local function dissect_sequence(dissect, st, buf, tree, pos, sz)
+  -- Dissect pieces of the packed in BUF with each of the dissectors in the
+  -- list DISSECT in turn.
+
+  for _, d in ipairs(dissect) do pos = d(st, buf, tree, pos, sz) end
+  return pos
+end
+
+local function dissect_wtf(st, buf, tree, pos, sz)
+  -- If POS is not at the end of the buffer, note that there's unexpected
+  -- stuff in the packet.
+
+  if pos < sz then tree:add(PF["secnet.wtf"], buf(pos, sz - pos)) end
+  return sz
+end
+
+local dissect_caps
+do
+  -- This will be a list of the capability protocol field names, in the right
+  -- order.  We just have to figure out what that will be.
+  local caplist = { }
+
+  do
+    local caps = { }
+
+    -- Firstly, build, in `caps', a list of the capability names and their
+    -- numbers.
+    local i = 1
+    for j, cap in pairs(CAPTAB) do
+      caps[i] = { i = j, cap = cap.name }
+      i = i + 1
+    end
+
+    -- Sort the list.  Now they're in the right order.
+    table.sort(caps, function (v0, v1) return v0.i < v1.i end)
+
+    -- Finally, write the entries to `caplist', with the `user' entry at the
+    -- start and the `unassigned' entry at the end.
+    i = 1
+    caplist[i] = "secnet.cap.user"; i = i + 1
+    for _, v in ipairs(caps) do
+      caplist[i] = "secnet.cap." .. v.cap
+      i = i + 1
+    end
+    caplist[i] = "secnet.cap.unassigned"; i = i + 1
+  end
+
+  function dissect_caps(st, buf, tree, pos, sz)
+    -- Dissect a capabilities word.
+
+    if pos < sz then
+      local cap = tree:add(PF["secnet.cap"], buf(pos, 4))
+      for _, pf in ipairs(caplist) do cap:add(PF[pf], buf(pos, 4)) end
+      pos = pos + 4
+    end
+    return pos
+  end
+end
+
+local function dissect_mtu(st, buf, tree, pos, sz)
+  -- Dissect an MTU request.
+
+  if pos < sz then tree:add(PF["secnet.mtu"], buf(pos, 2)); pos = pos + 2 end
+  return pos
+end
+
+local function make_dissect_name_xinfo(label, dissect_xinfo, hook)
+  -- Return a dissector function for reading a name and extra information.
+  -- The function will dissect a subtree rooted at the protocol field LABEL;
+  -- it will dissect the extra information using the list DISSECT_XINFO
+  -- (processed using `dissect_sequence'); and finally, if the packet hasn't
+  -- been visited yet, it will call HOOK(ST, NAME), where NAME is the name
+  -- string extracted from the packet.
+
+  return function (st, buf, tree, pos, sz)
+
+    -- Find the length of the whole thing.
+    local len = buf(pos, 2):uint()
+
+    -- Make the subtree root.
+    local sub = tree:add(PF[label], buf(pos, len + 2))
+
+    -- Find the length of the name.  This is rather irritating: I'd like to
+    -- get Wireshark to do this, but it seems that `stringz' doesn't pay
+    -- attention to the buffer limits it's given.  So read the whole lot and
+    -- find the null by hand.
+    local name = buf(pos + 2, len):string()
+    local z, _ = string.find(name, "\0", 1, true)
+    if z == nil then
+      z = len
+    else
+      z = z - 1
+      name = string.sub(name, 1, z)
+    end
+
+    -- Fill in the subtree.
+    sub:add(PF["secnet.namex.len"], buf(pos, 2)); pos = pos + 2
+    sub:add(PF["secnet.namex.name"], buf(pos, z))
+    if z < len then
+      dissect_sequence(dissect_xinfo, st, buf, sub, pos + z + 1, pos + len)
+    end
+
+    -- Maybe call the hook.
+    if hook ~= nil and not st.pinfo.visited then hook(st, name) end
+
+    -- We're done.
+    return pos + len
+  end
+end
+
+local function dissect_sndnonce(st, buf, tree, pos, sz)
+  -- Dissect the sender's nonce.
+
+  tree:add(PF["secnet.kx.sndnonce"], buf(pos, 8)); pos = pos + 8
+  return pos
+end
+
+local function dissect_rcvnonce(st, buf, tree, pos, sz)
+  -- Dissect the recipient's nonce.
+
+  tree:add(PF["secnet.kx.rcvnonce"], buf(pos, 8)); pos = pos + 8
+  return pos
+end
+
+local function dissect_transform(st, buf, tree, pos, sz)
+  -- Dissect the selected transform.  Note this in the packet state for
+  -- later.
+
+  st.transform = buf(pos, 1):uint()
+  tree:add(PF["secnet.kx.transform"], buf(pos, 1)); pos = pos + 1
+  return pos
+end
+
+local function dissect_lenstr(st, buf, tree, label, pos, sz)
+  -- Dissect a simple string given its length.
+  local len = buf(pos, 2):uint()
+  local sub = tree:add(PF[label], buf(pos, len + 2))
+  sub:add(PF[label .. ".len"], buf(pos, 2)); pos = pos + 2
+  sub:add(PF[label .. ".text"], buf(pos, len)); pos = pos + len
+  return pos
+end
+
+local function dissect_dhval(st, buf, tree, pos, sz)
+  -- Dissect a Diffie--Hellman public value.
+
+  return dissect_lenstr(st, buf, tree, "secnet.kx.dhval", pos, sz)
+end
+
+local function dissect_sig(st, buf, tree, pos, sz)
+  -- Dissect a signature.
+
+  return dissect_lenstr(st, buf, tree, "secnet.kx.sig", pos, sz)
+end
+
+local function find_algs_lookup(map, sock, now, ix)
+  -- Utility for `find_algs': look SOCK up in the address map ADDR, to find a
+  -- site; find its peer with index IX; and return the algorithm selection
+  -- current between the pair at time NOW.  If the lookup fails, return nil.
+
+  local name = lookup_timeline(map, sock, now)
+  if name == nil then return nil end
+  local site = SITEMAP[name]
+  if site == nil then return nil end
+  local peername = lookup_timeline(site.index, ix, now)
+  if peername == nil then return nil end
+  return lookup_timeline(site.algs, peername, now)
+end
+
+local function find_algs(st)
+  -- Return the algorithm selection which applies to the packet described in
+  -- ST.
+
+  local now = st.pinfo.rel_ts
+  local sock = snd_sockname(st)
+  local algs = find_algs_lookup(ADDRMAP, sock, now, st.sndix)
+  if algs ~= nil then return algs
+  else return  find_algs_lookup(GUESSMAP, sock, now, st.rcvix)
+  end
+end
+
+-- Transform-specific dissectors...
+local dissect_ct = { }
+function dissect_ct.unknown(st, why, buf, tree, pos, sz)
+  tree:add(PF["secnet.ciphertext.unknown"], buf(pos, sz - pos),
+          "Ciphertext with unknown structure: " .. why)
+  return sz
+end
+function dissect_ct.serpent256cbc(st, buf, tree, pos, sz)
+  tree:add(PF["secnet.ciphertext.iv"], buf(pos, 4)); pos = pos + 4
+  tree:add(PF["secnet.ciphertext.payload"], buf(pos, sz - pos))
+  return sz
+end
+function dissect_ct.eaxserpent(st, buf, tree, pos, sz)
+  local len = sz - pos - 20
+  tree:add(PF["secnet.ciphertext.payload"], buf(pos, len)); pos = pos + len
+  tree:add(PF["secnet.ciphertext.tag"], buf(pos, 16)); pos = pos + 16
+  tree:add(PF["secnet.ciphertext.sequence"], buf(pos, 4)); pos = pos + 4
+  return pos
+end
+
+local function dissect_ciphertext(st, buf, tree, pos, sz)
+  -- Dissect a ciphertext.
+
+  local sub = tree:add(PF["secnet.ciphertext"], buf(pos, sz - pos))
+  local algs = find_algs(st)
+  local xform
+  if algs == nil then xform = nil else xform = algs.transform end
+  if xform == nil then
+    pos = dissect_ct.unknown(st, "unable to find negotiated transform",
+                            buf, sub, pos, sz)
+  else
+    local func = dissect_ct[xform]
+    if func == nil then
+      pos = dissect_ct.unknown(st, "unsupported transform " .. xform,
+                              buf, sub, pos, sz)
+    else
+      pos = func(st, buf, sub, pos, sz)
+    end
+  end
+  return pos
+end
+
+-----------------------------------------------------------------------------
+--- The protocol information table.
+
+local PKTINFO = {
+  -- This is the main table which describes the protocol.  The top level maps
+  -- message labels to structures:
+  --
+  --   * `label' is the category code's symbolic name;
+  --
+  --   * `info' is a prefix for the information column display; and
+  --
+  --   * `dissect' is a sequence of primitive dissectors to run in order to
+  --     parse the rest of the packet.
+
+  [M.NAK] = {
+    label = "NAK",
+    info = "Stimulate fresh key exchange",
+    dissect = { dissect_wtf }
+  },
+  [M.MSG0] = {
+    label = "MSG0",
+    info = "MSG0",
+    dissect = { dissect_ciphertext }
+  },
+  [M.MSG1] = {
+    label = "MSG1",
+    info = "MSG1",
+    dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
+                                       { dissect_caps, dissect_wtf },
+                                       notice_sndname),
+               make_dissect_name_xinfo("secnet.kx.rcvname",
+                                       { dissect_wtf },
+                                       notice_rcvname),
+               dissect_sndnonce,
+               dissect_wtf }
+  },
+  [M.MSG2] = {
+    label = "MSG2",
+    info = "MSG2",
+    dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
+                                       { dissect_caps, dissect_wtf },
+                                       notice_sndname),
+               make_dissect_name_xinfo("secnet.kx.rcvname",
+                                       { dissect_wtf },
+                                       notice_rcvname),
+               dissect_sndnonce, dissect_rcvnonce,
+               dissect_wtf }
+  },
+  [M.MSG3] = {
+    label = "MSG3",
+    info = "MSG3",
+    dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
+                                       { dissect_caps,
+                                         dissect_mtu,
+                                         dissect_wtf },
+                                       notice_sndname),
+               make_dissect_name_xinfo("secnet.kx.rcvname",
+                                       { dissect_wtf },
+                                       notice_rcvname),
+               dissect_sndnonce, dissect_rcvnonce,
+               dissect_wtf },
+    hook = notice_alg_selection
+  },
+  [M.MSG3BIS] = {
+    label = "MSG3BIS",
+    info = "MSG3BIS",
+    dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
+                                       { dissect_caps,
+                                         dissect_mtu,
+                                         dissect_wtf },
+                                       notice_sndname),
+               make_dissect_name_xinfo("secnet.kx.rcvname",
+                                       { dissect_wtf },
+                                       notice_rcvname),
+               dissect_sndnonce, dissect_rcvnonce,
+               dissect_transform,
+               dissect_dhval, dissect_sig,
+               dissect_wtf },
+    hook = notice_alg_selection
+  },
+  [M.MSG4] = {
+    label = "MSG4",
+    info = "MSG4",
+    dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
+                                       { dissect_caps,
+                                         dissect_mtu,
+                                         dissect_wtf },
+                                       notice_sndname),
+               make_dissect_name_xinfo("secnet.kx.rcvname",
+                                       { dissect_wtf },
+                                       notice_rcvname),
+               dissect_sndnonce, dissect_rcvnonce,
+               dissect_dhval, dissect_sig,
+               dissect_wtf }
+  },
+  [M.MSG5] = {
+    label = "MSG5",
+    info = "MSG5",
+    dissect = { dissect_ciphertext }
+  },
+  [M.MSG6] = {
+    label = "MSG6",
+    info = "MSG6",
+    dissect = { dissect_ciphertext }
+  },
+  [M.PROD] = {
+    label = "PROD",
+    info = "PROD",
+    dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
+                                       { dissect_caps,
+                                         dissect_wtf },
+                                       notice_sndname),
+               make_dissect_name_xinfo("secnet.kx.rcvname",
+                                       { dissect_wtf },
+                                       notice_rcvname),
+               dissect_wtf }
+  },
+}
+
+do
+  -- Work through the master table and build the `msgtab'' table, mapping
+  -- message codes to their symbolic names for presentation.
+  local msgtab = { }
+  for i, v in pairs(PKTINFO) do msgtab[i] = v.label end
+
+  local capmap = { transform = { }, early = { } }
+  for i, v in pairs(CAPTAB) do capmap[v.kind][i] = v.desc end
+
+  local ftab = {
+    -- The protocol fields.  This table maps the field names to structures
+    -- used to build the fields, which are then stored in `PF' (declared way
+    -- above):
+    --
+    --   * `name' is the field name to show in the dissector tree view;
+    --
+    --   * `type' is the field type;
+    --
+    --   * `base' is a tweak describing how the field should be formatted;
+    --
+    --   * `mask' is used to single out a piece of a larger bitfield;
+    --
+    --   * `tab' names a mapping table used to convert numerical values to
+    --     symbolic names; and
+    --
+    --   * `hook' is a hook function to run the first time we see a packet,
+    --     to keep track of things.
+
+    ["secnet.hdr"] = {
+      name = "Common message header", type = ftypes.NONE
+    },
+    ["secnet.hdr.rcvix"] = {
+      name = "Recipient's site index for sender",
+      type = ftypes.UINT32, base = base.DEC
+    },
+    ["secnet.hdr.sndix"] = {
+      name = "Sender's site index for recipient",
+      type = ftypes.UINT32, base = base.DEC
+    },
+    ["secnet.hdr.label"] = {
+      name = "Message label", type = ftypes.UINT32,
+      base = base.HEX, tab = msgtab
+    },
+    ["secnet.kx.sndname"] = {
+      name = "Sender's site name and extended information",
+      type = ftypes.NONE
+    },
+    ["secnet.kx.rcvname"] = {
+      name = "Recipient's site name and extended information",
+      type = ftypes.NONE
+    },
+    ["secnet.namex.len"] = {
+      name = "Name/extended info length",
+      type = ftypes.UINT16, base = base.DEC
+    },
+    ["secnet.namex.name"] = {
+      name = "Site name", type = ftypes.STRING,
+      field = true, base = base.ASCII,
+    },
+    ["secnet.cap"] = {
+      name = "Advertised capability bits",
+      type = ftypes.UINT32, base = base.HEX
+    },
+    ["secnet.cap.user"] = {
+      name = "User-assigned capability bits",
+      type = ftypes.UINT32, mask = 0x000000ff, base = base.HEX
+    },
+    ["secnet.mtu"] = {
+      name = "Sender's requested MTU", type = ftypes.UINT16, base = base.DEC
+    },
+    ["secnet.kx.sndnonce"] = {
+      name = "Sender's nonce", type = ftypes.BYTES, base = base.SPACE
+    },
+    ["secnet.kx.rcvnonce"] = {
+      name = "Recipient's nonce", type = ftypes.BYTES, base = base.SPACE
+    },
+    ["secnet.kx.transform"] = {
+      name = "Selected bulk-crypto transform", type = ftypes.UINT8,
+      base = base.DEC, tab = capmap.transform
+    },
+    ["secnet.kx.dhval"] = {
+      name = "Sender's public Diffie--Hellman value", type = ftypes.NONE
+    },
+    ["secnet.kx.dhval.len"] = {
+      name = "Sender's public Diffie--Hellman length",
+      type = ftypes.UINT16, base = base.DEC
+    },
+    ["secnet.kx.dhval.text"] = {
+      name = "Sender's public Diffie--Hellman text", type = ftypes.STRING,
+      base = base.ASCII
+    },
+    ["secnet.kx.sig"] = {
+      name = "Sender's signature", type = ftypes.NONE
+    },
+    ["secnet.kx.sig.len"] = {
+      name = "Sender's signature length",
+      type = ftypes.UINT16, base = base.DEC
+    },
+    ["secnet.kx.sig.text"] = {
+      name = "Sender's signature text", type = ftypes.STRING,
+      base = base.ASCII
+    },
+    ["secnet.ciphertext"] = {
+      name = "Encrypted data", type = ftypes.NONE
+    },
+    ["secnet.ciphertext.unknown"] = {
+      name = "Ciphertext with unknown structure",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["secnet.ciphertext.iv"] = {
+      name = "Initialization vector", type = ftypes.BYTES, base = base.SPACE
+    },
+    ["secnet.ciphertext.sequence"] = {
+      name = "Sequence number", type = ftypes.UINT32, base = base.DEC
+    },
+    ["secnet.ciphertext.payload"] = {
+      name = "Encrypted payload", type = ftypes.BYTES, base = base.SPACE
+    },
+    ["secnet.ciphertext.tag"] = {
+      name = "Authentication tag", type = ftypes.BYTES, base = base.SPACE
+    },
+    ["secnet.wtf"] = {
+      name = "Unexpected trailing data",
+      type = ftypes.BYTES, base = base.SPACE
+    }
+  }
+
+  -- Add the remaining capability fields.  Calculate the unassigned mask
+  -- based on the assigned bits.
+  local unasgn = 0x7fff7f00
+  for i, v in pairs(CAPTAB) do
+    local flag = bit.lshift(1, i)
+    ftab["secnet.cap." .. v.name] = {
+      name = v.desc, type = ftypes.BOOLEAN,
+      mask = flag, base = 32
+    }
+    unasgn = bit.band(unasgn, bit.bnot(flag))
+  end
+  ftab["secnet.cap.unassigned"] = {
+    name = "Unassigned capability bits",
+    type = ftypes.UINT32, mask = unasgn, base = base.HEX
+  }
+
+  -- Convert this table into the protocol fields, and populate `PF'.
+  local ff = { }
+  local i = 1
+
+  -- Figure out whether we can use `none' fields (see below).
+  local use_none_p = rawget(ProtoField, 'none') ~= nil
+  for abbr, args in pairs(ftab) do
+
+    -- An annoying hack.  Older versions of Wireshark don't allow setting
+    -- fields with type `none', which is a shame because they're ideal as
+    -- internal tree nodes.
+    ty = args.type
+    b = args.base
+    if ty == ftypes.NONE then
+      if use_none_p then
+       b = base.NONE
+      else
+       ty = ftypes.BYTES
+       b = base.SPACE
+      end
+    end
+
+    -- Go make the field.
+    local f = ProtoField.new(args.name, abbr, ty,
+                            args.tab, b, args.mask, args.descr)
+    PF[abbr] = f
+    ff[i] = f; i = i + 1
+  end
+  secnet.fields = PF
+
+  -- Make readable fields corresponding to especially interesting protocol
+  -- fields.
+  for abbr, args in pairs(ftab) do
+    if args.field then F[abbr] = Field.new(abbr) end
+  end
+end
+
+-----------------------------------------------------------------------------
+--- The main dissector.
+
+function secnet.dissector(buf, pinfo, tree)
+
+  -- Fill in the obvious stuff.
+  pinfo.cols.protocol = "Secnet"
+
+  local sz = buf:reported_length_remaining()
+  local sub = tree:add(secnet, buf(0, sz), "Secnet packet")
+  local p = 12
+
+  -- Decode the message header.
+  hdr = sub:add(PF["secnet.hdr"], buf(0, 12))
+  local rcvix = buf(0, 4):uint(); hdr:add(PF["secnet.hdr.rcvix"], buf(0, 4))
+  local sndix = buf(4, 4):uint(); hdr:add(PF["secnet.hdr.sndix"], buf(4, 4))
+  local label = buf(8, 4):uint()
+  hdr:add(PF["secnet.hdr.label"], buf(8, 4), label,
+         string.format("Message label (major = 0x%04x, minor = 0x%04x)",
+                       msgmajor(label), msgminor(label)))
+  local st = { pinfo = pinfo, label = label, rcvix = rcvix, sndix = sndix  }
+  local info = PKTINFO[label]
+
+  -- Dispatch using the master protocol table.
+  if info == nil then
+    pinfo.cols.info = string.format("Unknown message label 0x%08x", label)
+  else
+    pinfo.cols.info = info.info
+    p = dissect_sequence(info.dissect, st, buf, sub, p, sz)
+  end
+
+  -- Invoke the hook if necessary.
+  if not pinfo.visited and info.hook ~= nil then info.hook(st) end
+
+  -- Return the final position we reached.
+  return p
+end
+
+-- We're done.  Register the dissector.
+DissectorTable.get("udp.port"):add(410, secnet)
+
+-------- That's all, folks --------------------------------------------------
index 48cfaba2ff3c871babf36b59f20160d6cff4c0ce..c92a0e3a8d90f8220055f2f104621decfc0ea69b 100644 (file)
--- a/secnet.8
+++ b/secnet.8
@@ -1,3 +1,21 @@
+.\" Man page for secnet.
+.\"
+.\" See the secnet.git README, or the Debian copyright file, for full
+.\" list of copyright holders.
+.\"
+.\" secnet is free software; you can redistribute it and/or modify it
+.\" under the terms of the GNU General Public License as published by
+.\" the Free Software Foundation; either version 3 of the License, or
+.\" (at your option) any later version.
+.\" 
+.\" secnet is distributed in the hope that it will be useful, but
+.\" WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+.\" General Public License for more details.
+.\" 
+.\" You should have received a copy of the GNU General Public License
+.\" version 3 along with secnet; if not, see
+.\" https://www.gnu.org/licenses/gpl.html.
 .TH secnet 8
 
 .SH NAME
@@ -45,6 +63,36 @@ Check configuration and exit.
 Configuration file key defining active sites.
 The default is \fBsites\fR.
 
+.SH "CAPABILITY NEGOTIATION"
+Sites negotiate with each other during key exchange
+in order to determine which cryptographic algorithms and other features
+\(en termed
+.I capabilities
+\(en
+they each support.
+Capabilities are assigned small integer numbers.
+In many cases,
+capability numbers can be assigned in the configuration file,
+as described below;
+but secnet's default assignments will often be satisfactory.
+.PP
+Capability numbers between 0 and 7 inclusive
+are reserved for local use:
+secnet will never make use of them without explicit configuration.
+This may be useful to migrate from one set of parameters
+for a particular cryptographic algorithm
+to different, incompatible, parameters for the same algorithm.
+Other capability numbers are assigned by default
+by various kinds of closures.
+See the descriptions below for details.
+.PP
+It is essential that a capability number mean the same thing
+to each of a pair of peers.
+It's possible to configure a site
+so that it uses different capability numbers for the same feature
+when it communicates with different peer sites,
+but this is likely to be more confusing than useful.
+
 .SH "CONFIGURATION FILE"
 .SS Overview
 The default configuration file is \fI/etc/secnet/secnet.conf\fR.
@@ -266,7 +314,6 @@ Boolean.
 If \fBtrue\fR (the default) then check if \fIp\fR is prime.
 .PP
 A \fIdh closure\fR defines a group to be used for key exchange.
-The same group must be used by all sites in the VPN.
 
 .SS logfile
 \fBlogfile(\fIDICT\fB)\fR => \fIlog closure\fR
@@ -416,7 +463,7 @@ A \fIrandomsource closure\fR is a source of random numbers.
 Read the contents of the file \fIPATH\fR (a string) and return it as a string.
 
 .SS eax-serpent
-\eax-fBserpent(\fIDICT\fB)\fR => \fItransform closure\fR
+\fBeax-serpent(\fIDICT\fB)\fR => \fItransform closure\fR
 .PP
 Valid keys in the \fIDICT\fR argument are:
 .TP
@@ -436,15 +483,8 @@ Messages are padded to a multiple of this many bytes.  This
 serves to obscure the exact length of messages.  The default is 16,
 .TP
 .B capab-num
-The transform capability number to use when advertising this
-transform.  Both ends must have the same meaning (or, at least, a
-compatible transform) for each transform capability number they have
-in common.  The default for serpent-eax is 9.
-.IP
-Transform capability numbers in the range 8..15 are intended for
-allocation by the implementation, and may be assigned as the default
-for new transforms in the future.  Transform capability numbers in the
-range 0..7 are reserved for definition by the user.
+The capability number to use when advertising this
+transform.  The default for serpent-eax is 9.
 .PP
 A \fItransform closure\fR is a reversible means of transforming
 messages for transmission over a (presumably) insecure network.
@@ -468,7 +508,7 @@ As above.
 Note that this uses a big-endian variant of the Serpent block cipher
 (which is not compatible with most other Serpent implementations).
 .SS rsa-private
-\fBrsa-private(\fIPATH\fB\fR[, \fICHECK\fR]\fB)\fR => \fIrsaprivkey closure\fR
+\fBrsa-private(\fIPATH\fB\fR[, \fICHECK\fR]\fB)\fR => \fIsigprivkey closure\fR
 .TP
 .I PATH
 String.
@@ -481,7 +521,7 @@ Boolean.
 If \fBtrue\fR (the default) then check that the key is valid.
 
 .SS rsa-public
-\fBrsa-public(\fIKEY\fB, \fIMODULUS\fB)\fR => \fIrsapubkey closure\fR
+\fBrsa-public(\fIKEY\fB, \fIMODULUS\fB)\fR => \fIsigpubkey closure\fR
 .TP
 .I KEY
 String.
@@ -520,7 +560,7 @@ A \fIresolver closure\fR.
 A \fIrandomsource closure\fR.
 .TP
 .B local-key
-An \fIrsaprivkey closure\fR.
+An \fIsigprivkey closure\fR.
 The key used to prove our identity to the peer.
 .TP
 .B address
@@ -534,14 +574,14 @@ Number.
 The port to contact the peer.
 .TP
 .B key
-An \fIrsapubkey closure\fR.
+An \fIsigpubkey closure\fR.
 The key used to verify the peer's identity.
 .TP
 .B transform
 One or more \fItransform closures\fR.
 Used to protect packets exchanged with the peer.  These should
 all have distinct \fBcapab-num\fR values, and the same \fBcapab-num\fR
-value should refer to the same (or a compatible) transform at both
+value should have the same (or a compatible) meaning at both
 ends.  The list should be in order of preference, most preferred
 first.  (The end which sends MSG1,MSG3 ends up choosing; the ordering
 at the other end is irrelevant.)
index c604d44b65781c2cbc7b60604621991cd9e55d32..2ebcddc1ae94491bbf5ad90edb1f12e3a84049dd 100644 (file)
--- a/secnet.c
+++ b/secnet.c
@@ -1,3 +1,22 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #include "secnet.h"
 #include <stdio.h>
 #include <assert.h>
@@ -36,16 +55,19 @@ static pid_t secnet_pid;
 
 /* Structures dealing with poll() call */
 struct poll_interest {
-    beforepoll_fn *before;
+    beforepoll_fn *before; /* 0 if deregistered and waiting to be deleted */
     afterpoll_fn *after;
     void *state;
-    int32_t max_nfds;
     int32_t nfds;
     cstring_t desc;
-    struct poll_interest *next;
+    LIST_ENTRY(poll_interest) entry;
 };
-static struct poll_interest *reg=NULL;
-static int32_t total_nfds=10;
+static LIST_HEAD(, poll_interest) reg = LIST_HEAD_INITIALIZER(&reg);
+
+static bool_t interest_isregistered(const struct poll_interest *i)
+{
+    return !!i->before;
+}
 
 static bool_t finished=False;
 
@@ -105,6 +127,9 @@ static void parse_options(int argc, char **argv)
            exit(0);
            break;
 
+       case 'd':
+           message_level|=M_DEBUG_CONFIG|M_DEBUG_PHASE|M_DEBUG;
+           /* fall through */
        case 'v':
            message_level|=M_INFO|M_NOTICE|M_WARNING|M_ERR|M_SECURITY|
                M_FATAL;
@@ -114,10 +139,6 @@ static void parse_options(int argc, char **argv)
            message_level&=(~M_WARNING);
            break;
 
-       case 'd':
-           message_level|=M_DEBUG_CONFIG|M_DEBUG_PHASE|M_DEBUG;
-           break;
-
        case 'f':
            message_level=M_FATAL;
            break;
@@ -166,11 +187,9 @@ static void parse_options(int argc, char **argv)
 static void setup(dict_t *config)
 {
     list_t *l;
-    item_t *site;
     dict_t *system;
     struct passwd *pw;
     struct cloc loc;
-    int i;
 
     l=dict_lookup(config,"system");
 
@@ -205,6 +224,12 @@ static void setup(dict_t *config)
              "that secnet retain root privileges while running.",
              require_root_privileges_explanation);
     }
+}
+
+static void start_sites(dict_t *config) {
+    int i;
+    list_t *l;
+    item_t *site;
 
     /* Go along site list, starting sites */
     l=dict_lookup(config,sites_key);
@@ -222,28 +247,32 @@ static void setup(dict_t *config)
                cfgfatal(site->loc,"system","non-site closure in site list");
            }
            s=site->data.closure->interface;
-           s->control(s->st,True);
+           s->startup(s->st);
        }
     }
 }
 
-void register_for_poll(void *st, beforepoll_fn *before,
-                      afterpoll_fn *after, int32_t max_nfds, cstring_t desc)
+struct poll_interest *register_for_poll(void *st, beforepoll_fn *before,
+                      afterpoll_fn *after, cstring_t desc)
 {
     struct poll_interest *i;
 
-    i=safe_malloc(sizeof(*i),"register_for_poll");
+    NEW(i);
     i->before=before;
     i->after=after;
     i->state=st;
-    i->max_nfds=max_nfds;
     i->nfds=0;
     i->desc=desc;
-    assert(total_nfds < INT_MAX - max_nfds);
-    total_nfds+=max_nfds;
-    i->next=reg;
-    reg=i;
-    return;
+    LIST_INSERT_HEAD(&reg, i, entry);
+    return i;
+}
+
+void deregister_for_poll(struct poll_interest *i)
+{
+    /* We cannot simply throw this away because we're reentrantly
+     * inside the main loop, which needs to remember which range of
+     * fds corresponds to this now-obsolete interest */
+    i->before=0;
 }
 
 static void system_phase_hook(void *sst, uint32_t newphase)
@@ -294,14 +323,11 @@ uint64_t now_global;
 
 static void run(void)
 {
-    struct poll_interest *i;
-    int rv, nfds, remain, idx;
+    struct poll_interest *i, *itmp;
+    int rv, nfds, idx;
     int timeout;
-    struct pollfd *fds;
-
-    fds=safe_malloc(sizeof(*fds)*total_nfds, "run");
-
-    Message(M_NOTICE,"%s [%d]: starting\n",version,secnet_pid);
+    struct pollfd *fds=0;
+    int allocdfds=0, shortfall=0;
 
     do {
        if (gettimeofday(&tv_now_global, NULL)!=0) {
@@ -310,33 +336,54 @@ static void run(void)
        now_global=((uint64_t)tv_now_global.tv_sec*(uint64_t)1000)+
                   ((uint64_t)tv_now_global.tv_usec/(uint64_t)1000);
        idx=0;
-       for (i=reg; i; i=i->next) {
+       LIST_FOREACH(i, &reg, entry) {
            int check;
-           for (check=0; check<i->nfds; check++) {
-               if(fds[idx+check].revents & POLLNVAL) {
-                   fatal("run: poll (%s#%d) set POLLNVAL", i->desc, check);
+           if (interest_isregistered(i)) {
+               for (check=0; check<i->nfds; check++) {
+                   if(fds[idx+check].revents & POLLNVAL) {
+                       fatal("run: poll (%s#%d) set POLLNVAL", i->desc, check);
+                   }
                }
+               i->after(i->state, fds+idx, i->nfds);
            }
-           i->after(i->state, fds+idx, i->nfds);
            idx+=i->nfds;
        }
-       remain=total_nfds;
+       if (shortfall) {
+           allocdfds *= 2;
+           allocdfds += shortfall;
+           REALLOC_ARY(fds,allocdfds);
+       }
+       shortfall=0;
        idx=0;
        timeout=-1;
-       for (i=reg; i; i=i->next) {
+       LIST_FOREACH_SAFE(i, &reg, entry, itmp) {
+           int remain=allocdfds-idx;
            nfds=remain;
-           rv=i->before(i->state, fds+idx, &nfds, &timeout);
-           if (rv!=0) {
-               /* XXX we need to handle this properly: increase the
-                  nfds available */
-               fatal("run: beforepoll_fn (%s) returns %d",i->desc,rv);
+           if (interest_isregistered(i)) {
+               rv=i->before(i->state, fds+idx, &nfds, &timeout);
+               if (rv!=0) {
+                   if (rv!=ERANGE)
+                       fatal("run: beforepoll_fn (%s) returns %d",i->desc,rv);
+                   assert(nfds < INT_MAX/4 - shortfall);
+                   shortfall += nfds-remain;
+                   nfds=0;
+                   timeout=0;
+               }
+           } else {
+               nfds=0;
            }
            if (timeout<-1) {
                fatal("run: beforepoll_fn (%s) set timeout to %d",
                      i->desc,timeout);
            }
+           if (!interest_isregistered(i)) {
+               /* check this here, rather than earlier, so that we
+                  handle the case where i->before() calls deregister */
+               LIST_REMOVE(i, entry);
+               free(i);
+               continue;
+           }
            idx+=nfds;
-           remain-=nfds;
            i->nfds=nfds;
        }
        do {
@@ -356,6 +403,12 @@ static void run(void)
     free(fds);
 }
 
+bool_t will_droppriv(void)
+{
+    assert(current_phase >= PHASE_SETUP);
+    return !!uid;
+}
+
 /* Surrender privileges, if necessary */
 static void droppriv(void)
 {
@@ -403,9 +456,7 @@ static void become_daemon(void)
     }
     if (secnet_is_daemon) {
        /* stderr etc are redirected to the system/log facility */
-       if (pipe(errfds)!=0) {
-           fatal_perror("can't create pipe for stderr");
-       }
+       pipe_cloexec(errfds);
        if (dup2(errfds[1],0) < 0
            || dup2(errfds[1],1) < 0
            || dup2(errfds[1],2) < 0)
@@ -444,6 +495,9 @@ int main(int argc, char **argv)
 {
     dict_t *config;
 
+    log_early_init();
+    phase_hooks_init();
+
     enter_phase(PHASE_GETOPTS);
     parse_options(argc,argv);
 
@@ -452,6 +506,7 @@ int main(int argc, char **argv)
 
     enter_phase(PHASE_SETUP);
     setup(config);
+    start_sites(config);
 
     if (just_check_config) {
        Message(M_INFO,"configuration file check complete\n");
@@ -460,6 +515,7 @@ int main(int argc, char **argv)
 
     enter_phase(PHASE_DAEMONIZE);
     become_daemon();
+    Message(M_NOTICE,"%s [%d]: starting\n",version,secnet_pid);
     
     enter_phase(PHASE_GETRESOURCES);
     /* Appropriate phase hooks will have been run */
index 9bec310c1b8f280a54a0dec91377faad930780f4..fd4b48f70cbeed0fd78d9ccafa7f201c8668292d 100644 (file)
--- a/secnet.h
+++ b/secnet.h
 /* Core interface of secnet, to be used by all modules */
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 
 #ifndef secnet_h
 #define secnet_h
 
+#define ADNS_FEATURE_MANYAF
+
 #include "config.h"
 #include <stdlib.h>
 #include <stdarg.h>
 #include <stdio.h>
+#include <stdint.h>
+#include <inttypes.h>
 #include <string.h>
 #include <assert.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <fnmatch.h>
 #include <sys/poll.h>
 #include <sys/types.h>
+#include <sys/wait.h>
 #include <sys/time.h>
 #include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <bsd/sys/queue.h>
+
+#include "osdep.h"
+
+#define MAX_PEER_ADDRS 5
+/* send at most this many copies; honour at most that many addresses */
+
+#define MAX_NAK_MSG 80
+#define MAX_SIG_KEYS 4
+
+struct hash_if;
+struct comm_if;
+struct comm_addr;
+struct priomsg;
+struct log_if;
+struct buffer_if;
+struct sigpubkey_if;
+struct sigprivkey_if;
 
 typedef char *string_t;
 typedef const char *cstring_t;
-typedef enum {False,True} bool_t;
+
+#define False (_Bool)0
+#define True  (_Bool)1
+typedef _Bool bool_t;
+
+union iaddr {
+    struct sockaddr sa;
+    struct sockaddr_in sin;
+#ifdef CONFIG_IPV6
+    struct sockaddr_in6 sin6;
+#endif
+};
+
+#define GRPIDSZ 4
+#define ALGIDSZ 1
+#define KEYIDSZ (GRPIDSZ+ALGIDSZ)
+  /* Changing these is complex: this is the group id plus algo id */
+  /* They are costructed by pubkeys.fl.pl.  Also hardcoded in _PR_ */
+struct sigkeyid { uint8_t b[KEYIDSZ]; };
+
+#define SIGKEYID_PR_FMT "%02x%02x%02x%02x%02x"
+#define SIGKEYID_PR_VAL(id) /* SIGKEYID_PR_VAL(const sigkeyid *id) */  \
+    ((id) == (const struct sigkeyid*)0, (id)->b[0]),                   \
+    (id)->b[1],(id)->b[2],(id)->b[3],(id)->b[4]
+static inline bool_t sigkeyid_equal(const struct sigkeyid *a,
+                                   const struct sigkeyid *b) {
+    return !memcmp(a->b, b->b, KEYIDSZ);
+}
+
+#define SERIALSZ 4
+typedef uint32_t serialt;
+static inline int serial_cmp(serialt a, serialt b) {
+    if (a==b) return 0;
+    if (!a) return -1;
+    if (!b) return +1;
+    return b-a <= (serialt)0x7fffffffUL ? +1 : -1;
+}
 
 #define ASSERT(x) do { if (!(x)) { fatal("assertion failed line %d file " \
                                         __FILE__,__LINE__); } } while(0)
@@ -33,6 +117,16 @@ extern struct log_if *system_log;
 /* from process.c */
 extern void start_signal_handling(void);
 
+void afterfork(void);
+/* Must be called before exec in every child made after
+   start_signal_handling.  Safe to call in earlier children too. */
+
+void childpersist_closefd_hook(void *fd_p, uint32_t newphase);
+/* Convenience hook function for use with add_hook PHASE_CHILDPERSIST.
+   With `int fd' in your state struct, pass fd_p=&fd.  The hook checks
+   whether fd>=0, so you can use it for an fd which is only sometimes
+   open.  This function will set fd to -1, so it is idempotent. */
+
 /***** CONFIGURATION support *****/
 
 extern bool_t just_check_config; /* If True then we're going to exit after
@@ -94,7 +188,7 @@ extern cstring_t *dict_keys(dict_t *dict);
 
 /* List-manipulation functions */
 extern list_t *list_new(void);
-extern int32_t list_length(list_t *a);
+extern int32_t list_length(const list_t *a);
 extern list_t *list_append(list_t *a, item_t *i);
 extern list_t *list_append_list(list_t *a, list_t *b);
 /* Returns an item from the list (index starts at 0), or NULL */
@@ -104,7 +198,7 @@ extern item_t *list_elem(list_t *l, int32_t index);
 extern list_t *new_closure(closure_t *cl);
 extern void add_closure(dict_t *dict, cstring_t name, apply_fn apply);
 extern void *find_cl_if(dict_t *dict, cstring_t name, uint32_t type,
-                       bool_t fail_if_invalid, cstring_t desc,
+                       bool_t required, cstring_t desc,
                        struct cloc loc);
 extern item_t *dict_find_item(dict_t *dict, cstring_t key, bool_t required,
                              cstring_t desc, struct cloc loc);
@@ -116,6 +210,16 @@ extern uint32_t dict_read_number(dict_t *dict, cstring_t key, bool_t required,
   /* return value can safely be assigned to int32_t */
 extern bool_t dict_read_bool(dict_t *dict, cstring_t key, bool_t required,
                             cstring_t desc, struct cloc loc, bool_t def);
+extern dict_t *dict_read_dict(dict_t *dict, cstring_t key, bool_t required,
+                       cstring_t desc, struct cloc loc);
+const char **dict_read_string_array(dict_t *dict, cstring_t key,
+                                   bool_t required, cstring_t desc,
+                                   struct cloc loc, const char *const *def);
+  /* Return value is a NULL-terminated array obtained from malloc;
+   * Individual string values are still owned by config file machinery
+   * and must not be modified or freed.  Returns NULL if key not
+   * found. */
+
 struct flagstr {
     cstring_t name;
     uint32_t value;
@@ -132,6 +236,22 @@ extern uint32_t string_list_to_word(list_t *l, struct flagstr *f,
 extern char *safe_strdup(const char *string, const char *message);
 extern void *safe_malloc(size_t size, const char *message);
 extern void *safe_malloc_ary(size_t size, size_t count, const char *message);
+extern void *safe_realloc_ary(void *p, size_t size, size_t count,
+                             const char *message);
+
+#define NEW(p)                                 \
+    ((p)=safe_malloc(sizeof(*(p)),             \
+                    __FILE__ ":" #p))
+#define NEW_ARY(p,count)                                       \
+    ((p)=safe_malloc_ary(sizeof(*(p)),(count),                 \
+                        __FILE__ ":" #p "[" #count "]"))
+#define REALLOC_ARY(p,count)                                   \
+    ((p)=safe_realloc_ary((p),sizeof(*(p)),(count),            \
+                         __FILE__ ":" #p "[" #count "]"))
+
+void setcloexec(int fd); /* cannot fail */
+void setnonblock(int fd); /* cannot fail */
+void pipe_cloexec(int fd[2]); /* pipe(), setcloexec() twice; cannot fail */
 
 extern int sys_cmd(const char *file, const char *argc, ...);
 
@@ -160,20 +280,37 @@ int32_t calculate_max_start_pad(void);
 
 /* If nfds_io is insufficient for your needs, set it to the required
    number and return ERANGE. timeout is in milliseconds; if it is too
-   high then lower it. It starts at -1 (==infinite) */
+   high then lower it. It starts at -1 (==infinite). */
+/* Note that beforepoll_fn may NOT do anything which might change the
+   fds or timeouts wanted by other registered poll loop loopers.
+   Callers should make sure of this by not making any calls into other
+   modules from the beforepoll_fn; the easiest way to ensure this is
+   for beforepoll_fn to only retreive information and not take any
+   action.
+ */
 typedef int beforepoll_fn(void *st, struct pollfd *fds, int *nfds_io,
                          int *timeout_io);
 typedef void afterpoll_fn(void *st, struct pollfd *fds, int nfds);
+  /* If beforepoll_fn returned ERANGE, afterpoll_fn gets nfds==0.
+     afterpoll_fn never gets !!(fds[].revents & POLLNVAL) - such
+     a report is detected as a fatal error by the event loop. */
+
+/* void BEFOREPOLL_WANT_FDS(int want);
+ *   Expects: int *nfds_io;
+ *   Can perform non-local exit.
+ * Checks whether there is space for want fds.  If so, sets *nfds_io.
+ * If not, sets *nfds_io and returns. */
+#define BEFOREPOLL_WANT_FDS(want) do{                          \
+    if (*nfds_io<(want)) { *nfds_io=(want); return ERANGE; }   \
+    *nfds_io=(want);                                           \
+  }while(0)
 
 /* Register interest in the main loop of the program. Before a call
    to poll() your supplied beforepoll function will be called. After
-   the call to poll() the supplied afterpoll function will be called.
-   max_nfds is a _hint_ about the maximum number of struct pollfd
-   structures you may require - you can always ask for more in
-   *nfds_io. */
-extern void register_for_poll(void *st, beforepoll_fn *before,
-                             afterpoll_fn *after, int32_t max_nfds,
-                             cstring_t desc);
+   the call to poll() the supplied afterpoll function will be called. */
+struct poll_interest *register_for_poll(void *st, beforepoll_fn *before,
+                             afterpoll_fn *after, cstring_t desc);
+void deregister_for_poll(struct poll_interest *i);
 
 /***** END of scheduling support */
 
@@ -196,10 +333,18 @@ enum phase {
     PHASE_DROPPRIV,            /* Last chance for privileged operations */
     PHASE_RUN,
     PHASE_SHUTDOWN,            /* About to die; delete key material, etc. */
+    PHASE_CHILDPERSIST,        /* Forked long-term child: close fds, etc. */
     /* Keep this last: */
     NR_PHASES,
 };
 
+/* Each module should, in its CHILDPERSIST hooks, close all fds which
+   constitute ownership of important operating system resources, or
+   which are used for IPC with other processes who want to get the
+   usual disconnection effects if the main secnet process dies.
+   CHILDPERSIST hooks are not run if the child is going to exec;
+   so fds such as described above should be CLOEXEC too. */
+
 typedef void hook_fn(void *self, uint32_t newphase);
 bool_t add_hook(uint32_t phase, hook_fn *f, void *state);
 bool_t remove_hook(uint32_t phase, hook_fn *f, void *state);
@@ -207,12 +352,20 @@ bool_t remove_hook(uint32_t phase, hook_fn *f, void *state);
 extern uint32_t current_phase;
 extern void enter_phase(uint32_t new_phase);
 
+void phase_hooks_init(void); /* for main() only */
+void clear_phase_hooks(uint32_t phase); /* for afterfork() */
+
 /* Some features (like netlink 'soft' routes) require that secnet
    retain root privileges.  They should indicate that here when
    appropriate. */
 extern bool_t require_root_privileges;
 extern cstring_t require_root_privileges_explanation;
 
+/* Some modules may want to know whether secnet is going to drop
+   privilege, so that they know whether to do privsep.  Call only
+   in phases SETUP and later. */
+bool_t will_droppriv(void);
+
 /***** END of program lifetime support *****/
 
 /***** MODULE support *****/
@@ -224,9 +377,11 @@ typedef void init_module(dict_t *dict);
 
 extern void init_builtin_modules(dict_t *dict);
 
+extern init_module pubkeys_init;
 extern init_module resolver_module;
 extern init_module random_module;
 extern init_module udp_module;
+extern init_module polypath_module;
 extern init_module util_module;
 extern init_module site_module;
 extern init_module transform_eax_module;
@@ -239,16 +394,64 @@ extern init_module slip_module;
 extern init_module tun_module;
 extern init_module sha1_module;
 extern init_module log_module;
+extern init_module privcache_module;
 
 /***** END of module support *****/
 
+/***** SIGNATURE SCHEMES *****/
+
+struct sigscheme_info;
+
+typedef bool_t sigscheme_loadpub(const struct sigscheme_info *algo,
+                                struct buffer_if *pubkeydata,
+                                struct sigpubkey_if **sigpub_r,
+                                closure_t **closure_r,
+                                struct log_if *log, struct cloc loc);
+  /* pubkeydata is (supposedly) for this algorithm.
+   * loadpub should log an error if it fails.
+   * pubkeydata may be modified (but not freed).
+   * both *sigpub_r and *closure_r must always be written and must
+   * refer to the same object, so on successful return
+   * (*closure_r)->type==CL_SIGPUBKEY
+   * and (*closure_r)->interface==*sigpub_r */
+
+typedef bool_t sigscheme_loadpriv(const struct sigscheme_info *algo,
+                                 struct buffer_if *privkeydata,
+                                 struct sigprivkey_if **sigpriv_r,
+                                 closure_t **closure_r,
+                                 struct log_if *log, struct cloc loc);
+  /* Ideally, check whether privkeydata contains data for any algorithm.
+   * That avoids security problems if a key file is misidentified (which
+   * might happen if the file is simply renamed).
+   * If there is an error (including that the key data is not for this
+   * algorithm, return False and log an error at M_ERROR.
+   * On entry privkeydata->base==start.  loadpriv may modify
+   * privkeydata, including the contents. */
+
+struct sigscheme_info {
+    const char *name;
+    const uint8_t algid;
+    sigscheme_loadpub *loadpub;
+    sigscheme_loadpriv *loadpriv;
+};
+
+extern const struct sigscheme_info rsa1_sigscheme;
+extern const struct sigscheme_info sigschemes[]; /* sentinel has name==0 */
+
+const struct sigscheme_info *sigscheme_lookup(const char *name);
+
+extern sigscheme_loadpriv rsa1_loadpriv;
+extern sigscheme_loadpub  rsa1_loadpub;
+
+/***** END of signature schemes *****/
+
 /***** CLOSURE TYPES and interface definitions *****/
 
 #define CL_PURE         0
 #define CL_RESOLVER     1
 #define CL_RANDOMSRC    2
-#define CL_RSAPUBKEY    3
-#define CL_RSAPRIVKEY   4
+#define CL_SIGPUBKEY    3
+#define CL_SIGPRIVKEY   4
 #define CL_COMM         5
 #define CL_IPIF         6
 #define CL_LOG          7
@@ -258,20 +461,32 @@ extern init_module log_module;
 #define CL_HASH        12
 #define CL_BUFFER      13
 #define CL_NETLINK     14
+#define CL_PRIVCACHE   15
 
 struct buffer_if;
 
+struct alg_msg_data {
+    uint8_t *start;
+    int32_t len;
+};
+
 /* PURE closure requires no interface */
 
 /* RESOLVER interface */
 
 /* Answers to queries are delivered to a function of this
    type. 'address' will be NULL if there was a problem with the query. It
-   will be freed once resolve_answer_fn returns. It is in network byte
-   order. */
-/* XXX extend to be able to provide multiple answers */
-typedef void resolve_answer_fn(void *st, struct in_addr *addr);
+   will be freed once resolve_answer_fn returns.  naddrs is the actual
+   size of the array at addrs; was_naddrs is the number of addresses
+   actually found in the DNS, which may be bigger if addrs is equal
+   to MAX_PEER_ADDRS (ie there were too many). */
+typedef void resolve_answer_fn(void *st, const struct comm_addr *addrs,
+                              int naddrs, int was_naddrs,
+                              const char *name, const char *failwhy);
+  /* name is the same ptr as passed to request, so its lifetime must
+   * be suitable*/
 typedef bool_t resolve_request_fn(void *st, cstring_t name,
+                                 int remoteport, struct comm_if *comm,
                                  resolve_answer_fn *cb, void *cst);
 struct resolver_if {
     void *st;
@@ -280,8 +495,8 @@ struct resolver_if {
 
 /* RANDOMSRC interface */
 
-/* Return some random data. Returns TRUE for success. */
-typedef bool_t random_fn(void *st, int32_t bytes, uint8_t *buff);
+/* Return some random data. Cannot fail. */
+typedef void random_fn(void *st, int32_t bytes, uint8_t *buff);
 
 struct random_if {
     void *st;
@@ -289,21 +504,45 @@ struct random_if {
     random_fn *generate;
 };
 
-/* RSAPUBKEY interface */
+/* SIGPUBKEY interface */
 
-typedef bool_t rsa_checksig_fn(void *st, uint8_t *data, int32_t datalen,
-                              cstring_t signature);
-struct rsapubkey_if {
+typedef void sig_dispose_fn(void *st);
+
+typedef bool_t sig_unpick_fn(void *sst, struct buffer_if *msg,
+                            struct alg_msg_data *sig);
+typedef bool_t sig_checksig_fn(void *st, uint8_t *data, int32_t datalen,
+                              const struct alg_msg_data *sig);
+struct sigpubkey_if {
     void *st;
-    rsa_checksig_fn *check;
+    sig_unpick_fn *unpick;
+    sig_checksig_fn *check;
+    const struct hash_if *hash;
+    sig_dispose_fn *dispose;
 };
 
-/* RSAPRIVKEY interface */
+/* SIGPRIVKEY interface */
 
-typedef string_t rsa_makesig_fn(void *st, uint8_t *data, int32_t datalen);
-struct rsaprivkey_if {
+/* Appends the signature to msg.
+ * Can fail and returnn False, eg if the buffer is too small. */
+typedef bool_t sig_makesig_fn(void *st, uint8_t *data, int32_t datalen,
+                             struct buffer_if *msg);
+struct sigprivkey_if {
     void *st;
-    rsa_makesig_fn *sign;
+    sig_makesig_fn *sign;
+    const struct hash_if *hash;
+    sig_dispose_fn *dispose;
+};
+
+/* PRIVCACHE interface */
+
+typedef struct sigprivkey_if *privcache_lookup_fn(void *st,
+                                          const struct sigkeyid *id,
+                                          struct log_if*);
+  /* Return is valid only until you return from the current event! */
+
+struct privcache_if {
+    void *st;
+    privcache_lookup_fn *lookup;
 };
 
 /* COMM interface */
@@ -311,45 +550,79 @@ struct rsaprivkey_if {
 struct comm_addr {
     /* This struct is pure data; in particular comm's clients may
        freely copy it. */
-    /* Everyone is also guaranteed that all padding is set to zero, ie
-       that comm_addrs referring to semantically identical peers will
-       compare equal with memcmp.  Anyone who constructs a comm_addr
-       must start by memsetting it with FILLZERO, or some
-       equivalent. */
     struct comm_if *comm;
-    struct sockaddr_in sin;
+    union iaddr ia;
+    int ix; /* see comment `Re comm_addr.ix' in udp.c */
+};
+
+struct comm_clientinfo; /* private for comm */
+
+typedef struct comm_clientinfo *comm_clientinfo_fn(void *state, dict_t*,
+                                                  struct cloc cloc);
+/* A comm client may call this during configuration, and then pass
+ * the resulting comm_clientinfo* to some or all sendmsg calls.
+ * The semantics depend on the dict and defined by the comm, and
+ * should be documented in README. */
+
+enum {
+    comm_notify_whynot_general,
+    comm_notify_whynot_unpick,
+    comm_notify_whynot_name_local,
+    comm_notify_whynot_name_remote,
 };
 
 /* Return True if the packet was processed, and shouldn't be passed to
-   any other potential receivers. */
+   any other potential receivers. (buf is freed iff True returned.) */
 typedef bool_t comm_notify_fn(void *state, struct buffer_if *buf,
-                             const struct comm_addr *source);
+                             const struct comm_addr *source,
+                             struct priomsg *whynot);
 typedef void comm_request_notify_fn(void *commst, void *nst,
                                    comm_notify_fn *fn);
 typedef void comm_release_notify_fn(void *commst, void *nst,
                                    comm_notify_fn *fn);
 typedef bool_t comm_sendmsg_fn(void *commst, struct buffer_if *buf,
-                              const struct comm_addr *dest);
+                              const struct comm_addr *dest,
+                              struct comm_clientinfo* /* 0 OK */);
+  /* Only returns false if (we know that) the local network
+   * environment is such that this address cannot work; transient
+   * or unknown/unexpected failures return true. */
 typedef const char *comm_addr_to_string_fn(void *commst,
                                           const struct comm_addr *ca);
         /* Returned string is in a static buffer. */
 struct comm_if {
     void *st;
+    comm_clientinfo_fn *clientinfo;
     comm_request_notify_fn *request_notify;
     comm_release_notify_fn *release_notify;
     comm_sendmsg_fn *sendmsg;
     comm_addr_to_string_fn *addr_to_string;
 };
 
+bool_t iaddr_equal(const union iaddr *ia, const union iaddr *ib,
+                  bool_t ignoreport);
+
+static inline const char *comm_addr_to_string(const struct comm_addr *ca)
+{
+    return ca->comm->addr_to_string(ca->comm->st, ca);
+}
+
+static inline bool_t comm_addr_equal(const struct comm_addr *a,
+                                    const struct comm_addr *b)
+{
+    return a->comm==b->comm && iaddr_equal(&a->ia,&b->ia,False);
+}
+
 /* LOG interface */
 
+#define LOG_MESSAGE_BUFLEN 1023
+
 typedef void log_msg_fn(void *st, int class, const char *message, ...);
 typedef void log_vmsg_fn(void *st, int class, const char *message,
                         va_list args);
 struct log_if {
     void *st;
-    log_msg_fn *logfn;   /* Do not call these directly - you don't get */
     log_vmsg_fn *vlogfn; /* printf format checking.  Use [v]slilog instead */
+    char buff[LOG_MESSAGE_BUFLEN+1];
 };
 /* (convenience functions, defined in util.c) */
 extern void slilog(struct log_if *lf, int class, const char *message, ...)
@@ -357,15 +630,40 @@ FORMAT(printf,3,4);
 extern void vslilog(struct log_if *lf, int class, const char *message, va_list)
 FORMAT(printf,3,0);
 
+/* Versions which take (parts of) (multiple) messages, using \n to
+ * distinguish one message from another. */
+extern void slilog_part(struct log_if *lf, int class, const char *message, ...)
+FORMAT(printf,3,4);
+extern void vslilog_part(struct log_if *lf, int class, const char *message,
+                        va_list) FORMAT(printf,3,0);
+
+void cfgfile_log__vmsg(void *sst, int class, const char *message, va_list);
+struct cfgfile_log {
+    struct log_if log;
+    /* private fields */
+    struct cloc loc;
+    const char *facility;
+};
+static inline void cfgfile_log_init(struct cfgfile_log *cfl,
+                                   struct cloc loc, const char *facility)
+{
+    cfl->log.st=cfl;
+    cfl->log.vlogfn=cfgfile_log__vmsg;
+    cfl->loc=loc;
+    cfl->facility=facility;
+}
+
+void log_early_init(void);
+
 /* SITE interface */
 
 /* Pretty much a placeholder; allows starting and stopping of processing,
    key expiry, etc. */
-typedef void site_control_fn(void *st, bool_t run);
+typedef void site_startup_fn(void *st);
 typedef uint32_t site_status_fn(void *st);
 struct site_if {
     void *st;
-    site_control_fn *control;
+    site_startup_fn *startup;
     site_status_fn *status;
 };
 
@@ -388,13 +686,24 @@ typedef bool_t transform_setkey_fn(void *st, uint8_t *key, int32_t keylen,
 typedef bool_t transform_valid_fn(void *st); /* 0: no key; 1: ok */
 typedef void transform_delkey_fn(void *st);
 typedef void transform_destroyinstance_fn(void *st);
-/* Returns:
- *   0: all is well
- *   1: for any other problem
- *   2: message decrypted but sequence number was out of range
- */
-typedef uint32_t transform_apply_fn(void *st, struct buffer_if *buf,
-                                   const char **errmsg);
+
+typedef enum {
+    transform_apply_ok       = 0, /* all is well (everyone may assume==0) */
+    transform_apply_err      = 1, /* any other problem */
+    transform_apply_seqrange = 2,
+        /* message decrypted but sequence number was out of recent range */
+    transform_apply_seqdupe  = 3,
+        /* message decrypted but was dupe of recent packet */
+} transform_apply_return;
+
+static inline bool_t
+transform_apply_return_badseq(transform_apply_return problem) {
+    return problem == transform_apply_seqrange ||
+          problem == transform_apply_seqdupe;
+}
+
+typedef transform_apply_return transform_apply_fn(void *st,
+        struct buffer_if *buf, const char **errmsg);
 
 struct transform_inst_if {
     void *st;
@@ -408,7 +717,7 @@ struct transform_inst_if {
 
 struct transform_if {
     void *st;
-    int capab_transformnum;
+    int capab_bit;
     int32_t keylen; /* <<< INT_MAX */
     transform_createinstance_fn *create;
 };
@@ -431,7 +740,7 @@ typedef void netlink_deliver_fn(void *st, struct buffer_if *buf);
 #define MAXIMUM_LINK_QUALITY 3
 typedef void netlink_link_quality_fn(void *st, uint32_t quality);
 typedef void netlink_register_fn(void *st, netlink_deliver_fn *deliver,
-                                void *dst);
+                                void *dst, uint32_t *localmtu_r /* NULL ok */);
 typedef void netlink_output_config_fn(void *st, struct buffer_if *buf);
 typedef bool_t netlink_check_config_fn(void *st, struct buffer_if *buf);
 typedef void netlink_set_mtu_fn(void *st, int32_t new_mtu);
@@ -462,27 +771,29 @@ struct dh_if {
 
 /* HASH interface */
 
-typedef void *hash_init_fn(void);
+typedef void hash_init_fn(void *st /* slen bytes alloc'd by caller */);
 typedef void hash_update_fn(void *st, const void *buf, int32_t len);
-typedef void hash_final_fn(void *st, uint8_t *digest);
+typedef void hash_final_fn(void *st, uint8_t *digest /* hlen bytes */);
 struct hash_if {
-    int32_t len; /* Hash output length in bytes */
+    int32_t slen; /* State length in bytes */
+    int32_t hlen; /* Hash output length in bytes */
     hash_init_fn *init;
     hash_update_fn *update;
     hash_final_fn *final;
 };
 
+extern struct hash_if *const sha1_hash_if; /* for where this is hardcoded */
+
 /* BUFFER interface */
 
 struct buffer_if {
     bool_t free;
     cstring_t owner; /* Set to constant string */
-    uint32_t flags; /* How paranoid should we be? */
     struct cloc loc; /* Where we were defined */
     uint8_t *base;
     uint8_t *start;
     int32_t size; /* Size of buffer contents */
-    int32_t len; /* Total length allocated at base */
+    int32_t alloclen; /* Total length allocated at base */
 };
 
 /***** LOG functions *****/
@@ -505,13 +816,26 @@ extern NORETURN(fatal_status(int status, const char *message, ...))
 extern NORETURN(fatal_perror_status(int status, const char *message, ...))
        FORMAT(printf,2,3);
 
+/* Convenient nonfatal logging.  Requires message that does not end in '\n'.
+ * If class contains M_FATAL, exits (after entering PHASE_SHUTDOWN).
+ * lg, errnoval and loc may sensibly be 0.  desc must NOT be 0.
+ * lg_[v]perror save and restore errno. */
+void lg_vperror(struct log_if *lg, const char *desc, struct cloc *loc,
+               int class, int errnoval, const char *fmt, va_list al)
+    FORMAT(printf,6,0);
+void lg_perror(struct log_if *lg, const char *desc, struct cloc *loc,
+              int class, int errnoval, const char *fmt, ...)
+    FORMAT(printf,6,7);
+void lg_exitstatus(struct log_if *lg, const char *desc, struct cloc *loc,
+                  int class, int status, const char *progname);
+
 /* The cfgfatal() family of functions require messages that end in '\n' */
 extern NORETURN(cfgfatal(struct cloc loc, cstring_t facility,
                         const char *message, ...)) FORMAT(printf,3,4);
 extern void cfgfile_postreadcheck(struct cloc loc, FILE *f);
 extern NORETURN(vcfgfatal_maybefile(FILE *maybe_f, struct cloc loc,
                                    cstring_t facility, const char *message,
-                                   va_list))
+                                   va_list, const char *suffix))
     FORMAT(printf,4,0);
 extern NORETURN(cfgfatal_maybefile(FILE *maybe_f, struct cloc loc,
                                   cstring_t facility,
@@ -528,5 +852,19 @@ extern void log_from_fd(int fd, cstring_t prefix, struct log_if *log);
 #define STRING(x) STRING2(x)
 
 #define FILLZERO(obj) (memset(&(obj),0,sizeof((obj))))
+#define ARRAY_SIZE(ary) (sizeof((ary))/sizeof((ary)[0]))
+
+/*
+ * void COPY_OBJ(  OBJECT& dst, const OBJECT& src);
+ * void COPY_ARRAY(OBJECT *dst, const OBJECT *src, INTEGER count);
+ *   // Typesafe: we check that the type OBJECT is the same in both cases.
+ *   // It is OK to use COPY_OBJ on an array object, provided dst is
+ *   // _actually_ the whole array object and not decayed into a
+ *   // pointer (e.g. a formal parameter).
+ */
+#define COPY_OBJ(dst,src) \
+    (&(dst)==&(src), memcpy(&(dst),&(src),sizeof((dst))))
+#define COPY_ARRAY(dst,src,count) \
+    (&(dst)[0]==&(src)[0], memcpy((dst),(src),sizeof((dst)[0])*(count)))
 
 #endif /* secnet_h */
index 7c5850535f24f87655f2508b0c4045f89e3dcd9a..a8bfe2aa341fb6f2aaeaa3c268ded14319e34b24 100644 (file)
--- a/serpent.c
+++ b/serpent.c
@@ -1,23 +1,35 @@
 /*
- * This file is
- *   Copyright (C) 1998 Ross Anderson, Eli Biham, Lars Knudsen
+ * serpent.c: Implementation of the Serpent block cipher
+ */
+/*
+ * This file is Free Software.  It has been modified to as part of its
+ * incorporation into secnet.
+ *
+ * Copyright 1998      Ross Anderson, Eli Biham, Lars Knudsen
+ * Copyright 1995-2001 Stephen Early <steve@greenend.org.uk>
+ * Copyright 2011-2013 Ian Jackson
+ *
+ * For more information about Serpent see
+ * http://www.cl.cam.ac.uk/users/rja14/serpent.html
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
  *
- * For more information see http://www.cl.cam.ac.uk/users/rja14/serpent.html
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later
+ * version.
  *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2, or (at your option)
- *  any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software Foundation,
- *  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
  *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
  */
 
 #include <stdint.h>
index 57450f3d275604bb8a0e294e79d1c547c87dc36d..c264d633da9199743155d0b49f553cb47a21c4eb 100644 (file)
@@ -1,23 +1,35 @@
 /*
- * This file is
- *   Copyright (C) 1998 Ross Anderson, Eli Biham, Lars Knudsen
+ * serpentsboxes.h: S-boxes; internal to Serpent implementation.
+ */
+/*
+ * This file is Free Software.  It is now being distributed with
+ * secnet.
+ *
+ * Copyright 1998      Ross Anderson, Eli Biham, Lars Knudsen
+ * Copyright 1995-2001 Stephen Early <steve@greenend.org.uk>
+ * Copyright 2011-2013 Ian Jackson
+ *
+ * For more information about Serpent see
+ * http://www.cl.cam.ac.uk/users/rja14/serpent.html
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
  *
- * For more information see http://www.cl.cam.ac.uk/users/rja14/serpent.html
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later
+ * version.
  *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2, or (at your option)
- *  any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software Foundation,
- *  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
  *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
  */
 
 
index fd8c832805821269d78ff4b357d9f33db3c9b788..a7a7359f64ecdd65186fa2d2f8bb49b59cd54051 100755 (executable)
--- a/setup.mac
+++ b/setup.mac
@@ -2,6 +2,23 @@
 #
 # Richard Kettlewell 2011-06-18
 #
+# This file is part of secnet.
+# See README for full list of copyright holders.
+#
+# secnet is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# 
+# secnet is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# version 3 along with secnet; if not, see
+# https://www.gnu.org/licenses/gpl.html.
+#
 set -e
 
 group=${group:-secnet}
diff --git a/sha1.c b/sha1.c
index a0f26e8be6facdadfed882d1cf4dd65fd6da9523..a78345a55f6378dc51d1bb67b3e2dab4eb8d404f 100644 (file)
--- a/sha1.c
+++ b/sha1.c
@@ -2,6 +2,7 @@
 SHA-1 in C
 By Steve Reid <sreid@sea-to-sky.net>
 100% Public Domain
+[I interpet this as a blanket permision -iwj.]
 
 Note: parts of this file have been removed or modified to work in secnet.
 Instead of using this file in new projects, I suggest you use the
@@ -59,7 +60,9 @@ By Saul Kravitz <Saul.Kravitz@celera.com>
 Still 100% PD
 Modified to run on Compaq Alpha hardware.  
 
-
+-----------------
+(Further modifications as part of secnet.  See git history for full details.
+ - Ian Jackson et al)
 */
 
 /*
@@ -75,6 +78,7 @@ A million repetitions of "a"
 /* #define SHA1HANDSOFF  */
 
 #include "secnet.h"
+#include "util.h"
 #include <stdio.h>
 #include <string.h>
 
@@ -284,14 +288,11 @@ unsigned char finalcount[8];
 /*************************************************************/
 
 /* Everything below here is the interface to secnet */
-static void *sha1_init(void)
+static void sha1_init(void *sst)
 {
-    SHA1_CTX *ctx;
+    SHA1_CTX *ctx=sst;
 
-    ctx=safe_malloc(sizeof(*ctx),"sha1_init");
     SHA1Init(ctx);
-
-    return ctx;
 }
 
 static void sha1_update(void *sst, const void *buf, int32_t len)
@@ -306,7 +307,6 @@ static void sha1_final(void *sst, uint8_t *digest)
     SHA1_CTX *ctx=sst;
 
     SHA1Final(digest,ctx);
-    free(ctx);
 }
 
 struct sha1 {
@@ -314,10 +314,11 @@ struct sha1 {
     struct hash_if ops;
 };
 
+static struct sha1 st[1];
+struct hash_if *const sha1_hash_if = &st->ops;
+
 void sha1_module(dict_t *dict)
 {
-    struct sha1 *st;
-    void *ctx;
     cstring_t testinput="abcdbcdecdefdefgefghfghigh"
        "ijhijkijkljklmklmnlmnomnopnopq";
     uint8_t expected[20]=
@@ -329,21 +330,19 @@ void sha1_module(dict_t *dict)
     uint8_t digest[20];
     int i;
 
-    st=safe_malloc(sizeof(*st),"sha1_module");
     st->cl.description="sha1";
     st->cl.type=CL_HASH;
     st->cl.apply=NULL;
     st->cl.interface=&st->ops;
-    st->ops.len=20;
+    st->ops.hlen=20;
+    st->ops.slen=sizeof(SHA1_CTX);
     st->ops.init=sha1_init;
     st->ops.update=sha1_update;
     st->ops.final=sha1_final;
 
     dict_add(dict,"sha1",new_closure(&st->cl));
 
-    ctx=sha1_init();
-    sha1_update(ctx,testinput,strlen(testinput));
-    sha1_final(ctx,digest);
+    hash_hash(&st->ops,testinput,strlen(testinput),digest);
     for (i=0; i<20; i++) {
        if (digest[i]!=expected[i]) {
            fatal("sha1 module failed self-test");
diff --git a/site.c b/site.c
index 0b3923252382af007ba1bafa730151bad04774f0..191c36463da7cf7bd1d6186a1a1f51ebb0484877 100644 (file)
--- a/site.c
+++ b/site.c
@@ -1,5 +1,24 @@
 /* site.c - manage communication with a remote network site */
 
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 /* The 'site' code doesn't know anything about the structure of the
    packets it's transmitting.  In fact, under the new netlink
    configuration scheme it doesn't need to know anything at all about
@@ -24,6 +43,7 @@
 #include "util.h"
 #include "unaligned.h"
 #include "magic.h"
+#include "pubkeys.h"
 
 #define SETUP_BUFFER_LEN 2048
 
@@ -40,7 +60,8 @@
 #define DEFAULT_MOBILE_WAIT_TIME                (10*1000) /* [ms] */
 
 #define DEFAULT_MOBILE_PEER_EXPIRY            (2*60)      /* [s] */
-#define DEFAULT_MOBILE_PEERS_MAX 3 /* send at most this many copies (default) */
+
+#define PEERKEYS_SUFFIX_MAXLEN (sizeof("~incoming")-1)
 
 /* Each site can be in one of several possible states. */
 
 #define SITE_SENTMSG5 7
 #define SITE_WAIT     8
 
+#define CASES_MSG3_KNOWN LABEL_MSG3: case LABEL_MSG3BIS
+
+struct msg;
+
 int32_t site_max_start_pad = 4*4;
 
 static cstring_t state_name(uint32_t state)
@@ -120,6 +145,7 @@ static cstring_t state_name(uint32_t state)
 #define LOG_DUMP          0x00000100
 #define LOG_ERROR         0x00000400
 #define LOG_PEER_ADDRS    0x00000800
+#define LOG_SIGKEYS       0x00001000
 
 static struct flagstr log_event_table[]={
     { "unexpected", LOG_UNEXPECTED },
@@ -133,8 +159,9 @@ static struct flagstr log_event_table[]={
     { "dump-packets", LOG_DUMP },
     { "errors", LOG_ERROR },
     { "peer-addrs", LOG_PEER_ADDRS },
+    { "sigkeys", LOG_SIGKEYS },
     { "default", LOG_SETUP_INIT|LOG_SETUP_TIMEOUT|
-      LOG_ACTIVATE_KEY|LOG_TIMEOUT_KEY|LOG_SEC|LOG_ERROR },
+      LOG_ACTIVATE_KEY|LOG_TIMEOUT_KEY|LOG_SEC|LOG_ERROR|LOG_SIGKEYS },
     { "all", 0xffffffff },
     { NULL, 0 }
 };
@@ -143,48 +170,79 @@ static struct flagstr log_event_table[]={
 /***** TRANSPORT PEERS declarations *****/
 
 /* Details of "mobile peer" semantics:
+
+   - We use the same data structure for the different configurations,
+     but manage it with different algorithms.
    
-   - We record mobile_peers_max peer address/port numbers ("peers")
-     for key setup, and separately mobile_peers_max for data
-     transfer.  If these lists fill up, we retain the newest peers.
-     (For non-mobile peers we only record one of each.)
+   - We record up to mobile_peers_max peer address/port numbers
+     ("peers") for key setup, and separately up to mobile_peers_max
+     for data transfer.
+
+   - In general, we make a new set of addrs (see below) when we start
+     a new key exchange; the key setup addrs become the data transport
+     addrs when key setup complets.
+
+   If our peer is mobile:
+
+   - We send to all recent addresses of incoming packets, plus
+     initially all configured addresses (which we also expire).
+
+   - So, we record addrs of good incoming packets, as follows:
+      1. expire any peers last seen >120s ("mobile-peer-expiry") ago
+      2. add the peer of the just received packet to the applicable list
+         (possibly evicting the oldest entries to make room)
+     NB that we do not expire peers until an incoming packet arrives.
+
+   - If the peer has a configured address or name, we record them the
+     same way, but only as a result of our own initiation of key
+     setup.  (We might evict some incoming packet addrs to make room.)
 
-   - Outgoing packets are sent to every recorded peer in the
-     applicable list.
+   - The default number of addrs to keep is 3, or 4 if we have a
+     configured name or address.  That's space for two configured
+     addresses (one IPv6 and one IPv4), plus two received addresses.
 
-   - Data transfer peers are straightforward: whenever we successfully
-     process a data packet, we record the peer.  Also, whenever we
-     successfully complete a key setup, we merge the key setup
+   - Outgoing packets are sent to every recorded address in the
+     applicable list.  Any unsupported[1] addresses are deleted from
+     the list right away.  (This should only happen to configured
+     addresses, of course, but there is no need to check that.)
+
+   - When we successfully complete a key setup, we merge the key setup
      peers into the data transfer peers.
 
-     (For "non-mobile" peers we simply copy the peer used for
-     successful key setup, and don't change the peer otherwise.)
+   [1] An unsupported address is one for whose AF we don't have a
+     socket (perhaps because we got EAFNOSUPPORT or some such) or for
+     which sendto gives ENETUNREACH.
 
-   - Key setup peers are slightly more complicated.
+   If neither end is mobile:
 
-     Whenever we receive and successfully process a key exchange
-     packet, we record the peer.
+   - When peer initiated the key exchange, we use the incoming packet
+     address.
 
-     Whenever we try to initiate a key setup, we copy the list of data
-     transfer peers and use it for key setup.  But we also look to see
-     if the config supplies an address and port number and if so we
-     add that as a key setup peer (possibly evicting one of the data
-     transfer peers we just copied).
+   - When we initiate the key exchange, we try configured addresses
+     until we get one which isn't unsupported then fixate on that.
 
-     (For "non-mobile" peers, if we if we have a configured peer
-     address and port, we always use that; otherwise if we have a
-     current data peer address we use that; otherwise we do not
-     attempt to initiate a key setup for lack of a peer address.)
+   - When we complete a key setup, we replace the data transport peers
+     with those from the key setup.
 
-   "Record the peer" means
-    1. expire any peers last seen >120s ("mobile-peer-expiry") ago
-    2. add the peer of the just received packet to the applicable list
-       (possibly evicting older entries)
-   NB that we do not expire peers until an incoming packet arrives.
+   If we are mobile:
 
-   */
+   - We can't tell when local network setup changes so we can't cache
+     the unsupported addrs and completely remove the spurious calls to
+     sendto, but we can optimise things a bit by deprioritising addrs
+     which seem to be unsupported.
+
+   - Use only configured addresses.  (Except, that if our peer
+     initiated a key exchange we use the incoming packet address until
+     our name resolution completes.)
+
+   - When we send a packet, try each address in turn; if addr
+     supported, put that address to the end of the list for future
+     packets, and go onto the next address.
+
+   - When we complete a key setup, we replace the data transport peers
+     with those from the key setup.
 
-#define MAX_MOBILE_PEERS_MAX 5 /* send at most this many copies, compiled max */
+   */
 
 typedef struct {
     struct timeval last;
@@ -195,21 +253,40 @@ typedef struct {
 /* configuration information */
 /* runtime information */
     int npeers;
-    transport_peer peers[MAX_MOBILE_PEERS_MAX];
+    transport_peer peers[MAX_PEER_ADDRS];
 } transport_peers;
 
+/* Basic operations on transport peer address sets */
+static void transport_peers_init(struct site *st, transport_peers *peers);
 static void transport_peers_clear(struct site *st, transport_peers *peers);
 static int transport_peers_valid(transport_peers *peers);
 static void transport_peers_copy(struct site *st, transport_peers *dst,
                                 const transport_peers *src);
 
+/* Record address of incoming setup packet; resp. data packet. */
 static void transport_setup_msgok(struct site *st, const struct comm_addr *a);
 static void transport_data_msgok(struct site *st, const struct comm_addr *a);
+
+/* Initialise the setup addresses.  Called before we send the first
+ * packet in a key exchange.  If we are the initiator, as a result of
+ * resolve completing (or being determined not to be relevant) or an
+ * incoming PROD; if we are the responder, as a result of the MSG1. */
 static bool_t transport_compute_setupinit_peers(struct site *st,
-        const struct comm_addr *configured_addr /* 0 if none or not found */,
-        const struct comm_addr *prod_hint_addr /* 0 if none */);
-static void transport_record_peer(struct site *st, transport_peers *peers,
-                                 const struct comm_addr *addr, const char *m);
+        const struct comm_addr *configured_addrs /* 0 if none or not found */,
+        int n_configured_addrs /* 0 if none or not found */,
+        const struct comm_addr *incoming_packet_addr /* 0 if none */);
+
+/* Called if we are the responder in a key setup, when the resolve
+ * completes.  transport_compute_setupinit_peers will hvae been called
+ * earlier.  If _complete is called, we are still doing the key setup
+ * (and we should use the new values for both the rest of the key
+ * setup and the ongoing data exchange); if _tardy is called, the key
+ * setup is done (either completed or not) and only the data peers are
+ * relevant */
+static void transport_resolve_complete(struct site *st,
+        const struct comm_addr *addrs, int naddrs);
+static void transport_resolve_complete_tardy(struct site *st,
+        const struct comm_addr *addrs, int naddrs);
 
 static void transport_xmit(struct site *st, transport_peers *peers,
                           struct buffer_if *buf, bool_t candebug);
@@ -229,43 +306,53 @@ struct site {
 /* configuration information */
     string_t localname;
     string_t remotename;
-    bool_t peer_mobile; /* Mobile client support */
+    bool_t keepalive;
+    bool_t local_mobile, peer_mobile; /* Mobile client support */
     int32_t transport_peers_max;
     string_t tunname; /* localname<->remotename by default, used in logs */
-    string_t address; /* DNS name for bootstrapping, optional */
+    cstring_t *addresses; /* DNS name or address(es) for bootstrapping, optional */
     int remoteport; /* Port for bootstrapping, optional */
+    uint32_t mtu_target;
     struct netlink_if *netlink;
     struct comm_if **comms;
+    struct comm_clientinfo **commclientinfos;
     int ncomms;
     struct resolver_if *resolver;
     struct log_if *log;
     struct random_if *random;
-    struct rsaprivkey_if *privkey;
-    struct rsapubkey_if *pubkey;
+    struct privcache_if *privkeys;
+    struct sigprivkey_if *privkey_fixed;
     struct transform_if **transforms;
     int ntransforms;
     struct dh_if *dh;
-    struct hash_if *hash;
 
     uint32_t index; /* Index of this site */
+    uint32_t early_capabilities;
     uint32_t local_capabilities;
     int32_t setup_retries; /* How many times to send setup packets */
     int32_t setup_retry_interval; /* Initial timeout for setup packets */
-    int32_t wait_timeout; /* How long to wait if setup unsuccessful */
+    int32_t wait_timeout_mean; /* How long to wait if setup unsuccessful */
     int32_t mobile_peer_expiry; /* How long to remember 2ary addresses */
     int32_t key_lifetime; /* How long a key lasts once set up */
     int32_t key_renegotiate_time; /* If we see traffic (or a keepalive)
                                      after this time, initiate a new
                                      key exchange */
 
-    bool_t setup_priority; /* Do we have precedence if both sites emit
-                             message 1 simultaneously? */
+    bool_t our_name_later; /* our name > peer name */
     uint32_t log_events;
 
 /* runtime information */
     uint32_t state;
     uint64_t now; /* Most recently seen time */
     bool_t allow_send_prod;
+    bool_t msg1_crossed_logged;
+    int resolving_count;
+    int resolving_n_results_all;
+    int resolving_n_results_stored;
+    struct comm_addr resolving_results[MAX_PEER_ADDRS];
+    const char *peerkeys_path;
+    struct pathprefix_template peerkeys_tmpl;
+    struct peer_keyset *peerkeys_current, *peerkeys_kex;
 
     /* The currently established session */
     struct data_key current;
@@ -282,6 +369,7 @@ struct site {
        timeout before we can listen for another setup packet); perhaps
        we should keep a list of 'bad' sources for setup packets. */
     uint32_t remote_capabilities;
+    uint16_t remote_adv_mtu;
     struct transform_if *chosen_transform;
     uint32_t setup_session_id;
     transport_peers setup_peers;
@@ -297,35 +385,84 @@ struct site {
     struct transform_inst_if *new_transform; /* For key setup/verify */
 };
 
+static uint32_t event_log_priority(struct site *st, uint32_t event)
+{
+    if (!(event&st->log_events))
+       return 0;
+    switch(event) {
+    case LOG_UNEXPECTED:    return M_INFO;
+    case LOG_SETUP_INIT:    return M_INFO;
+    case LOG_SETUP_TIMEOUT: return M_NOTICE;
+    case LOG_ACTIVATE_KEY:  return M_INFO;
+    case LOG_TIMEOUT_KEY:   return M_INFO;
+    case LOG_SEC:           return M_SECURITY;
+    case LOG_STATE:         return M_DEBUG;
+    case LOG_DROP:          return M_DEBUG;
+    case LOG_DUMP:          return M_DEBUG;
+    case LOG_ERROR:         return M_ERR;
+    case LOG_PEER_ADDRS:    return M_DEBUG;
+    case LOG_SIGKEYS:       return M_INFO;
+    default:                return M_ERR;
+    }
+}
+
+static uint32_t slog_start(struct site *st, uint32_t event)
+{
+    uint32_t class=event_log_priority(st, event);
+    if (class) {
+       slilog_part(st->log,class,"%s: ",st->tunname);
+    }
+    return class;
+}
+
+static void vslog(struct site *st, uint32_t event, cstring_t msg, va_list ap)
+FORMAT(printf,3,0);
+static void vslog(struct site *st, uint32_t event, cstring_t msg, va_list ap)
+{
+    uint32_t class;
+
+    class=slog_start(st,event);
+    if (class) {
+       vslilog_part(st->log,class,msg,ap);
+       slilog_part(st->log,class,"\n");
+    }
+}
+
 static void slog(struct site *st, uint32_t event, cstring_t msg, ...)
 FORMAT(printf,3,4);
 static void slog(struct site *st, uint32_t event, cstring_t msg, ...)
 {
     va_list ap;
-    char buf[240];
-    uint32_t class;
-
     va_start(ap,msg);
+    vslog(st,event,msg,ap);
+    va_end(ap);
+}
 
-    if (event&st->log_events) {
-       switch(event) {
-       case LOG_UNEXPECTED: class=M_INFO; break;
-       case LOG_SETUP_INIT: class=M_INFO; break;
-       case LOG_SETUP_TIMEOUT: class=M_NOTICE; break;
-       case LOG_ACTIVATE_KEY: class=M_INFO; break;
-       case LOG_TIMEOUT_KEY: class=M_INFO; break;
-       case LOG_SEC: class=M_SECURITY; break;
-       case LOG_STATE: class=M_DEBUG; break;
-       case LOG_DROP: class=M_DEBUG; break;
-       case LOG_DUMP: class=M_DEBUG; break;
-       case LOG_ERROR: class=M_ERR; break;
-       case LOG_PEER_ADDRS: class=M_DEBUG; break;
-       default: class=M_ERR; break;
-       }
+static void logtimeout(struct site *st, const char *fmt, ...)
+FORMAT(printf,2,3);
+static void logtimeout(struct site *st, const char *fmt, ...)
+{
+    uint32_t class=event_log_priority(st,LOG_SETUP_TIMEOUT);
+    if (!class)
+       return;
 
-       vsnprintf(buf,sizeof(buf),msg,ap);
-       slilog(st->log,class,"%s: %s",st->tunname,buf);
+    va_list ap;
+    va_start(ap,fmt);
+
+    slilog_part(st->log,class,"%s: ",st->tunname);
+    vslilog_part(st->log,class,fmt,ap);
+
+    const char *delim;
+    int i;
+    for (i=0, delim=" (tried ";
+        i<st->setup_peers.npeers;
+        i++, delim=", ") {
+       transport_peer *peer=&st->setup_peers.peers[i];
+       const char *s=comm_addr_to_string(&peer->addr);
+       slilog_part(st->log,class,"%s%s",delim,s);
     }
+
+    slilog_part(st->log,class,")\n");
     va_end(ap);
 }
 
@@ -339,7 +476,10 @@ static bool_t initiate_key_setup(struct site *st, cstring_t reason,
                                 const struct comm_addr *prod_hint);
 static void enter_state_run(struct site *st);
 static bool_t enter_state_resolve(struct site *st);
-static bool_t enter_new_state(struct site *st,uint32_t next);
+static void decrement_resolving_count(struct site *st, int by);
+static bool_t enter_new_state(struct site *st,uint32_t next,
+                             const struct msg *prompt
+                             /* may be 0 for SENTMSG1 */);
 static void enter_state_wait(struct site *st);
 static void activate_new_key(struct site *st);
 
@@ -354,14 +494,15 @@ static bool_t current_valid(struct site *st)
 }
 
 #define DEFINE_CALL_TRANSFORM(fwdrev)                                  \
-static int call_transform_##fwdrev(struct site *st,                    \
+static transform_apply_return                                           \
+call_transform_##fwdrev(struct site *st,                               \
                                   struct transform_inst_if *transform, \
                                   struct buffer_if *buf,               \
                                   const char **errmsg)                 \
 {                                                                      \
     if (!is_transform_valid(transform)) {                              \
        *errmsg="transform not set up";                                 \
-       return 1;                                                       \
+       return transform_apply_err;                                     \
     }                                                                  \
     return transform->fwdrev(transform->st,buf,errmsg);                        \
 }
@@ -386,6 +527,21 @@ static void dispose_transform(struct transform_inst_if **transform_var)
     type=buf_unprepend_uint32((b)); \
     if (type!=(t)) return False; } while(0)
 
+static _Bool type_is_msg23(uint32_t type)
+{
+    switch (type) {
+       case LABEL_MSG2: case CASES_MSG3_KNOWN: return True;
+       default: return False;
+    }
+}
+static _Bool type_is_msg34(uint32_t type)
+{
+    switch (type) {
+       case CASES_MSG3_KNOWN: case LABEL_MSG4: return True;
+       default: return False;
+    }
+}
+
 struct parsedname {
     int32_t len;
     uint8_t *name;
@@ -399,26 +555,44 @@ struct msg {
     struct parsedname remote;
     struct parsedname local;
     uint32_t remote_capabilities;
+    uint16_t remote_mtu;
     int capab_transformnum;
     uint8_t *nR;
     uint8_t *nL;
     int32_t pklen;
     char *pk;
     int32_t hashlen;
-    int32_t siglen;
-    char *sig;
+    struct alg_msg_data sig;
+    int n_pubkeys_accepted_nom; /* may be > MAX_SIG_KEYS ! */
+    const struct sigkeyid *pubkeys_accepted[MAX_SIG_KEYS];
+    int signing_key_index;
 };
 
-static void set_new_transform(struct site *st, char *pk)
+static const struct sigkeyid keyid_zero;
+
+static int32_t wait_timeout(struct site *st) {
+    int32_t t = st->wait_timeout_mean;
+    int8_t factor;
+    if (t < INT_MAX/2) {
+       st->random->generate(st->random->st,sizeof(factor),&factor);
+       t += (t / 256) * factor;
+    }
+    return t;
+}
+
+static _Bool set_new_transform(struct site *st, char *pk)
 {
+    _Bool ok;
+
     /* Make room for the shared key */
     st->sharedsecretlen=st->chosen_transform->keylen?:st->dh->ceil_len;
     assert(st->sharedsecretlen);
     if (st->sharedsecretlen > st->sharedsecretallocd) {
        st->sharedsecretallocd=st->sharedsecretlen;
-       st->sharedsecret=realloc(st->sharedsecret,st->sharedsecretallocd);
+       st->sharedsecret=safe_realloc_ary(st->sharedsecret,1,
+                                         st->sharedsecretallocd,
+                                         "site:sharedsecret");
     }
-    if (!st->sharedsecret) fatal_perror("site:sharedsecret");
 
     /* Generate the shared key */
     st->dh->makeshared(st->dh->st,st->dhsecret,st->dh->len,pk,
@@ -427,15 +601,18 @@ static void set_new_transform(struct site *st, char *pk)
     /* Set up the transform */
     struct transform_if *generator=st->chosen_transform;
     struct transform_inst_if *generated=generator->create(generator->st);
-    generated->setkey(generated->st,st->sharedsecret,
-                     st->sharedsecretlen,st->setup_priority);
+    ok = generated->setkey(generated->st,st->sharedsecret,
+                          st->sharedsecretlen,st->our_name_later);
+
     dispose_transform(&st->new_transform);
+    if (!ok) return False;
     st->new_transform=generated;
 
     slog(st,LOG_SETUP_INIT,"key exchange negotiated transform"
         " %d (capabilities ours=%#"PRIx32" theirs=%#"PRIx32")",
-        st->chosen_transform->capab_transformnum,
+        st->chosen_transform->capab_bit,
         st->local_capabilities, st->remote_capabilities);
+    return True;
 }
 
 struct xinfoadd {
@@ -468,11 +645,13 @@ static void append_string_xinfo_done(struct buffer_if *buf,
 
 /* Build any of msg1 to msg4. msg5 and msg6 are built from the inside
    out using a transform of config data supplied by netlink */
-static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what)
+static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what,
+                          const struct msg *prompt
+                          /* may be 0 for MSG1 */)
 {
-    void *hst;
-    uint8_t *hash;
-    string_t dhpub, sig;
+    string_t dhpub;
+    unsigned minor;
+    int ki;
 
     st->retries=st->setup_retries;
     BUF_ALLOC(&st->buffer,what);
@@ -484,34 +663,85 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what)
 
     struct xinfoadd xia;
     append_string_xinfo_start(&st->buffer,&xia,st->localname);
-    if ((st->local_capabilities & CAPAB_EARLY) || (type != LABEL_MSG1)) {
-       buf_append_uint32(&st->buffer,st->local_capabilities);
+    buf_append_uint32(&st->buffer,st->local_capabilities);
+    if (type_is_msg34(type)) {
+       buf_append_uint16(&st->buffer,st->mtu_target);
+    }
+    if (type_is_msg23(type)) {
+       buf_append_uint8(&st->buffer,st->peerkeys_kex->nkeys);
+       for (ki=0; ki<st->peerkeys_kex->nkeys; ki++) {
+           struct peer_pubkey *pk = &st->peerkeys_kex->keys[ki];
+           BUF_ADD_OBJ(append,&st->buffer,pk->id);
+       }
+    }
+    struct sigprivkey_if *privkey=0;
+    if (type_is_msg34(type)) {
+       assert(prompt->n_pubkeys_accepted_nom>0);
+       for (ki=0;
+            ki<prompt->n_pubkeys_accepted_nom && ki<MAX_SIG_KEYS;
+            ki++) {
+           const struct sigkeyid *kid=prompt->pubkeys_accepted[ki];
+           if (st->privkeys) {
+               privkey=st->privkeys->lookup(st->privkeys->st,kid,st->log);
+               if (privkey) goto privkey_found;
+           } else {
+               if (sigkeyid_equal(&keyid_zero,kid)) {
+                   privkey=st->privkey_fixed;
+                   goto privkey_found;
+               }
+           }
+       }
+       uint32_t class = slog_start(st,LOG_ERROR);
+       if (class) {
+           slilog_part(st->log,class,"no suitable private key, peer wanted");
+           for (ki=0;
+                ki<prompt->n_pubkeys_accepted_nom && ki<MAX_SIG_KEYS;
+                ki++) {
+               slilog_part(st->log,class, " " SIGKEYID_PR_FMT,
+                           SIGKEYID_PR_VAL(prompt->pubkeys_accepted[ki]));
+           }
+           if (prompt->n_pubkeys_accepted_nom > MAX_SIG_KEYS)
+               slilog_part(st->log,class," +%d",
+                           prompt->n_pubkeys_accepted_nom - MAX_SIG_KEYS);
+           slilog_part(st->log,class,"\n");
+       }
+       return False;
+
+    privkey_found:
+       slog(st,LOG_SIGKEYS,"using private key #%d " SIGKEYID_PR_FMT,
+            ki, SIGKEYID_PR_VAL(prompt->pubkeys_accepted[ki]));
+       buf_append_uint8(&st->buffer,ki);
     }
+
     append_string_xinfo_done(&st->buffer,&xia);
 
     buf_append_string(&st->buffer,st->remotename);
-    memcpy(buf_append(&st->buffer,NONCELEN),st->localN,NONCELEN);
+    BUF_ADD_OBJ(append,&st->buffer,st->localN);
     if (type==LABEL_MSG1) return True;
-    memcpy(buf_append(&st->buffer,NONCELEN),st->remoteN,NONCELEN);
+    BUF_ADD_OBJ(append,&st->buffer,st->remoteN);
     if (type==LABEL_MSG2) return True;
 
     if (hacky_par_mid_failnow()) return False;
 
-    if (type==LABEL_MSG3BIS)
-       buf_append_uint8(&st->buffer,st->chosen_transform->capab_transformnum);
+    if (MSGMAJOR(type) == 3) do {
+       minor = MSGMINOR(type);
+       if (minor < 1) break;
+       buf_append_uint8(&st->buffer,st->chosen_transform->capab_bit);
+    } while (0);
 
     dhpub=st->dh->makepublic(st->dh->st,st->dhsecret,st->dh->len);
     buf_append_string(&st->buffer,dhpub);
     free(dhpub);
-    hash=safe_malloc(st->hash->len, "generate_msg");
-    hst=st->hash->init();
-    st->hash->update(hst,st->buffer.start,st->buffer.size);
-    st->hash->final(hst,hash);
-    sig=st->privkey->sign(st->privkey->st,hash,st->hash->len);
-    buf_append_string(&st->buffer,sig);
-    free(sig);
-    free(hash);
+
+    bool_t ok=privkey->sign(privkey->st,
+                           st->buffer.start,
+                           st->buffer.size,
+                           &st->buffer);
+    if (!ok) goto fail;
     return True;
+
+ fail:
+    return False;
 }
 
 static bool_t unpick_name(struct buffer_if *msg, struct parsedname *nm)
@@ -533,7 +763,11 @@ static bool_t unpick_name(struct buffer_if *msg, struct parsedname *nm)
 static bool_t unpick_msg(struct site *st, uint32_t type,
                         struct buffer_if *msg, struct msg *m)
 {
+    unsigned minor;
+
+    m->n_pubkeys_accepted_nom=-1;
     m->capab_transformnum=-1;
+    m->signing_key_index=-1;
     m->hashstart=msg->start;
     CHECK_AVAIL(msg,4);
     m->dest=buf_unprepend_uint32(msg);
@@ -542,10 +776,32 @@ static bool_t unpick_msg(struct site *st, uint32_t type,
     CHECK_TYPE(msg,type);
     if (!unpick_name(msg,&m->remote)) return False;
     m->remote_capabilities=0;
+    m->remote_mtu=0;
     if (m->remote.extrainfo.size) {
        CHECK_AVAIL(&m->remote.extrainfo,4);
        m->remote_capabilities=buf_unprepend_uint32(&m->remote.extrainfo);
     }
+    if (type_is_msg34(type) && m->remote.extrainfo.size) {
+       CHECK_AVAIL(&m->remote.extrainfo,2);
+       m->remote_mtu=buf_unprepend_uint16(&m->remote.extrainfo);
+    }
+    if (type_is_msg23(type) && m->remote.extrainfo.size) {
+       m->n_pubkeys_accepted_nom = buf_unprepend_uint8(&m->remote.extrainfo);
+       if (!m->n_pubkeys_accepted_nom) return False;
+       for (int ki_nom=0; ki_nom<m->n_pubkeys_accepted_nom; ki_nom++) {
+           CHECK_AVAIL(&m->remote.extrainfo,KEYIDSZ);
+           struct sigkeyid *kid = buf_unprepend(&m->remote.extrainfo,KEYIDSZ);
+           if (ki_nom<MAX_SIG_KEYS) m->pubkeys_accepted[ki_nom] = kid;
+       }
+    } else {
+       m->n_pubkeys_accepted_nom = 1;
+       m->pubkeys_accepted[0] = &keyid_zero;
+    }
+    if (type_is_msg34(type) && m->remote.extrainfo.size) {
+       m->signing_key_index=buf_unprepend_uint8(&m->remote.extrainfo);
+    } else {
+       m->signing_key_index=0;
+    }
     if (!unpick_name(msg,&m->local)) return False;
     if (type==LABEL_PROD) {
        CHECK_EMPTY(msg);
@@ -563,22 +819,37 @@ static bool_t unpick_msg(struct site *st, uint32_t type,
        CHECK_EMPTY(msg);
        return True;
     }
-    if (type==LABEL_MSG3BIS) {
-       CHECK_AVAIL(msg,1);
-       m->capab_transformnum = buf_unprepend_uint8(msg);
-    } else {
-       m->capab_transformnum = CAPAB_TRANSFORMNUM_ANCIENT;
-    }
+    if (MSGMAJOR(type) == 3) do {
+       minor = MSGMINOR(type);
+#define MAYBE_READ_CAP(minminor, kind, dflt) do {                      \
+    if (minor < (minminor))                                            \
+       m->capab_##kind##num = (dflt);                                  \
+    else {                                                             \
+       CHECK_AVAIL(msg, 1);                                            \
+       m->capab_##kind##num = buf_unprepend_uint8(msg);                \
+    }                                                                  \
+} while (0)
+       MAYBE_READ_CAP(1, transform, CAPAB_BIT_ANCIENTTRANSFORM);
+#undef MAYBE_READ_CAP
+    } while (0);
     CHECK_AVAIL(msg,2);
     m->pklen=buf_unprepend_uint16(msg);
     CHECK_AVAIL(msg,m->pklen);
     m->pk=buf_unprepend(msg,m->pklen);
     m->hashlen=msg->start-m->hashstart;
-    CHECK_AVAIL(msg,2);
-    m->siglen=buf_unprepend_uint16(msg);
-    CHECK_AVAIL(msg,m->siglen);
-    m->sig=buf_unprepend(msg,m->siglen);
+
+    if (m->signing_key_index < 0 ||
+       m->signing_key_index >= st->peerkeys_kex->nkeys) {
+       return False;
+    }
+    struct sigpubkey_if *pubkey=
+       st->peerkeys_kex->keys[m->signing_key_index].pubkey;
+    if (!pubkey->unpick(pubkey->st,msg,&m->sig)) {
+       return False;
+    }
+
     CHECK_EMPTY(msg);
+
     return True;
 }
 
@@ -610,13 +881,13 @@ static bool_t check_msg(struct site *st, uint32_t type, struct msg *m,
        return False;
     }
     if (type==LABEL_MSG2) return True;
-    if (!consttime_memeq(m->nR,st->remoteN,NONCELEN)!=0) {
+    if (!consttime_memeq(m->nR,st->remoteN,NONCELEN)) {
        *error="wrong remotely-generated nonce";
        return False;
     }
     /* MSG3 has complicated rules about capabilities, which are
      * handled in process_msg3. */
-    if (type==LABEL_MSG3 || type==LABEL_MSG3BIS) return True;
+    if (MSGMAJOR(type) == 3) return True;
     if (m->remote_capabilities!=st->remote_capabilities) {
        *error="remote capabilities changed";
        return False;
@@ -626,186 +897,305 @@ static bool_t check_msg(struct site *st, uint32_t type, struct msg *m,
     return False;
 }
 
-static bool_t generate_msg1(struct site *st)
+static void peerkeys_maybe_incorporate(struct site *st, const char *file,
+                                      const char *whatmore,
+                                      int logcl_enoent)
+{
+    struct peer_keyset *atsuffix=
+       keyset_load(file,&st->scratch,st->log,logcl_enoent);
+    if (!atsuffix) return;
+
+    if (st->peerkeys_current &&
+       serial_cmp(atsuffix->serial,st->peerkeys_current->serial) <= 0) {
+       slog(st,LOG_SIGKEYS,"keys from %s%s are older, discarding",
+            file,whatmore);
+       keyset_dispose(&atsuffix);
+       int r=unlink(file);
+       if (r) slog(st,LOG_ERROR,"failed to remove old key update %s: %s\n",
+                   st->peerkeys_tmpl.buffer,strerror(errno));
+       return;
+    } else {
+       slog(st,LOG_SIGKEYS,"keys from %s%s are newer, installing",
+            file,whatmore);
+       keyset_dispose(&st->peerkeys_current);
+       st->peerkeys_current=atsuffix;
+       int r=rename(file,st->peerkeys_path);
+       if (r) slog(st,LOG_ERROR,"failed to install key update %s as %s: %s\n",
+                   st->peerkeys_tmpl.buffer,st->peerkeys_path,
+                   strerror(errno));
+    }
+}
+
+static void peerkeys_check_for_update(struct site *st)
 {
+    if (!st->peerkeys_path) return;
+
+    pathprefix_template_setsuffix(&st->peerkeys_tmpl,"~proc");
+    peerkeys_maybe_incorporate(st,st->peerkeys_tmpl.buffer,
+                              " (found old update)",
+                              M_DEBUG);
+
+    pathprefix_template_setsuffix(&st->peerkeys_tmpl,"~update");
+    const char *inputp=st->peerkeys_tmpl.buffer;
+    if (access(inputp,R_OK)) {
+       if (errno!=ENOENT)
+           slog(st,LOG_ERROR,"cannot access peer key update file %s\n",
+                inputp);
+       return;
+    }
+
+    buffer_init(&st->scratch,0);
+    BUF_ADD_BYTES(append,&st->scratch,
+                 st->peerkeys_tmpl.buffer,
+                 strlen(st->peerkeys_tmpl.buffer)+1);
+    inputp=st->scratch.start;
+
+    pathprefix_template_setsuffix(&st->peerkeys_tmpl,"~proc");
+    const char *oursp=st->peerkeys_tmpl.buffer;
+
+    int r=rename(inputp,oursp);
+    if (r) {
+       slog(st,LOG_ERROR,"failed to claim key update file %s as %s: %s",
+            inputp,oursp,strerror(errno));
+       return;
+    }
+
+    peerkeys_maybe_incorporate(st,oursp," (update)",M_ERR);
+}
+
+
+static bool_t kex_init(struct site *st)
+{
+    keyset_dispose(&st->peerkeys_kex);
+    peerkeys_check_for_update(st);
+    if (!st->peerkeys_current) {
+       slog(st,LOG_SETUP_INIT,"no peer public keys, abandoning key setup");
+       return False;
+    }
+    st->peerkeys_kex = keyset_dup(st->peerkeys_current);
     st->random->generate(st->random->st,NONCELEN,st->localN);
-    return generate_msg(st,LABEL_MSG1,"site:MSG1");
+    return True;
+}
+
+static bool_t generate_msg1(struct site *st, const struct msg *prompt_maybe_0)
+{
+    return
+       generate_msg(st,LABEL_MSG1,"site:MSG1",prompt_maybe_0);
 }
 
 static bool_t process_msg1(struct site *st, struct buffer_if *msg1,
-                          const struct comm_addr *src, struct msg *m)
+                          const struct comm_addr *src,
+                          const struct msg *m)
 {
     /* We've already determined we're in an appropriate state to
        process an incoming MSG1, and that the MSG1 has correct values
        of A and B. */
 
-    transport_record_peer(st,&st->setup_peers,src,"msg1");
     st->setup_session_id=m->source;
     st->remote_capabilities=m->remote_capabilities;
     memcpy(st->remoteN,m->nR,NONCELEN);
     return True;
 }
 
-static bool_t generate_msg2(struct site *st)
+static bool_t generate_msg2(struct site *st,
+                           const struct msg *prompt_may_be_null)
 {
-    st->random->generate(st->random->st,NONCELEN,st->localN);
-    return generate_msg(st,LABEL_MSG2,"site:MSG2");
+    return
+       generate_msg(st,LABEL_MSG2,"site:MSG2",prompt_may_be_null);
 }
 
 static bool_t process_msg2(struct site *st, struct buffer_if *msg2,
-                          const struct comm_addr *src)
+                          const struct comm_addr *src,
+                          struct msg *m /* returned */)
 {
-    struct msg m;
     cstring_t err;
 
-    if (!unpick_msg(st,LABEL_MSG2,msg2,&m)) return False;
-    if (!check_msg(st,LABEL_MSG2,&m,&err)) {
+    if (!unpick_msg(st,LABEL_MSG2,msg2,m)) return False;
+    if (!check_msg(st,LABEL_MSG2,m,&err)) {
        slog(st,LOG_SEC,"msg2: %s",err);
        return False;
     }
-    st->setup_session_id=m.source;
-    st->remote_capabilities=m.remote_capabilities;
+    st->setup_session_id=m->source;
+    st->remote_capabilities=m->remote_capabilities;
 
     /* Select the transform to use */
 
-    uint32_t remote_transforms = st->remote_capabilities & CAPAB_TRANSFORM_MASK;
-    if (!remote_transforms)
+    uint32_t remote_crypto_caps = st->remote_capabilities & CAPAB_TRANSFORM_MASK;
+    if (!remote_crypto_caps)
        /* old secnets only had this one transform */
-       remote_transforms = 1UL << CAPAB_TRANSFORMNUM_ANCIENT;
+       remote_crypto_caps = 1UL << CAPAB_BIT_ANCIENTTRANSFORM;
+
+#define CHOOSE_CRYPTO(kind, whats) do {                                        \
+    struct kind##_if *iface;                                           \
+    uint32_t bit, ours = 0;                                            \
+    int i;                                                             \
+    for (i= 0; i < st->n##kind##s; i++) {                              \
+       iface=st->kind##s[i];                                           \
+       bit = 1UL << iface->capab_bit;                                  \
+       if (bit & remote_crypto_caps) goto kind##_found;                \
+       ours |= bit;                                                    \
+    }                                                                  \
+    slog(st,LOG_ERROR,"no " whats " in common"                         \
+        " (us %#"PRIx32"; them: %#"PRIx32")",                          \
+        st->local_capabilities & ours, remote_crypto_caps);            \
+    return False;                                                      \
+kind##_found:                                                          \
+    st->chosen_##kind = iface;                                         \
+} while (0)
 
-    struct transform_if *ti;
-    int i;
-    for (i=0; i<st->ntransforms; i++) {
-       ti=st->transforms[i];
-       if ((1UL << ti->capab_transformnum) & remote_transforms)
-           goto transform_found;
-    }
-    slog(st,LOG_ERROR,"no transforms in common"
-        " (us %#"PRIx32"; them: %#"PRIx32")",
-        st->local_capabilities & CAPAB_TRANSFORM_MASK,
-        remote_transforms);
-    return False;
- transform_found:
-    st->chosen_transform=ti;
+    CHOOSE_CRYPTO(transform, "transforms");
+
+#undef CHOOSE_CRYPTO
 
-    memcpy(st->remoteN,m.nR,NONCELEN);
+    memcpy(st->remoteN,m->nR,NONCELEN);
     return True;
 }
 
-static bool_t generate_msg3(struct site *st)
+static bool_t generate_msg3(struct site *st, const struct msg *prompt)
 {
     /* Now we have our nonce and their nonce. Think of a secret key,
        and create message number 3. */
     st->random->generate(st->random->st,st->dh->len,st->dhsecret);
     return generate_msg(st,
-                       (st->remote_capabilities & CAPAB_TRANSFORM_MASK
-                        ? LABEL_MSG3BIS : LABEL_MSG3),
-                       "site:MSG3");
+                       (st->remote_capabilities & CAPAB_TRANSFORM_MASK)
+                       ? LABEL_MSG3BIS
+                       : LABEL_MSG3,
+                       "site:MSG3",prompt);
+}
+
+static bool_t process_msg3_msg4(struct site *st, struct msg *m)
+{
+    /* Check signature and store g^x mod m */
+    int ki;
+
+    if (m->signing_key_index >= 0) {
+       if (m->signing_key_index >= st->peerkeys_kex->nkeys)
+           return False;
+       ki=m->signing_key_index;
+    } else {
+       for (ki=0; ki<st->peerkeys_kex->nkeys; ki++)
+           if (sigkeyid_equal(&keyid_zero,&st->peerkeys_kex->keys[ki].id))
+               goto found;
+       /* not found */
+       slog(st,LOG_ERROR,
+            "peer signed with keyid zero, which we do not accept");
+       return False;
+    found:;
+    }
+    struct sigpubkey_if *pubkey=st->peerkeys_kex->keys[ki].pubkey;
+
+    if (!pubkey->check(pubkey->st,
+                      m->hashstart,m->hashlen,
+                      &m->sig)) {
+       slog(st,LOG_SEC,"msg3/msg4 signature failed check!"
+            " (key #%d " SIGKEYID_PR_FMT ")",
+            ki, SIGKEYID_PR_VAL(&st->peerkeys_kex->keys[ki].id));
+       return False;
+    }
+    slog(st,LOG_SIGKEYS,"verified peer signature with key #%d "
+        SIGKEYID_PR_FMT, ki,
+        SIGKEYID_PR_VAL(&st->peerkeys_kex->keys[ki].id));
+
+    st->remote_adv_mtu=m->remote_mtu;
+
+    return True;
 }
 
 static bool_t process_msg3(struct site *st, struct buffer_if *msg3,
-                          const struct comm_addr *src, uint32_t msgtype)
+                          const struct comm_addr *src, uint32_t msgtype,
+                          struct msg *m /* returned */)
 {
-    struct msg m;
-    uint8_t *hash;
-    void *hst;
     cstring_t err;
 
-    assert(msgtype==LABEL_MSG3 || msgtype==LABEL_MSG3BIS);
+    switch (msgtype) {
+       case CASES_MSG3_KNOWN: break;
+       default: assert(0);
+    }
 
-    if (!unpick_msg(st,msgtype,msg3,&m)) return False;
-    if (!check_msg(st,msgtype,&m,&err)) {
+    if (!unpick_msg(st,msgtype,msg3,m)) return False;
+    if (!check_msg(st,msgtype,m,&err)) {
        slog(st,LOG_SEC,"msg3: %s",err);
        return False;
     }
-    uint32_t capab_adv_late = m.remote_capabilities
-       & ~st->remote_capabilities & CAPAB_EARLY;
+    uint32_t capab_adv_late = m->remote_capabilities
+       & ~st->remote_capabilities & st->early_capabilities;
     if (capab_adv_late) {
        slog(st,LOG_SEC,"msg3 impermissibly adds early capability flag(s)"
             " %#"PRIx32" (was %#"PRIx32", now %#"PRIx32")",
-            capab_adv_late, st->remote_capabilities, m.remote_capabilities);
+            capab_adv_late, st->remote_capabilities, m->remote_capabilities);
        return False;
     }
-    st->remote_capabilities|=m.remote_capabilities;
 
-    struct transform_if *ti;
-    int i;
-    for (i=0; i<st->ntransforms; i++) {
-       ti=st->transforms[i];
-       if (ti->capab_transformnum == m.capab_transformnum)
-           goto transform_found;
-    }
-    slog(st,LOG_SEC,"peer chose unknown-to-us transform %d!",
-        m.capab_transformnum);
-    return False;
- transform_found:
-    st->chosen_transform=ti;
+#define CHOSE_CRYPTO(kind, what) do {                                  \
+    struct kind##_if *iface;                                           \
+    int i;                                                             \
+    for (i=0; i<st->n##kind##s; i++) {                                 \
+       iface=st->kind##s[i];                                           \
+       if (iface->capab_bit == m->capab_##kind##num)                   \
+           goto kind##_found;                                          \
+    }                                                                  \
+    slog(st,LOG_SEC,"peer chose unknown-to-us " what " %d!",           \
+        m->capab_##kind##num);                                                 \
+    return False;                                                      \
+kind##_found:                                                          \
+    st->chosen_##kind=iface;                                           \
+} while (0)
 
-    /* Check signature and store g^x mod m */
-    hash=safe_malloc(st->hash->len, "process_msg3");
-    hst=st->hash->init();
-    st->hash->update(hst,m.hashstart,m.hashlen);
-    st->hash->final(hst,hash);
-    /* Terminate signature with a '0' - cheating, but should be ok */
-    m.sig[m.siglen]=0;
-    if (!st->pubkey->check(st->pubkey->st,hash,st->hash->len,m.sig)) {
-       slog(st,LOG_SEC,"msg3 signature failed check!");
-       free(hash);
+    CHOSE_CRYPTO(transform, "transform");
+
+#undef CHOSE_CRYPTO
+
+    if (!process_msg3_msg4(st,m))
        return False;
-    }
-    free(hash);
+
+    /* Update our idea of the remote site's capabilities, now that we've
+     * verified that its message was authentic.
+     *
+     * Our previous idea of the remote site's capabilities came from the
+     * unauthenticated MSG1.  We've already checked that this new message
+     * doesn't change any of the bits we relied upon in the past, but it may
+     * also have set additional capability bits.  We simply throw those away
+     * now, and use the authentic capabilities from this MSG3. */
+    st->remote_capabilities=m->remote_capabilities;
 
     /* Terminate their DH public key with a '0' */
-    m.pk[m.pklen]=0;
+    m->pk[m->pklen]=0;
     /* Invent our DH secret key */
     st->random->generate(st->random->st,st->dh->len,st->dhsecret);
 
     /* Generate the shared key and set up the transform */
-    set_new_transform(st,m.pk);
+    if (!set_new_transform(st,m->pk)) return False;
 
     return True;
 }
 
-static bool_t generate_msg4(struct site *st)
+static bool_t generate_msg4(struct site *st, const struct msg *prompt)
 {
     /* We have both nonces, their public key and our private key. Generate
        our public key, sign it and send it to them. */
-    return generate_msg(st,LABEL_MSG4,"site:MSG4");
+    return generate_msg(st,LABEL_MSG4,"site:MSG4",prompt);
 }
 
 static bool_t process_msg4(struct site *st, struct buffer_if *msg4,
-                          const struct comm_addr *src)
+                          const struct comm_addr *src,
+                          struct msg *m /* returned */)
 {
-    struct msg m;
-    uint8_t *hash;
-    void *hst;
     cstring_t err;
 
-    if (!unpick_msg(st,LABEL_MSG4,msg4,&m)) return False;
-    if (!check_msg(st,LABEL_MSG4,&m,&err)) {
+    if (!unpick_msg(st,LABEL_MSG4,msg4,m)) return False;
+    if (!check_msg(st,LABEL_MSG4,m,&err)) {
        slog(st,LOG_SEC,"msg4: %s",err);
        return False;
     }
     
-    /* Check signature and store g^x mod m */
-    hash=safe_malloc(st->hash->len, "process_msg4");
-    hst=st->hash->init();
-    st->hash->update(hst,m.hashstart,m.hashlen);
-    st->hash->final(hst,hash);
-    /* Terminate signature with a '0' - cheating, but should be ok */
-    m.sig[m.siglen]=0;
-    if (!st->pubkey->check(st->pubkey->st,hash,st->hash->len,m.sig)) {
-       slog(st,LOG_SEC,"msg4 signature failed check!");
-       free(hash);
+    if (!process_msg3_msg4(st,m))
        return False;
-    }
-    free(hash);
 
     /* Terminate their DH public key with a '0' */
-    m.pk[m.pklen]=0;
+    m->pk[m->pklen]=0;
 
     /* Generate the shared key and set up the transform */
-    set_new_transform(st,m.pk);
+    if (!set_new_transform(st,m->pk)) return False;
 
     return True;
 }
@@ -829,7 +1219,7 @@ static bool_t unpick_msg0(struct site *st, struct buffer_if *msg0,
     /* Leaves transformed part of buffer untouched */
 }
 
-static bool_t generate_msg5(struct site *st)
+static bool_t generate_msg5(struct site *st, const struct msg *prompt)
 {
     cstring_t transform_err;
 
@@ -886,15 +1276,16 @@ static void create_msg6(struct site *st, struct transform_inst_if *transform,
     /* Give the netlink code an opportunity to put its own stuff in the
        message (configuration information, etc.) */
     buf_prepend_uint32(&st->buffer,LABEL_MSG6);
-    int problem = call_transform_forwards(st,transform,
-                                         &st->buffer,&transform_err);
+    transform_apply_return problem =
+       call_transform_forwards(st,transform,
+                               &st->buffer,&transform_err);
     assert(!problem);
     buf_prepend_uint32(&st->buffer,LABEL_MSG6);
     buf_prepend_uint32(&st->buffer,st->index);
     buf_prepend_uint32(&st->buffer,session_id);
 }
 
-static bool_t generate_msg6(struct site *st)
+static bool_t generate_msg6(struct site *st, const struct msg *prompt)
 {
     if (!is_transform_valid(st->new_transform))
        return False;
@@ -927,12 +1318,13 @@ static bool_t process_msg6(struct site *st, struct buffer_if *msg6,
     return True;
 }
 
-static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0,
+static transform_apply_return
+decrypt_msg0(struct site *st, struct buffer_if *msg0,
                           const struct comm_addr *src)
 {
     cstring_t transform_err, auxkey_err, newkey_err="n/a";
     struct msg0 m;
-    uint32_t problem;
+    transform_apply_return problem;
 
     if (!unpick_msg0(st,msg0,&m)) return False;
 
@@ -945,15 +1337,15 @@ static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0,
        if (!st->auxiliary_is_new)
            delete_one_key(st,&st->auxiliary_key,
                           "peer has used new key","auxiliary key",LOG_SEC);
-       return True;
+       return 0;
     }
-    if (problem==2)
-       goto skew;
+    if (transform_apply_return_badseq(problem))
+       goto badseq;
 
     buffer_copy(msg0, &st->scratch);
     problem = call_transform_reverse(st,st->auxiliary_key.transform,
                                     msg0,&auxkey_err);
-    if (problem==0) {
+    if (!problem) {
        slog(st,LOG_DROP,"processing packet which uses auxiliary key");
        if (st->auxiliary_is_new) {
            /* We previously timed out in state SENTMSG5 but it turns
@@ -970,10 +1362,10 @@ static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0,
            st->auxiliary_is_new=0;
            st->renegotiate_key_time=st->auxiliary_renegotiate_key_time;
        }
-       return True;
+       return 0;
     }
-    if (problem==2)
-       goto skew;
+    if (transform_apply_return_badseq(problem))
+       goto badseq;
 
     if (st->state==SITE_SENTMSG5) {
        buffer_copy(msg0, &st->scratch);
@@ -986,29 +1378,41 @@ static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0,
            BUF_FREE(&st->buffer);
            st->timeout=0;
            activate_new_key(st);
-           return True; /* do process the data in this packet */
+           return 0; /* do process the data in this packet */
        }
-       if (problem==2)
-           goto skew;
+       if (transform_apply_return_badseq(problem))
+           goto badseq;
     }
 
     slog(st,LOG_SEC,"transform: %s (aux: %s, new: %s)",
         transform_err,auxkey_err,newkey_err);
     initiate_key_setup(st,"incoming message would not decrypt",0);
     send_nak(src,m.dest,m.source,m.type,msg0,"message would not decrypt");
-    return False;
+    assert(problem);
+    return problem;
 
- skew:
-    slog(st,LOG_DROP,"transform: %s (merely skew)",transform_err);
-    return False;
+ badseq:
+    slog(st,LOG_DROP,"transform: %s (bad seq.)",transform_err);
+    assert(problem);
+    return problem;
 }
 
 static bool_t process_msg0(struct site *st, struct buffer_if *msg0,
                           const struct comm_addr *src)
 {
     uint32_t type;
-
-    if (!decrypt_msg0(st,msg0,src))
+    transform_apply_return problem;
+
+    problem = decrypt_msg0(st,msg0,src);
+    if (problem==transform_apply_seqdupe) {
+       /* We recently received another copy of this packet, maybe due
+        * to polypath.  That's not a problem; indeed, for the
+        * purposes of transport address management it is a success.
+        * But we don't want to process the packet. */
+       transport_data_msgok(st,src);
+       return False;
+    }
+    if (problem)
        return False;
 
     CHECK_AVAIL(msg0,4);
@@ -1017,6 +1421,10 @@ static bool_t process_msg0(struct site *st, struct buffer_if *msg0,
     case LABEL_MSG7:
        /* We must forget about the current session. */
        delete_keys(st,"request from peer",LOG_SEC);
+       /* probably, the peer is shutting down, and this is going to fail,
+        * but we need to be trying to bring the link up again */
+       if (st->keepalive)
+           initiate_key_setup(st,"peer requested key teardown",0);
        return True;
     case LABEL_MSG9:
        /* Deliver to netlink layer */
@@ -1035,16 +1443,34 @@ static bool_t process_msg0(struct site *st, struct buffer_if *msg0,
 }
 
 static void dump_packet(struct site *st, struct buffer_if *buf,
-                       const struct comm_addr *addr, bool_t incoming)
+                       const struct comm_addr *addr, bool_t incoming,
+                       bool_t ok)
 {
     uint32_t dest=get_uint32(buf->start);
     uint32_t source=get_uint32(buf->start+4);
     uint32_t msgtype=get_uint32(buf->start+8);
 
     if (st->log_events & LOG_DUMP)
-       slilog(st->log,M_DEBUG,"%s: %s: %08x<-%08x: %08x:",
+       slilog(st->log,M_DEBUG,"%s: %s: %08x<-%08x: %08x: %s%s",
               st->tunname,incoming?"incoming":"outgoing",
-              dest,source,msgtype);
+              dest,source,msgtype,comm_addr_to_string(addr),
+              ok?"":" - fail");
+}
+
+static bool_t comm_addr_sendmsg(struct site *st,
+                               const struct comm_addr *dest,
+                               struct buffer_if *buf)
+{
+    int i;
+    struct comm_clientinfo *commclientinfo = 0;
+
+    for (i=0; i < st->ncomms; i++) {
+       if (st->comms[i] == dest->comm) {
+           commclientinfo = st->commclientinfos[i];
+           break;
+       }
+    }
+    return dest->comm->sendmsg(dest->comm->st, buf, dest, commclientinfo);
 }
 
 static uint32_t site_status(void *st)
@@ -1060,7 +1486,7 @@ static bool_t send_msg(struct site *st)
        st->retries--;
        return True;
     } else if (st->state==SITE_SENTMSG5) {
-       slog(st,LOG_SETUP_TIMEOUT,"timed out sending MSG5, stashing new key");
+       logtimeout(st,"timed out sending MSG5, stashing new key");
        /* We stash the key we have produced, in case it turns out that
         * our peer did see our MSG5 after all and starts using it. */
        /* This is a bit like some of activate_new_key */
@@ -1078,52 +1504,125 @@ static bool_t send_msg(struct site *st)
        enter_state_wait(st);
        return False;
     } else {
-       slog(st,LOG_SETUP_TIMEOUT,"timed out sending key setup packet "
+       logtimeout(st,"timed out sending key setup packet "
            "(in state %s)",state_name(st->state));
        enter_state_wait(st);
        return False;
     }
 }
 
-static void site_resolve_callback(void *sst, struct in_addr *address)
+static void site_resolve_callback(void *sst, const struct comm_addr *addrs,
+                                 int stored_naddrs, int all_naddrs,
+                                 const char *address, const char *failwhy)
 {
     struct site *st=sst;
-    struct comm_addr ca_buf, *ca_use;
 
-    if (st->state!=SITE_RESOLVE) {
-       slog(st,LOG_UNEXPECTED,"site_resolve_callback called unexpectedly");
-       return;
-    }
-    if (address) {
-       FILLZERO(ca_buf);
-       ca_buf.comm=st->comms[0];
-       ca_buf.sin.sin_family=AF_INET;
-       ca_buf.sin.sin_port=htons(st->remoteport);
-       ca_buf.sin.sin_addr=*address;
-       ca_use=&ca_buf;
+    if (!stored_naddrs) {
+       slog(st,LOG_ERROR,"resolution of %s failed: %s",address,failwhy);
     } else {
-       slog(st,LOG_ERROR,"resolution of %s failed",st->address);
-       ca_use=0;
+       slog(st,LOG_PEER_ADDRS,"resolution of %s completed, %d addrs, eg: %s",
+            address, all_naddrs, comm_addr_to_string(&addrs[0]));;
+
+       int space=st->transport_peers_max-st->resolving_n_results_stored;
+       int n_tocopy=MIN(stored_naddrs,space);
+       COPY_ARRAY(st->resolving_results + st->resolving_n_results_stored,
+                  addrs,
+                  n_tocopy);
+       st->resolving_n_results_stored += n_tocopy;
+       st->resolving_n_results_all += all_naddrs;
     }
-    if (transport_compute_setupinit_peers(st,ca_use,0)) {
-       enter_new_state(st,SITE_SENTMSG1);
-    } else {
-       /* Can't figure out who to try to to talk to */
-       slog(st,LOG_SETUP_INIT,"key exchange failed: cannot find peer address");
-       enter_state_run(st);
+
+    decrement_resolving_count(st,1);
+}
+
+static void decrement_resolving_count(struct site *st, int by)
+{
+    assert(st->resolving_count>0);
+    st->resolving_count-=by;
+
+    if (st->resolving_count)
+       return;
+
+    /* OK, we are done with them all.  Handle combined results. */
+
+    const struct comm_addr *addrs=st->resolving_results;
+    int naddrs=st->resolving_n_results_stored;
+    assert(naddrs<=st->transport_peers_max);
+
+    if (naddrs) {
+       if (naddrs != st->resolving_n_results_all) {
+           slog(st,LOG_SETUP_INIT,"resolution of supplied addresses/names"
+                " yielded too many results (%d > %d), some ignored",
+                st->resolving_n_results_all, naddrs);
+       }
+       slog(st,LOG_STATE,"resolution completed, %d addrs, eg: %s",
+            naddrs, iaddr_to_string(&addrs[0].ia));;
+    }
+
+    switch (st->state) {
+    case SITE_RESOLVE:
+        if (transport_compute_setupinit_peers(st,addrs,naddrs,0)) {
+           enter_new_state(st,SITE_SENTMSG1,0);
+       } else {
+           /* Can't figure out who to try to to talk to */
+           slog(st,LOG_SETUP_INIT,
+                "key exchange failed: cannot find peer address");
+           enter_state_run(st);
+       }
+       break;
+    case SITE_SENTMSG1: case SITE_SENTMSG2:
+    case SITE_SENTMSG3: case SITE_SENTMSG4:
+    case SITE_SENTMSG5:
+       if (naddrs) {
+           /* We start using the address immediately for data too.
+            * It's best to store it in st->peers now because we might
+            * go via SENTMSG5, WAIT, and a MSG0, straight into using
+            * the new key (without updating the data peer addrs). */
+           transport_resolve_complete(st,addrs,naddrs);
+       } else if (st->local_mobile) {
+           /* We can't let this rest because we may have a peer
+            * address which will break in the future. */
+           slog(st,LOG_SETUP_INIT,"resolution failed: "
+                "abandoning key exchange");
+           enter_state_wait(st);
+       } else {
+           slog(st,LOG_SETUP_INIT,"resolution failed: "
+                " continuing to use source address of peer's packets"
+                " for key exchange and ultimately data");
+       }
+       break;
+    case SITE_RUN:
+       if (naddrs) {
+           slog(st,LOG_SETUP_INIT,"resolution completed tardily,"
+                " updating peer address(es)");
+           transport_resolve_complete_tardy(st,addrs,naddrs);
+       } else if (st->local_mobile) {
+           /* Not very good.  We should queue (another) renegotiation
+            * so that we can update the peer address. */
+           st->key_renegotiate_time=st->now+wait_timeout(st);
+       } else {
+           slog(st,LOG_SETUP_INIT,"resolution failed: "
+                " continuing to use source address of peer's packets");
+       }
+       break;
+    case SITE_WAIT:
+    case SITE_STOP:
+       /* oh well */
+       break;
     }
 }
 
 static bool_t initiate_key_setup(struct site *st, cstring_t reason,
                                 const struct comm_addr *prod_hint)
 {
+    /* Reentrancy hazard: can call enter_new_state/enter_state_* */
     if (st->state!=SITE_RUN) return False;
     slog(st,LOG_SETUP_INIT,"initiating key exchange (%s)",reason);
-    if (st->address) {
-       slog(st,LOG_SETUP_INIT,"resolving peer address");
+    if (st->addresses) {
+       slog(st,LOG_SETUP_INIT,"resolving peer address(es)");
        return enter_state_resolve(st);
-    } else if (transport_compute_setupinit_peers(st,0,prod_hint)) {
-       return enter_new_state(st,SITE_SENTMSG1);
+    } else if (transport_compute_setupinit_peers(st,0,0,prod_hint)) {
+       return enter_new_state(st,SITE_SENTMSG1,0);
     }
     slog(st,LOG_SETUP_INIT,"key exchange failed: no address for peer");
     return False;
@@ -1149,7 +1648,15 @@ static void activate_new_key(struct site *st)
     transport_peers_copy(st,&st->peers,&st->setup_peers);
     st->current.remote_session_id=st->setup_session_id;
 
-    slog(st,LOG_ACTIVATE_KEY,"new key activated");
+    /* Compute the inter-site MTU.  This is min( our_mtu, their_mtu ).
+     * But their mtu be unspecified, in which case we just use ours. */
+    uint32_t intersite_mtu=
+       MIN(st->mtu_target, st->remote_adv_mtu ?: ~(uint32_t)0);
+    st->netlink->set_mtu(st->netlink->st,intersite_mtu);
+
+    slog(st,LOG_ACTIVATE_KEY,"new key activated"
+        " (mtu ours=%"PRId32" theirs=%"PRId32" intersite=%"PRId32")",
+        st->mtu_target, st->remote_adv_mtu, intersite_mtu);
     enter_state_run(st);
 }
 
@@ -1193,7 +1700,7 @@ static void set_link_quality(struct site *st)
        quality=LINK_QUALITY_UP;
     else if (st->state==SITE_WAIT || st->state==SITE_STOP)
        quality=LINK_QUALITY_DOWN;
-    else if (st->address)
+    else if (st->addresses)
        quality=LINK_QUALITY_DOWN_CURRENT_ADDRESS;
     else if (transport_peers_valid(&st->peers))
        quality=LINK_QUALITY_DOWN_STALE_ADDRESS;
@@ -1205,44 +1712,91 @@ static void set_link_quality(struct site *st)
 
 static void enter_state_run(struct site *st)
 {
-    slog(st,LOG_STATE,"entering state RUN");
+    if (st->state!=SITE_STOP)
+       slog(st,LOG_STATE,"entering state RUN%s",
+            current_valid(st) ? " (keyed)" : " (unkeyed)");
     st->state=SITE_RUN;
     st->timeout=0;
 
     st->setup_session_id=0;
     transport_peers_clear(st,&st->setup_peers);
-    memset(st->localN,0,NONCELEN);
-    memset(st->remoteN,0,NONCELEN);
+    keyset_dispose(&st->peerkeys_kex);
+    FILLZERO(st->localN);
+    FILLZERO(st->remoteN);
     dispose_transform(&st->new_transform);
     memset(st->dhsecret,0,st->dh->len);
-    memset(st->sharedsecret,0,st->sharedsecretlen);
+    if (st->sharedsecret) memset(st->sharedsecret,0,st->sharedsecretlen);
     set_link_quality(st);
+
+    if (st->keepalive && !current_valid(st))
+       initiate_key_setup(st, "keepalive", 0);
+}
+
+static bool_t ensure_resolving(struct site *st)
+{
+    /* Reentrancy hazard: may call site_resolve_callback and hence
+     * enter_new_state, enter_state_* and generate_msg*. */
+    if (st->resolving_count)
+        return True;
+
+    assert(st->addresses);
+
+    /* resolver->request might reentrantly call site_resolve_callback
+     * which will decrement st->resolving, so we need to increment it
+     * twice beforehand to prevent decrement from thinking we're
+     * finished, and decrement it ourselves.  Alternatively if
+     * everything fails then there are no callbacks due and we simply
+     * set it to 0 and return false.. */
+    st->resolving_n_results_stored=0;
+    st->resolving_n_results_all=0;
+    st->resolving_count+=2;
+    const char **addrp=st->addresses;
+    const char *address;
+    bool_t anyok=False;
+    for (; (address=*addrp++); ) {
+       bool_t ok = st->resolver->request(st->resolver->st,address,
+                                         st->remoteport,st->comms[0],
+                                         site_resolve_callback,st);
+       if (ok)
+           st->resolving_count++;
+       anyok|=ok;
+    }
+    if (!anyok) {
+       st->resolving_count=0;
+       return False;
+    }
+    decrement_resolving_count(st,2);
+    return True;
 }
 
 static bool_t enter_state_resolve(struct site *st)
 {
+    /* Reentrancy hazard!  See ensure_resolving. */
     state_assert(st,st->state==SITE_RUN);
     slog(st,LOG_STATE,"entering state RESOLVE");
     st->state=SITE_RESOLVE;
-    st->resolver->request(st->resolver->st,st->address,
-                         site_resolve_callback,st);
-    return True;
+    return ensure_resolving(st);
 }
 
-static bool_t enter_new_state(struct site *st, uint32_t next)
+static bool_t enter_new_state(struct site *st, uint32_t next,
+                             const struct msg *prompt
+                             /* may be 0 for SENTMSG1 */)
 {
-    bool_t (*gen)(struct site *st);
+    bool_t (*gen)(struct site *st, const struct msg *prompt);
     int r;
 
     slog(st,LOG_STATE,"entering state %s",state_name(next));
     switch(next) {
     case SITE_SENTMSG1:
        state_assert(st,st->state==SITE_RUN || st->state==SITE_RESOLVE);
+       if (!kex_init(st)) return False;
        gen=generate_msg1;
+       st->msg1_crossed_logged = False;
        break;
     case SITE_SENTMSG2:
        state_assert(st,st->state==SITE_RUN || st->state==SITE_RESOLVE ||
                     st->state==SITE_SENTMSG1 || st->state==SITE_WAIT);
+       if (!kex_init(st)) return False;
        gen=generate_msg2;
        break;
     case SITE_SENTMSG3:
@@ -1273,7 +1827,7 @@ static bool_t enter_new_state(struct site *st, uint32_t next)
 
     if (hacky_par_start_failnow()) return False;
 
-    r= gen(st) && send_msg(st);
+    r= gen(st,prompt) && send_msg(st);
 
     hacky_par_end(&r,
                  st->setup_retries, st->setup_retry_interval,
@@ -1327,7 +1881,7 @@ static bool_t send_msg7(struct site *st, cstring_t reason)
 static void enter_state_wait(struct site *st)
 {
     slog(st,LOG_STATE,"entering state WAIT");
-    st->timeout=st->now+st->wait_timeout;
+    st->timeout=st->now+wait_timeout(st);
     st->state=SITE_WAIT;
     set_link_quality(st);
     BUF_FREE(&st->buffer); /* will have had an outgoing packet in it */
@@ -1354,8 +1908,8 @@ static void generate_send_prod(struct site *st,
     slog(st,LOG_SETUP_INIT,"prodding peer for key exchange");
     st->allow_send_prod=0;
     generate_prod(st,&st->scratch);
-    dump_packet(st,&st->scratch,source,False);
-    source->comm->sendmsg(source->comm->st, &st->scratch, source);
+    bool_t ok = comm_addr_sendmsg(st, source, &st->scratch);
+    dump_packet(st,&st->scratch,source,False,ok);
 }
 
 static inline void site_settimeout(uint64_t timeout, int *timeout_io)
@@ -1374,7 +1928,7 @@ static int site_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
 {
     struct site *st=sst;
 
-    *nfds_io=0; /* We don't use any file descriptors */
+    BEFOREPOLL_WANT_FDS(0); /* We don't use any file descriptors */
     st->now=*now;
 
     /* Work out when our next timeout is. The earlier of 'timeout' or
@@ -1457,15 +2011,60 @@ static void site_outgoing(void *sst, struct buffer_if *buf)
 }
 
 static bool_t named_for_us(struct site *st, const struct buffer_if *buf_in,
-                          uint32_t type, struct msg *m)
+                          uint32_t type, struct msg *m,
+                          struct priomsg *whynot)
     /* For packets which are identified by the local and remote names.
      * If it has our name and our peer's name in it it's for us. */
 {
     struct buffer_if buf[1];
     buffer_readonly_clone(buf,buf_in);
-    return unpick_msg(st,type,buf,m)
-       && name_matches(&m->remote,st->remotename)
-       && name_matches(&m->local,st->localname);
+
+    if (!unpick_msg(st,type,buf,m)) {
+       priomsg_update_fixed(whynot, comm_notify_whynot_unpick, "malformed");
+       return False;
+    }
+#define NAME_MATCHES(lr)                                               \
+    if (!name_matches(&m->lr, st->lr##name)) {                         \
+       if (priomsg_update_fixed(whynot, comm_notify_whynot_name_##lr,  \
+                                 "unknown " #lr " name: ")) {          \
+            truncmsg_add_packet_string(&whynot->m, m->lr.len, m->lr.name); \
+        }                                                              \
+        return False;                                                  \
+    }
+    NAME_MATCHES(remote);
+    NAME_MATCHES(local );
+#undef NAME_MATCHES
+
+    return True;
+}
+
+static bool_t we_have_priority(struct site *st, const struct msg *m) {
+    if (st->local_capabilities & m->remote_capabilities &
+       CAPAB_PRIORITY_MOBILE) {
+       if (st->local_mobile) return True;
+       if (st-> peer_mobile) return False;
+    }
+    return st->our_name_later;
+}
+
+static bool_t setup_late_msg_ok(struct site *st, 
+                               const struct buffer_if *buf_in,
+                               uint32_t msgtype,
+                               const struct comm_addr *source,
+                               struct msg *m /* returned */) {
+    /* For setup packets which seem from their type like they are
+     * late.  Maybe they came via a different path.  All we do is make
+     * a note of the sending address, iff they look like they are part
+     * of the current key setup attempt. */
+    if (!named_for_us(st,buf_in,msgtype,m,0))
+       /* named_for_us calls unpick_msg which gets the nonces */
+       return False;
+    if (!consttime_memeq(m->nR,st->remoteN,NONCELEN) ||
+       !consttime_memeq(m->nL,st->localN, NONCELEN))
+       /* spoof ?  from stale run ?  who knows */
+       return False;
+    transport_setup_msgok(st,source);
+    return True;
 }
 
 /* This function is called by the communication device to deliver
@@ -1474,7 +2073,8 @@ static bool_t named_for_us(struct site *st, const struct buffer_if *buf_in,
    this current site instance (and should therefore not be processed
    by other sites), even if the packet was otherwise ignored. */
 static bool_t site_incoming(void *sst, struct buffer_if *buf,
-                           const struct comm_addr *source)
+                           const struct comm_addr *source,
+                           struct priomsg *whynot)
 {
     struct site *st=sst;
 
@@ -1482,19 +2082,25 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
 
     uint32_t dest=get_uint32(buf->start);
     uint32_t msgtype=get_uint32(buf->start+8);
-    struct msg named_msg;
+    struct msg msg;
+      /* initialised by named_for_us, or process_msgN for N!=1 */
 
     if (msgtype==LABEL_MSG1) {
-       if (!named_for_us(st,buf,msgtype,&named_msg))
+       if (!named_for_us(st,buf,msgtype,&msg,whynot))
            return False;
        /* It's a MSG1 addressed to us. Decide what to do about it. */
-       dump_packet(st,buf,source,True);
+       dump_packet(st,buf,source,True,True);
        if (st->state==SITE_RUN || st->state==SITE_RESOLVE ||
            st->state==SITE_WAIT) {
            /* We should definitely process it */
-           if (process_msg1(st,buf,source,&named_msg)) {
+           transport_compute_setupinit_peers(st,0,0,source);
+           if (process_msg1(st,buf,source,&msg)) {
                slog(st,LOG_SETUP_INIT,"key setup initiated by peer");
-               enter_new_state(st,SITE_SENTMSG2);
+               bool_t entered=enter_new_state(st,SITE_SENTMSG2,&msg);
+               if (entered && st->addresses && st->local_mobile)
+                   /* We must do this as the very last thing, because
+                      the resolver callback might reenter us. */
+                   ensure_resolving(st);
            } else {
                slog(st,LOG_ERROR,"failed to process incoming msg1");
            }
@@ -1504,17 +2110,19 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
            /* We've just sent a message 1! They may have crossed on
               the wire. If we have priority then we ignore the
               incoming one, otherwise we process it as usual. */
-           if (st->setup_priority) {
+           if (we_have_priority(st,&msg)) {
                BUF_FREE(buf);
-               slog(st,LOG_DUMP,"crossed msg1s; we are higher "
-                    "priority => ignore incoming msg1");
+               if (!st->msg1_crossed_logged++)
+                   slog(st,LOG_SETUP_INIT,"crossed msg1s; we are higher "
+                        "priority => ignore incoming msg1");
                return True;
            } else {
-               slog(st,LOG_DUMP,"crossed msg1s; we are lower "
+               slog(st,LOG_SETUP_INIT,"crossed msg1s; we are lower "
                     "priority => use incoming msg1");
-               if (process_msg1(st,buf,source,&named_msg)) {
+               if (process_msg1(st,buf,source,&msg)) {
                    BUF_FREE(&st->buffer); /* Free our old message 1 */
-                   enter_new_state(st,SITE_SENTMSG2);
+                   transport_setup_msgok(st,source);
+                   enter_new_state(st,SITE_SENTMSG2,&msg);
                } else {
                    slog(st,LOG_ERROR,"failed to process an incoming "
                         "crossed msg1 (we have low priority)");
@@ -1522,17 +2130,29 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
                BUF_FREE(buf);
                return True;
            }
+       } else if (st->state==SITE_SENTMSG2 ||
+                  st->state==SITE_SENTMSG4) {
+           if (consttime_memeq(msg.nR,st->remoteN,NONCELEN)) {
+               /* We are ahead in the protocol, but that msg1 had the
+                * peer's nonce so presumably it is from this key
+                * exchange run, via a slower route */
+               transport_setup_msgok(st,source);
+           } else {
+               slog(st,LOG_UNEXPECTED,"competing incoming message 1");
+           }
+           BUF_FREE(buf);
+           return True;
        }
        /* The message 1 was received at an unexpected stage of the
-          key setup. XXX POLICY - what do we do? */
+          key setup.  Well, they lost the race. */
        slog(st,LOG_UNEXPECTED,"unexpected incoming message 1");
        BUF_FREE(buf);
        return True;
     }
     if (msgtype==LABEL_PROD) {
-       if (!named_for_us(st,buf,msgtype,&named_msg))
+       if (!named_for_us(st,buf,msgtype,&msg,whynot))
            return False;
-       dump_packet(st,buf,source,True);
+       dump_packet(st,buf,source,True,True);
        if (st->state!=SITE_RUN) {
            slog(st,LOG_DROP,"ignoring PROD when not in state RUN");
        } else if (current_valid(st)) {
@@ -1545,14 +2165,14 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
     }
     if (dest==st->index) {
        /* Explicitly addressed to us */
-       if (msgtype!=LABEL_MSG0) dump_packet(st,buf,source,True);
+       if (msgtype!=LABEL_MSG0) dump_packet(st,buf,source,True,True);
        switch (msgtype) {
        case LABEL_NAK:
            /* If the source is our current peer then initiate a key setup,
               because our peer's forgotten the key */
            if (get_uint32(buf->start+4)==st->current.remote_session_id) {
                bool_t initiated;
-               initiated = initiate_key_setup(st,"received a NAK",0);
+               initiated = initiate_key_setup(st,"received a NAK",source);
                if (!initiated) generate_send_prod(st,source);
            } else {
                slog(st,LOG_SEC,"bad incoming NAK");
@@ -1569,22 +2189,28 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
        case LABEL_MSG2:
            /* Setup packet: expected only in state SENTMSG1 */
            if (st->state!=SITE_SENTMSG1) {
+               if ((st->state==SITE_SENTMSG3 ||
+                    st->state==SITE_SENTMSG5) &&
+                   setup_late_msg_ok(st,buf,msgtype,source,&msg))
+                   break;
                slog(st,LOG_UNEXPECTED,"unexpected MSG2");
-           } else if (process_msg2(st,buf,source)) {
+           } else if (process_msg2(st,buf,source,&msg)) {
                transport_setup_msgok(st,source);
-               enter_new_state(st,SITE_SENTMSG3);
+               enter_new_state(st,SITE_SENTMSG3,&msg);
            } else {
                slog(st,LOG_SEC,"invalid MSG2");
            }
            break;
-       case LABEL_MSG3:
-       case LABEL_MSG3BIS:
+       case CASES_MSG3_KNOWN:
            /* Setup packet: expected only in state SENTMSG2 */
            if (st->state!=SITE_SENTMSG2) {
+               if ((st->state==SITE_SENTMSG4) &&
+                   setup_late_msg_ok(st,buf,msgtype,source,&msg))
+                   break;
                slog(st,LOG_UNEXPECTED,"unexpected MSG3");
-           } else if (process_msg3(st,buf,source,msgtype)) {
+           } else if (process_msg3(st,buf,source,msgtype,&msg)) {
                transport_setup_msgok(st,source);
-               enter_new_state(st,SITE_SENTMSG4);
+               enter_new_state(st,SITE_SENTMSG4,&msg);
            } else {
                slog(st,LOG_SEC,"invalid MSG3");
            }
@@ -1592,10 +2218,13 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
        case LABEL_MSG4:
            /* Setup packet: expected only in state SENTMSG3 */
            if (st->state!=SITE_SENTMSG3) {
+               if ((st->state==SITE_SENTMSG5) &&
+                   setup_late_msg_ok(st,buf,msgtype,source,&msg))
+                   break;
                slog(st,LOG_UNEXPECTED,"unexpected MSG4");
-           } else if (process_msg4(st,buf,source)) {
+           } else if (process_msg4(st,buf,source,&msg)) {
                transport_setup_msgok(st,source);
-               enter_new_state(st,SITE_SENTMSG5);
+               enter_new_state(st,SITE_SENTMSG5,&msg);
            } else {
                slog(st,LOG_SEC,"invalid MSG4");
            }
@@ -1610,7 +2239,7 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
            if (st->state==SITE_SENTMSG4) {
                if (process_msg5(st,buf,source,st->new_transform)) {
                    transport_setup_msgok(st,source);
-                   enter_new_state(st,SITE_RUN);
+                   enter_new_state(st,SITE_RUN,&msg);
                } else {
                    slog(st,LOG_SEC,"invalid MSG5");
                }
@@ -1650,17 +2279,18 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
        return True;
     }
 
+    priomsg_update_fixed(whynot, comm_notify_whynot_general,
+                        "not MSG1 or PROD; unknown dest index");
     return False;
 }
 
-static void site_control(void *vst, bool_t run)
+static void site_startup(void *vst)
 {
     struct site *st=vst;
-    if (run) enter_state_run(st);
-    else enter_state_stop(st);
+    enter_state_run(st);
 }
 
-static void site_phase_hook(void *sst, uint32_t newphase)
+static void site_phase_shutdown_hook(void *sst, uint32_t newphase)
 {
     struct site *st=sst;
 
@@ -1668,6 +2298,30 @@ static void site_phase_hook(void *sst, uint32_t newphase)
     send_msg7(st,"shutting down");
 }
 
+static void site_phase_run_hook(void *sst, uint32_t newphase)
+{
+    struct site *st=sst;
+    slog(st,LOG_STATE,"entering phase RUN in state %s",
+        state_name(st->state));
+}
+
+static void site_childpersist_clearkeys(void *sst, uint32_t newphase)
+{
+    struct site *st=sst;
+    dispose_transform(&st->current.transform);
+    dispose_transform(&st->auxiliary_key.transform);
+    dispose_transform(&st->new_transform);
+    /* Not much point overwiting the signing key, since we loaded it
+       from disk, and it is only valid prospectively if at all,
+       anyway. */
+    /* XXX it would be best to overwrite the DH state, because that
+       _is_ relevant to forward secrecy.  However we have no
+       convenient interface for doing that and in practice gmp has
+       probably dribbled droppings all over the malloc arena.  A good
+       way to fix this would be to have a privsep child for asymmetric
+       crypto operations, but that's a task for another day. */
+}
+
 static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
                          list_t *args)
 {
@@ -1677,15 +2331,18 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     dict_t *dict;
     int i;
 
-    st=safe_malloc(sizeof(*st),"site_apply");
+    NEW(st);
 
     st->cl.description="site";
     st->cl.type=CL_SITE;
     st->cl.apply=NULL;
     st->cl.interface=&st->ops;
     st->ops.st=st;
-    st->ops.control=site_control;
+    st->ops.startup=site_startup;
     st->ops.status=site_status;
+    st->peerkeys_path=0;
+    st->peerkeys_tmpl.buffer=0;
+    st->peerkeys_current=st->peerkeys_kex=0;
 
     /* First parameter must be a dict */
     item=list_elem(args,0);
@@ -1693,11 +2350,23 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
        cfgfatal(loc,"site","parameter must be a dictionary\n");
     
     dict=item->data.dict;
+    st->log=find_cl_if(dict,"log",CL_LOG,True,"site",loc);
+    st->log_events=string_list_to_word(dict_lookup(dict,"log-events"),
+                                      log_event_table,"site");
+
     st->localname=dict_read_string(dict, "local-name", True, "site", loc);
     st->remotename=dict_read_string(dict, "name", True, "site", loc);
 
+    st->tunname=safe_malloc(strlen(st->localname)+strlen(st->remotename)+5,
+                           "site_apply");
+    sprintf(st->tunname,"%s<->%s",st->localname,st->remotename);
+
+    /* Now slog is working */
+
+    st->keepalive=dict_read_bool(dict,"keepalive",False,"site",loc,False);
+
     st->peer_mobile=dict_read_bool(dict,"mobile",False,"site",loc,False);
-    bool_t local_mobile=
+    st->local_mobile=
        dict_read_bool(dict,"local-mobile",False,"site",loc,False);
 
     /* Sanity check (which also allows the 'sites' file to include
@@ -1706,14 +2375,14 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     if (strcmp(st->localname,st->remotename)==0) {
        Message(M_DEBUG,"site %s: local-name==name -> ignoring this site\n",
                st->localname);
-       if (st->peer_mobile != local_mobile)
+       if (st->peer_mobile != st->local_mobile)
            cfgfatal(loc,"site","site %s's peer-mobile=%d"
                    " but our local-mobile=%d\n",
-                   st->localname, st->peer_mobile, local_mobile);
+                   st->localname, st->peer_mobile, st->local_mobile);
        free(st);
        return NULL;
     }
-    if (st->peer_mobile && local_mobile) {
+    if (st->peer_mobile && st->local_mobile) {
        Message(M_WARNING,"site %s: site is mobile but so are we"
                " -> ignoring this site\n", st->remotename);
        free(st);
@@ -1723,6 +2392,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     assert(index_sequence < 0xffffffffUL);
     st->index = ++index_sequence;
     st->local_capabilities = 0;
+    st->early_capabilities = CAPAB_PRIORITY_MOBILE;
     st->netlink=find_cl_if(dict,"link",CL_NETLINK,True,"site",loc);
 
 #define GET_CLOSURE_LIST(dictkey,things,nthings,CL_TYPE) do{           \
@@ -1730,7 +2400,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     if (!things##_cfg)                                                 \
        cfgfatal(loc,"site","closure list \"%s\" not found\n",dictkey); \
     st->nthings=list_length(things##_cfg);                             \
-    st->things=safe_malloc_ary(sizeof(*st->things),st->nthings,dictkey "s"); \
+    NEW_ARY(st->things,st->nthings);                                   \
     assert(st->nthings);                                               \
     for (i=0; i<st->nthings; i++) {                                    \
        item_t *item=list_elem(things##_cfg,i);                         \
@@ -1745,40 +2415,76 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
 
     GET_CLOSURE_LIST("comm",comms,ncomms,CL_COMM);
 
+    NEW_ARY(st->commclientinfos, st->ncomms);
+    dict_t *comminfo = dict_read_dict(dict,"comm-info",False,"site",loc);
+    for (i=0; i<st->ncomms; i++) {
+       st->commclientinfos[i] =
+           !comminfo ? 0 :
+           st->comms[i]->clientinfo(st->comms[i],comminfo,loc);
+    }
+
     st->resolver=find_cl_if(dict,"resolver",CL_RESOLVER,True,"site",loc);
-    st->log=find_cl_if(dict,"log",CL_LOG,True,"site",loc);
     st->random=find_cl_if(dict,"random",CL_RANDOMSRC,True,"site",loc);
 
-    st->privkey=find_cl_if(dict,"local-key",CL_RSAPRIVKEY,True,"site",loc);
-    st->address=dict_read_string(dict, "address", False, "site", loc);
-    if (st->address)
+    st->privkeys=find_cl_if(dict,"key-cache",CL_PRIVCACHE,False,"site",loc);
+    if (!st->privkeys) {
+       st->privkey_fixed=
+           find_cl_if(dict,"local-key",CL_SIGPRIVKEY,True,"site",loc);
+    }
+
+    struct sigpubkey_if *fixed_pubkey
+       =find_cl_if(dict,"key",CL_SIGPUBKEY,False,"site",loc);
+    st->peerkeys_path=dict_read_string(dict,"peer-keys",fixed_pubkey==0,
+                                      "site",loc);
+    if (st->peerkeys_path) {
+       pathprefix_template_init(&st->peerkeys_tmpl,st->peerkeys_path,
+                                PEERKEYS_SUFFIX_MAXLEN + 1 /* nul */);
+       st->peerkeys_current=keyset_load(st->peerkeys_path,
+                                        &st->scratch,st->log,M_ERR);
+       if (fixed_pubkey) {
+           fixed_pubkey->dispose(fixed_pubkey->st);
+       }
+    } else {
+       assert(fixed_pubkey);
+       NEW(st->peerkeys_current);
+       st->peerkeys_current->refcount=1;
+       st->peerkeys_current->nkeys=1;
+       st->peerkeys_current->keys[0].id=keyid_zero;
+       st->peerkeys_current->keys[0].pubkey=fixed_pubkey;
+       slog(st,LOG_SIGKEYS,
+            "using old-style fixed peer public key (no `peer-keys')");
+    }
+
+    st->addresses=dict_read_string_array(dict,"address",False,"site",loc,0);
+    if (st->addresses)
        st->remoteport=dict_read_number(dict,"port",True,"site",loc,0);
     else st->remoteport=0;
-    st->pubkey=find_cl_if(dict,"key",CL_RSAPUBKEY,True,"site",loc);
 
     GET_CLOSURE_LIST("transform",transforms,ntransforms,CL_TRANSFORM);
 
     st->dh=find_cl_if(dict,"dh",CL_DH,True,"site",loc);
-    st->hash=find_cl_if(dict,"hash",CL_HASH,True,"site",loc);
 
-#define DEFAULT(D) (st->peer_mobile || local_mobile    \
+#define DEFAULT(D) (st->peer_mobile || st->local_mobile        \
                     ? DEFAULT_MOBILE_##D : DEFAULT_##D)
 #define CFG_NUMBER(k,D) dict_read_number(dict,(k),False,"site",loc,DEFAULT(D));
 
     st->key_lifetime=         CFG_NUMBER("key-lifetime",  KEY_LIFETIME);
     st->setup_retries=        CFG_NUMBER("setup-retries", SETUP_RETRIES);
     st->setup_retry_interval= CFG_NUMBER("setup-timeout", SETUP_RETRY_INTERVAL);
-    st->wait_timeout=         CFG_NUMBER("wait-time",     WAIT_TIME);
+    st->wait_timeout_mean=    CFG_NUMBER("wait-time",     WAIT_TIME);
+    st->mtu_target= dict_read_number(dict,"mtu-target",False,"site",loc,0);
 
     st->mobile_peer_expiry= dict_read_number(
        dict,"mobile-peer-expiry",False,"site",loc,DEFAULT_MOBILE_PEER_EXPIRY);
 
-    st->transport_peers_max= !st->peer_mobile ? 1 : dict_read_number(
-       dict,"mobile-peers-max",False,"site",loc,DEFAULT_MOBILE_PEERS_MAX);
+    const char *peerskey= st->peer_mobile
+       ? "mobile-peers-max" : "static-peers-max";
+    st->transport_peers_max= dict_read_number(
+       dict,peerskey,False,"site",loc, st->addresses ? 4 : 3);
     if (st->transport_peers_max<1 ||
-       st->transport_peers_max>=MAX_MOBILE_PEERS_MAX) {
-       cfgfatal(loc,"site","mobile-peers-max must be in range 1.."
-                STRING(MAX_MOBILE_PEERS_MAX) "\n");
+       st->transport_peers_max>MAX_PEER_ADDRS) {
+       cfgfatal(loc,"site", "%s must be in range 1.."
+                STRING(MAX_PEER_ADDRS) "\n", peerskey);
     }
 
     if (st->key_lifetime < DEFAULT(KEY_RENEGOTIATE_GAP)*2)
@@ -1792,52 +2498,56 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
                 "renegotiate-time must be less than key-lifetime\n");
     }
 
-    st->log_events=string_list_to_word(dict_lookup(dict,"log-events"),
-                                      log_event_table,"site");
-
+    st->resolving_count=0;
     st->allow_send_prod=0;
 
-    st->tunname=safe_malloc(strlen(st->localname)+strlen(st->remotename)+5,
-                           "site_apply");
-    sprintf(st->tunname,"%s<->%s",st->localname,st->remotename);
-
     /* The information we expect to see in incoming messages of type 1 */
     /* fixme: lots of unchecked overflows here, but the results are only
        corrupted packets rather than undefined behaviour */
-    st->setup_priority=(strcmp(st->localname,st->remotename)>0);
+    st->our_name_later=(strcmp(st->localname,st->remotename)>0);
 
     buffer_new(&st->buffer,SETUP_BUFFER_LEN);
 
-    buffer_new(&st->scratch,0);
+    buffer_new(&st->scratch,SETUP_BUFFER_LEN);
     BUF_ALLOC(&st->scratch,"site:scratch");
 
     /* We are interested in poll(), but only for timeouts. We don't have
        any fds of our own. */
-    register_for_poll(st, site_beforepoll, site_afterpoll, 0, "site");
+    register_for_poll(st, site_beforepoll, site_afterpoll, "site");
     st->timeout=0;
 
     st->remote_capabilities=0;
     st->chosen_transform=0;
     st->current.key_timeout=0;
     st->auxiliary_key.key_timeout=0;
-    transport_peers_clear(st,&st->peers);
-    transport_peers_clear(st,&st->setup_peers);
+    transport_peers_init(st,&st->peers);
+    transport_peers_init(st,&st->setup_peers);
     /* XXX mlock these */
     st->dhsecret=safe_malloc(st->dh->len,"site:dhsecret");
     st->sharedsecretlen=st->sharedsecretallocd=0;
     st->sharedsecret=0;
 
-    for (i=0; i<st->ntransforms; i++) {
-       struct transform_if *ti=st->transforms[i];
-       uint32_t capbit = 1UL << ti->capab_transformnum;
-       if (st->local_capabilities & capbit)
-           slog(st,LOG_ERROR,"transformnum capability bit"
-                " %d (%#"PRIx32") reused", ti->capab_transformnum, capbit);
-       st->local_capabilities |= capbit;
-    }
+#define SET_CAPBIT(bit) do {                                           \
+    uint32_t capflag = 1UL << (bit);                                   \
+    if (st->local_capabilities & capflag)                              \
+       slog(st,LOG_ERROR,"capability bit"                              \
+            " %d (%#"PRIx32") reused", (bit), capflag);                \
+    st->local_capabilities |= capflag;                                 \
+} while (0)
+
+    for (i=0; i<st->ntransforms; i++)
+       SET_CAPBIT(st->transforms[i]->capab_bit);
+
+#undef SET_CAPBIT
+
+    if (st->local_mobile || st->peer_mobile)
+       st->local_capabilities |= CAPAB_PRIORITY_MOBILE;
 
     /* We need to register the remote networks with the netlink device */
-    st->netlink->reg(st->netlink->st, site_outgoing, st);
+    uint32_t netlink_mtu; /* local virtual interface mtu */
+    st->netlink->reg(st->netlink->st, site_outgoing, st, &netlink_mtu);
+    if (!st->mtu_target)
+       st->mtu_target=netlink_mtu;
     
     for (i=0; i<st->ncomms; i++)
        st->comms[i]->request_notify(st->comms[i]->st, st, site_incoming);
@@ -1849,7 +2559,9 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
 
     enter_state_stop(st);
 
-    add_hook(PHASE_SHUTDOWN,site_phase_hook,st);
+    add_hook(PHASE_SHUTDOWN,site_phase_shutdown_hook,st);
+    add_hook(PHASE_RUN,     site_phase_run_hook,     st);
+    add_hook(PHASE_CHILDPERSIST,site_childpersist_clearkeys,st);
 
     return new_closure(&st->cl);
 }
@@ -1882,29 +2594,22 @@ static void transport_peers_debug(struct site *st, transport_peers *dst,
         i++, (argp+=stride?stride:sizeof(*args))) {
        const struct comm_addr *ca=(void*)argp;
        slog(st, LOG_PEER_ADDRS, " args: addrs[%d]=%s",
-            i, ca->comm->addr_to_string(ca->comm->st,ca));
+            i, comm_addr_to_string(ca));
     }
     for (i=0; i<dst->npeers; i++) {
        struct timeval diff;
        timersub(tv_now,&dst->peers[i].last,&diff);
        const struct comm_addr *ca=&dst->peers[i].addr;
        slog(st, LOG_PEER_ADDRS, " peers: addrs[%d]=%s T-%ld.%06ld",
-            i, ca->comm->addr_to_string(ca->comm->st,ca),
+            i, comm_addr_to_string(ca),
             (unsigned long)diff.tv_sec, (unsigned long)diff.tv_usec);
     }
 }
 
-static int transport_peer_compar(const void *av, const void *bv) {
-    const transport_peer *a=av;
-    const transport_peer *b=bv;
-    /* put most recent first in the array */
-    if (timercmp(&a->last, &b->last, <)) return +1;
-    if (timercmp(&a->last, &b->last, >)) return -11;
-    return 0;
-}
-
 static void transport_peers_expire(struct site *st, transport_peers *peers) {
     /* peers must be sorted first */
+    if (st->local_mobile) return;
+
     int previous_peers=peers->npeers;
     struct timeval oldest;
     oldest.tv_sec  = tv_now->tv_sec - st->mobile_peer_expiry;
@@ -1916,60 +2621,106 @@ static void transport_peers_expire(struct site *st, transport_peers *peers) {
        transport_peers_debug(st,peers,"expire", 0,0,0);
 }
 
-static void transport_record_peer(struct site *st, transport_peers *peers,
-                                 const struct comm_addr *addr, const char *m) {
-    int slot, changed=0;
-
-    for (slot=0; slot<peers->npeers; slot++)
-       if (!memcmp(&peers->peers[slot].addr, addr, sizeof(*addr)))
-           goto found;
+static bool_t transport_peer_record_one(struct site *st, transport_peers *peers,
+                                       const struct comm_addr *ca,
+                                       const struct timeval *tv) {
+    /* returns false if output is full */
+    int search;
+
+    if (peers->npeers >= st->transport_peers_max)
+       return 0;
+
+    for (search=0; search<peers->npeers; search++)
+       if (comm_addr_equal(&peers->peers[search].addr, ca))
+           return 1;
+
+    peers->peers[peers->npeers].addr = *ca;
+    peers->peers[peers->npeers].last = *tv;
+    peers->npeers++;
+    return 1;
+}
+
+static void transport_record_peers(struct site *st, transport_peers *peers,
+                                  const struct comm_addr *addrs, int naddrs,
+                                  const char *m) {
+    /* We add addrs into peers.  The new entries end up at the front
+     * and displace entries towards the end (perhaps even off the
+     * end).  Any existing matching entries are moved up to the front.
+     *
+     * Caller must first call transport_peers_expire. */
+
+    if (naddrs==1) {
+       /* avoids debug for uninteresting updates */
+       int i;
+       for (i=0; i<peers->npeers; i++) {
+           if (comm_addr_equal(&addrs[0], &peers->peers[i].addr)) {
+               memmove(peers->peers+1, peers->peers,
+                       sizeof(peers->peers[0]) * i);
+               peers->peers[0].addr = addrs[0];
+               peers->peers[0].last = *tv_now;
+               return;
+           }
+       }
+    }
 
-    changed=1;
-    if (peers->npeers==st->transport_peers_max)
-       slot=st->transport_peers_max;
-    else
-       slot=peers->npeers++;
+    int old_npeers=peers->npeers;
+    transport_peer old_peers[old_npeers];
+    COPY_ARRAY(old_peers,peers->peers,old_npeers);
 
- found:
-    peers->peers[slot].addr=*addr;
-    peers->peers[slot].last=*tv_now;
+    peers->npeers=0;
+    int i;
+    for (i=0; i<naddrs; i++) {
+       if (!transport_peer_record_one(st,peers, &addrs[i], tv_now))
+           break;
+    }
+    for (i=0; i<old_npeers; i++) {
+       const transport_peer *old=&old_peers[i];
+       if (!transport_peer_record_one(st,peers, &old->addr, &old->last))
+           break;
+    }
 
-    if (peers->npeers>1)
-       qsort(peers->peers, peers->npeers,
-             sizeof(*peers->peers), transport_peer_compar);
+    transport_peers_debug(st,peers,m, naddrs,addrs,0);
+}
 
-    if (changed || peers->npeers!=1)
-       transport_peers_debug(st,peers,m, 1,addr,0);
-    transport_peers_expire(st, peers);
+static void transport_expire_record_peers(struct site *st,
+                                         transport_peers *peers,
+                                         const struct comm_addr *addrs,
+                                         int naddrs, const char *m) {
+    /* Convenience function */
+    transport_peers_expire(st,peers);
+    transport_record_peers(st,peers,addrs,naddrs,m);
 }
 
 static bool_t transport_compute_setupinit_peers(struct site *st,
-        const struct comm_addr *configured_addr /* 0 if none or not found */,
-        const struct comm_addr *prod_hint_addr /* 0 if none */) {
-
-    if (!configured_addr && !prod_hint_addr &&
+        const struct comm_addr *configured_addrs /* 0 if none or not found */,
+        int n_configured_addrs /* 0 if none or not found */,
+        const struct comm_addr *incoming_packet_addr /* 0 if none */) {
+    if (!n_configured_addrs && !incoming_packet_addr &&
        !transport_peers_valid(&st->peers))
        return False;
 
     slog(st,LOG_SETUP_INIT,
-        "using:%s%s %d old peer address(es)",
-        configured_addr ? " configured address;" : "",
-        prod_hint_addr ? " PROD hint address;" : "",
+        "using: %d configured addr(s);%s %d old peer addrs(es)",
+        n_configured_addrs,
+        incoming_packet_addr ? " incoming packet address;" : "",
         st->peers.npeers);
 
-    /* Non-mobile peers havve st->peers.npeers==0 or ==1, since they
-     * have transport_peers_max==1.  The effect is that this code
-     * always uses the configured address if supplied, or otherwise
-     * the address of the incoming PROD, or the existing data peer if
-     * one exists; this is as desired. */
+    /* Non-mobile peers try addresses until one is plausible.  The
+     * effect is that this code always tries first the configured
+     * address if supplied, or otherwise the address of the incoming
+     * PROD, or finally the existing data peer if one exists; this is
+     * as desired. */
 
     transport_peers_copy(st,&st->setup_peers,&st->peers);
+    transport_peers_expire(st,&st->setup_peers);
 
-    if (prod_hint_addr)
-       transport_record_peer(st,&st->setup_peers,prod_hint_addr,"prod");
+    if (incoming_packet_addr)
+       transport_record_peers(st,&st->setup_peers,
+                              incoming_packet_addr,1, "incoming");
 
-    if (configured_addr)
-       transport_record_peer(st,&st->setup_peers,configured_addr,"setupinit");
+    if (n_configured_addrs)
+       transport_record_peers(st,&st->setup_peers,
+                             configured_addrs,n_configured_addrs, "setupinit");
 
     assert(transport_peers_valid(&st->setup_peers));
     return True;
@@ -1977,37 +2728,107 @@ static bool_t transport_compute_setupinit_peers(struct site *st,
 
 static void transport_setup_msgok(struct site *st, const struct comm_addr *a) {
     if (st->peer_mobile)
-       transport_record_peer(st,&st->setup_peers,a,"setupmsg");
+       transport_expire_record_peers(st,&st->setup_peers,a,1,"setupmsg");
 }
 static void transport_data_msgok(struct site *st, const struct comm_addr *a) {
     if (st->peer_mobile)
-       transport_record_peer(st,&st->peers,a,"datamsg");
+       transport_expire_record_peers(st,&st->peers,a,1,"datamsg");
 }
 
 static int transport_peers_valid(transport_peers *peers) {
     return peers->npeers;
 }
+static void transport_peers_init(struct site *st, transport_peers *peers) {
+    peers->npeers= 0;
+}
 static void transport_peers_clear(struct site *st, transport_peers *peers) {
+    bool_t need_debug=!!peers->npeers;
     peers->npeers= 0;
-    transport_peers_debug(st,peers,"clear",0,0,0);
+    if (need_debug)
+       transport_peers_debug(st,peers,"clear",0,0,0);
 }
 static void transport_peers_copy(struct site *st, transport_peers *dst,
                                 const transport_peers *src) {
     dst->npeers=src->npeers;
-    memcpy(dst->peers, src->peers, sizeof(*dst->peers) * dst->npeers);
+    COPY_ARRAY(dst->peers, src->peers, dst->npeers);
     transport_peers_debug(st,dst,"copy",
                          src->npeers, &src->peers->addr, sizeof(*src->peers));
 }
 
+static void transport_resolve_complete(struct site *st,
+                                      const struct comm_addr *addrs,
+                                      int naddrs) {
+    transport_expire_record_peers(st,&st->peers,addrs,naddrs,
+                                 "resolved data");
+    transport_expire_record_peers(st,&st->setup_peers,addrs,naddrs,
+                                 "resolved setup");
+}
+
+static void transport_resolve_complete_tardy(struct site *st,
+                                            const struct comm_addr *addrs,
+                                            int naddrs) {
+    transport_expire_record_peers(st,&st->peers,addrs,naddrs,
+                                 "resolved tardily");
+}
+
+static void transport_peers__copy_by_mask(transport_peer *out, int *nout_io,
+                                         unsigned mask,
+                                         const transport_peers *inp) {
+    /* out and in->peers may be the same region, or nonoverlapping */
+    const transport_peer *in=inp->peers;
+    int slot;
+    for (slot=0; slot<inp->npeers; slot++) {
+       if (!(mask & (1U << slot)))
+           continue;
+       if (!(out==in && slot==*nout_io))
+           COPY_OBJ(out[*nout_io], in[slot]);
+       (*nout_io)++;
+    }
+}
+
 void transport_xmit(struct site *st, transport_peers *peers,
                    struct buffer_if *buf, bool_t candebug) {
     int slot;
     transport_peers_expire(st, peers);
+    unsigned failed=0; /* bitmask */
+    assert(MAX_PEER_ADDRS < sizeof(unsigned)*CHAR_BIT);
+
+    int nfailed=0;
     for (slot=0; slot<peers->npeers; slot++) {
        transport_peer *peer=&peers->peers[slot];
+       bool_t ok = comm_addr_sendmsg(st, &peer->addr, buf);
        if (candebug)
-           dump_packet(st, buf, &peer->addr, False);
-       peer->addr.comm->sendmsg(peer->addr.comm->st, buf, &peer->addr);
+           dump_packet(st, buf, &peer->addr, False, ok);
+       if (!ok) {
+           failed |= 1U << slot;
+           nfailed++;
+       }
+       if (ok && !st->peer_mobile)
+           break;
+    }
+    /* Now we need to demote/delete failing addrs: if we are mobile we
+     * merely demote them; otherwise we delete them. */
+    if (st->local_mobile) {
+       unsigned expected = ((1U << nfailed)-1) << (peers->npeers-nfailed);
+       /* `expected' has all the failures at the end already */
+       if (failed != expected) {
+           int fslot=0;
+           transport_peer failedpeers[nfailed];
+           transport_peers__copy_by_mask(failedpeers, &fslot, failed,peers);
+           assert(fslot == nfailed);
+           int wslot=0;
+           transport_peers__copy_by_mask(peers->peers,&wslot,~failed,peers);
+           assert(wslot+nfailed == peers->npeers);
+           COPY_ARRAY(peers->peers+wslot, failedpeers, nfailed);
+           transport_peers_debug(st,peers,"mobile failure reorder",0,0,0);
+       }
+    } else {
+       if (failed && peers->npeers > 1) {
+           int wslot=0;
+           transport_peers__copy_by_mask(peers->peers,&wslot,~failed,peers);
+           peers->npeers=wslot;
+           transport_peers_debug(st,peers,"non-mobile failure cleanup",0,0,0);
+       }
     }
 }
 
diff --git a/slip.c b/slip.c
index 9e63cb3cd6f3714c37524ca10f1e66f70c6cf9a1..6631fae34921563784352668a57a91c777254066 100644 (file)
--- a/slip.c
+++ b/slip.c
@@ -3,6 +3,25 @@
    just whole packets.  When transmitting we need to bytestuff anyway,
    and may be part-way through receiving. */
 
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #include "secnet.h"
 #include "util.h"
 #include "netlink.h"
@@ -27,11 +46,31 @@ struct slip {
     bool_t ignoring_packet; /* If this packet was corrupt or overlong,
                               we ignore everything up to the next END */
     netlink_deliver_fn *netlink_to_tunnel;
-    uint32_t local_address;
 };
 
 /* Generic SLIP mangling code */
 
+static void slip_write(int fd, const uint8_t *p, size_t l)
+{
+    while (l) {
+       ssize_t written=write(fd,p,l);
+       if (written<0) {
+           if (errno==EINTR) {
+               continue;
+           } else if (iswouldblock(errno)) {
+               lg_perror(0,"slip",0,M_ERR,errno,"write() (packet(s) lost)");
+               return;
+           } else {
+               fatal_perror("slip_stuff: write()");
+           }
+       }
+       assert(written>0);
+       assert((size_t)written<=l);
+       p+=written;
+       l-=written;
+    }
+}
+
 static void slip_stuff(struct slip *st, struct buffer_if *buf, int fd)
 {
     uint8_t txbuf[DEFAULT_BUFSIZE];
@@ -57,16 +96,12 @@ static void slip_stuff(struct slip *st, struct buffer_if *buf, int fd)
            break;
        }
        if ((j+2)>DEFAULT_BUFSIZE) {
-           if (write(fd,txbuf,j)<0) {
-               fatal_perror("slip_stuff: write()");
-           }
+           slip_write(fd,txbuf,j);
            j=0;
        }
     }
     txbuf[j++]=SLIP_END;
-    if (write(fd,txbuf,j)<0) {
-       fatal_perror("slip_stuff: write()");
-    }
+    slip_write(fd,txbuf,j);
     BUF_FREE(buf);
 }
 
@@ -128,7 +163,7 @@ static void slip_unstuff(struct slip *st, uint8_t *buf, uint32_t l)
                }
                st->buff->size=0;
            } else if (outputchr != OUTPUT_NOTHING) {
-               if (st->buff->size < st->buff->len) {
+               if (buf_remaining_space(st->buff)) {
                    buf_append_uint8(st->buff,outputchr);
                } else {
                    Message(M_WARNING, "userv_afterpoll: dropping overlong"
@@ -147,8 +182,6 @@ static void slip_init(struct slip *st, struct cloc loc, dict_t *dict,
        netlink_init(&st->nl,st,loc,dict,
                     "netlink-userv-ipif",NULL,to_host);
     st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"name",loc);
-    st->local_address=string_item_to_ipaddr(
-       dict_find_item(dict,"local-address", True, name, loc),"netlink");
     BUF_ALLOC(st->buff,"slip_init");
     st->pending_esc=False;
     st->ignoring_packet=False;
@@ -173,13 +206,13 @@ static int userv_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
     struct userv *st=sst;
 
     if (st->rxfd!=-1) {
-       *nfds_io=2;
+       BEFOREPOLL_WANT_FDS(2);
        fds[0].fd=st->txfd;
        fds[0].events=0; /* Might want to pick up POLLOUT sometime */
        fds[1].fd=st->rxfd;
        fds[1].events=POLLIN;
     } else {
-       *nfds_io=0;
+       BEFOREPOLL_WANT_FDS(0);
     }
     return 0;
 }
@@ -198,7 +231,7 @@ static void userv_afterpoll(void *sst, struct pollfd *fds, int nfds)
     if (fds[1].revents&POLLIN) {
        l=read(st->rxfd,rxbuf,DEFAULT_BUFSIZE);
        if (l<0) {
-           if (errno!=EINTR)
+           if (errno!=EINTR && !iswouldblock(errno))
                fatal_perror("%s: userv_afterpoll: read(rxfd)",
                             st->slip.nl.name);
        } else if (l==0) {
@@ -213,6 +246,14 @@ static void userv_deliver_to_kernel(void *sst, struct buffer_if *buf)
 {
     struct userv *st=sst;
 
+    if (buf->size > st->slip.nl.mtu) {
+       Message(M_ERR,"%s: packet of size %"PRIu32" exceeds mtu %"PRIu32":"
+               " cannot be injected into kernel, dropped\n",
+               st->slip.nl.name, buf->size, st->slip.nl.mtu);
+       BUF_FREE(buf);
+       return;
+    }
+
     slip_stuff(&st->slip,buf,st->txfd);
 }
 
@@ -225,20 +266,13 @@ static void userv_userv_callback(void *sst, pid_t pid, int status)
                "(expected %d)\n",pid,st->pid);
        return;
     }
-    if (!st->expecting_userv_exit) {
-       if (WIFEXITED(status)) {
-           fatal("%s: userv exited unexpectedly with status %d",
-                 st->slip.nl.name,WEXITSTATUS(status));
-       } else if (WIFSIGNALED(status)) {
-           fatal("%s: userv exited unexpectedly: uncaught signal %d",
-                 st->slip.nl.name,WTERMSIG(status));
-       } else {
-           fatal("%s: userv stopped unexpectedly",
-                 st->slip.nl.name);
-       }
+    if (!(st->expecting_userv_exit &&
+         (!status ||
+          (WIFSIGNALED(status) && WTERMSIG(status)==SIGTERM)))) {
+       lg_exitstatus(0,st->slip.nl.name,0,
+                     st->expecting_userv_exit ? M_WARNING : M_FATAL,
+                     status,"userv");
     }
-    Message(M_WARNING,"%s: userv subprocess died with status %d\n",
-           st->slip.nl.name,WEXITSTATUS(status));
     st->pid=0;
 }
 
@@ -257,10 +291,7 @@ static void userv_entry(void *sst)
     dup2(st->in,0);
     dup2(st->out,1);
 
-    /* XXX close all other fds */
     setsid();
-    /* XXX We really should strdup() all of argv[] but because we'll just
-       exit anyway if execvp() fails it doesn't seem worth bothering. */
     execvp(st->path,(char *const*)st->argv);
     perror("userv-entry: execvp()");
     exit(1);
@@ -268,10 +299,9 @@ static void userv_entry(void *sst)
 
 static void userv_invoke_userv(struct userv *st)
 {
-    struct userv_entry_rec *er;
+    struct userv_entry_rec er[1];
     int c_stdin[2];
     int c_stdout[2];
-    string_t addrs;
     string_t nets;
     string_t s;
     struct netlink_client *r;
@@ -287,9 +317,9 @@ static void userv_invoke_userv(struct userv *st)
     /* This is where we actually invoke userv - all the networks we'll
        be using should already have been registered. */
 
-    addrs=safe_malloc(512,"userv_invoke_userv:addrs");
-    snprintf(addrs,512,"%s,%s,%d,slip",
-            ipaddr_to_string(st->slip.local_address),
+    char addrs[512];
+    snprintf(addrs,sizeof(addrs),"%s,%s,%d,slip",
+            ipaddr_to_string(st->slip.nl.local_address),
             ipaddr_to_string(st->slip.nl.secnet_address),st->slip.nl.mtu);
 
     allnets=ipset_new();
@@ -310,7 +340,6 @@ static void userv_invoke_userv(struct userv *st)
        s=subnet_to_string(snets->list[i]);
        strcat(nets,s);
        strcat(nets,",");
-       free(s);
     }
     nets[strlen(nets)-1]=0;
     subnet_list_free(snets);
@@ -321,17 +350,11 @@ static void userv_invoke_userv(struct userv *st)
     st->slip.pending_esc=False;
 
     /* Invoke userv */
-    if (pipe(c_stdin)!=0) {
-       fatal_perror("userv_invoke_userv: pipe(c_stdin)");
-    }
-    if (pipe(c_stdout)!=0) {
-       fatal_perror("userv_invoke_userv: pipe(c_stdout)");
-    }
+    pipe_cloexec(c_stdin);
+    pipe_cloexec(c_stdout);
     st->txfd=c_stdin[1];
     st->rxfd=c_stdout[0];
 
-    er=safe_malloc(sizeof(*r),"userv_invoke_userv: er");
-
     er->in=c_stdin[0];
     er->out=c_stdout[1];
     /* The arguments are:
@@ -340,7 +363,8 @@ static void userv_invoke_userv(struct userv *st)
        service-name
        local-addr,secnet-addr,mtu,protocol
        route1,route2,... */
-    er->argv=safe_malloc(sizeof(*er->argv)*6,"userv_invoke_userv:argv");
+    const char *er_argv[6];
+    er->argv=er_argv;
     er->argv[0]=st->userv_path;
     er->argv[1]=st->service_user;
     er->argv[2]=st->service_name;
@@ -353,9 +377,6 @@ static void userv_invoke_userv(struct userv *st)
                        er, st, st->slip.nl.name);
     close(er->in);
     close(er->out);
-    free(er->argv);
-    free(er);
-    free(addrs);
     free(nets);
     Message(M_INFO,"%s: userv-ipif pid is %d\n",st->slip.nl.name,st->pid);
     /* Read a single character from the pipe to confirm userv-ipif is
@@ -379,6 +400,11 @@ static void userv_invoke_userv(struct userv *st)
                  st->slip.nl.name,confirm);
        }
     }
+    setnonblock(st->txfd);
+    setnonblock(st->rxfd);
+
+    add_hook(PHASE_CHILDPERSIST,childpersist_closefd_hook,&st->txfd);
+    add_hook(PHASE_CHILDPERSIST,childpersist_closefd_hook,&st->rxfd);
 }
 
 static void userv_kill_userv(struct userv *st)
@@ -397,7 +423,7 @@ static void userv_phase_hook(void *sst, uint32_t newphase)
     if (newphase==PHASE_RUN) {
        userv_invoke_userv(st);
        /* Register for poll() */
-       register_for_poll(st, userv_beforepoll, userv_afterpoll, 2,
+       register_for_poll(st, userv_beforepoll, userv_afterpoll,
                          st->slip.nl.name);
     }
     if (newphase==PHASE_SHUTDOWN) {
@@ -412,7 +438,7 @@ static list_t *userv_apply(closure_t *self, struct cloc loc, dict_t *context,
     item_t *item;
     dict_t *dict;
 
-    st=safe_malloc(sizeof(*st),"userv_apply");
+    NEW(st);
 
     /* First parameter must be a dict */
     item=list_elem(args,0);
diff --git a/stest/Dir.sd.mk b/stest/Dir.sd.mk
new file mode 100644 (file)
index 0000000..f3c0685
--- /dev/null
@@ -0,0 +1,39 @@
+
+&TARGETS += & udp-preload.so
+
+&DEPS += & udp-preload.so
+&DEPS += &^ common.tcl
+&DEPS += secnet
+&DEPS += base91s/base91s
+&DEPS += test-example/sites.conf
+&DEPS += test-example/sites-nonego.conf
+&DEPS += $(test-example_PRIVKEYS)
+
+&:include test-common.sd.mk
+
+&OBJECTS += & udp-preload.o
+
+$(&OBJECTS) : ALL_CFLAGS += -D_REENTRANT -fPIC -Wno-unused-result
+
+&udp-preload.so: $(&OBJECTS)
+       $(CC) -shared -Wl,-soname,$@.1 $^ -o $@ -ldl
+
+# These test scripts use little cpu but contain sleeps etc.  So when
+# there are several, we are going to want to run *loads* in parallel.
+#
+# Ideally we would do something like "every one of these counts for a
+# tenth of a job" but make can't do that.  So bodge it: we treat all the
+# tests as a single job, and disconnect the parent's jobserver.
+#
+# make.info says $(MAKE) causes special handling of the rule but only
+# if it's written literally like that in the rule, hence this
+# indirection.  We need no squash MAKEFLAGS and MFLAGS too.
+# MAKELEVEL seems like it will be fine to pass on.
+
+MAKE_NOTSPECIAL:=$(MAKE)
+
+&check:: $(&DEPS)
+       env -u MAKEFLAGS -u MFLAGS \
+       $(MAKE_NOTSPECIAL) -f main.mk -j$(shell nproc || echo 1)0 &check-real
+
+&:include subdirmk/cdeps.sd.mk
diff --git a/stest/common.tcl b/stest/common.tcl
new file mode 100644 (file)
index 0000000..8897bd9
--- /dev/null
@@ -0,0 +1,375 @@
+source test-common.tcl
+
+package require Tclx
+
+load chiark_tcl_hbytes-1.so
+load chiark_tcl_dgram-1.so
+
+set netlink(inside) {
+    local-address "172.18.232.9";
+    secnet-address "172.18.232.10";
+    remote-networks "172.18.232.0/28";
+}
+set netlink(outside) {
+    local-address "172.18.232.1";
+    secnet-address "172.18.232.2";
+    remote-networks "172.18.232.0/28";
+}
+
+set ports(inside) {16913 16910}
+set ports(outside) 16900
+
+set extra(inside) {
+    local-mobile True;
+    mtu-target 1260;
+}
+set extra(outside) {}
+
+set privkey(inside) test-example/inside.privkeys/
+set privkey(outside) test-example/outside.privkeys/
+
+set initiator inside
+
+proc sitesconf_hook {l} { return $l }
+
+proc oldsecnet {site} {
+    upvar #0 oldsecnet($site) oldsecnet
+    expr {[info exists oldsecnet] && [set oldsecnet]}
+}
+
+proc mkconf {location site} {
+    global tmp
+    global builddir
+    global netlink
+    global ports
+    global extra
+    global netlinkfh
+    upvar #0 privkey($site) privkey
+    set pipefp $tmp/$site.netlink
+    foreach tr {t r} {
+       file delete $pipefp.$tr
+       exec mkfifo -m600 $pipefp.$tr
+       set netlinkfh($site.$tr) [set fh [open $pipefp.$tr r+]]
+       fconfigure $fh -blocking 0 -buffering none -translation binary
+    }
+    fileevent $netlinkfh($site.r) readable \
+       [list netlink-readable $location $site]
+    set fakeuf $tmp/$site.fake-userv
+    set fakeuh [open $fakeuf w 0755]
+    puts $fakeuh "#!/bin/sh
+set -e
+exec 3<&0
+cat <&3 3<&- >$pipefp.r &
+exec 3<>$pipefp.t
+exec <$pipefp.t
+exec 3<&-
+exec cat
+"
+    close $fakeuh
+    set cfg "
+       hash sha1;
+       netlink userv-ipif {
+           name \"netlink\";
+            userv-path \"$fakeuf\";
+       $netlink($site)
+           mtu 1400;
+           buffer sysbuffer(2048);
+           interface \"secnet-test-[string range $site 0 0]\";
+        };
+        comm
+"
+    set delim {}
+    foreach port $ports($site) {
+       append cfg "$delim
+           udp {
+                port $port;
+                address \"::1\", \"127.0.0.1\";
+               buffer sysbuffer(4096);
+           }
+       "
+        set delim ,
+    }
+    append cfg ";
+       local-name \"test-example/$location/$site\";
+"
+    switch -glob $privkey {
+       */ {
+           set sitesconf sites.conf
+           append cfg "
+               key-cache priv-cache({
+                   privkeys \"$builddir/${privkey}priv.\";
+                });
+"
+       }
+       {load-private *} {
+           set sitesconf sites-nonego.conf
+           append cfg "
+               local-key load-private(\"[lindex $privkey 1]\",\"$builddir/[lindex $privkey 2]\");
+"
+       }
+       * {
+           set sitesconf sites-nonego.conf
+           append cfg "
+               local-key rsa-private(\"$builddir/$privkey\");
+"
+       }
+    }
+    set sitesconf $builddir/test-example/$sitesconf
+    
+    append cfg $extra($site)
+    append cfg "
+       log logfile {
+           prefix \"$site\";
+           class \"debug\",\"info\",\"notice\",\"warning\",\"error\",\"security\",\"fatal\";
+    "
+    if {[oldsecnet $site]} { append cfg "
+           filename \"/dev/stderr\";
+    " }
+    append cfg "
+       };
+    "
+    append cfg {
+       system {
+       };
+       resolver adns {
+       };
+       log-events "all";
+       random randomfile("/dev/urandom",no);
+       transform eax-serpent { }, serpent256-cbc { };
+    }
+
+    set pubkeys $tmp/$site.pubkeys
+    file delete -force $pubkeys
+    exec cp -rl $builddir/test-example/pubkeys $pubkeys
+
+    set f [open $sitesconf r]
+    while {[gets $f l] >= 0} {
+       regsub {\"[^\"]*test-example/pubkeys/} $l "\"$pubkeys/" l
+       set l [sitesconf_hook $l]
+       append cfg $l "\n"
+    }
+    set sites [read $f]
+    close $f
+    append cfg $sites
+    append cfg {
+       sites map(site,all-sites);
+    }
+
+    return $cfg
+}
+
+proc spawn-secnet {location site} {
+    global tmp
+    global builddir
+    global netlinkfh
+    global env
+    global pidmap
+    global readbuf
+    upvar #0 pids($site) pid
+    set readbuf($site) {}
+    set cf $tmp/$site.conf
+    set ch [open $cf w]
+    puts $ch [mkconf $location $site]
+    close $ch
+    set secnet $builddir/secnet
+    if {[oldsecnet $site]} {
+       set secnet $env(OLD_SECNET_DIR)/secnet
+    }
+    set argl [list $secnet -dvnc $cf]
+    set divertk SECNET_STEST_DIVERT_$site
+    puts "spawn:"
+    foreach k [array names env] {
+       switch -glob $k {
+           SECNET_STEST_DIVERT_* -
+           SECNET_TEST_BUILDDIR - OLD_SECNET_DIR { }
+           *SECNET* -
+           *PRELOAD* { puts -nonewline " $k=$env($k)" }
+       }
+    }
+    if {[info exists env($divertk)]} {
+       switch -glob $env($divertk) {
+           i - {i *} {
+               regsub {^i} $env($divertk) {} divert_prefix
+               puts "$divert_prefix $argl"
+               puts -nonewline "run ^ command, hit return "
+               flush stdout
+               gets stdin
+               set argl {}
+           }
+           0 - "" {
+               puts " $argl"
+           }
+           /* - ./* {
+               puts " $argl"
+               set argl [split $env($divertk)]
+               puts "... $argl"
+           }
+           * {
+               error "$divertk not understood"
+           }
+       }
+    }
+    if {[llength $argl]} { 
+       set pid [fork]
+       set pidmap($pid) "secnet $location/$site"
+       if {!$pid} {
+           execl [lindex $argl 0] [lrange $argl 1 end]
+       }
+    }
+    puts -nonewline $netlinkfh($site.t) [hbytes h2raw c0]
+}
+
+proc netlink-readable {location site} {
+    global ok
+    upvar #0 readbuf($site) buf
+    upvar #0 netlinkfh($site.r) fh
+    while 1 {
+       set x [read $fh]
+       set h [hbytes raw2h $x]
+       if {![hbytes length $h]} return
+       append buf $h
+       #puts "READABLE $site buf=$buf"
+       while {[regexp {^((?:..)*?)c0(.*)$} $buf dummy now buf]} {
+           #puts "READABLE $site now=$now (buf=$buf)"
+           regsub -all {^((?:..)*?)dbdc} $now {\1c0} now
+           regsub -all {^((?:..)*?)dbdd} $now {\1db} now
+           puts "netlink-got-packet $location $site $now"
+           netlink-got-packet $location $site $now
+       }
+    }
+}
+
+proc netlink-got-packet {location site data} {
+    global initiator
+    if {![hbytes length $data]} return 
+    switch -exact $site!$initiator {
+       inside!inside - outside!outside {
+           switch -glob $data {
+               45000054ed9d4000fe0166d9ac12e802ac12e80900* {
+                   puts "OK $data"
+                   finish 0
+               }
+               * {
+                   error "unexpected $site $data"
+               }
+           }
+       }
+       default {
+           error "$site rx'd! (initiator $initiator)"
+       }
+    }
+}
+
+proc bgerror {message} {
+    global errorInfo errorCode
+    catch {
+       puts stderr "
+----------------------------------------
+$errorInfo
+
+$errorCode
+$message
+----------------------------------------
+    "
+    }
+    finish 1
+}
+
+proc sendpkt {} {
+    global netlinkfh
+    global initiator
+    set p {
+        4500 0054 ed9d 4000 4001 24da ac12 e809
+        ac12 e802 0800 1de4 2d96 0001 f1d4 a05d
+        0000 0000 507f 0b00 0000 0000 1011 1213
+        1415 1617 1819 1a1b 1c1d 1e1f 2021 2223
+        2425 2627 2829 2a2b 2c2d 2e2f 3031 3233
+        3435 3637
+    }
+    puts -nonewline $netlinkfh($initiator.t) \
+       [hbytes h2raw c0[join $p ""]c0]
+}
+
+set socktmp $tmp/s
+exec mkdir -p -m700 $socktmp
+regsub {^(?!/|\./)} $socktmp {./} socktmp ;# dgram-socket wants ./ or /
+
+proc prefix_preload {lib} { prefix_some_path LD_PRELOAD $lib }
+
+set env(UDP_PRELOAD_DIR) $socktmp
+prefix_preload $builddir/stest/udp-preload.so
+
+proc finish {estatus} {
+    puts stderr "FINISHING $estatus"
+    signal default SIGCHLD
+    global pidmap
+    foreach pid [array names pidmap] {
+       kill KILL $pid
+    }
+    exit $estatus
+}
+
+proc reap {} {
+    global pidmap
+    #puts stderr REAPING
+    foreach pid [array names pidmap] {
+       set got [wait -nohang $pid]
+       if {![llength $got]} continue
+       set info $pidmap($pid)
+       unset pidmap($pid)
+       puts stderr "reaped $info: $got"
+       finish 1
+    }
+}
+
+signal -restart trap SIGCHLD { after idle reap }
+
+proc udp-proxy {} {
+    global socktmp udpsock
+    set u $socktmp/udp
+    file delete $u
+    regsub {^(?!/)} $u {./} u
+    set udpsock [dgram-socket create $u]
+    dgram-socket on-receive $udpsock udp-relay
+}
+
+proc udp-relay {data src sock args} {
+    global udpsock socktmp
+    set headerlen [expr {52+1}]
+    set orgsrc $src
+
+    set dst [hbytes range $data 0 $headerlen]
+    regsub {(?:00)*$} $dst {} dst
+    set dst [hbytes h2raw $dst]
+
+    hbytes overwrite data 0 [hbytes zeroes $headerlen]
+    regsub {.*/} $src {} src
+    set srch [hbytes raw2h $src]
+    hbytes append srch 00
+    if {[catch {
+       if {[regexp {[^.,:0-9a-f]} $dst c]} { error "bad dst" }
+       if {[hbytes length $srch] > $headerlen} { error "src addr too long" }
+       hbytes overwrite data 0 $srch
+       dgram-socket transmit $udpsock $data $socktmp/$dst
+    } emsg]} {
+       puts stderr "$orgsrc -> $dst: $emsg"
+    }
+}
+
+proc adj-after {timeout args} {
+    upvar #0 env(SECNET_STEST_TIMEOUT_MUL) mul
+    if {[info exists mul]} { set timeout [expr {$timeout * $mul}] }
+    eval after $timeout $args
+}
+
+proc test-kex {} {
+    udp-proxy
+    spawn-secnet in inside
+    spawn-secnet out outside
+
+    adj-after 500 sendpkt
+    adj-after 1000 sendpkt
+    adj-after 5000 timed-out
+
+    vwait ok
+}
diff --git a/stest/t-Cnonnego-on b/stest/t-Cnonnego-on
new file mode 100755 (executable)
index 0000000..5aec1a3
--- /dev/null
@@ -0,0 +1,3 @@
+#! /usr/bin/tclsh
+set oldsecnet(inside) 1
+source stest/t-nonnego-on
diff --git a/stest/t-Cnonnego-onr b/stest/t-Cnonnego-onr
new file mode 100755 (executable)
index 0000000..dc3fdbf
--- /dev/null
@@ -0,0 +1,4 @@
+#! /usr/bin/tclsh
+set oldsecnet(inside) 1
+set initiator outside
+source stest/t-nonnego-on
diff --git a/stest/t-basic-kex b/stest/t-basic-kex
new file mode 100755 (executable)
index 0000000..0952d5f
--- /dev/null
@@ -0,0 +1,5 @@
+#! /usr/bin/tclsh
+
+source stest/common.tcl
+
+test-kex
diff --git a/stest/t-nonnego-on b/stest/t-nonnego-on
new file mode 100755 (executable)
index 0000000..813d2f8
--- /dev/null
@@ -0,0 +1,12 @@
+#! /usr/bin/tclsh
+
+source stest/common.tcl
+
+# `non-negotiating' ends:
+set privkey(inside) test-example/inside.key
+# So old, new; ie -on
+
+# There is no -no because the sites file tells a new inside to expect
+# a different key.
+
+test-kex
diff --git a/stest/t-nonnego-oo b/stest/t-nonnego-oo
new file mode 100755 (executable)
index 0000000..1633d6a
--- /dev/null
@@ -0,0 +1,24 @@
+#! /usr/bin/tclsh
+
+source stest/common.tcl
+
+# `non-negotiating' ends:
+set privkey(inside) {load-private rsa1 test-example/inside.key}
+set privkey(outside) {load-private rsa1 test-example/outside.key}
+# So old, old; ie -oo
+
+# There is no -no because the sites file tells a new inside to expect
+# a different key.
+
+proc sitesconf_hook {l} {
+    global builddir
+    # Use `make-public' verb, so we have a test case for it
+    if {[regexp {^(.* key )rsa-public\("(\d+)","(\d+)"\)(;.*)$} \
+            $l dummy lhs rsa_e rsa_n rhs]} {
+       set b91 [exec $builddir/base91s/base91s -w0 << "42 $rsa_e $rsa_n"]
+       set l "${lhs}make-public(\"rsa1\",\"$b91\")${rhs}"
+    }
+    return $l
+}
+
+test-kex
diff --git a/stest/udp-preload.c b/stest/udp-preload.c
new file mode 100644 (file)
index 0000000..b5897bb
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ *  udp-preload.c - testing mock library for secnet udp
+ *  This file is part of secnet.
+ *
+ *  Copyright (C) 1998,2003-2004,2012,2017,2019 Ian Jackson
+ * 
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3, or (at your option)
+ *  any later version.
+ * 
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#define _GNU_SOURCE
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+
+#define STDERRSTR_CONST(m) write(2,m,sizeof(m)-1)
+#define STDERRSTR_STRING(m) write(2,m,strlen(m))
+
+typedef void anyfn_type(void);
+
+static anyfn_type *find_any(const char *name) {
+  static const char *dlerr;
+  anyfn_type *kv;
+
+  kv= dlsym(RTLD_NEXT,name); if (kv) return kv;
+  dlerr= dlerror(); if (!dlerr) dlerr= "dlsym() failed for no reason";
+  STDERRSTR_CONST("udp-preload: error finding original version of ");
+  STDERRSTR_STRING(name);
+  STDERRSTR_CONST(": ");
+  STDERRSTR_STRING(dlerr);
+  STDERRSTR_STRING("\n");
+  errno= ENOSYS;
+  return 0;
+}
+
+#define socket_args int domain, int type, int protocol
+#define close_args  int fd
+#define bind_args   int fd, const struct sockaddr *addr, socklen_t addrlen
+#define sendto_args int fd, const void *buf, size_t len, int flags, \
+                    const struct sockaddr *addr, socklen_t addrlen
+#define recvfrom_args  int fd, void *buf, size_t len, int flags, \
+                       struct sockaddr *addr, socklen_t *addrlen
+#define setsockopt_args  int fd, int level, int optname, \
+                         const void *optval, socklen_t optlen
+#define getsockname_args int fd, struct sockaddr *addr, socklen_t *addrlen
+#define WRAPS(X)                                               \
+    X(socket,     int,     (domain,type,protocol))             \
+    X(close,      int,     (fd))                               \
+    X(bind,       int,     (fd,addr,addrlen))                  \
+    X(sendto,     ssize_t, (fd,buf,len,flags,addr,addrlen))    \
+    X(recvfrom,   ssize_t, (fd,buf,len,flags,addr,addrlen))    \
+    X(setsockopt, int,     (fd,level,optname,optval,optlen))   \
+    X(getsockname,int,     (fd,addr,addrlen))
+
+#define DEF_OLD(fn,rt,args)                            \
+  typedef rt fn##_fn_type(fn##_args);                  \
+  static fn##_fn_type find_##fn, *old_##fn=find_##fn;  \
+  static rt find_##fn(fn##_args) {                     \
+    anyfn_type *anyfn;                                 \
+    anyfn= find_any(#fn); if (!anyfn) return -1;       \
+    old_##fn= (fn##_fn_type*)anyfn;                    \
+    return old_##fn args;                              \
+  }
+
+WRAPS(DEF_OLD)
+
+#define WRAP(fn) int fn(fn##_args)
+#define TWRAP(fn) fn(fn##_args)
+
+typedef struct{
+    int af;
+} fdinfo;
+static fdinfo **table;
+static int tablesz;
+
+static fdinfo *lookup(int fd) {
+    if (fd<0 || fd>=tablesz) return 0;
+    return table[fd];
+}
+
+#define ADDRPORTSTRLEN (INET6_ADDRSTRLEN+1+5) /* not including nul */
+
+static int addrport2str(char buf[ADDRPORTSTRLEN+1],
+                       const struct sockaddr *addr, socklen_t addrlen) {
+    const void *addrv=addr;
+    const void *iav;
+    const struct sockaddr_in  *sin;
+    const struct sockaddr_in6 *sin6;
+    uint16_t port;
+    socklen_t el;
+    switch (addr->sa_family) {
+    case AF_INET:  sin =addrv; el=sizeof(*sin ); iav=&sin ->sin_addr ; port=sin ->sin_port ; break;
+    case AF_INET6: sin6=addrv; el=sizeof(*sin6); iav=&sin6->sin6_addr; port=sin6->sin6_port; break;
+    default: errno=ESRCH; return -1;
+    }
+//fprintf(stderr,"af=%lu el=%lu addrlen=%lu\n",
+//     (unsigned long)addr->sa_family,
+//     (unsigned long)el,
+//     (unsigned long)addrlen);
+    if (addrlen!=el) { errno=EINVAL; return -1; }
+    char *p=buf;
+    if (!inet_ntop(addr->sa_family,iav,p,INET6_ADDRSTRLEN)) return -1;
+    p+=strlen(p);
+    sprintf(p,",%u",(unsigned)ntohs(port));
+    return 0;
+}
+
+static int str2addrport(char *str,
+                       struct sockaddr *addr, socklen_t *addrlen) {
+    union {
+       struct sockaddr_in  sin;
+       struct sockaddr_in6 sin6;
+    } si;
+
+    memset(&si,0,sizeof(si));
+
+    int af;
+    void *iav;
+    uint16_t *portp;
+    socklen_t al;
+    switch (str[strcspn(str,".:")]) {
+    case '.': af=AF_INET ; iav=&si.sin .sin_addr ; al=sizeof(si.sin ); portp=&si.sin .sin_port ; break;
+    case ':': af=AF_INET6; iav=&si.sin6.sin6_addr; al=sizeof(si.sin6); portp=&si.sin6.sin6_port; break;
+    default: errno=ESRCH; return -1;
+    }
+    si.sin.sin_family=af;
+
+    char *comma=strchr(str,',');
+    if (!comma) { errno=ESRCH; return -1; }
+    *comma++=0;
+    int r=inet_pton(af,str,iav);
+//fprintf(stderr,"inet_pton(%d,\"%s\",)=%d\n",af,str,r);
+    if (r<0) return -1;
+    if (r==0) { errno=ENOTTY; return -1; }
+
+    char *ep;
+    errno=0;
+    unsigned long port=strtoul(comma,&ep,10);
+    if (ep==comma || *ep || errno || port>65536) { errno=ESRCH; return -1; }
+    *portp= htons(port);
+
+    if (addr) memcpy(addr,&si, *addrlen<al ? *addrlen : al);
+    *addrlen=al;
+    return 0;
+}
+
+static int sun_prep(struct sockaddr_un *sun, const char *leaf) {
+    const char *dir=getenv("UDP_PRELOAD_DIR");
+    if (!dir) { errno=ECHILD; return 0; }
+
+    memset(sun,0,sizeof(*sun));
+    sun->sun_family=AF_UNIX;
+    size_t dl = strlen(dir);
+    if (dl + 1 + strlen(leaf) + 1 > sizeof(sun->sun_path)) {
+       errno=ENAMETOOLONG; return -1;
+    }
+    strcpy(sun->sun_path,dir);
+    sun->sun_path[dl]='/';
+    strcpy(sun->sun_path+dl+1,leaf);
+    return 0;
+}
+
+WRAP(socket) {
+    if (!((domain==AF_INET || domain==AF_INET6) &&
+         type==SOCK_DGRAM))
+       return old_socket(domain,type,protocol);
+    int fd=socket(AF_UNIX,SOCK_DGRAM,0);
+    if (fd<0) return fd;
+    if (fd>=tablesz) {
+       int newsz=(fd+1)*2;
+       table=realloc(table,newsz*sizeof(*table));
+       if (!table) goto fail;
+       while (tablesz<newsz) table[tablesz++]=0;
+    }
+    free(table[fd]);
+    table[fd]=malloc(sizeof(*table[fd]));
+    if (!table[fd]) goto fail;
+    table[fd]->af=domain;
+    return fd;
+
+ fail:
+    close(fd);
+    return -1;
+}
+
+WRAP(close) {
+    if (fd>=0 && fd<tablesz) {
+       free(table[fd]);
+       table[fd]=0;
+    }
+    return old_close(fd);
+}
+
+WRAP(bind) {
+    fdinfo *ent=lookup(fd);
+    if (!ent) return old_bind(fd,addr,addrlen);
+    struct sockaddr_un sun;
+    char tbuf[ADDRPORTSTRLEN+1];
+    if (addrport2str(tbuf,addr,addrlen)) return -1;
+    if (sun_prep(&sun,tbuf)) return -1;
+//fprintf(stderr,"binding %s\n",sun.sun_path);
+    if (unlink(sun.sun_path) && errno!=ENOENT) return -1;
+    return old_bind(fd,(const void*)&sun,sizeof(sun));
+}
+
+WRAP(setsockopt) {
+    fdinfo *ent=lookup(fd);
+    if (!ent) return old_setsockopt(fd,level,optname,optval,optlen);
+    if (ent->af==AF_INET6 && level==IPPROTO_IPV6 && optname==IPV6_V6ONLY
+       && optlen==sizeof(int) && *(int*)optval==1) {
+       return 0;
+    }
+    errno=ENOTTY;
+    return -1;
+}
+
+WRAP(getsockname) {
+    fdinfo *ent=lookup(fd);
+    if (!ent) return old_getsockname(fd,addr,addrlen);
+    struct sockaddr_un sun;
+    socklen_t sunlen=sizeof(sun);
+    if (old_getsockname(fd,(void*)&sun,&sunlen)) return -1;
+    if (sun.sun_family!=AF_UNIX || sunlen>sizeof(sun)) {
+//fprintf(stderr,"old_getsockname af=%lu sunlen=%lu\n",
+//     (unsigned long)sun.sun_family,
+//     (unsigned long)sunlen);
+       errno=EDOM; return -1;
+    }
+    char *slash=strrchr(sun.sun_path,'/');
+    if (str2addrport(slash ? slash+1 : sun.sun_path,
+                    addr,addrlen)) return -1;
+    return 0;
+}
+
+ssize_t TWRAP(sendto) {
+    fdinfo *ent=lookup(fd);
+    if (!ent) return old_sendto(fd,buf,len,flags,addr,addrlen);
+
+    if (flags) { errno=ENOEXEC; return -1; }
+
+    const char *leaf=getenv("UDP_PRELOAD_SERVER");
+    if (!leaf) leaf="udp";
+    if (strlen(leaf) > ADDRPORTSTRLEN) { errno=ENAMETOOLONG; return -1; }
+    struct sockaddr_un sun;
+    if (sun_prep(&sun,leaf)) return -1;
+
+    char tbuf[ADDRPORTSTRLEN+1];
+    memset(tbuf,0,sizeof(tbuf));
+    if (addrport2str(tbuf,addr,addrlen)) return -1;
+
+    struct iovec iov[2];
+    iov[0].iov_base=tbuf;
+    iov[0].iov_len=sizeof(tbuf);
+    iov[1].iov_base=(void*)buf;
+    iov[1].iov_len=len;
+    
+    struct msghdr m;
+    memset(&m,0,sizeof(m));
+    m.msg_name=&sun;
+    m.msg_namelen=sizeof(sun);
+    m.msg_iov=iov;
+    m.msg_iovlen=2;
+
+    return sendmsg(fd,&m,0);
+}
+
+ssize_t TWRAP(recvfrom) {
+    fdinfo *ent=lookup(fd);
+    if (!ent) return old_recvfrom(fd,buf,len,flags,addr,addrlen);
+
+//fprintf(stderr,"recvfrom %d len=%lu flags=%d al=%lu\n",
+//     fd, (unsigned long)len, flags, (unsigned long)*addrlen);
+
+    if (flags) { errno=ENOEXEC; return -1; }
+
+    char tbuf[ADDRPORTSTRLEN+1];
+
+    struct iovec iov[2];
+    iov[0].iov_base=tbuf;
+    iov[0].iov_len=sizeof(tbuf);
+    iov[1].iov_base=buf;
+    iov[1].iov_len=len;
+
+    struct msghdr m;
+    memset(&m,0,sizeof(m));
+    m.msg_iov=iov;
+    m.msg_iovlen=2;
+
+    ssize_t rr=recvmsg(fd,&m,0);
+    if (rr==-1) return rr;
+    if ((size_t)rr<sizeof(tbuf)) { errno=ENXIO; return -1; }
+    if (tbuf[ADDRPORTSTRLEN]) { errno=E2BIG; return -1; }
+    if (str2addrport(tbuf,addr,addrlen)) {
+       fprintf(stderr, "recvfrom str2addrport `%s' %s\n",tbuf,
+               strerror(errno));
+       return -1;
+    }
+
+    rr -= sizeof(tbuf);
+//fprintf(stderr,"recvfrom %s %lu ok\n",tbuf,(unsigned long)rr);
+    return rr;
+}
diff --git a/subdirmk/.gitignore b/subdirmk/.gitignore
new file mode 100644 (file)
index 0000000..0b5bd46
--- /dev/null
@@ -0,0 +1,9 @@
+# subdirmk - subdirmk/.gitignore
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+#----- subdirmk-generated -----
+/regen.mk
+/usual.mk
diff --git a/subdirmk/DEVELOPER-CERTIFICATE b/subdirmk/DEVELOPER-CERTIFICATE
new file mode 100644 (file)
index 0000000..912d22e
--- /dev/null
@@ -0,0 +1,38 @@
+Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
+1 Letterman Drive
+Suite D4700
+San Francisco, CA, 94129
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+    have the right to submit it under the open source license
+    indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+    of my knowledge, is covered under an appropriate open source
+    license and I have the right under that license to submit that
+    work with modifications, whether created in whole or in part
+    by me, under the same open source license (unless I am
+    permitted to submit under a different license), as indicated
+    in the file; or
+
+(c) The contribution was provided directly to me by some other
+    person who certified (a), (b) or (c) and I have not modified
+    it.
+
+(d) I understand and agree that this project and the contribution
+    are public and that a record of the contribution (including all
+    personal information I submit with it, including my sign-off) is
+    maintained indefinitely and may be redistributed consistent with
+    this project or the open source license(s) involved.
+
diff --git a/subdirmk/LGPL-2 b/subdirmk/LGPL-2
new file mode 100644 (file)
index 0000000..5bc8fb2
--- /dev/null
@@ -0,0 +1,481 @@
+                  GNU LIBRARY GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL.  It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it.  You can use it for
+your libraries, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the library, or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library.  If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, so that any problems introduced by others will not reflect on
+the original authors' reputations.
+\f
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software.  To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+  Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs.  This
+license, the GNU Library General Public License, applies to certain
+designated libraries.  This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+  The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it.  Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program.  However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+  Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries.  We
+concluded that weaker conditions might promote sharing better.
+
+  However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves.  This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them.  (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.)  The hope is that this
+will lead to faster development of free libraries.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+  Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+\f
+                  GNU LIBRARY GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License").  Each licensee is
+addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+\f
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+  6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    c) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    d) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+\f
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Library General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/subdirmk/README b/subdirmk/README
new file mode 100644 (file)
index 0000000..f79a280
--- /dev/null
@@ -0,0 +1,575 @@
+subdirmk - assistance for non-recursive use of make
+===================================================
+
+Introduction
+------------
+
+Peter Miller's 1997 essay _Recursive Make Considered Harmful_
+persuasively argues that it is better to arrange to have a single
+make invocation with the project's complete dependency tree, rather
+than the currently conventional `$(MAKE) -C subdirectory' approach.
+
+However, actually writing a project's build system in a non-recursive
+style is not very ergonomic.  The main difficulties are:
+  - constantly having to write out long file and directory names
+  - the lack of a per-directory make variable namespace means
+    long make variables (or namespace clashes)
+  - it is difficult to arrange that one can cd to a subdirectory
+    and say `make all' and have something reasonable happen
+    (to wit, build an appropriate subset)
+
+`subdirmk' is an attempt to solve these problems (and it also slightly
+alleviates some of the boilerplate needed to support out-of-tree
+builds well, and helps a bit with metaprogramming and rule writing).
+
+Basic approach
+--------------
+
+The developer is expected to write a makefile fragment, in each
+relevant subdirectory, called `Dir.sd.mk'.
+
+These fragments may contain ordinary make language.  Unqualified
+filenames are relative to the build toplevel, and all commands all run
+there.
+
+However, the sigil & is treated specially.  By and large, it refers to
+`the build directory corresponding to this .sd.mk file', etc.
+There are a variety of convenient constructions.
+
+The result is that to a large extent, the Dir.sd.mk has an easy way
+to namespace its "local" make variables, and an easy way to refer to
+its "local" filenames (and filenames in general).
+
+The Dir.sd.mk's are filtered, fed through autoconf in the usual way
+(for @..@-substitutions) and included by one autogenerated toplevel
+makefile.
+
+So all of the input is combined and passed to one make invocation.
+(A corollary is that there is no enforcement of the namespacing:
+discipline is required to prefix relevant variable names with &, etc.)
+
+Each subdirectory is also provided with an autogenerated `Makefile'
+which exists purely to capture ordinary make invocations and arrange
+for something suitable to happen.
+
+Where there are dependencies between subdirectories, each Dir.sd.mk
+can simply refer to files in other subdirectories directly.
+
+Substitution syntax
+-------------------
+
+In general & expands to the subdirectory name when used for a
+filename, and to the subdirectory name with / replaced with _ for
+variable names.  (If your variables start with capital letters and
+your filenames with lowercase.  Otherwise, use &/ or &_.)
+
+Note that & is processed *even in makefile comments*.  The substitutor
+does not understand make syntax, or shell syntax, at all.  However,
+the substitution rules are chosen to work well with constructs which
+are common in makefiles.
+
+In the notation below, we suppose that the substitution is being in
+done in a subdirectory sub/dir of the source tree.  In the RH column
+we describe the expansion at the top level, which is often a special
+case (in general in variable names we call that TOP rather than the
+empty string).
+
+&CAPS          =>      sub_dir_CAPS                    or TOP_CAPS
+&lc            =>      sub/dir/lc                      or lc
+       Here CAPS is any ASCII letter A-Z and lc is a-z.
+       The assumption is that filenames are usually lowercase and
+       variables usually uppercase.  Otherwise, use another syntax:
+
+&/             =>      sub/dir/                        or nothing
+&_             =>      sub_dir_                        or TOP_
+&.             =>      sub/dir                         or .
+       (This implies that `&./' works roughly like `&/', although
+       it can produce a needless `./')
+
+&=             =>      sub_dir                         or TOP
+
+&^lc           =>      ${top_srcdir}/sub/dir/lc
+&^/            =>      ${top_srcdir}/sub/dir/
+&^.            =>      ${top_srcdir}/sub/dir
+
+&~lc           =>      ${top_srcdir}/lc
+&~/            =>      ${top_srcdir}/
+&~.            =>      ${top_srcdir}
+
+In general:
+    ^   pathname of this subdirectory in source tree
+    ~   pathname of top level of source tree
+    /  terminates the path escape } needed if next is
+    _   terminates the var escape  } not letter or space)
+    .   terminates path escape giving dir name (excluding /)
+    =  terminates var escape giving only prefix part (rarely needed)
+  lwsp  starts multi-word processing (see below)
+
+So pathname syntax is a subset of:
+    '&' [ '^' | '~' ] [ lc | '/' | '.' ]
+
+&&             =>      &&              for convenience in shell runes
+
+&\&            =>      &               general escaping mechanism
+&\$            =>      $               provided for $-doubling regimes
+&\NEWLINE                              eats the newline and vanishes
+
+&$VARIABLE     =>      ${sub_dir_VARIABLE}     or ${TOP_VARIABLE}
+       VARIABLE is ASCII starting with a letter and matching \w+
+
+& thing thing... &
+&^ thing thing... &
+&~ thing thing... &
+       Convenience syntax for prefixing multiple filenames.
+       Introduced by & followed by lwsp where lc could go.
+       Each lwsp-separated non-ws word is prefixed by &/ etc.
+        etc. respectively.  No other & escapes are recognised.
+       This processing continues until & preceded by lwsp,
+       or until EOL (the end of the line), or \ then EOL.
+
+&:<directive> <args>....
+       recognised at start of line only (possibly after lwsp)
+
+&:             =>      &:
+       for make multiple targets syntax
+       recognised anywhere *except* start of line
+
+&:include filename             filename should usually be [&]foo.sd.mk
+&:-include filename            tolerate nonexistent file
+       RHS is &-expanded but filenames are relative to the top
+       srcdir.  This implies that unqualified names are like &~/
+       whereas &/ is like &^/.  &^ and &~ do not work here because
+       they expand to constructions involving literally
+       `$(top_srcdir)', but the RHS is not make-expanded.
+
+&!<lwsp>       disables & until EOL (and then disappears)
+
+&#     delete everything to end of line
+       (useful if the RHS contains unrecognised & constructions)
+
+&TARGETS_things
+       Handled specially.  If mentioned at the start of a line
+       (possibly following whitespace), declares that this
+       subdir ought to have a target `things'.  The rule will be
+               &/things:: $(&TARGETS_things)
+
+       You may extend it by adding more :: rules for the target,
+       but the preferred style is to do things like this:
+               &TARGETS_check += & test-passed.stamp
+
+       It is important to mention &TARGETS_things at least once in
+       the context of each applicable directory, because doing so
+       arranges that the *parent* will also have a `things' target
+       which recursively implies this directory's `things'.
+
+       Must be spelled exactly &TARGETS_things.  &_TARGETS_things,
+       for example, is not magic.  To make the target exist
+       without providing any prerequisites for it, write a line
+       containing just `&TARGETS_things +='.
+
+       `all' is extra special: every directory has an `all'
+       target, which corresponds to &TARGETS.
+
+Directives
+- - - - -
+
+&:warn [!]WARNTAG ...
+       Suppress (with !) or re-enable (without !) warnings tagged
+       WARNTAG (see section `Warnings', below).  The suppression list
+       is reset at the start of processing in each subdirectory.
+       Warnings that appear at the end of processing are controlled
+       by the final warning state after processing all the toplevel
+       input files (including Final.sd.mk).
+
+&:local+global [!][&]VARIABLE ...
+       Suppresses any warnings relating to forthcoming mentions
+       to VARIABLE or &VARIABLE, as applicable.  Scope ends at
+       the end of the current directory's Suffix.sd.mk.
+       Prefixing with ! removes [&]VARIABLE from the suppresion list.
+
+&:changequote NEWQUOTE
+       changes the escape sequence from & to literally NEWQUOTE
+       NEWQUOTE may be any series of of non-whitespace characters,
+       and is terminated by EOL or lwsp.  The whole line is
+       discarded.
+
+       After this, write NEWQUOTE instead of &, everywhere.
+       The effect is unscoped and lasts until the next setting,
+       or until the end of the current directory's Suffix.sd.mk.
+       It takes effect on &:include'd files too, so maybe set
+       it back before using &:include.
+
+       Notably
+               NEWQUOTENEWQUOTE        => NEWQUOTENEWQUOTE
+               NEWQUOTE\NEWQUOTE       => NEWQUOTE
+               NEWQUOTE\$              => $
+               NEWQUOTE:changequote &  set escape back to &
+
+
+Dollar doubling and macro assistance
+------------------------------------
+
+&$+            Starts dollar-doubling
+&$-            Stops dollar-doubling
+       Both are idempotent and local to the file or context.
+
+This is useful both for make macrology involving $(eval ...), and
+possibly for helping write complicated recipes involving shell
+variables, inline Perl code, etc.
+
+Sometimes we will show $'s being doubled inside another construct.
+This means the content of the construct is $-doubled: $-doubling is
+locally enabled, and restored afterwards.
+
+&:macro NAME   =>      define NAME
+STUFF $ THINGS ..      STUFF $$ THINGS
+&:endm         ..      endef
+       NAME is processed for &
+
+&{..$..}       =>      ${eval ${call ..$$..}}
+       (matches { } pairs to find the end)
+       content is $-doubled (unless it contains &$- to turn that off)
+       contrast &(...), see "Convenience syntax for call", below.
+
+Together &:macro and &{...} provide a more reasonable macro facility
+than raw make.  They solve the problem that make expansions cannot
+directly generate multiple rules, variables, etc.; instead, `$(eval )'
+must be used, but that re-expands the argument, meaning that all the
+literal text must be $-doubled.  This applies to the macro text and to
+the arguments.  Also `$(eval $(call ...))' is an unfortunate syntax.
+Hence &:macro and &{...}.
+
+While dollar-doubling:
+- - - - - - - - - - -
+
+$      =>      $$      including $'s produced by other
+                        &-expansions not mentioned here
+
+&\$    =>      $
+&$(    =>      $(
+&$NN   =>      ${NN}   where N are digits
+
+A few contexts do not support $-doubling, such as directive arguments
+or places where this might imply $-quadrupling.  (There is no way to
+get $-quadrupling.)
+
+Convenience syntax for call
+- - - - - - - - - - - - - -
+
+&(thing                =>      $(call thing
+&( thing       =>      $(call thing
+       and specially:
+&(&lc          =>      $(call sub_dir_lc       or $(call TOP_lc
+&( &lc         =>      $(call sub_dir_lc       or $(call TOP_lc
+       even though lc would normally be thought a filename
+
+Unlike &{...}, this does not involve any dollar-doubling.
+
+Use this when the expansion is going to be a piece of text to be used
+as part of a rule, filename, etc.  When the expansion is top-level
+make text (eg, rules), use &:macro and &{...}.
+
+
+Invocation, "recursive" per-directory targets
+---------------------------------------------
+
+Arrangements are made so that when you run `make foo' in a
+subdirectory, it is like running the whole toplevel makefile, from the
+toplevel, as `make subdir/foo'.  If `subdir/foo' is a file that might
+be built, that builds it.
+
+But `foo' can also be a conventional target like `all'.
+
+Each subdirectory has its own `all' target.  For example a
+subdirectory `src' has a target `src/all'.  The rules for these are
+automatically generated from the settings of the per-directory
+&TARGETS variables.  &TARGETS is magic in this way.  (In
+src/Dir.sd.mk, &TARGETS of course refers to a make variable called
+src_TARGETS.)
+
+The `all' target in a parent directory is taken to imply the `all'
+targets in all of its subdirectories, recursively.  And in the
+autogenerated stub Makefiles, `all' is the default target.  So if you
+just type `make' in the toplevel, you are asking for `&all'
+(<subdir>/all) for every directory in the project.
+
+In a parallel build, the rules for all these various subdirectory
+targets may be in run in parallel: there is only one `make' invocation
+at a time.  There is no sequencing between subdirectories, only been
+individual targets (as specified according to their dependencies).
+
+You can define other per-directory recursive targets too: set the
+variable &TARGETS_zonk, or whatever (being sure to write &TARGETS_zonk
+at the start of a line).  This will create a src/zonk target (for
+appropriate value of src/).  Unlike `all', these other targets only
+exist in areas of the project where at least something mentions them.
+So for example, if &TARGETS_zonk is set in src but not lib, `make
+zonk' in lib will fail.  If you want to make a target exist
+everywhere, += it with nothing in Prefix.sd.mk or Suffix.sd.mk (see
+below).
+
+Prefix.sd.mk, Suffix.sd.mk, Final.sd.mk, inclusion
+--------------------------------------------------
+
+The files Prefix.sd.mk and Suffix.sd.mk in the toplevel of the source
+are automatically processed before and after each individual
+directory's Dir.sd.mk, and the &-substituted contents therefore
+appear once for each subdirectory.
+
+This lets you do per-directory boilerplate.  Some useful boilerplate
+is already provided in subdirmk, for you to reference like this:
+  &:include subdirmk/cdeps.sd.mk
+  &:include subdirmk/clean.sd.mk
+For example you could put that in Suffix.sd.mk.
+
+The top-level Dir.sd.mk is the first makefile included after the
+autogenerated `main.mk' which merely has some basic settings and
+includes.  So if you want to get in early and set global variables,
+put them near the top of Dir.sd.mk.
+
+The file Final.sd.mk in the toplevel directory is processed and
+the result included after all the other files.  Its subdirmk
+filtering context inherits warning suppressions from the toplevel's
+Dir.sd.mk etc., but not anything else.
+
+subdirmk's filter script itself sets (only) these variables:
+  top_srcdir
+  abs_top_srcdir
+  SUBDIRMK_MAKEFILES
+  MAKEFILE_TEMPLATES
+You are likely to want to define $(PWD), and shorter names for
+top_srdir and abs_top_srcdir (we suggest $(src) and $(abs_src)).
+
+Warnings
+--------
+
+subdirmk's `generate' program, which does the acual &-substitution,
+can produce some warnings about your .sd.mk files.  These can be
+suppressed with the &:warn directive.  The warning tags are:
+
+    local+global
+       The same VARNAME was used both with and without an & prefix.
+       This can be confusing.  Also, if you avoid this then you will
+       get a warning iff you accidentally leave off a needed &.
+       The generation of this warning depends on scanning your
+       makefile for things that look like variable references, which
+       subdirmk does not do completely perfectly.  Exciting make
+       syntax may evade this warning, or require suppressions.
+       (You can suppress this warning for a particular VARNAME with
+       the &:local+global directive.)
+
+    single-char-var
+       A variable expansion like $FBAR.  make's expansion rules
+       interpret this as $(F)BAR.  It's normally better to write
+       it this way, at least if the variable expansion is followed
+       by more letters.  Note that &$FOO works differently to
+       raw make: it expands to ${sub_dir_FOO}.
+
+    broken-var-ref
+        An attempt at variable expansion looking like $&...
+       You probably expected this to mean $(TOP_F)BAR but it
+       expands to $TOP_FBAR which make thinks means $(T)OP_FBAR.
+
+    unknown-warning
+       &:warn was used to try to enable a warning that this version
+       of subdirmk does not understand.  (Note that an attempt to
+       *dis*able an unknown warning is only reported if some other
+       warning was issued which might have been disabled.)
+
+
+Guides, hints, and further explanations
+=======================================
+
+Incorporating this into your project
+------------------------------------
+
+Use `git-subtree' to merge the subdirmk/ directory.  You may find it
+useful to symlink the DEVELOPER-CERTIFICATE file (git can store
+symlinks as symlinks - just `git add' the link).  And you probably
+want to mention the situation in your top-level COPYING and HACKING.
+
+Symlink autogen.sh into your project toplevel.
+
+In your configure.ac, say
+
+  m4_include([subdirmk/subdirmk.ac])
+  SUBDIRMK_SUBDIRS([...list of subdirectories in relative syntax...])
+
+Write a Dir.sd.mk in each directory.  See the substitution syntax
+reference, above, and the example/ directory here.  The toplevel
+Dir.sd.mk should probably contain:
+
+  include subdirmk/usual.mk
+  include subdirmk/regen.mk
+
+Write a Suffix.sd.mk in the toplevel, if you want.  It should probably
+have:
+
+  &:include subdirmk/cdeps.sd.mk
+  &:include subdirmk/clean.sd.mk
+
+
+Hints
+-----
+
+You can convert your project incrementally.  Start with the top-level
+Makefile.in and rename it to Dir.sd.mk, and add the appropriate
+stuff to configure.ac, and fix everything up.  Leave the existing
+$(MAKE) -C for your existing subdirectories alone.  Then you can
+convert individual subdirectories, or classes of subdirectories, at
+your leisure.  (You must be /sure/ that each recursive (non-subdirmk)
+subdirectory will be entered only once at a time, but your existing
+recursive make descent system should already do that or you already
+have concurrency bugs.)
+
+Aside from this, be very wary of any invocation of $(MAKE) anywhere.
+This is a frequent source of concurrency bugs in recursive make build
+systems.  When combined with nonrecursive make it's all in the same
+directory and there is nothing stopping the different invocations
+ending up trying to make the same targets at the same time. That
+causes hideous racy lossage.  There are ways to get this to work
+reliably but it is advanced stuff.
+
+If you make syntax errors, or certain kinds of other errors, in your
+makefiles, you may find that just `make' is broken now and cannot get
+far enough to regenerate a working set of makefiles.  If this happens
+just rerun ./config.status by hand.
+
+If you go back and forth between different versions of your code you
+can sometimes find that `make' complains that one of your Dir.sd.mk
+files is missing: typically, if iot was used and therefore a
+dependency in some other version of your code.  If you run `make
+clean' (or `make realclean') these dependencies are suppressed, which
+will clear up the problem.
+
+
+Global definitions
+------------------
+
+If want to set global variables, such as CC, that should only be done
+once.  You can put them in your top-level Dir.sd.mk, or a separate
+file you `include' and declare using SUBDIRMK_MAKEFILES.
+
+If you need different settings of variables like CC for different
+subdirectories, you should probably do that with target-specific
+variable settings.  See the info node `(make) Target-specific'.
+
+
+Directory templates `.sd.mk' vs plain autoconf templates `.mk.in'
+--------------------------------------------------------------------
+
+There are two kinds of template files.
+
+ Filename                 .sd.mk                  .mk.in
+
+ Processed by             &-substitution,         autoconf only
+                          then autoconf
+
+ Instantiated             Usu. once per subdir    Once only
+
+ Need to be mentioned     No, but Dir.sd.mk       All not in subdirmk/
+ in configure.ac?         via SUBDIRMK_SUBDIRS    via SUBDIRMK_MAKEFILES
+
+ How to include           `&:include foo.sd.mk'   `include foo.mk'
+                         in all relevant .sd.mk  in only one
+                          (but not needed for     Dir.sd.mk
+                           Prefix, Suffix, Final)
+
+If you `include subdirmk/regen.mk', dependency management and
+automatic regeneration for all of this template substitution, and for
+config.status etc. is done for you.
+
+
+Tables of file reference syntaxes
+---------------------------------
+
+In a nonrecursive makefile supporting out of tree builds there are
+three separate important distinctions between different file
+locations:
+
+ (i) In the build tree, or in the source tree ?
+
+ (ii) In (or relative to) the subdirectory to which this Dir.sd.mk
+     relates, or relative to the project's top level ?
+
+ (iii) Absolute or relative pathname ?  Usually relative pathnames
+     suffice.  Where an absolute pathname is needed, it can be built
+     out of &/ and an appropriate make variable such as $(PWD).
+
+Path construction &-expansions are built from the following:
+
+                      Relative paths in...
+                      build     source
+                                                       
+  This directory      &         &^
+  Top level           .         &~
+
+In more detail, with all the various options laid out:
+
+      Recommended     Relative paths in...   Absolute paths in...
+             for      build     source       build         source
+                                                       
+  This       lc       &file     &^file       $(PWD)/&file  $(abs_src)/&file
+  directory  any      &/file    &^/file      $(PWD)/&/file $(abs_src)/&/file
+             several  & f g h   &^ f g h     $(addprefix...)
+                                             
+  Top        lc       file      &~file
+  level      any      file      &~/file      $(PWD)/file   $(abs_src)/file
+             .mk.in   file      $(src)/file  $(PWD)/file   $(abs_src)/file
+             several  f g h     &~ f g h     $(addprefix...)
+
+(This assumes you have appropriate make variables src, PWD and
+abs_src.)
+
+
+Subdirectory and variable naming
+--------------------------------
+
+The simple variable decoration scheme does not enforce a strict
+namespace distinction between parts of variable names which come from
+subdirectory names, and parts that mean something else.
+
+So it is a good idea to be a bit careful with your directory naming.
+`TOP', names that contain `_', and names that are similar to parts of
+make variables (whether conventional ones, or ones used in your
+project) are best avoided.
+
+If you name your variables in ALL CAPS and your subdirectories in
+lower case with `-' rather than `_', there will be no confusion.
+
+
+Legal information
+=================
+
+subdirmk is
+ Copyright 2019-2020 Ian Jackson
+ Copyright 2019 Mark Wooding
+
+    subdirmk and its example is free software; you can redistribute it
+    and/or modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public
+    License along with this library as the file LGPL-2.
+    If not, see https://www.gnu.org/.
+
+Individual files generally contain the following tag in the copyright
+notice, instead of the full licence grant text:
+  SPDX-License-Identifier: LGPL-2.0-or-later
+As is conventional, this should be read as a licence grant.
+
+Contributions are accepted based on the git commit Signed-off-by
+convention, by which the contributors' certify their contributions
+according to the Developer Certificate of Origin version 1.1 - see
+the file DEVELOPER-CERTIFICATE.
+
+Where subdirmk is used by and incorporated into another project (eg
+via git subtree), the directory subdirmk/ is under GNU LGPL-2.0+, and
+the rest of the project are under that other project's licence(s).
+(The project's overall licence must be compatible with LGPL-2.0+.)
diff --git a/subdirmk/autogen.sh b/subdirmk/autogen.sh
new file mode 100755 (executable)
index 0000000..528cfbd
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+# subdirmk, autogen.sh (conventional autoconf invocation script)
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+set -e
+cd ${0%/*}
+autoconf
diff --git a/subdirmk/cdeps.sd.mk b/subdirmk/cdeps.sd.mk
new file mode 100644 (file)
index 0000000..37ea60f
--- /dev/null
@@ -0,0 +1,27 @@
+# subdirmk - useful rules for making and using cpp .*.d files
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+&# Usage:
+&#   &:include subdirmk/cdeps.sd.mk
+&# (probably in Suffix.sd.mk)
+&#
+&# Arranges for automatic #include dependency tracking for
+&# C compilation.  The compiler is asked to write the dependencies to
+&#  .*.d and these are automatically included.
+&#
+&# There is a bug: if a #included file is deleted and all references
+&# in .c files to it removed, `make' will complain that it is needed
+&# and can't be built.  `make clean' will fix this.
+
+CDEPS_CFLAGS ?= -MD -MF $(*D)/.$(*F).d
+
+&CDEPS_OBJECTS += $(&OBJECTS)
+
+&DEPFILES += $(foreach b,$(patsubst %.o,%,$(&CDEPS_OBJECTS)), \
+               $(dir $b).$(notdir $b).d)
+-include $(&DEPFILES)
+
+&CLEAN += $(&DEPFILES)
diff --git a/subdirmk/clean.sd.mk b/subdirmk/clean.sd.mk
new file mode 100644 (file)
index 0000000..a38fb22
--- /dev/null
@@ -0,0 +1,26 @@
+# subdirmk - useful rules for clean target
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+&# Usage:
+&#   &:include subdirmk/clean.sd.mk
+&# (probably in Suffix.sd.mk)
+&#
+&# Provides a per-directory `clean' target, which deletes all the files
+&# in &CLEAN.  &OBJECTS, &DEPFILES and &TARGETS are automatically deleted.
+&#
+&# If you want to delete a directory, extend the target with
+&#   &/clean::
+&#     $(RM) -r somethingn
+&# ($(RM) conventionally contains `-f'.)
+
+&CLEAN += & *~ .*~ *.tmp
+&CLEAN += $(&OBJECTS)
+&CLEAN += $(&TARGETS)
+
+&TARGETS_clean +=
+
+&/clean::
+       $(RM) $(&CLEAN)
diff --git a/subdirmk/example/.gitignore b/subdirmk/example/.gitignore
new file mode 100644 (file)
index 0000000..13c6cfa
--- /dev/null
@@ -0,0 +1,33 @@
+# subdirmk example - .gitignore
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+#----- Build artifacts -----
+*.a
+.*.d
+*.o
+*.stamp
+/lib/t/toytest
+/src/toy
+
+#----- Autoconf cruft -----
+
+/autom4te.cache/
+/config.log
+/config.status
+/configure
+
+.makefiles.stamp
+/main.mk
+Makefile
+Dir.mk
+Final.mk
+*.tmp
+
+#----- For our tests -----
+/build
+/for-test-final.sd.mk
+/src/for-test.sd.mk
+/lib/for-test.mk.in
diff --git a/subdirmk/example/DEVELOPER-CERTIFICATE b/subdirmk/example/DEVELOPER-CERTIFICATE
new file mode 120000 (symlink)
index 0000000..d56384e
--- /dev/null
@@ -0,0 +1 @@
+subdirmk/DEVELOPER-CERTIFICATE
\ No newline at end of file
diff --git a/subdirmk/example/Dir.sd.mk b/subdirmk/example/Dir.sd.mk
new file mode 100644 (file)
index 0000000..045ea90
--- /dev/null
@@ -0,0 +1,10 @@
+# subdirmk example - top-level Dir.sd.mk
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+INCLUDES       += -I&^/lib/
+
+include subdirmk/usual.mk
+include subdirmk/regen.mk
diff --git a/subdirmk/example/Final.sd.mk b/subdirmk/example/Final.sd.mk
new file mode 100644 (file)
index 0000000..6dd8953
--- /dev/null
@@ -0,0 +1,8 @@
+# subdirmk - example Final.sd.mk
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+# This is a hook for subdirmk's test suite.
+&:-include for-test-final.sd.mk
diff --git a/subdirmk/example/LGPL-2 b/subdirmk/example/LGPL-2
new file mode 120000 (symlink)
index 0000000..318395d
--- /dev/null
@@ -0,0 +1 @@
+subdirmk/LGPL-2
\ No newline at end of file
diff --git a/subdirmk/example/Suffix.sd.mk b/subdirmk/example/Suffix.sd.mk
new file mode 100644 (file)
index 0000000..7eb9880
--- /dev/null
@@ -0,0 +1,8 @@
+# subdirmk example - general replicated per-directory template
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+&:include subdirmk/cdeps.sd.mk
+&:include subdirmk/clean.sd.mk
diff --git a/subdirmk/example/autogen.sh b/subdirmk/example/autogen.sh
new file mode 120000 (symlink)
index 0000000..589d835
--- /dev/null
@@ -0,0 +1 @@
+subdirmk/autogen.sh
\ No newline at end of file
diff --git a/subdirmk/example/configure.ac b/subdirmk/example/configure.ac
new file mode 100644 (file)
index 0000000..8646e11
--- /dev/null
@@ -0,0 +1,27 @@
+dnl -*-autoconf-*-
+dnl subdirmk example - configuration script
+dnl  Copyright 2019 Mark Wooding
+dnl  Copyright 2019 Ian Jackson
+dnl SPDX-License-Identifier: LGPL-2.0-or-later
+dnl There is NO WARRANTY.
+
+AC_INIT([mktoy], [0.9.0], [mdw@distorted.org.uk])
+AC_CONFIG_SRCDIR([src/toy.c])
+
+AC_PROG_CC
+INCLUDES=
+AC_SUBST(INCLUDES)
+
+m4_include([subdirmk/subdirmk.ac])
+
+SUBDIRMK_SUBDIRS([lib])
+SUBDIRMK_SUBDIRS([lib/t src])
+
+# This is a hook for subdirmk's test suite.
+if test -f $srcdir/lib/for-test.mk.in; then
+   SUBDIRMK_MAKEFILES([lib/for-test.mk])
+fi
+
+AC_OUTPUT
+
+dnl----- That's all, folks --------------------------------------------------
diff --git a/subdirmk/example/lib/Dir.sd.mk b/subdirmk/example/lib/Dir.sd.mk
new file mode 100644 (file)
index 0000000..2639fcb
--- /dev/null
@@ -0,0 +1,15 @@
+# subdirmk example - subdirectory rules
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+&TARGETS       += & libtoy.a
+
+&OBJECTS       += & toylib.o
+
+&libtoy.a:     $(&OBJECTS)
+       $(AR) rc $@ $^
+
+# This is a hook for subdirmk's test suite.
+-include &for-test.mk
diff --git a/subdirmk/example/lib/t/Dir.sd.mk b/subdirmk/example/lib/t/Dir.sd.mk
new file mode 100644 (file)
index 0000000..9ec08c7
--- /dev/null
@@ -0,0 +1,19 @@
+# subdirmk example - subdirectory rules
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+&TARGETS_check += & toytest.stamp
+
+&OBJECTS       += & toytest.o
+&LIBS          += lib/libtoy.a
+
+&CLEAN         += & toytest toytest.stamp
+
+&toytest:      $(&OBJECTS) $(&LIBS)
+       $(LINK) $^
+
+&toytest.stamp: & toytest
+       $<
+       touch $@
diff --git a/subdirmk/example/lib/t/toytest.c b/subdirmk/example/lib/t/toytest.c
new file mode 100644 (file)
index 0000000..1fc5f79
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * subdirmk - example code
+ *  Copyright 2019 Mark Wooding
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ * There is NO WARRANTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "toylib.h"
+
+int main(void)
+{
+  const char *p;
+
+  p = greeting();
+  if (STRNCMP(p, !=, "Hello", 5)) {
+    fprintf(stderr, "greeting `%s' has bad salutation\n", p);
+    exit(1);
+  }
+  printf("all ok\n");
+  return (0);
+}
diff --git a/subdirmk/example/lib/toylib.c b/subdirmk/example/lib/toylib.c
new file mode 100644 (file)
index 0000000..afa423d
--- /dev/null
@@ -0,0 +1,11 @@
+/*
+ * subdirmk - example code
+ *  Copyright 2019 Mark Wooding
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ * There is NO WARRANTY.
+ */
+
+#include "toylib.h"
+
+const char *greeting(void)
+  { return "Hello, world!\n"; }
diff --git a/subdirmk/example/lib/toylib.h b/subdirmk/example/lib/toylib.h
new file mode 100644 (file)
index 0000000..2d2dc93
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * subdirmk - example code
+ *  Copyright 2019 Mark Wooding
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ * There is NO WARRANTY.
+ */
+
+#ifndef LIBTOY_H
+#define LIBTOY_H
+
+#include <string.h>
+
+#define STRCMP(x, op, y) (strcmp((x), (y)) op 0)
+#define STRNCMP(x, op, y, n) (strncmp((x), (y), (n)) op 0)
+#define MEMCMP(x, op, y, n) (memcmp((x), (y), (n)) op 0)
+
+extern const char *greeting(void);
+
+#endif
diff --git a/subdirmk/example/src/Dir.sd.mk b/subdirmk/example/src/Dir.sd.mk
new file mode 100644 (file)
index 0000000..c67a89f
--- /dev/null
@@ -0,0 +1,16 @@
+# subdirmk example - subdirectory rules
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+&TARGETS       += & toy
+
+&OBJECTS       += & toy.o
+&LIBS          += lib/libtoy.a
+
+&toy: $(&OBJECTS) $(&LIBS)
+       $(LINK) $^
+
+# This is a hook for subdirmk's test suite.
+&:-include src/for-test.sd.mk
diff --git a/subdirmk/example/src/toy.c b/subdirmk/example/src/toy.c
new file mode 100644 (file)
index 0000000..2f5dcb6
--- /dev/null
@@ -0,0 +1,12 @@
+/*
+ * subdirmk - example code
+ *  Copyright 2019 Mark Wooding
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ * There is NO WARRANTY.
+ */
+
+#include <stdio.h>
+#include "toylib.h"
+
+int main(void)
+  { fputs(greeting(), stdout); return (0); }
diff --git a/subdirmk/example/subdirmk b/subdirmk/example/subdirmk
new file mode 120000 (symlink)
index 0000000..a96aa0e
--- /dev/null
@@ -0,0 +1 @@
+..
\ No newline at end of file
diff --git a/subdirmk/generate b/subdirmk/generate
new file mode 100755 (executable)
index 0000000..b65ba1d
--- /dev/null
@@ -0,0 +1,568 @@
+#!/usr/bin/perl -w
+#
+# subdirmk - &-filter (makefile generation program)
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+#
+# $(srcdir)/subdirmk/generate [--srcdir=SRCDIR] [--] SUBDIR...
+#
+# generates in each subdirectory
+#     Dir.mk.tmp
+#     Makefile
+# and in toplevel
+#     main.mk.tmp
+
+use strict;
+use POSIX;
+
+print "$0 @ARGV\n" or die $!;
+
+our $srcdir='.';
+
+# error handling methods:
+#
+# Error in input file, while $err_file and $. set, eg in most of
+# process_input_mk:
+#         err "message";
+#
+# Other input or usage errors:
+#         die "subdirmk: $file:$lno: problem\n";
+#         die "subdirmk: some problem not locatable in that way\n";
+#
+# Usage error:
+#         die "subdirmk $0: explanation of problem\n";
+#
+# System call error (not ENOENT) accessing input/output files:
+#         die "description of problem eg maybe erbing noun: $!\n";
+#
+# Bug detedcted in `generate':
+#         die "internal error (some information)?"; # or similar
+
+while (@ARGV && $ARGV[0] =~ m/^-/) {
+    $_ = shift @ARGV;
+    last if $_ eq '--';
+    if (s/^--srcdir=//) {
+       $srcdir=$';
+    } else {
+       die "subdirmk $0: unknown option \`$_'\n";
+    }
+}
+our @subdirs = @ARGV;
+
+s{/+$}{} foreach @subdirs;
+
+our $root = [ '.', [ ], 1 ];
+# each node is [ 'relative subdir name', \@children, $mentioned ]
+
+sub build_tree () {
+    foreach my $subdir (@subdirs) {
+       my @path = $subdir eq '.' ? () : split m{/+}, $subdir;
+       my $node = $root;
+       foreach my $d (@path) {
+           my ($c,) = grep { $_->[0] eq $d } @{ $node->[1] };
+           if (!$c) {
+               $c = [ $d, [ ] ];
+               push @{ $node->[1] }, $c;
+           }
+           $node = $c;
+       }
+       $node->[2] = 1;
+    }
+}
+
+sub target_varname ($$) {
+    my ($var_prefix, $target) = @_;
+    return $var_prefix.'TARGETS'.($target eq 'all' ? '' : "_$target");
+}
+
+our $writing_output;
+our $buffering_output;
+our %output_files;
+our %input_files;
+our @output_makefiles;
+
+sub close_any_output_file() {
+    return unless defined $writing_output;
+    O->error and die "error writing $writing_output.tmp: $! (?)\n";
+    close O or die "error closing $writing_output.tmp: $!\n";
+    $writing_output = undef;
+}
+
+sub oraw {
+    die 'internal error' unless defined $writing_output;
+    print O @_ or die "error writing $writing_output.tmp: $!\n";
+}
+
+sub oud { # undoubled
+    if (defined $buffering_output) {
+       $buffering_output .= $_ foreach @_;
+       return;
+    }
+    oraw @_;
+}
+
+our $ddbl;
+
+sub od { # maybe $-doubled
+    if (!$ddbl) {
+       oud @_;
+       return;
+    }
+    foreach (@_) {
+       my $e = $_;
+       $e =~ s{\$}{\$\$}g;
+       oud $e;
+    }
+}
+
+sub start_output_file ($) {
+    close_any_output_file();
+    ($writing_output) = @_;
+    die "internal error ($writing_output?)"
+       if $output_files{$writing_output}++;
+    my $tmp = "$writing_output.tmp";
+    open O, ">", $tmp or die "create $tmp: $!\n";
+    oraw "# autogenerated - do not edit\n";
+}
+
+sub install_output_files () {
+    close_any_output_file();
+    foreach my $f (sort keys %output_files) {
+       rename "$f.tmp", $f or die "install new $f: $!\n";
+    }
+}
+
+sub write_makefile ($$) {
+    my ($dir_prefix,$depth) = @_;
+    #print STDERR "write_makefile @_\n";
+    start_output_file("${dir_prefix}Makefile");
+    my $cd = $depth ? join('/', ('..',) x $depth) : '.';
+    my $suppress_templates=
+       '$(if $(filter-out clean real-clean, $(subdirmk_targets)),,'.
+       ' MAKEFILE_TEMPLATES=)';
+    oraw <<END;
+default: all
+\$(filter-out all,\$(MAKECMDGOALS)) all: run-main.mk
+       \@:
+subdirmk_targets:=\$(or \$(MAKECMDGOALS),all)
+Makefile run-main.mk:
+       \$(MAKE) -C $cd -f main.mk \$(addprefix ${dir_prefix},\$(subdirmk_targets))$suppress_templates
+.SUFFIXES:
+.PHONY:        run-main.mk
+END
+}
+
+our %varref;
+our %varref_exp;
+
+our ($dir_prefix, $dir_suffix, $dir_name,
+     $var_prefix, $var_prefix_name);
+
+sub dir_prefix ($) {
+    my ($path) = @_;
+    join '', map { "$_/" } @$path;
+}
+
+sub set_dir_vars ($) {
+    my ($path) = @_;
+    $dir_prefix = dir_prefix($path);
+    $dir_suffix = join '', map { "/$_" } @$path;
+    $dir_name = join '/', @$path ? @$path : '.';
+    $var_prefix_name = join '_', @$path ? @$path : qw(TOP);
+    $var_prefix = "${var_prefix_name}_";
+}
+
+our $err_file;
+
+our @warn_ena_dfl = map { $_ => 1 } qw(
+    local+global
+    single-char-var
+    unknown-warning
+    broken-var-ref
+);
+our %warn_ena = @warn_ena_dfl;
+
+our $warned;
+our %warn_unk;
+
+sub err ($) {
+    my ($m) = @_;
+    die defined $err_file
+       ? "subdirmk: ${err_file}:$.: $m\n"
+       : "subdirmk: $m\n";
+}
+
+sub wrncore ($$) {
+    my ($wk,$m) = @_;
+    return 0 unless $warn_ena{$wk} // warn "internal error $wk ?";
+    $warned++;
+    print STDERR "subdirmk: warning ($wk): $m\n";
+    return 1;
+}
+
+sub wrn ($$) {
+    my ($wk,$m) = @_;
+    our %warn_dedupe;
+    return 0 if $warn_dedupe{$err_file,$.,$wk,$m}++;
+    wrncore($wk, "${err_file}:$.: $m");
+}
+
+sub ddbl_only ($) {
+    my ($e) = @_;
+    return if $ddbl;
+    err "escape &$e is valid only during \$-doubling";
+}
+
+sub process_input_mk ($$$$);
+sub process_input_mk ($$$$) {
+    my ($targets, $f, $esclitr, $enoent_ok) = @_;
+
+    my $caps_re = qr{[A-Z]};
+    my $lc_re = qr{[a-z]};
+
+    my $esc;
+    my $set_esc = sub {
+       $esc = $$esclitr;
+       $esc =~ s/\W/\\$&/g;
+    };
+    $set_esc->();
+
+    my $input = new IO::File $f, '<';
+    if (!$input) {
+       err "open $f: $!" unless $!==ENOENT && $enoent_ok;
+       return;
+    }
+    $input_files{$f}++;
+
+    local $err_file=$f;
+
+    my %srcdirmap = (
+                 '^' => "\${top_srcdir}${dir_suffix}",
+                 '~' => "\${top_srcdir}",
+                   );
+    my %pfxmap = (
+                 ''  => $dir_prefix,
+                );
+    $pfxmap{$_} = $srcdirmap{$_}.'/' foreach keys %srcdirmap;
+
+    local $ddbl;
+    my @nest = (['']);
+    my $evalcall_brackets;
+
+    my $push_nest = sub {
+       my ($nk, $nndbl, $what) = @_;
+       unshift @nest, [ $nk, $ddbl, $what, $. ];
+       $ddbl = $nndbl;
+    };
+    my $pop_nest = sub {
+       my ($nk) = @_;
+       err "unexpectedly closed $nk in middle of $nest[0][0] ($nest[0][2])"
+           unless $nest[0][0] eq $nk;
+       $ddbl = (shift @nest)[1];
+    };
+
+    # Our detection of variable settings does not have to be completely
+    # accurate, since it is only going to be used for advice to the user.
+    my $note_varref = sub {
+       my ($vn,$amp) = @_;
+       my $exp = !!$varref_exp{$vn}{$amp};
+       $varref{$vn}{$exp}{$amp}{"$f:$."} = 1;
+    };
+
+    while (<$input>) {
+       if (m#^\s*($esc)?(\w+)\s*(?:=|\+=|\?=|:=)# ||
+           m#^\s*(?:$esc\:macro|define)\s+($esc)?(\S+)\s#) {
+           $note_varref->($2,!!$1);
+       }
+       if (s#^\s*$esc\:changequote\s+(\S+)\s+$##) {
+           $$esclitr = $1;
+           $set_esc->();
+           next;
+       } elsif (s#^\s*$esc\:endm\s+$##) {
+           $pop_nest->('macro');
+           od "endef\n";
+           next;
+       } elsif (s#^\s*$esc\:warn\s+(\S.*)$##) {
+           foreach my $wk (split /\s+/, $1) {
+               my $yes = $wk !~ s{^!}{};
+               if (defined $warn_ena{$wk}) {
+                   $warn_ena{$wk} = $yes;
+                   next;
+               } elsif ($yes) {
+                   wrn 'unknown-warning',
+                       "unknown warning $wk requested";
+               } else {
+                   $warn_unk{$wk} //= "$f:$.";
+               }
+           }
+           next;
+       } elsif (s#^\s*$esc\:local\+global\s+(\S.*)$##) {
+           foreach my $vn (split /\s+/, $1) {
+               my $pos = !($vn =~ s{^!}{});
+               my $amp = $vn =~ s{^$esc}{};
+               $varref_exp{$vn}{!!$amp} = $pos;
+           }
+           next;
+       } elsif (s#^\s*$esc\:(?=(-?)include|macro)##) {
+           $buffering_output='';
+       } elsif (m#^\s*$esc\:([a-z][-+0-9a-z_]*)#) {
+           err "unknown directive &:$1 or bad argumnt syntax";
+       } elsif (s{^\s*${esc}TARGETS(?:_([0-9a-zA-Z_]+))?(?=\W)}{}) {
+           my $t = $1 // 'all';
+           my $vn = target_varname($var_prefix, $t);
+           $note_varref->($vn,1);
+           od $vn;
+           $targets->{$t} //= [ ];
+       }
+       for (;;) {
+           err 'cannot $-double &-processed RHS of directive'
+               if $ddbl && defined $buffering_output;
+           unless ($nest[0][0] eq 'eval'
+                   ? s{^(.*?)($esc|\$|[{}])}{}
+                   : s{^(.*?)($esc|\$)}{}) { od $_; last; }
+           od $1;
+           if ($2 eq '{') {
+               od $2;
+               $evalcall_brackets++;
+               next;
+           } elsif ($2 eq '}') {
+               od $2;
+               next if --$evalcall_brackets;
+               $pop_nest->('eval');
+               od '}';
+               next;
+           } elsif ($2 eq '$') {
+               od $2;
+               if (s{^\$}{}) { od $&; }
+               elsif (m{^[a-zA-Z]\w}) {
+                   wrn 'single-char-var',
+                   'possibly confusing unbracketed single-char $-expansion';
+               }
+               elsif (m{^$esc}) {
+                   wrn 'broken-var-ref',
+                   'broken $&... expansion; you probably meant &$';
+               }
+               elsif (m{^\(($esc)?([^()\$]+)\)} ||
+                      m{^\{($esc)?([^{}\$]+)\}}) {
+                   $note_varref->($2,!!$1);
+               }
+               next;
+           }
+           if (s{^\\$esc}{}) { od "$$esclitr" }
+           elsif (s{^:}{}) { od "$$esclitr:" }
+           elsif (s{^\\\$}{}) { oud '$' }
+           elsif (s{^\\\s+$}{}) { }
+           elsif (s{^$esc}{}) { od "$$esclitr$$esclitr" }
+           elsif (m{^(?=$caps_re)}) { od $var_prefix }
+           elsif (s{^\$([A-Za-z]\w+)}{}) {
+               $note_varref->($1,1);
+               od "\${${var_prefix}$1}";
+           }
+           elsif (s{^([~^]?)(?=$lc_re)}{}) { od $pfxmap{$1} }
+           elsif (s{^_}{}) { od $var_prefix }
+           elsif (s{^=}{}) { od $var_prefix_name }
+           elsif (s{^([~^]?)/}{}) { od $pfxmap{$1} }
+           elsif (s{^\.}{}) { od $dir_name }
+           elsif (s{^([~^])\.}{}) { od $srcdirmap{$1} }
+           elsif (s{^\$\-}{}) { $ddbl=undef; }
+           elsif (s{^\$\+}{}) { $ddbl=1; }
+           elsif (s{^\$\(}{}) {
+               ddbl_only($&); oud "\$(";
+               $note_varref->($2,!!$1) if m{^($esc)?([^()\$]+\))};
+           }
+           elsif (s{^\$(\d+)}{}) { ddbl_only($&); oud "\${$1}"; }
+           elsif (s{^\(\s*$esc(?=$lc_re)}{}) { od "\$(call ${var_prefix}" }
+           elsif (s{^\(\s*(?=\S)}{}        ) { od "\$(call "              }
+           elsif (s{^\{}{}) {
+               err 'macro invocation cannot be re-$-doubled' if $ddbl;
+               od '${eval ${call ';
+               $evalcall_brackets = 1;
+               $push_nest->('eval',1, '&{...}');
+               $note_varref->($2,!!$1) if m{^\s*($esc)?([^,{}\$]+)};
+           } elsif (s{^([~^]?)(?=[ \t])}{}) {
+               my $prefix = $pfxmap{$1} // die "internal error ($1?)";
+               my $after='';
+               if (m{([ \t])$esc}) { ($_,$after) = ($`, $1.$'); }
+               s{(?<=[ \t])(?=\S)(?!\\\s*$)}{$prefix}g;
+               od $_;
+               $_ = $after;
+           } elsif (s{^\#}{}) {
+               $_ = '';
+           } elsif (s{^![ \t]+}{}) {
+               od $_;
+               $_ = '';
+           } else {
+               m{^.{0,5}};
+               err "bad &-escape \`$$esclitr$&'";
+           }
+       }
+       if (defined $buffering_output) {
+           $_=$buffering_output;
+           $buffering_output=undef;
+           if (m#^(-?)include\s+(\S+)\s+$#) {
+               my $subf = "$srcdir/$2";
+               process_input_mk($targets, $subf, $esclitr, $1);
+               od "\n";
+           } elsif (m#^macro\s+(\S+)\s+$#) {
+               od "define $1\n";
+               $push_nest->('macro', 1, '&:macro');
+           } else {
+               err "bad directive argument syntax";
+           }
+       }
+    }
+    die "subdirmk: $f:$nest[0][3]: unclosed $nest[0][0] ($nest[0][2])\n"
+       if $nest[0][0];
+    $input->error and die "read $f: $!\n";
+    close $input or die "close $f: $!\n";
+}
+
+sub filter_subdir_mk ($) {
+    my ($targets) = @_;
+
+    #use Data::Dumper;
+    #print STDERR "filter @_\n";
+
+    my $esclit = '&';
+
+    my $pi = sub {
+       my ($f, $enoentok) = @_;
+       process_input_mk($targets, "${srcdir}/$f", \$esclit, $enoentok);
+    };
+    $pi->("Prefix.sd.mk",           1);
+    $pi->("${dir_prefix}Dir.sd.mk", 0);
+    $pi->("Suffix.sd.mk",           1);
+}
+
+sub process_subtree ($$);
+sub process_subtree ($$) {
+    # => list of targets (in form SUBDIR/)
+    # recursive, children first
+    my ($node, $path) = @_;
+
+    #use Data::Dumper;
+    #print STDERR Dumper(\@_);
+
+    local %varref_exp;
+
+    my $dir_prefix = dir_prefix($path);
+    # ^ this is the only var which we need before we come back from
+    #   the recursion.
+
+    push @output_makefiles, "${dir_prefix}Dir.mk";
+    write_makefile($dir_prefix, scalar @$path);
+
+    my %targets = (all => []);
+    foreach my $child (@{ $node->[1] }) {
+       my @childpath = (@$path, $child->[0]);
+       my $child_subdir = join '/', @childpath;
+       mkdir $child_subdir or $!==EEXIST or die "mkdir $child_subdir: $!\n";
+       local %warn_ena = @warn_ena_dfl;
+       push @{ $targets{$_} }, $child_subdir foreach
+           process_subtree($child, \@childpath);
+    }
+
+    set_dir_vars($path);
+    start_output_file("${dir_prefix}Dir.mk.tmp");
+
+    if ($node->[2]) {
+       filter_subdir_mk(\%targets);
+    } else {
+       my $sdmk = "${dir_prefix}Dir.sd.mk";
+       if (stat $sdmk) {
+           die
+ "subdirmk: $sdmk unexpectedly exists (${dir_prefix} not mentioned on subdirmk/generate command line, maybe directory is missing from SUBDIRMK_SUBDIRS)";
+       } elsif ($!==ENOENT) {
+       } else {
+           die "stat $sdmk: $!\n";
+       }
+    }
+
+    oraw "\n";
+
+    my @targets = sort keys %targets;
+    foreach my $target (@targets) {
+       my $target_varname = target_varname($var_prefix, $target);
+       oraw "${dir_prefix}${target}:: \$($target_varname)";
+       foreach my $child_subdir (@{ $targets{$target} }) {
+           oraw " $child_subdir/$target";
+       }
+       oraw "\n";
+    }
+    if (@targets) {
+       oraw ".PHONY:";
+       oraw " ${dir_prefix}${_}" foreach @targets;
+       oraw "\n";
+    }
+
+    return @targets;
+}
+
+sub process_final ($) {
+    my ($otargets) = @_;
+    set_dir_vars([]);
+    push @output_makefiles, "Final.mk";
+    start_output_file("Final.mk.tmp");
+    my %ntargets;
+    my $esclit='&';
+    process_input_mk(\%ntargets, "${srcdir}/Final.sd.mk", \$esclit, 1);
+    delete $ntargets{$_} foreach @$otargets;
+    my @ntargets = sort keys %ntargets;
+    die "subdirmk: Final.sd.mk may not introduce new top-level targets".
+       " (@ntargets)\n" if @ntargets;
+}
+
+sub process_tree() {
+    my @targets = process_subtree($root, [ ]);
+    process_final(\@targets);
+    start_output_file("main.mk.tmp");
+    foreach my $v (qw(top_srcdir abs_top_srcdir)) {
+       oraw "$v=\@$v@\n";
+    }
+    oraw "SUBDIRMK_MAKEFILES :=\n";
+    oraw "MAKEFILE_TEMPLATES :=\n";
+    foreach my $mf (@output_makefiles) {
+       oraw "SUBDIRMK_MAKEFILES += $mf\n";
+    }
+    foreach my $input (sort keys %input_files) {
+       oraw "MAKEFILE_TEMPLATES += $input\n";
+    }
+    oraw "include \$(SUBDIRMK_MAKEFILES)\n";
+}
+
+sub flmap ($) { local ($_) = @_; s{:(\d+)$}{ sprintf ":%10d", $1 }e; $_; }
+
+sub print_varref_warnings () {
+    foreach my $vn (sort keys %varref) {
+       my $vv = $varref{$vn};
+       next unless $vv->{''}{''} && $vv->{''}{1};
+       wrncore 'local+global', "saw both $vn and &$vn" or return;
+       foreach my $exp ('', 1) {
+       foreach my $amp ('', 1) {
+           printf STDERR
+               ($exp
+                ? " expectedly saw %s%s at %s\n"
+                : " saw %s%s at %s\n"),
+               ($amp ? '&' : ''), $vn, $_
+               foreach
+               sort { flmap($a) cmp flmap($b) }
+               keys %{ $vv->{$exp}{$amp} };
+       }
+        }
+    }
+}
+
+sub print_warning_warnings () {
+    return unless $warned;
+    foreach my $wk (sort keys %warn_unk) {
+       wrncore 'unknown-warning',
+           "$warn_unk{$wk}: attempt to suppress unknown warning(s) \`$wk'";
+    }
+}
+
+build_tree();
+process_tree();
+print_varref_warnings();
+print_warning_warnings();
+install_output_files();
diff --git a/subdirmk/regen.mk.in b/subdirmk/regen.mk.in
new file mode 100644 (file)
index 0000000..d11e05c
--- /dev/null
@@ -0,0 +1,81 @@
+# subdirmk - rules for regenerating makefiles etc.
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+# Usage:
+#   include subdirmk/regen.mk
+# (probably in toplevel Dir.sd.mk)
+#
+# Arranges that config.status is automatically rerun to update
+# makefiles from templates, whenever a template *.sd.mk or *.mk.in is
+# edited; and that autoconf is rerun if configure's inputs are edited.
+#
+# If you add includes to configure.ac, add them to CONFIGURE_ACS.
+#
+# Makefiles updated by config.status and passed to SUBDIRMK_MAKEFILES
+# in configure.ac are automatically handled too.  If you have other
+# files updated by config.status (eg, the output of autoheader) you
+# need to put them in CONFIG_STATUS_OUTPUTS (before your inclusion
+# of regen.mk).
+#
+# Also provides a `realclean::' target at the toplevel which deletes
+# the autoconf output.  (This is suitable for being part of a recursive
+# target creaed by setting &TARGETS_realclean in appropriate .sd.mk.)
+
+CONFIGURE      ?= configure
+CONFIGURE_AC   ?= $(CONFIGURE).ac
+CONFIG_STATUS  ?= config.status
+
+CONFIGURE_ACS  += $(CONFIGURE_AC)
+CONFIGURE_ACS  += subdirmk/subdirmk.ac
+
+# To turn on debugging here, export SUBDIRMK_REGEN_NDEBUG=''
+SUBDIRMK_REGEN_NDEBUG ?= @
+
+$(top_srcdir)/$(CONFIGURE): $(addprefix $(top_srcdir)/,$(CONFIGURE_ACS))
+       cd $(top_srcdir) && autoconf
+
+$(CONFIG_STATUS): $(top_srcdir)/$(CONFIGURE)
+       ./$(CONFIG_STATUS) --recheck
+
+# generate will add all its own inputs and outputs to these variables
+SUBDIRMK_MAKEFILES += @_SUBDIRMK_MAKEFILES@
+MAKEFILE_TEMPLATES += $(addprefix $(top_srcdir)/, $(addsuffix .in, \
+       @_SUBDIRMK_MAKEFILES@ \
+       ))
+
+main.mk $(SUBDIRMK_MAKEFILES) $(CONFIG_STATUS_OUTPUTS): .makefiles.stamp
+       $(SUBDIRMK_REGEN_NDEBUG): REGEN STAMP CAUSES TARGET=$@
+
+.makefiles.stamp:                                              \
+               $(top_srcdir)/subdirmk/generate                 \
+               $(CONFIG_STATUS)                                \
+               $(MAKEFILE_TEMPLATES)
+# This filtering arranges that we can often run config.status to
+# generate only particular output files.  We look for *inputs* that
+# have changed.  If the only inputs that have changed are ones that we
+# know affect only one output (Dir.sd.mk, Final.sd.mk and *.mk.in),
+# we pass config.status the corresponding output file names.
+# Otherwise we pass nothing and config.status does them all.  We need
+# to mention Dir.sd.mk twice because if $(top_srcdir) is `.', make
+# elides the directory part from $?.  Similarly but not identically
+# Final.sd.mk.
+       $(SUBDIRMK_REGEN_NDEBUG): REGEN STAMP WANTS DEPS=$?
+       ./$(CONFIG_STATUS) $(if                                 \
+               $(filter-out Dir.sd.mk %/Dir.sd.mk              \
+                            Final.sd.mk $(top_srcdir)/Final.sd.mk \
+                            %.mk.in                            \
+                       , $?),,                                 \
+               $(patsubst $(top_srcdir)/%,%, $(sort            \
+                       $(patsubst %.sd.mk,%.mk,$(filter %.sd.mk,$?)) \
+                       $(patsubst %.mk.in,%.mk,$(filter %.mk.in,$?)))))
+       touch $@
+
+realclean:: clean
+       $(RM) config.status config.log
+       $(RM) main.mk $(SUBDIRMK_MAKEFILES) @_SUBDIRMK_MAKEFILES@
+       $(RM) $(addsuffix Makefile,$(dir $(SUBDIRMK_MAKEFILES)))
+
+-include $(ALL_DEPFILES)
diff --git a/subdirmk/subdirmk.ac b/subdirmk/subdirmk.ac
new file mode 100644 (file)
index 0000000..93c77d9
--- /dev/null
@@ -0,0 +1,38 @@
+dnl -*-autoconf-*-
+dnl subdirmk - autoconf macros
+dnl  Copyright 2019 Mark Wooding
+dnl  Copyright 2019 Ian Jackson
+dnl SPDX-License-Identifier: LGPL-2.0-or-later
+dnl There is NO WARRANTY.
+
+_SUBDIRMK_MAKEFILES=""
+AC_SUBST([_SUBDIRMK_MAKEFILES])
+
+AC_DEFUN([SUBDIRMK_SUBDIRS],
+[_SUBDIRMK_INIT
+m4_map_args_w([$1],[_SUBDIRMK_SUBDIR(],[/)])])dnl
+
+AC_DEFUN_ONCE([_SUBDIRMK_INIT],[
+  AC_CONFIG_FILES([
+       main.mk:main.mk.tmp
+       Dir.mk:Dir.mk.tmp
+       Final.mk:Final.mk.tmp
+       ],[],[
+     '$srcdir'/subdirmk/generate --srcdir='$srcdir' $subdirmk_subdirs
+  ])
+  SUBDIRMK_MAKEFILES([subdirmk/regen.mk subdirmk/usual.mk])
+])
+
+AC_DEFUN([_SUBDIRMK_SUBDIR],[
+  subdirmk_subdirs="$subdirmk_subdirs '$1'"
+  AC_CONFIG_FILES([$1Dir.mk:$1Dir.mk.tmp])
+])
+
+AC_DEFUN([SUBDIRMK_MAKEFILES],
+[_SUBDIRMK_INIT
+m4_map_args_w([$1],[_SUBDIRMK_MAKEFILE(],[)])])dnl
+
+AC_DEFUN([_SUBDIRMK_MAKEFILE],[
+  _SUBDIRMK_MAKEFILES="$_SUBDIRMK_MAKEFILES $1"
+  AC_CONFIG_FILES([$1:$1.in])
+])
diff --git a/subdirmk/tests/.gitignore b/subdirmk/tests/.gitignore
new file mode 100644 (file)
index 0000000..4d0028c
--- /dev/null
@@ -0,0 +1,7 @@
+# subdirmk - part of the test suite
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+*/log
diff --git a/subdirmk/tests/advance-tested b/subdirmk/tests/advance-tested
new file mode 100755 (executable)
index 0000000..fc38869
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+# subdirmk - test suite runner helper script
+# Copyright various contributors - see top level README.
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+set -e
+
+branch=$(git symbolic-ref -q HEAD || test $? = 1)
+base=$(git merge-base tested HEAD)
+
+git branch -D test-failed 2>&1 ||:
+
+case "$branch" in
+refs/heads/tested|refs/heads/test-failed)
+       echo >&2 "unexpectedly on branch $branch"; exit 1 ;;
+refs/heads/*)
+       branch=${branch#refs/heads/} ;;
+*)
+       branch='';
+esac
+
+restore-branch () {
+       if [ "$branch" ]; then git checkout $branch; fi
+}
+
+git checkout --detach
+git clean -xdff
+
+if ! git rebase --exec 'tests/check && git branch -f tested' $base; then
+       git branch -f test-failed
+       git rebase --abort
+       echo >&2 '^ ignore previous message from git-rebase!'
+       echo >&2 'Test failed, made local branch ref test-failed'
+       restore-branch
+       exit 1
+fi
+
+restore-branch
diff --git a/subdirmk/tests/build-common b/subdirmk/tests/build-common
new file mode 100644 (file)
index 0000000..2f0cb60
--- /dev/null
@@ -0,0 +1,23 @@
+# subdirmk - part of the test suite
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+SUBDIRMK_REGEN_NDEBUG=''
+export SUBDIRMK_REGEN_NDEBUG
+
+make_copy () {
+       rm -rf tests/$1/example
+       mkdir tests/$1/example
+
+       git ls-files -z example \
+       | xargs -0 \
+       sh -xec 'rsync -R -l "$@" tests/'$1'/' x
+
+       rm tests/$1/example/subdirmk
+
+       git ls-files -z :. :!example \
+       | xargs -0 \
+       sh -xec 'rsync -R -l "$@" tests/'$1'/example/subdirmk' x
+}
diff --git a/subdirmk/tests/check b/subdirmk/tests/check
new file mode 100755 (executable)
index 0000000..2bb0d68
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+# subdirmk - toplevel invocation script for the test suite
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+set -e
+
+j=$(nproc 2>/dev/null || echo 1)
+j=$(( $j * 5 / 4 + 1 ))
+
+x () { echo "$@"; "$@"; }
+x ${MAKE-make} -f tests/tests.mk -j$j
+echo 'ok.'
diff --git a/subdirmk/tests/example/.gitignore b/subdirmk/tests/example/.gitignore
new file mode 100644 (file)
index 0000000..cbd3871
--- /dev/null
@@ -0,0 +1,7 @@
+# subdirmk - part of the test suite
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+example
diff --git a/subdirmk/tests/example/check b/subdirmk/tests/example/check
new file mode 100755 (executable)
index 0000000..d951eea
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/sh
+# subdirmk - part of the test suite
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+set -ex
+
+. tests/build-common
+
+make_copy example
+
+cd tests/example/example
+
+: ----- out of tree build -----
+
+mkdir build
+cd build
+>>../src/for-test.sd.mk
+>>../lib/for-test.mk.in
+>>../for-test-final.sd.mk
+.././autogen.sh && ../configure
+make -j4 all check
+
+: ----- testing rebuild on input change -----
+
+reset_times () {
+       cd ..
+
+       find ! -path './build/*' -type f -print0 \
+       | xargs -0 \
+       touch -hmd 'now -2000 seconds' --
+
+       cd build
+
+       find -type f -print0 \
+       | xargs -0 \
+       touch -hmd 'now -1000 seconds' --
+}
+
+: ----- for-check-1 -----
+reset_times
+echo 'for-check-1:' >>../src/for-test.sd.mk
+make -j4 for-check-1
+grep '^for-check-1:' src/Dir.mk || false
+
+: ----- for-check-2 -----
+reset_times
+echo 'for-check-2:' >>../lib/for-test.mk.in
+make -j4 for-check-2
+grep '^for-check-2:' lib/for-test.mk || false
+
+: ----- for-check-3 -----
+reset_times
+echo 'for-check-3:' >>../for-test-final.sd.mk
+make -j4 for-check-3
+grep '^for-check-3:' Final.mk
+
+echo ok.
diff --git a/subdirmk/tests/filter/.gitignore b/subdirmk/tests/filter/.gitignore
new file mode 100644 (file)
index 0000000..7b3ce72
--- /dev/null
@@ -0,0 +1,10 @@
+# subdirmk - part of the test suite
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+Makefile
+*.tmp
+doctests.sd.mk
+doctests.mk.part
diff --git a/subdirmk/tests/filter/Dir.mk.expected b/subdirmk/tests/filter/Dir.mk.expected
new file mode 100644 (file)
index 0000000..c195980
--- /dev/null
@@ -0,0 +1,30 @@
+# autogenerated - do not edit
+# subdirmk - test cases for generate script
+# Copyright various contributors - see top level README.
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+# Prefix in .
+
+WARN += 3
+TOP_WARN += 3
+# $WARN
+# $(WARN)
+# $(TOP_WARN)
+# ${TOP_WARN}
+
+# ${TOP_NOWARN1} $(NOWARN1)
+# ${TOP_NOWARN2} $(NOWARN2)
+
+${eval ${call  some-macro, 42, $$x, {  $(foreach something) } }}
+
+$TOP_FBAR
+
+# doctests:
+
+# Suffix in .
+
+all:: $(TOP_TARGETS) sub/all
+sometarget1:: $(TOP_TARGETS_sometarget1) sub/sometarget1
+sometarget2:: $(TOP_TARGETS_sometarget2) sub/sometarget2
+.PHONY: all sometarget1 sometarget2
diff --git a/subdirmk/tests/filter/Dir.sd.mk b/subdirmk/tests/filter/Dir.sd.mk
new file mode 100644 (file)
index 0000000..f5e2868
--- /dev/null
@@ -0,0 +1,22 @@
+&# subdirmk - test cases for generate script
+&# Copyright various contributors - see top level README.
+&# SPDX-License-Identifier: LGPL-2.0-or-later
+&# There is NO WARRANTY.
+
+WARN += 3
+&WARN += 3
+# $WARN
+# $(WARN)
+# $(&WARN)
+# &$WARN
+
+&:local+global NOWARN1 &NOWARN2
+# &$NOWARN1 $(NOWARN1)
+# &$NOWARN2 $(NOWARN2)
+
+&{ some-macro, 42, $x, { &$- $(foreach something) } }
+
+$&FBAR
+
+# doctests:
+&:include &doctests.sd.mk
diff --git a/subdirmk/tests/filter/Final.mk.expected b/subdirmk/tests/filter/Final.mk.expected
new file mode 100644 (file)
index 0000000..0eafd9a
--- /dev/null
@@ -0,0 +1,7 @@
+# autogenerated - do not edit
+# subdirmk - test cases for generate script
+# Copyright various contributors - see top level README.
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+# Final 
diff --git a/subdirmk/tests/filter/Final.sd.mk b/subdirmk/tests/filter/Final.sd.mk
new file mode 100644 (file)
index 0000000..d5f7e12
--- /dev/null
@@ -0,0 +1,6 @@
+# subdirmk - test cases for generate script
+# Copyright various contributors - see top level README.
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+# Final &/
diff --git a/subdirmk/tests/filter/Prefix.sd.mk b/subdirmk/tests/filter/Prefix.sd.mk
new file mode 100644 (file)
index 0000000..e3784af
--- /dev/null
@@ -0,0 +1,7 @@
+# subdirmk - test cases for generate script
+# Copyright various contributors - see top level README.
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+# Prefix in &.
+&:warn many-requests-for-unknown-warning
diff --git a/subdirmk/tests/filter/Suffix.sd.mk b/subdirmk/tests/filter/Suffix.sd.mk
new file mode 100644 (file)
index 0000000..a398c1b
--- /dev/null
@@ -0,0 +1,6 @@
+&# subdirmk - test cases for generate script
+&# Copyright various contributors - see top level README.
+&# SPDX-License-Identifier: LGPL-2.0-or-later
+&# There is NO WARRANTY.
+
+# Suffix in &.
diff --git a/subdirmk/tests/filter/check b/subdirmk/tests/filter/check
new file mode 100755 (executable)
index 0000000..7c4e6ac
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/bash
+# subdirmk - part of the test suite
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+set -e
+set -o pipefail
+
+cd tests/filter
+
+expand <../../README | ./extract-doctests . >/dev/null
+
+set +e
+../../generate sub/dir 2>stderr.tmp
+rc=$?
+set -e
+if [ $rc != 0 ]; then cat stderr.tmp; exit 1; fi
+
+ok=true
+
+files=$(find -name \*.expected)
+for f in $files; do
+       i=$f
+       o=$f.tmp
+       sed <$i >$o '
+               /^# doctests:/ {
+                       r '"${f%/*}/doctests.mk.part"'
+                       a
+               }
+       '
+       diff -u $f.tmp ${f%.expected}.tmp || ok=false
+done
+
+$ok
+
+echo ok.
diff --git a/subdirmk/tests/filter/extract-doctests b/subdirmk/tests/filter/extract-doctests
new file mode 100755 (executable)
index 0000000..44a94b2
--- /dev/null
@@ -0,0 +1,173 @@
+#!/usr/bin/perl -w
+# subdirmk - script for extracting doctests from README
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+#
+# usage:
+#   expand <README | tests/filter/extract-doctests tests/filter/
+# writes:
+#   tests/filter/doctests.mk.part
+#   tests/filter/sub/dir/doctests.mk.part
+#
+# Relies on some properties of the way README is laid out.
+# See comments below marked `parse:' and `adhoc:'.
+
+use strict;
+use Carp;
+use Data::Dumper;
+
+our @exp;
+# $exp[]{In}
+# $exp[]{Out}
+# $exp[]{OutTop}
+
+my $cent;
+my $in_changequote;
+my $lastl;
+my $csection;
+my $withspcs = qr{\S+(?: \S+)*};
+
+my $outdir = shift @ARGV // confess;
+
+while (<>) {
+    # adhoc: rely on structure of indented examples in &:changequote part
+    $in_changequote = (m{^\&\:changequote}...m{^\S}) && m{^\s};
+    if (m{^-----|^- - - - -}) {
+       # parse: rely on underlines for (sub)section headings
+       $csection = $lastl;
+       next;
+    }
+    $lastl = $_;
+    my $e = { L => $. };
+    # parse: rely on looking for => (and .. on subsequent lines)
+    next unless m{\=\>} or ($cent and m{ \.\. });
+    my $mapop = '=>';
+    # adhoc: special case NEWQUOTE here so we recognise things in changequote
+    if (s{^()(\&\:\w+(?: \S+)*)\s{2,}(\=\>)\s{2,}($withspcs)$}{} ||
+        s{^(\s*)(\&$withspcs|NEWQUOTE\S+|\$)\s+(\=\>|\.\.)\s+($withspcs)\s+}{} ||
+       $cent && s{^()($withspcs)\s{2,}(\.\.)\s{2,}($withspcs)$}{}) {
+       # adhoc: expected indented iff in changequote part
+       confess if length($1) xor $in_changequote;
+       $mapop = $3;
+       confess if !$cent && $mapop ne '=>';
+       $e->{In} = $2;
+       $e->{Out} = $4;
+       if (# adhoc: `or ...' introduces the `at toplevel' expansion
+           s{^or ($withspcs)$}{}) {
+           $e->{OutTop} = $1 eq 'nothing' ? '' : $1;
+       } elsif (# parse: expect other wordish things to be comments
+                m{^(?!or\b)\(?\w{2,} }) {
+       } elsif (m/^$/) {
+       } else {
+           confess "unk rhs $_ (In=\"$e->{In}\" out=\"$e->{Out}\"?";
+       }
+       $e->{CQ} = $in_changequote;
+       # adhoc: rely on this specific section title
+       $e->{DD} = $csection =~ m{^while dollar[- ]doubling}i;
+    } else {
+       confess "$_ ?";
+    }
+    if ($mapop eq '=>') {
+       if ($e->{In} =~ m/\bNN\b/) {
+           # adhoc: special case NN in examples
+           confess if defined $cent->{OutTop};
+           foreach my $nn (0..11, 999) {
+               my $f = { %$e };
+               foreach my $k (qw(In Out)) {
+                   $f->{$k} = $e->{$k};
+                   ($f->{$k} =~ s/\bNN\b/$nn/g) == 1 or confess;
+               }
+               push @exp, $f;
+           }
+           $cent=undef;
+       } else {
+           push @exp, $e;
+           $cent=$e;
+       }
+    } elsif ($mapop eq '..') {
+       confess if defined $cent->{OutTop};
+       foreach my $k (qw(In Out)) {
+           $cent->{$k} .= "\n".$e->{$k};
+       }
+    }
+}
+
+print Dumper(\@exp);
+
+sub oi { print I @_ or die $!; }
+sub oo { print O @_ or die $!; }
+sub oh { oi @_; oo @_; }
+
+sub write_permode ($$$$$;$$) {
+    my ($dir_prefix, $start, $end, $senl, $what,
+       $filter, $omap) = @_;
+    $filter //= sub { 1 };
+    $omap //= sub { $_[0] };
+    oi $start;
+    oh "${senl}# ----- $what starts -----\n";
+    foreach my $e (@exp) {
+       next unless $filter->($e);
+       my $desc = $e->{In};
+       $desc =~ s/\&/AMP /g;
+       $desc =~ s/\$/DOLLAR /g;
+       $desc =~ s/NEWQUOTE/NEW_QUOTE /g;
+       my ($f,$pdesc) = $desc =~ m/^(.*)\n/
+               ? ("\n# %s:\n%s\n\n", $1)
+               : ("%-30s: %s .\n", $desc);
+       my $o;
+       $o = $e->{OutTop} if $dir_prefix eq '';
+       $o //= $e->{Out};
+       $o =~ s{/sub/dir}{} if $dir_prefix eq '' && !defined $e->{OutTop};
+       $o = $omap->($o, $e);
+       oi sprintf $f, $pdesc, $e->{In};
+       oo sprintf $f, $pdesc, $o;
+    }
+    oi $end;
+    oh "${senl}# ----- $what ends -----\n";
+}
+    
+sub writeout ($) {
+    my ($dir_prefix) = @_;
+    open I, '>', "$outdir/${dir_prefix}doctests.sd.mk" or die $!;
+    open O, '>', "$outdir/${dir_prefix}doctests.mk.part" or die $!;
+    oh "# doctests start $dir_prefix\n";
+    write_permode($dir_prefix,
+                 '','','', 'normal',
+                sub { !$_[0]{DD} && !$_[0]{CQ} } );
+    write_permode($dir_prefix,
+                 '&$+', '&$-', "\n",
+                 'dollar doubling',
+                 sub {
+                     my ($e) = @_;
+                     # adhoc: skip &:macro in already-doubling part
+                     return 0 if $e->{In} =~ m{^\&\:macro};
+                     # adhoc: skip &${ ie eval in already-doubling part
+                     return 0 if $e->{In} =~ m{^\&\{};
+                     return 0 if $e->{CQ};
+                     return $e->{DD} || !grep {
+                         # If there are two entries with the same In,
+                         # use only the one from the `while dollar
+                         # doubling' section.  So entries there override
+                         # entries in the rest o the file.
+                         $_ ne $e && $_->{In} eq $e->{In}
+                     } @exp;
+                 },
+                 sub {
+                     $_=$_[0];
+                     s/\$/\$\$/g unless $_[1]{DD};
+                     $_;
+                 } );
+    write_permode($dir_prefix,
+                 "&:changequote NEWQUOTE\n",
+                 "NEWQUOTE:changequote &\n",
+                 "",
+                 'changequote',
+                 sub { $_[0]{CQ} } );
+    oh "# doctests end\n";
+    close I or die $!;
+}
+
+writeout('');
+writeout('sub/dir/');
diff --git a/subdirmk/tests/filter/main.mk.expected b/subdirmk/tests/filter/main.mk.expected
new file mode 100644 (file)
index 0000000..9b4931c
--- /dev/null
@@ -0,0 +1,17 @@
+# autogenerated - do not edit
+top_srcdir=@top_srcdir@
+abs_top_srcdir=@abs_top_srcdir@
+SUBDIRMK_MAKEFILES :=
+MAKEFILE_TEMPLATES :=
+SUBDIRMK_MAKEFILES += Dir.mk
+SUBDIRMK_MAKEFILES += sub/Dir.mk
+SUBDIRMK_MAKEFILES += sub/dir/Dir.mk
+SUBDIRMK_MAKEFILES += Final.mk
+MAKEFILE_TEMPLATES += ./Dir.sd.mk
+MAKEFILE_TEMPLATES += ./Final.sd.mk
+MAKEFILE_TEMPLATES += ./Prefix.sd.mk
+MAKEFILE_TEMPLATES += ./Suffix.sd.mk
+MAKEFILE_TEMPLATES += ./doctests.sd.mk
+MAKEFILE_TEMPLATES += ./sub/dir/Dir.sd.mk
+MAKEFILE_TEMPLATES += ./sub/dir/doctests.sd.mk
+include $(SUBDIRMK_MAKEFILES)
diff --git a/subdirmk/tests/filter/stderr.expected b/subdirmk/tests/filter/stderr.expected
new file mode 100644 (file)
index 0000000..3389ef9
--- /dev/null
@@ -0,0 +1,19 @@
+subdirmk: warning (unknown-warning): ./Prefix.sd.mk:7: unknown warning many-requests-for-unknown-warning requested
+subdirmk: warning (single-char-var): ./Dir.sd.mk:8: possibly confusing unbracketed single-char $-expansion
+subdirmk: warning (broken-var-ref): ./Dir.sd.mk:19: broken $&... expansion; you probably meant &$
+subdirmk: warning (local+global): saw both NOWARN1 and &NOWARN1
+ saw NOWARN1 at ./sub/dir/Dir.sd.mk:24
+ saw &NOWARN1 at ./Dir.sd.mk:14
+ expectedly saw NOWARN1 at ./Dir.sd.mk:14
+subdirmk: warning (local+global): saw both WARN and &WARN
+ saw WARN at ./Dir.sd.mk:6
+ saw WARN at ./Dir.sd.mk:9
+ saw WARN at ./sub/dir/Dir.sd.mk:19
+ saw WARN at ./sub/dir/Dir.sd.mk:22
+ saw &WARN at ./Dir.sd.mk:7
+ saw &WARN at ./Dir.sd.mk:10
+ saw &WARN at ./Dir.sd.mk:11
+ saw &WARN at ./sub/dir/Dir.sd.mk:18
+ saw &WARN at ./sub/dir/Dir.sd.mk:27
+ expectedly saw &WARN at ./sub/dir/Dir.sd.mk:21
+subdirmk: warning (unknown-warning): ./sub/dir/Dir.sd.mk:6: attempt to suppress unknown warning(s) `some-unknown-warning'
diff --git a/subdirmk/tests/filter/sub/Dir.mk.expected b/subdirmk/tests/filter/sub/Dir.mk.expected
new file mode 100644 (file)
index 0000000..0cd6e3f
--- /dev/null
@@ -0,0 +1,6 @@
+# autogenerated - do not edit
+
+sub/all:: $(sub_TARGETS) sub/dir/all
+sub/sometarget1:: $(sub_TARGETS_sometarget1) sub/dir/sometarget1
+sub/sometarget2:: $(sub_TARGETS_sometarget2) sub/dir/sometarget2
+.PHONY: sub/all sub/sometarget1 sub/sometarget2
diff --git a/subdirmk/tests/filter/sub/dir/Dir.mk.expected b/subdirmk/tests/filter/sub/dir/Dir.mk.expected
new file mode 100644 (file)
index 0000000..cde814d
--- /dev/null
@@ -0,0 +1,34 @@
+# autogenerated - do not edit
+# subdirmk - test cases for generate script
+# Copyright various contributors - see top level README.
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+# Prefix in sub/dir
+
+
+sub/dir/
+
+# sub_dir_TARGETS_notarget += 42
+sub_dir_TARGETS_sometarget1
+sub_dir_TARGETS_sometarget2
+
+line joining
+
+sub_dir_WARN += 4
+WARN += 4
+sub_dir_WARN += 5 # this warning suppressed, precisely
+WARN += 5
+
+$(NOWARN1)
+
+sub_dir_WARN += 6
+
+# doctests:
+
+# Suffix in sub/dir
+
+sub/dir/all:: $(sub_dir_TARGETS)
+sub/dir/sometarget1:: $(sub_dir_TARGETS_sometarget1)
+sub/dir/sometarget2:: $(sub_dir_TARGETS_sometarget2)
+.PHONY: sub/dir/all sub/dir/sometarget1 sub/dir/sometarget2
diff --git a/subdirmk/tests/filter/sub/dir/Dir.sd.mk b/subdirmk/tests/filter/sub/dir/Dir.sd.mk
new file mode 100644 (file)
index 0000000..f358e77
--- /dev/null
@@ -0,0 +1,30 @@
+&# subdirmk - subdirectory test cases
+&# Copyright various contributors - see top level README.
+&# SPDX-License-Identifier: LGPL-2.0-or-later
+&# There is NO WARRANTY.
+
+&:warn !some-unknown-warning
+
+&:changequote &
+&/
+
+# &TARGETS_notarget += 42
+&TARGETS_sometarget1
+&TARGETS_sometarget2
+
+line &\
+joining
+
+&WARN += 4
+WARN += 4
+&:local+global &WARN
+&WARN += 5 # this warning suppressed, precisely
+WARN += 5
+
+$(NOWARN1)
+
+&:local+global !&WARN
+&WARN += 6
+
+# doctests:
+&:include &doctests.sd.mk
diff --git a/subdirmk/tests/filter/update-expected b/subdirmk/tests/filter/update-expected
new file mode 100755 (executable)
index 0000000..b9c79e7
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+# subdirmk - part of the test suite
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+#
+# Usual approach to updating the expected outputs is
+#   tests/filter/check
+#   tests/filter/update-expected
+#   selectively git-add the things that are right, after inspecting them
+
+set -e
+files=$(find tests/filter -name \*.expected.tmp)
+for f in $files; do
+       perl -pe '
+               (s/\n//, $stripnl=0) if $stripnl;
+               next unless /^# doctests start/../^# doctests end/;
+               $_="";
+               $stripnl=1;
+       ' \
+               <${f%.expected.tmp}.tmp >${f%.tmp}
+done
diff --git a/subdirmk/tests/intree/.gitignore b/subdirmk/tests/intree/.gitignore
new file mode 100644 (file)
index 0000000..cbd3871
--- /dev/null
@@ -0,0 +1,7 @@
+# subdirmk - part of the test suite
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+example
diff --git a/subdirmk/tests/intree/check b/subdirmk/tests/intree/check
new file mode 100755 (executable)
index 0000000..68cfc00
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/sh
+# subdirmk - part of the test suite
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+set -ex
+
+. tests/build-common
+
+make_copy intree
+
+cd tests/intree/example
+
+./autogen.sh && ./configure
+make -j4 all check
+make -j4 clean
+make -j4 all check
+
+echo ok.
diff --git a/subdirmk/tests/make-release b/subdirmk/tests/make-release
new file mode 100755 (executable)
index 0000000..1090feb
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+# subdirmk - release script
+# Copyright various contributors - see top level README.
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+set -e
+
+fail () { echo >&2 "error: $*"; $dryrun exit 1; }
+
+case "$1" in
+-n)    dryrun=: ; shift ;;
+-*)    fail "unknown option $1" ;;
+esac
+
+x () { echo >&2 " $*"; $dryrun "$@"; }
+
+head=$(git rev-parse HEAD~0)
+for branch in master tested; do
+       bv=$(git rev-parse refs/heads/$branch)
+       test $bv = $head || fail "error: HEAD=$head, $branch=$bv"
+done
+
+status=$(git status --porcelain --ignored)
+if [ "$status" ]; then
+       printf >&2 '%s\n' "$status"
+       fail 'tree not sufficiently clean'
+fi
+
+v="$1"
+
+case "$v" in
+subdirmk/*) v=${v#subdirmk/} ;;
+esac
+
+case "$v" in
+[0-9]*.*) ;;
+*) fail 'bad version' ;;
+esac
+
+tag=subdirmk/$v
+key=0x559AE46C2D6B6D3265E7CBA1E3E3392348B50D39
+
+export GPG_TTY=`tty` # wtf
+x git tag -s -u $key -m "subdirmk $v" $tag
+
+x git push origin master $tag
+
+$dryrun echo 'done.'
diff --git a/subdirmk/tests/tests.mk b/subdirmk/tests/tests.mk
new file mode 100644 (file)
index 0000000..34bb353
--- /dev/null
@@ -0,0 +1,15 @@
+# subdirmk - part of the test suite
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+TESTS=$(wildcard tests/*/check)
+
+all: $(addsuffix .done, $(TESTS))
+
+.PHONY: tests/%/check.done all
+
+tests/%/check.done:
+       tests/$*/check >tests/$*/log 2>&1
+       @echo $* ok.
diff --git a/subdirmk/usual.mk.in b/subdirmk/usual.mk.in
new file mode 100644 (file)
index 0000000..6cb0930
--- /dev/null
@@ -0,0 +1,33 @@
+# subdirmk - usual variable settings
+#  Copyright 2019 Mark Wooding
+#  Copyright 2019 Ian Jackson
+# SPDX-License-Identifier: LGPL-2.0-or-later
+# There is NO WARRANTY.
+
+# Usage:
+#   include subdirmk/usual.mk
+# (probably in toplevel Dir.sd.mk)
+#
+# Provides various conventional `make' variables, and a
+# rule for compiling C programs.
+
+VPATH          = $(top_srcdir)
+
+prefix         = @prefix@
+exec_prefix    = @exec_prefix@
+bindir         = @bindir@
+
+CC             ?= @CC@
+CFLAGS         ?= @CFLAGS@
+DEFS           ?= @DEFS@
+INCLUDES       ?= @INCLUDES@
+LD             ?= @CC@
+LDFLAGS                ?= @LDFLAGS@
+LIBS           ?= @LIBS@
+
+LINK           ?= $(CC) -o$@ $(CFLAGS) $(LDFLAGS)
+AR             ?= ar
+COMPILE                ?= $(CC) -c -o$@ $(CDEPS_CFLAGS) $(DEFS) $(INCLUDES) $(CFLAGS)
+
+%.o: %.c
+       $(COMPILE) $<
diff --git a/test-common.sd.mk b/test-common.sd.mk
new file mode 100644 (file)
index 0000000..2bc48eb
--- /dev/null
@@ -0,0 +1,36 @@
+
+include common.make
+
+&TESTSCRIPTS ?= $(wildcard &^/t-[a-z]*[0-9a-z])
+ifneq ($(OLD_SECNET_DIR),)
+&TESTSCRIPTS += $(wildcard &^/t-C*[0-9a-z])
+endif
+
+&TESTNAMES := $(patsubst t-%,%,$(notdir $(&TESTSCRIPTS)))
+
+&DEPS += $(src)/test-common.tcl
+&DEPS += common.make
+&DEPS += $(src)/test-common.sd.mk
+&DEPS += &/Dir.mk
+
+&check-real: $(foreach t,$(&TESTNAMES),&d-$t/ok)
+
+RECHECK_RM += &d-*
+
+CHECK_SILENT ?= @
+
+&d-%/ok: &^/t-% $(&DEPS)
+       $(CHECK_SILENT) rm -rf &d-$*; mkdir &d-$*
+       $(CHECK_SILENT) export SECNET_TEST_BUILDDIR=$(topbuilddir); \
+        export PYTHONBYTECODEBASE=/dev/null; \
+        cd $(src) && \
+        &/t-$* >$(topbuilddir)/&/d-$*/log 2>&\&1 \
+        || { cat $(topbuilddir)/&/d-$*/log >&\&2; false; }
+       $(CHECK_SILENT) printf "&/$* "
+       $(CHECK_SILENT) touch $@
+
+&CLEAN += & *.so
+
+&clean::
+       $(RM) -rf & tmp
+       $(RM) -rf & d-*
diff --git a/test-common.tcl b/test-common.tcl
new file mode 100644 (file)
index 0000000..04d229c
--- /dev/null
@@ -0,0 +1,28 @@
+
+proc prefix_some_path {pathvar entry} {
+    global env
+    set l {}
+    catch { set l [split $env($pathvar) :] }
+    set l [concat [list $entry] $l]
+    set env($pathvar) [join $l :]
+}
+
+proc prexec {args} {
+    puts "exec $args"
+    eval exec $args
+}
+
+if {![catch {
+    set builddir $env(SECNET_TEST_BUILDDIR)
+}]} {} else {
+    set builddir .
+}
+
+if {![catch {
+    set tmp $env(AUTOPKGTEST_ARTIACTS)
+}]} {} elseif {![catch {
+    set tmp $env(AUTOPKGTEST_TMP)
+}]} {} elseif {[regsub {^(?:\./)?([sm]test)/t-} $argv0 {\1/d-} tmp]} {
+    set tmp $builddir/$tmp
+    file mkdir $tmp
+}
diff --git a/test-example/Dir.sd.mk b/test-example/Dir.sd.mk
new file mode 100644 (file)
index 0000000..ff34215
--- /dev/null
@@ -0,0 +1,34 @@
+&TARGETS += & sites.conf sites-nonego.conf
+
+include common.make
+
+&/%.key: &^/%.key.b64
+       base64 -d <$< >$@.new && mv -f $@.new $@
+
+&sites-nonego.conf: $(src)/make-secnet-sites &^/sites &/Dir.mk
+       $(src)/make-secnet-sites --output-version=1 &^/sites $@
+
+&sites.conf: $(src)/make-secnet-sites &^/sites &/Dir.mk
+       mkdir -p &pubkeys
+       &~/make-secnet-sites --pubkeys-dir=&pubkeys --pubkeys-install \
+               &^/sites $@.tmp && mv -f $@.tmp $@
+
+&clean::
+       rm -rf &pubkeys
+
+&:macro &privkey
+&/&$1.privkeys/priv.&$2: &/&$3
+       mkdir -p $(dir $@) && cp $< $@.tmp && mv -f $@.tmp $@
+&PRIVKEYS += &/&$3 &/&$1.privkeys/priv.&$2
+&clean::
+       rm -rf &/&$1.privkeys
+&:endm
+
+&{&privkey,outside,5dc36a4700,rsa1-sites2.key}
+&{&privkey,outside,0000000000,outside.key}
+&{&privkey,inside,0000000000,inside.key}
+
+&all-privkeys:: $(&PRIVKEYS)
+
+&TARGETS += $(&PRIVKEYS)
+&CLEAN += *.new
diff --git a/test-example/Makefile b/test-example/Makefile
deleted file mode 100644 (file)
index 67230b8..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-all: sites.conf inside.key outside.key
-
-%.key: %.key.b64
-       base64 -d <$< >$@.new && mv -f $@.new $@
-
-sites.conf: ../make-secnet-sites sites Makefile
-       ../make-secnet-sites sites sites.conf
index 4c2eca30ada234c967c7c05ed577347d1b2c157d..72f98abf5f88d2c803cb4d104132423716d822fd 100644 (file)
@@ -24,3 +24,27 @@ For running under valgrind memcheck, do something like this:
     --leak-check=full --suppressions=test-example/memcheck.suppressions \
     ./secnet -dvnc test-example/outside.conf
 NB that --num-callers is needed as secnet's stack can be deep.
+
+The config file outside-unshare.conf can be used on Linux in
+conjunction with test-example/fake-userv and a built checkout of
+userv-utils.git to run the "outside" copy of secnet in a new "network
+namespace".
+
+
+
+Everything in this directory is part of secnet.  See README (in the
+directory above) for full list of copyright holders.
+
+secnet is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+secnet is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+version 3 along with secnet; if not, see
+https://www.gnu.org/licenses/gpl.html.
index b849052b78064662433b5feddb0401873c204470..8fefad6741da997f22ddbcf04fd1c05a41170e5e 100644 (file)
@@ -3,6 +3,7 @@ log logfile {
        class "info","notice","warning","error","security","fatal";
 };
 system {
+       userid "secnet";
 };
 resolver adns {
 };
diff --git a/test-example/fake-userv b/test-example/fake-userv
new file mode 100755 (executable)
index 0000000..6f5da40
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+set -e
+echo >&2 "$0: invoked as $0 $*"
+shift
+shift
+exec 3<&0 4>&1 5>&2 >&2 </dev/null
+exec xterm -T netns -e unshare -n -- sh -xc '
+  ../userv-utils.git/ipif/service \* -- "$@" <&3 >&4 2>&5 &
+  sleep 0.1
+  env - bash -i
+' x "$@"
diff --git a/test-example/inside-polypath.conf b/test-example/inside-polypath.conf
new file mode 100644 (file)
index 0000000..5e66e01
--- /dev/null
@@ -0,0 +1,20 @@
+comm polypath {
+       buffer sysbuffer(4096);
+       monitor-command "./polypath-interface-monitor-linux";
+       interfaces "!secnet-test*";
+       permit-loopback True;
+};
+netlink tun {
+       name "netlink-tun"; # Printed in log messages from this netlink
+       local-address "172.18.232.9";
+       secnet-address "172.18.232.10";
+       remote-networks "172.18.232.0/28";
+       mtu 1400;
+       buffer sysbuffer(2048);
+       interface "secnet-test-i";
+};
+local-name "test-example/inside/inside";
+local-key rsa-private("test-example/inside.key");
+local-mobile True;
+mtu-target 1260;
+include test-example/common.conf
index a64f1252cbf1c19727fff85f8d8de737ff58b872..060a0bfda04b93405ac2fe2c70390c1793c77b7c 100644 (file)
@@ -3,7 +3,7 @@ netlink tun {
        local-address "172.18.232.9";
        secnet-address "172.18.232.10";
        remote-networks "172.18.232.0/28";
-       mtu 500;
+       mtu 1400;
        buffer sysbuffer(2048);
        interface "secnet-test-i";
 };
@@ -17,4 +17,5 @@ comm udp {
 local-name "test-example/inside/inside";
 local-key rsa-private("test-example/inside.key");
 local-mobile True;
+mtu-target 1260;
 include test-example/common.conf
diff --git a/test-example/outside-random.conf b/test-example/outside-random.conf
new file mode 100644 (file)
index 0000000..5c23920
--- /dev/null
@@ -0,0 +1,16 @@
+netlink userv-ipif {
+       name "netlink-ipif"; # Printed in log messages from this netlink
+       local-address "172.18.232.1";
+       secnet-address "172.18.232.2";
+       remote-networks "172.18.232.0/28";
+       mtu 1400;
+       buffer sysbuffer(2048);
+       userv-path "test-example/random-fake-userv";
+};
+comm udp {
+       port 16900;
+       buffer sysbuffer(4096);
+};
+local-name "test-example/outside/outside";
+local-key rsa-private("test-example/outside.key");
+include test-example/common.conf
diff --git a/test-example/outside-unshare.conf b/test-example/outside-unshare.conf
new file mode 100644 (file)
index 0000000..2811962
--- /dev/null
@@ -0,0 +1,16 @@
+netlink userv-ipif {
+       name "netlink-ipif"; # Printed in log messages from this netlink
+       local-address "172.18.232.1";
+       secnet-address "172.18.232.2";
+       remote-networks "172.18.232.0/28";
+       mtu 1400;
+       buffer sysbuffer(2048);
+       userv-path "test-example/fake-userv";
+};
+comm udp {
+       port 16900;
+       buffer sysbuffer(4096);
+};
+local-name "test-example/outside/outside";
+local-key rsa-private("test-example/outside.key");
+include test-example/common.conf
index db78b7b76a2c8370801cbe77eaaddcf71a9d600e..89441301a5bde0f0deda5dae12926fa14583b66c 100644 (file)
@@ -3,7 +3,7 @@ netlink tun {
        local-address "172.18.232.1";
        secnet-address "172.18.232.2";
        remote-networks "172.18.232.0/28";
-       mtu 500;
+       mtu 1400;
        buffer sysbuffer(2048);
        interface "secnet-test-o";
 };
diff --git a/test-example/random-fake-userv b/test-example/random-fake-userv
new file mode 100755 (executable)
index 0000000..cccd22c
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/perl -w
+
+use strict;
+use POSIX;
+
+open R, '/dev/urandom' or die $!;
+
+system 'cat >/dev/null &';
+
+sub randbytes ($) {
+    my ($count) = @_;
+    my $s;
+    my $r = read R, $s, $count;
+    die $! unless $r==$count;
+    return $s;
+}
+
+sub randbyteval () {
+    my $b = randbytes 1;
+    my ($r) = unpack 'C', $b;
+    return $r;
+}
+
+sub randvalue ($$) {
+    my ($min,$maxplus1) = @_;
+    my $b = randbyteval;
+    return floor(($b/256.0) * ($maxplus1-$min)) + $min;
+}
+
+for (;;) {
+    my $lenbits = randvalue 0,14;
+    my $len= (randbyteval << 8) | randbyteval;
+    $len &= (1 << $lenbits)-1;
+    my $data = randbytes $len;
+    if (randbyteval >= 0x80) {
+       $data =~ s{[\xc0\xdb]}{
+            $& eq "\xc0" ? "\xcb\xdc" :
+            $& eq "\xdb" ? "\xcb\xdd" :
+            die
+        }ge;
+    }
+    print "\xc0";
+    print $data;
+    STDOUT->flush;
+}
diff --git a/test-example/rsa1-sites2.key.b64 b/test-example/rsa1-sites2.key.b64
new file mode 100644 (file)
index 0000000..6a2ab68
--- /dev/null
@@ -0,0 +1,18 @@
+U1NIIFBSSVZBVEUgS0VZIEZJTEUgRk9STUFUIDEuMQoAAAAAAAAAAAgACAC+8TCcl8qe3rzdNgTT
+UD/CTftxO+MZqUknkxvI26AA0NBjaYhtfCU2R281I+NtT48a99K7ByZ0+R4UHfWjaaeddfory2Q1
+bSL/JPmJ524vdnTp/jIr4mYEql4QBvy1q7x8giKXpjs3NTpFWD5rLsCN+dkmCkkMXk4UqS1lwJyL
+uyddzyDLZTp//tVQNra6ZhHbEuF3wn/b+A66CAGjHTNEhMS/dxz4NlQ4P8oKUgw0ajo/H6W/W0gt
+XCOPxqIA9mByLWL6L+TXM3H4dmyEORSVPrstCv0v/32E7O8xVCyrp7r2bbTtZD+8gO+IMll76HiO
+49YwrAsnmHVWhREZbe3PABEBAAEAAAAKaWFuQHplYWxvdI+rj6sH/jzebny89t7GaraqLcefYXPz
+oCBs0uyvRp7TCpxxRwR7jLLkRZWBaetvwkfSdQG3xwr1zbvJf7zyXWBUXKYHXGOZ5HJpmDxf2jQz
++Ui4+isvO/4MBrpupc7+8JVzZMQVRzT510U4vM/QrA7HHr7UXJXl/A9gYAENXx6+/7XcPQtdxSgZ
+LB3fDKh7ac9RIELrujM3RaInVTW1RuoKjxmza/ZRb8OGgrZCFXvDMvzaveGD06LSumuVOc35Gy/i
+pCfhyZU5ZKKCq99NQmbDMzVuZXhgbkFj6I5Hfn0K7sV9BI9wii/7a3wfqmSfQf6/ZyDcjxF1HFWb
+aZm39RD+1o8XwIED/AudMGNYF3auLToQDhfTbB1n2qni+88Mtjb1K0SetQQLRXEXRLzaACE4zU3l
++v78nKRhi+wV+W+UedVBYbCZaT6vY/Akllpc1f7TfLXiQ64vFWPzd4OgOXoZGFVNyag5r4Jp6Isw
+pagk3s9Js1lu1zGeZy63RTIzMw4JJVRTJhT9BADZlMJg+cn+eO09a/5LNaCVY3nCUji3GUSkhqrQ
+8JyZG53o6zwyKnwNFt4x8VvJ4XzNSlNb2SIcd6SFQ6baNns2XO6milV7D75D47TB3w/cTodla9iH
+pPwDfPnR58QaDKR7SrGFsLzQr0RMANQPv0S6cKQR8mpp6MVloJMdx8cVkQQA4KhHqVVEpRH/eqVM
+qv288awsDh8OeQ0S1mIB3PpsiPvKsv0Udnvp36Om6ksVAZ9Rvjzq6Dwyu5/rXOaLdrDfO6Rg4ERv
+b31VsTF1hhKwBY/SW8LWSf3m19ZUgehrbydntRoYSc7fhiI+XwgcY+g3hUlH+p2K6lxDRO2gTHOj
+nV8AAAAA
diff --git a/test-example/rsa1-sites2.key.pub b/test-example/rsa1-sites2.key.pub
new file mode 100644 (file)
index 0000000..d5b1368
--- /dev/null
@@ -0,0 +1 @@
+2048 65537 24104213110797824055140161151913051305999303744244245538616867433010123928547772585164857844773599715378140515394197403325476306321618029548629773764525038084664382384465558827523669858194978897965295333256643190607316924148587954891292549882502404695138437562562382196324392976634799923334183631987978068608255623637553440025606960971733876084111830783382626819339190125655665183104355565691970919273870624728431953156654768349323549801247517262721074491393358334108678866178248743405367462158492230242408187673473228900258799327086061288180749147397116786858491515534054986740592092556131569932455039077414318501327 ian@zealot
index 648819b438eaeff5072fe7b8445ebfd6717fabcd..f5ad631067b63ab164b9f628a2ca416619cfc1a2 100644 (file)
@@ -7,14 +7,19 @@ restrict-nets 172.18.232.0/28
 setup-timeout 2000
 setup-retries 5
 
-location outside root
+location out root
 site outside
   networks 172.18.232.0/29
   peer 172.18.232.1
-  address [127.0.0.1] 16900
+  address [::1] 16900
+  serial 5dcfe8e9
+  pkg 5dc36a47
+  pub rsa1 zt1E8Wddh26=^XgSyt2x2i)G7I5=Z[*T:4(aml1Xs1U<lN$S?+<,[h.GAJ::*NLU0tB.%kkMQJ4+y6$SH)YbKm-i$JC.WCbRgw_,UoKugJ7=R[7RJ)QbKmOuh2G?bvlTF2QbWo<Gh26=%NgS37;aBk*X819=aCASSz9E;mGSrID.pNFTF)WxUo9X@J5+Z[lTY%*EmlVds1E?KChSs!J.vn~R@Jn/66$SJ){x:mmMAJX<uC%SowCFVoUu[29=C+$S@+R.AkiMgJX<EkbR6t*E:m)Gx26+xNgS#7p.Vo9X[2V*pN8R=4CFxnVdrIm/LvFT$7<,vnCS%2W<q6FTA2{x2iBjAJm/tN8Re%+xBkwoG3s@|jAS;4dECkGS81H?9NmT5t*EMmGu<1<:@YASE2gbml5XQJ,(q6FTm!Dy]h6o81D.WCmT3t`E3i$G[2.(mCBSH)mxcjSu81E.tNcR6texllddgJX<1N*TjwVEBkbdgJ=:5NcRe%(a4iuM%2E?mCFT:4>xvnCS81>:tN$Ss!9E;mSuAJq@)YAS=4CF^heMR2=:_@7RVz>x2iooQJq@^jKUB29ECk5X7ID?N[*Tu!Yb:msMQJ9=EkgS37ux;m]i<1::rvKURztEbjkMQJD?OCmTb%tE;miM;Is@KC7RJ)gbWoiM$JD?iCFTQz`E4iVB$Jq@PvlT[+gb2i0o@JG?eChSpwQbllESQJ::xN+Tb%1ElluMs16+26FT-1;aLm[zAJm/26{Q[+),;m+GR2S*&6{Q-1_,N
+  pub unknown-algo TPwJh>A
+  pkgf 00000000
   pubkey 1024 65537 129251483458784900555621175262818292872587807329014927540074484804119474262261383244074013537736576331652560727149001626325243856012659665194546933097292703586821422085819615124517093786704646988649444946154384037948502112302285511195679291084694375811092516151263088200304199780052361048758446082354317801941 outside@example.com
 
-location inside root
+location in root
 site inside
   networks 172.18.232.8/29
   peer 172.18.232.9
index 26e0a127d37cf8dcd7297eab6768795811453629..b481346068833c12eefdc1463b20cf489a97286d 100644 (file)
@@ -1,5 +1,24 @@
 /* Transform module - bulk data transformation */
 
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 /* For now it's hard-coded to do sequence
    number/pkcs5/serpent-cbcmac/serpent with a 256 bit key for each
    instance of serpent. We also require key material for the IVs for
 /* Required key length in bytes */
 #define REQUIRED_KEYLEN ((512+64+32)/8)
 
+#include "transform-common.h"
+
+struct transform_params {
+    SEQNUM_PARAMS_FIELDS;
+};
+
 struct transform {
     closure_t cl;
     struct transform_if ops;
-    uint32_t max_seq_skew;
+    struct transform_params p;
 };
 
 struct transform_inst {
     struct transform_inst_if ops;
+    struct transform_params p;
     struct keyInstance cryptkey;
     struct keyInstance mackey;
     uint32_t cryptiv;
     uint32_t maciv;
-    uint32_t sendseq;
-    uint32_t lastrecvseq;
-    uint32_t max_skew;
-    bool_t keyed;
+    SEQNUM_KEYED_FIELDS;
 };
 
-#include "transform-common.h"
-
 #define PKCS5_MASK 15
 
 static bool_t transform_setkey(void *sst, uint8_t *key, int32_t keylen,
@@ -63,9 +84,8 @@ static bool_t transform_setkey(void *sst, uint8_t *key, int32_t keylen,
     serpentbe_makekey(&ti->mackey,256,key+32);
     ti->cryptiv=get_uint32(key+64);
     ti->maciv=get_uint32(key+68);
-    ti->sendseq=get_uint32(key+72);
-    ti->lastrecvseq=ti->sendseq;
-    ti->keyed=True;
+    uint32_t firstseq=get_uint32(key+72);
+    SEQNUM_KEYED_INIT(firstseq,firstseq);
 
     return True;
 }
@@ -81,8 +101,8 @@ static void transform_delkey(void *sst)
     ti->keyed=False;
 }
 
-static uint32_t transform_forward(void *sst, struct buffer_if *buf,
-                                 const char **errmsg)
+static transform_apply_return transform_forward(void *sst,
+            struct buffer_if *buf, const char **errmsg)
 {
     struct transform_inst *ti=sst;
     uint8_t *padp;
@@ -114,7 +134,7 @@ static uint32_t transform_forward(void *sst, struct buffer_if *buf,
        bother sending the IV - it's the same each time. (If we wanted to send
        it we've have to add 16 bytes to each message, not 4, so that the
        message stays a multiple of 16 bytes long.) */
-    memset(iv,0,16);
+    FILLZERO(iv);
     put_uint32(iv, ti->maciv);
     serpentbe_encrypt(&ti->mackey,iv,macacc);
 
@@ -127,11 +147,11 @@ static uint32_t transform_forward(void *sst, struct buffer_if *buf,
        serpentbe_encrypt(&ti->mackey,macplain,macacc);
     }
     serpentbe_encrypt(&ti->mackey,macacc,macacc);
-    memcpy(buf_append(buf,16),macacc,16);
+    BUF_ADD_BYTES(append,buf,macacc,16);
 
     /* Serpent-CBC. We expand the ID as for CBCMAC, do the encryption,
        and prepend the IV before increasing it. */
-    memset(iv,0,16);
+    FILLZERO(iv);
     put_uint32(iv, ti->cryptiv);
     serpentbe_encrypt(&ti->cryptkey,iv,iv);
 
@@ -152,8 +172,8 @@ static uint32_t transform_forward(void *sst, struct buffer_if *buf,
     return 0;
 }
 
-static uint32_t transform_reverse(void *sst, struct buffer_if *buf,
-                                 const char **errmsg)
+static transform_apply_return transform_reverse(void *sst,
+                struct buffer_if *buf, const char **errmsg)
 {
     struct transform_inst *ti=sst;
     uint8_t *padp;
@@ -171,11 +191,11 @@ static uint32_t transform_reverse(void *sst, struct buffer_if *buf,
 
     if (buf->size < 4 + 16 + 16) {
        *errmsg="msg too short";
-       return 1;
+       return transform_apply_err;
     }
 
     /* CBC */
-    memset(iv,0,16);
+    FILLZERO(iv);
     {
        uint32_t ivword = buf_unprepend_uint32(buf);
        put_uint32(iv, ivword);
@@ -183,7 +203,7 @@ static uint32_t transform_reverse(void *sst, struct buffer_if *buf,
     /* Assert bufsize is multiple of blocksize */
     if (buf->size&0xf) {
        *errmsg="msg not multiple of cipher blocksize";
-       return 1;
+       return transform_apply_err;
     }
     serpentbe_encrypt(&ti->cryptkey,iv,iv);
     for (n=buf->start; n<buf->start+buf->size; n+=16)
@@ -193,12 +213,12 @@ static uint32_t transform_reverse(void *sst, struct buffer_if *buf,
        serpentbe_decrypt(&ti->cryptkey,n,n);
        for (i = 0; i < 16; i++)
            n[i] ^= iv[i];
-       memcpy(iv, pct, 16);
+       COPY_OBJ(iv, pct);
     }
 
     /* CBCMAC */
     macexpected=buf_unappend(buf,16);
-    memset(iv,0,16);
+    FILLZERO(iv);
     put_uint32(iv, ti->maciv);
     serpentbe_encrypt(&ti->mackey,iv,macacc);
 
@@ -211,9 +231,9 @@ static uint32_t transform_reverse(void *sst, struct buffer_if *buf,
        serpentbe_encrypt(&ti->mackey,macplain,macacc);
     }
     serpentbe_encrypt(&ti->mackey,macacc,macacc);
-    if (!consttime_memeq(macexpected,macacc,16)!=0) {
+    if (!consttime_memeq(macexpected,macacc,16)) {
        *errmsg="invalid MAC";
-       return 1;
+       return transform_apply_err;
     }
 
     /* PKCS5, stolen from IWJ */
@@ -222,7 +242,7 @@ static uint32_t transform_reverse(void *sst, struct buffer_if *buf,
     padlen=*padp;
     if (!padlen || (padlen > PKCS5_MASK+1)) {
        *errmsg="pkcs5: invalid length";
-       return 1;
+       return transform_apply_err;
     }
 
     buf_unappend(buf,padlen-1);
@@ -230,7 +250,7 @@ static uint32_t transform_reverse(void *sst, struct buffer_if *buf,
     /* Sequence number must be within max_skew of lastrecvseq; lastrecvseq
        is only allowed to increase. */
     seqnum=buf_unprepend_uint32(buf);
-    SEQNUM_CHECK(seqnum, ti->max_skew);
+    SEQNUM_CHECK(seqnum, &ti->p);
     
     return 0;
 }
@@ -243,7 +263,7 @@ static struct transform_inst_if *transform_create(void *sst)
 
     TRANSFORM_CREATE_CORE;
 
-    ti->max_skew=st->max_seq_skew;
+    ti->p=st->p;
 
     return &ti->ops;
 }
@@ -255,7 +275,7 @@ static list_t *transform_apply(closure_t *self, struct cloc loc,
     item_t *item;
     dict_t *dict;
 
-    st=safe_malloc(sizeof(*st),"serpent");
+    NEW(st);
     st->cl.description="serpent-cbc256";
     st->cl.type=CL_TRANSFORM;
     st->cl.apply=NULL;
@@ -272,13 +292,13 @@ static list_t *transform_apply(closure_t *self, struct cloc loc,
     /* First parameter must be a dict */
     item=list_elem(args,0);
     if (!item || item->type!=t_dict)
-       cfgfatal(loc,"userv-ipif","parameter must be a dictionary\n");
+       cfgfatal(loc,"serpent256-cbc","parameter must be a dictionary\n");
     
     dict=item->data.dict;
-    st->max_seq_skew=dict_read_number(dict, "max-sequence-skew",
-                                     False, "serpent-cbc256", loc, 10);
 
-    SET_CAPAB_TRANSFORMNUM(CAPAB_TRANSFORMNUM_SERPENT256CBC);
+    SEQNUM_PARAMS_INIT(dict,&st->p,"serpent-cbc256",loc);
+
+    SET_CAPAB_BIT(CAPAB_BIT_SERPENT256CBC);
 
     return new_closure(&st->cl);
 }
@@ -336,7 +356,7 @@ void transform_cbcmac_module(dict_t *dict)
        const char *errmsg;
        int i;
 
-       tr = malloc(sizeof(struct transform));
+       NEW(tr);
        tr->max_seq_skew = 20;
        ti = transform_create(tr);
 
@@ -344,7 +364,7 @@ void transform_cbcmac_module(dict_t *dict)
 
         buf.base = malloc(4096);
        buffer_init(&buf, 2048);
-       memcpy(buf_append(&buf, sizeof(text)), text, sizeof(text));
+       BUF_ADD_OBJ(append, buf, text, sizeof(text));
        if (transform_forward(ti, &buf, &errmsg)) {
            fatal("transform_forward test: %s", errmsg);
        }
index 24ab8dc2bd54f042f79aaeeda920c968768378b3..4351ce88991142588b5340a4626424d1dd4db5bf 100644 (file)
@@ -1,3 +1,21 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
 
 #ifndef TRANSFORM_COMMON_H
 #define TRANSFORM_COMMON_H
 #define KEYED_CHECK do{                                \
        if (!ti->keyed) {                       \
            *errmsg="transform unkeyed";        \
-           return 1;                           \
+           return transform_apply_err;         \
        }                                       \
     }while(0)
 
-#define SEQNUM_CHECK(seqnum, max_skew) do{     \
-       uint32_t skew=seqnum-ti->lastrecvseq;   \
-       if (skew<0x8fffffff) {                  \
-           /* Ok */                            \
-           ti->lastrecvseq=seqnum;             \
-       } else if ((0-skew)<max_skew) { \
-           /* Ok */                            \
-       } else {                                \
-           /* Too much skew */                 \
-           *errmsg="seqnum: too much skew";    \
-           return 2;                           \
-       }                                       \
+#define RECVBITMAP_SIZE 32
+typedef uint32_t recvbitmap_type;
+
+#define SEQNUM_CHECK(seqnum, p) do{                            \
+       uint32_t skew=seqnum-ti->lastrecvseq;                   \
+       if (skew<0x8fffffff) {                                  \
+           /* Ok */                                            \
+           ti->lastrecvseq=seqnum;                             \
+           if (skew < RECVBITMAP_SIZE)                         \
+                ti->recvbitmap <<= skew;                       \
+            else                                               \
+                ti->recvbitmap=0;                              \
+            skew=0;                                            \
+       } else if ((0-skew)<(p)->max_seq_skew) {                \
+           /* Ok */                                            \
+       } else {                                                \
+           /* Too much skew */                                 \
+           *errmsg="seqnum: too much skew";                    \
+           return transform_apply_seqrange;                    \
+       }                                                       \
+       if ((p)->dedupe) {                                      \
+           recvbitmap_type recvbit=(uint32_t)1 << skew;        \
+           if (ti->recvbitmap & recvbit) {                     \
+               *errmsg="seqnum: duplicate";                    \
+               return transform_apply_seqdupe;                 \
+           }                                                   \
+           ti->recvbitmap |= recvbit;                          \
+       }                                                       \
     }while(0)
 
+#define SEQNUM_KEYED_FIELDS                                            \
+    uint32_t sendseq;                                                  \
+    uint32_t lastrecvseq;                                              \
+    recvbitmap_type recvbitmap; /* 1<<0 is lastrecvseq (i.e., most recent) */ \
+    bool_t keyed
+
+#define SEQNUM_KEYED_INIT(initlastrecvseq,initsendseq) \
+    (ti->lastrecvseq=(initlastrecvseq),                        \
+     ti->sendseq=(initsendseq),                                \
+     ti->recvbitmap=0,                                 \
+     ti->keyed=True)
+
 #define TRANSFORM_VALID                                \
     static bool_t transform_valid(void *sst)   \
     {                                          \
        free(st);                                       \
     }
 
-#define SET_CAPAB_TRANSFORMNUM(def) do{                                        \
-        st->ops.capab_transformnum=dict_read_number(dict, "capab-num", \
-                                     False, "transform", loc, def);    \
-        if (st->ops.capab_transformnum > CAPAB_TRANSFORMNUM_MAX)       \
+#define SET_CAPAB_BIT(def) do{                                         \
+        st->ops.capab_bit=dict_read_number(dict, "capab-num",          \
+                                     False, "transform", loc, (def));  \
+        if (st->ops.capab_bit > CAPAB_BIT_MAX)                         \
            cfgfatal(loc,"transform","capab-num out of range 0..%d\n",  \
-                    CAPAB_TRANSFORMNUM_MAX);                           \
+                    CAPAB_BIT_MAX);                                    \
     }while(0)
 
 #define TRANSFORM_CREATE_CORE                          \
        struct transform_inst *ti;                      \
-       ti=safe_malloc(sizeof(*ti),"transform_create"); \
+       NEW(ti);                                        \
        /* mlock XXX */                                 \
        ti->ops.st=ti;                                  \
        ti->ops.setkey=transform_setkey;                \
        ti->ops.destroy=transform_destroy;              \
        ti->keyed=False;
 
+#define SEQNUM_PARAMS_FIELDS                   \
+    uint32_t max_seq_skew;                     \
+    bool_t dedupe;
+
+#define SEQNUM_PARAMS_INIT(dict,p,desc,loc)                            \
+    (p)->max_seq_skew=dict_read_number((dict), "max-sequence-skew",    \
+                                       False, (desc), (loc), 10);      \
+    bool_t can_dedupe=(p)->max_seq_skew < RECVBITMAP_SIZE;             \
+    (p)->dedupe=dict_read_bool((dict), "dedupe",                       \
+                              False,(desc),(loc), can_dedupe);         \
+    if ((p)->dedupe && !can_dedupe)                                    \
+       cfgfatal(loc,"transform",                                       \
+                 "cannot dedupe with max-sequence-skew>=32");          \
+    else (void)0
+
 #endif /*TRANSFORM_COMMON_H*/
index f881abb9eb424ec444da7ce091e8a038592760f6..04cd0e65d7648b28c4f0afd29437c53d139156de 100644 (file)
@@ -1,6 +1,25 @@
 /*
  * eax-transform.c: EAX-Serpent bulk data transformation
+ */
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
  *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+/*
  * We use EAX with the following parameters:
  *
  *   Plaintext:
@@ -55,7 +74,8 @@
 #define SEQLEN 4
 
 struct transform_params {
-    uint32_t max_seq_skew, tag_length, padding_mask;
+    SEQNUM_PARAMS_FIELDS;
+    uint32_t tag_length, padding_mask;
 };
 
 struct transform {
@@ -67,11 +87,9 @@ struct transform {
 struct transform_inst {
     struct transform_inst_if ops;
     struct transform_params p;
-    unsigned keyed:1;
     /* remaining valid iff keyed */
     unsigned direction:1;
-    uint32_t sendseq;
-    uint32_t lastrecvseq;
+    SEQNUM_KEYED_FIELDS;
     struct keyInstance key;
     uint8_t info_b[BLOCK_SIZE], info_p[BLOCK_SIZE];
 };
@@ -127,11 +145,10 @@ static bool_t transform_setkey(void *sst, uint8_t *key, int32_t keylen,
     TEAX_DEBUG(hash_out+32,8);
 
     ti->direction=direction;
-    ti->sendseq=get_uint32(hash_out+32+direction*4);
-    ti->lastrecvseq=get_uint32(hash_out+32+!direction*4);
     serpent_makekey(&ti->key, 32*8, hash_out);
     eax_setup(ti);
-    ti->keyed=True;
+    SEQNUM_KEYED_INIT(get_uint32(hash_out+32+!direction*4),
+                     get_uint32(hash_out+32+direction*4));
 
     return True;
 }
@@ -150,8 +167,8 @@ static void transform_delkey(void *sst)
     ti->keyed=False;
 }
 
-static uint32_t transform_forward(void *sst, struct buffer_if *buf,
-                                 const char **errmsg)
+static transform_apply_return transform_forward(void *sst,
+        struct buffer_if *buf, const char **errmsg)
 {
     struct transform_inst *ti=sst;
 
@@ -179,7 +196,7 @@ static uint32_t transform_forward(void *sst, struct buffer_if *buf,
 
     TEAX_DEBUG(buf->start,buf->size);
 
-    memcpy(buf_append(buf,SEQLEN), nonce, SEQLEN);
+    BUF_ADD_BYTES(append,buf,nonce,SEQLEN);
 
     TEAX_DEBUG(nonce,SEQLEN);
 
@@ -188,8 +205,8 @@ static uint32_t transform_forward(void *sst, struct buffer_if *buf,
     return 0;
 }
 
-static uint32_t transform_reverse(void *sst, struct buffer_if *buf,
-                                 const char **errmsg)
+static transform_apply_return transform_reverse(void *sst,
+        struct buffer_if *buf, const char **errmsg)
 {
     struct transform_inst *ti=sst;
 
@@ -216,7 +233,7 @@ static uint32_t transform_reverse(void *sst, struct buffer_if *buf,
     if (!ok) {
        TEAX_DEBUG(0,0);
        *errmsg="EAX decryption failed";
-       return 1;
+       return transform_apply_err;
     }
     assert(buf->size >= (int)ti->p.tag_length);
     buf->size -= ti->p.tag_length;
@@ -231,7 +248,7 @@ static uint32_t transform_reverse(void *sst, struct buffer_if *buf,
     size_t padlen = *padp;
     if (!buf_unappend(buf,padlen-1)) goto too_short;
 
-    SEQNUM_CHECK(seqnum, ti->p.max_seq_skew);
+    SEQNUM_CHECK(seqnum, &ti->p);
 
     TEAX_DEBUG(buf->start,buf->size);
 
@@ -239,7 +256,7 @@ static uint32_t transform_reverse(void *sst, struct buffer_if *buf,
 
  too_short:
     *errmsg="ciphertext or plaintext too short";
-    return 1;
+    return transform_apply_err;
 }
 
 static struct transform_inst_if *transform_create(void *sst)
@@ -260,7 +277,7 @@ static list_t *transform_apply(closure_t *self, struct cloc loc,
     item_t *item;
     dict_t *dict;
 
-    st=safe_malloc(sizeof(*st),"eax-serpent");
+    NEW(st);
     st->cl.description="eax-serpent";
     st->cl.type=CL_TRANSFORM;
     st->cl.apply=NULL;
@@ -273,10 +290,9 @@ static list_t *transform_apply(closure_t *self, struct cloc loc,
        cfgfatal(loc,"eax-serpent","parameter must be a dictionary\n");
     dict=item->data.dict;
 
-    SET_CAPAB_TRANSFORMNUM(CAPAB_TRANSFORMNUM_EAXSERPENT);
+    SET_CAPAB_BIT(CAPAB_BIT_EAXSERPENT);
 
-    st->p.max_seq_skew=dict_read_number(dict, "max-sequence-skew",
-                                       False, "eax-serpent", loc, 10);
+    SEQNUM_PARAMS_INIT(dict,&st->p,"eax-serpent",loc);
 
     st->p.tag_length=dict_read_number(dict, "tag-length-bytes",
                                      False, "eax-serpent", loc, 128/8);
diff --git a/tun.c b/tun.c
index 40bf6dd91d8de62c5474f7552d77acd4b2d3e3b7..3ba62b3880c1feb522022c7dca67a3b0c8bc8788 100644 (file)
--- a/tun.c
+++ b/tun.c
@@ -1,3 +1,22 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #include "secnet.h"
 #include "util.h"
 #include "netlink.h"
@@ -12,7 +31,7 @@
 
 #ifdef HAVE_NET_IF_H
 #include <net/if.h>
-#ifdef HAVE_LINUX_IF_H
+#ifdef HAVE_LINUX_IF_TUN_H
 #include <linux/if_tun.h>
 #define LINUX_TUN_SUPPORTED
 #endif
@@ -81,7 +100,6 @@ struct tun {
     struct buffer_if *buff; /* We receive packets into here
                               and send them to the netlink code. */
     netlink_deliver_fn *netlink_to_tunnel;
-    uint32_t local_address; /* host interface address */
 };
 
 static cstring_t tun_flavour_str(uint32_t flavour)
@@ -99,7 +117,7 @@ static int tun_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
                          int *timeout_io)
 {
     struct tun *st=sst;
-    *nfds_io=1;
+    BEFOREPOLL_WANT_FDS(1);
     fds[0].fd=st->fd;
     fds[0].events=POLLIN;
     return 0;
@@ -117,8 +135,9 @@ static void tun_afterpoll(void *sst, struct pollfd *fds, int nfds)
     if (fds[0].revents&POLLIN) {
        BUF_ALLOC(st->buff,"tun_afterpoll");
        buffer_init(st->buff,calculate_max_start_pad());
-       l=read(st->fd,st->buff->start,st->buff->len-calculate_max_start_pad());
+       l=read(st->fd, st->buff->start, buf_remaining_space(st->buff));
        if (l<0) {
+           if (errno==EINTR || iswouldblock(errno)) return;
            fatal_perror("tun_afterpoll: read()");
        }
        if (l==0) {
@@ -241,10 +260,8 @@ static bool_t tun_set_route(void *sst, struct netlink_client *routes)
            fatal("tun_set_route: unsupported route command type");
            break;
        }
-       free(network); free(mask);
     }
-    free(secnetaddr);
-    if (st->route_type==TUN_CONFIG_IOCTL) {
+    if (fd >= 0) {
        close(fd);
     }
     routes->kup=up;
@@ -343,6 +360,8 @@ static void tun_phase_hook(void *sst, uint32_t newphase)
        st->interface_name=safe_malloc(10,"tun_apply");
        sprintf(st->interface_name,"tun%d",ppa);
        st->fd=tun_fd;
+       setcloexec(if_ifd);
+       setcloexec(ip_ifd);
 #else
        fatal("tun_phase_hook: TUN_FLAVOUR_STREAMS unexpected");
 #endif /* HAVE_TUN_STREAMS */
@@ -353,7 +372,10 @@ static void tun_phase_hook(void *sst, uint32_t newphase)
        to set the TUN device's address, and route to add routes to all
        our networks. */
 
-    hostaddr=ipaddr_to_string(st->local_address);
+    setcloexec(st->fd);
+    setnonblock(st->fd);
+
+    hostaddr=ipaddr_to_string(st->nl.local_address);
     secnetaddr=ipaddr_to_string(st->nl.secnet_address);
     snprintf(mtu,sizeof(mtu),"%d",st->nl.mtu);
     mtu[5]=0;
@@ -387,7 +409,7 @@ static void tun_phase_hook(void *sst, uint32_t newphase)
        sa=(struct sockaddr_in *)&ifr.ifr_addr;
        FILLZERO(*sa);
        sa->sin_family=AF_INET;
-       sa->sin_addr.s_addr=htonl(st->local_address);
+       sa->sin_addr.s_addr=htonl(st->nl.local_address);
        if (ioctl(fd,SIOCSIFADDR, &ifr)!=0) {
            fatal_perror("tun_apply: SIOCSIFADDR");
        }
@@ -439,8 +461,10 @@ static void tun_phase_hook(void *sst, uint32_t newphase)
        tun_set_route(st,r);
     }
 
+    add_hook(PHASE_CHILDPERSIST,childpersist_closefd_hook,&st->fd);
+
     /* Register for poll() */
-    register_for_poll(st, tun_beforepoll, tun_afterpoll, 1, st->nl.name);
+    register_for_poll(st, tun_beforepoll, tun_afterpoll, st->nl.name);
 }
 
 static list_t *tun_create(closure_t *self, struct cloc loc, dict_t *context,
@@ -451,7 +475,7 @@ static list_t *tun_create(closure_t *self, struct cloc loc, dict_t *context,
     dict_t *dict;
     string_t flavour,type;
 
-    st=safe_malloc(sizeof(*st),"tun_apply");
+    NEW(st);
 
     /* First parameter must be a dict */
     item=list_elem(args,0);
@@ -491,8 +515,6 @@ static list_t *tun_create(closure_t *self, struct cloc loc, dict_t *context,
     st->route_path=dict_read_string(dict,"route-path",False,"tun-netlink",loc);
 
     st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"tun-netlink",loc);
-    st->local_address=string_item_to_ipaddr(
-       dict_find_item(dict,"local-address", True, "netlink", loc),"netlink");
 
     if (st->tun_flavour==TUN_FLAVOUR_GUESS) {
        /* If we haven't been told what type of TUN we're using, take
diff --git a/udp.c b/udp.c
index 97b92a6017b81677b9c50f2be3db6ee282d1fde2..d1ada01f6f1d83eb56dce10336a1b33a552bc927 100644 (file)
--- a/udp.c
+++ b/udp.c
@@ -1,5 +1,24 @@
 /* UDP send/receive module for secnet */
 
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 /* This module enables sites to communicate by sending UDP
  * packets. When an instance of the module is created we can
  * optionally bind to a particular local IP address (not implemented
 #include "unaligned.h"
 #include "ipaddr.h"
 #include "magic.h"
+#include "comm-common.h"
 
-static beforepoll_fn udp_beforepoll;
-static afterpoll_fn udp_afterpoll;
-static comm_request_notify_fn request_notify;
-static comm_release_notify_fn release_notify;
 static comm_sendmsg_fn udp_sendmsg;
 
-struct notify_list {
-    comm_notify_fn *fn;
-    void *state;
-    struct notify_list *next;
-};
-
 struct udp {
-    closure_t cl;
-    struct comm_if ops;
-    struct cloc loc;
-    uint32_t addr;
-    uint16_t port;
-    int fd;
-    string_t authbind;
-    struct buffer_if *rbuf;
-    struct notify_list *notify;
-    bool_t use_proxy;
-    struct sockaddr_in proxy;
+    struct udpcommon uc;
+    struct udpsocks socks;
+    bool_t addr_configured;
+    unsigned counter;
 };
 
-static const char *saddr_to_string(const struct sockaddr_in *sin) {
-    static char bufs[2][100];
-    static int b;
-
-    b ^= 1;
-    snprintf(bufs[b], sizeof(bufs[b]), "[%s]:%d",
-            inet_ntoa(sin->sin_addr),
-            ntohs(sin->sin_port));
-    return bufs[b];
-}
+/*
+ * Re comm_addr.ix: This field allows us to note in the comm_addr
+ * which socket an incoming packet was received on.  This is required
+ * for conveniently logging the actual source of a packet.  But the ix
+ * does not formally form part of the address: it is not used when
+ * sending, nor when comparing two comm_addrs.
+ *
+ * The special value -1 means that the comm_addr was constructed by
+ * another module in secnet (eg the resolver), rather than being a
+ * description of the source of an incoming packet.
+ */
 
-static const char *addr_to_string(void *commst, const struct comm_addr *ca) {
+static const char *udp_addr_to_string(void *commst, const struct comm_addr *ca)
+{
     struct udp *st=commst;
+    struct udpsocks *socks=&st->socks;
     static char sbuf[100];
-
-    struct sockaddr_in la;
-    la.sin_addr.s_addr=htonl(st->addr);
-    la.sin_port=htons(st->port);
-
-    snprintf(sbuf, sizeof(sbuf), "udp:%s-%s",
-            saddr_to_string(&la), saddr_to_string(&ca->sin));
+    int ix=ca->ix>=0 ? ca->ix : 0;
+
+    assert(ix>=0 && ix<socks->n_socks);
+    snprintf(sbuf, sizeof(sbuf), "udp#%u@l%d:%s%s-%s",
+            st->counter, st->uc.cc.loc.line,
+            iaddr_to_string(&socks->socks[ix].addr),
+            ca->ix<0 && socks->n_socks>1 ? "&" : "",
+            iaddr_to_string(&ca->ia));
     return sbuf;
 }
 
-static int udp_beforepoll(void *state, struct pollfd *fds, int *nfds_io,
-                         int *timeout_io)
+static int udp_socks_beforepoll(void *state, struct pollfd *fds, int *nfds_io,
+                               int *timeout_io)
 {
-    struct udp *st=state;
-    if (*nfds_io<1) {
-       *nfds_io=1;
-       return ERANGE;
+    struct udpsocks *socks=state;
+    int i;
+    BEFOREPOLL_WANT_FDS(socks->n_socks);
+    for (i=0; i<socks->n_socks; i++) {
+       fds[i].fd=socks->socks[i].fd;
+       fds[i].events=POLLIN;
     }
-    *nfds_io=1;
-    fds->fd=st->fd;
-    fds->events=POLLIN;
     return 0;
 }
 
-static void udp_afterpoll(void *state, struct pollfd *fds, int nfds)
+const char *af_name(int af)
+{
+    switch (af) {
+    case AF_INET6: return "IPv6";
+    case AF_INET:  return "IPv4";
+    case 0:        return "(any)";
+    default: abort();
+    }
+}
+
+void udp_sock_experienced(struct log_if *lg, struct udpcommon *uc,
+                         struct udpsocks *socks, struct udpsock *us,
+                         const union iaddr *dest, int af,
+                         int r, int errnoval)
+{
+    bool_t success=r>=0;
+    if (us->experienced[!!dest][af][success]++)
+       return;
+    lg_perror(lg, uc->cc.cl.description, &uc->cc.loc,
+             success ? M_INFO : M_WARNING,
+             success ? 0 : errnoval,
+             "%s %s experiencing some %s %s%s%s%s%s%s",
+             socks->desc,iaddr_to_string(&us->addr),
+             success?"success":"trouble",
+             dest?"transmitting":"receiving",
+             af?" ":"", af?af_name(af):"",
+             dest?" (to ":"",
+             dest?iaddr_to_string(dest):"",
+             dest?")":"");
+}
+
+static void udp_socks_afterpoll(void *state, struct pollfd *fds, int nfds)
 {
-    struct udp *st=state;
-    struct sockaddr_in from;
+    struct udpsocks *socks=state;
+    struct udpcommon *uc=socks->uc;
+    union iaddr from;
     socklen_t fromlen;
-    struct notify_list *n;
     bool_t done;
     int rv;
+    int i;
 
-    if (nfds && (fds->revents & POLLIN)) {
+    struct commcommon *cc=&uc->cc;
+
+    for (i=0; i<socks->n_socks; i++) {
+       struct udpsock *us=&socks->socks[i];
+       if (i>=nfds) continue;
+       if (!(fds[i].revents & POLLIN)) continue;
+       assert(fds[i].fd == us->fd);
+       int fd=us->fd;
        do {
-           FILLZERO(from);
            fromlen=sizeof(from);
-           BUF_ASSERT_FREE(st->rbuf);
-           BUF_ALLOC(st->rbuf,"udp_afterpoll");
-           buffer_init(st->rbuf,calculate_max_start_pad());
-           rv=recvfrom(st->fd, st->rbuf->start, st->rbuf->len, 0,
-                       (struct sockaddr *)&from, &fromlen);
+           BUF_ASSERT_FREE(cc->rbuf);
+           BUF_ALLOC(cc->rbuf,"udp_afterpoll");
+           buffer_init(cc->rbuf,calculate_max_start_pad());
+           rv=recvfrom(fd, cc->rbuf->start,
+                       buf_remaining_space(cc->rbuf),
+                       0, &from.sa, &fromlen);
            if (rv>0) {
-               st->rbuf->size=rv;
-               if (st->use_proxy) {
+               cc->rbuf->size=rv;
+               if (uc->use_proxy) {
                    /* Check that the packet came from our poxy server;
                       we shouldn't be contacted directly by anybody else
                       (since they can trivially forge source addresses) */
-                   if (memcmp(&from.sin_addr,&st->proxy.sin_addr,4)!=0 ||
-                       memcmp(&from.sin_port,&st->proxy.sin_port,2)!=0) {
+                   if (!iaddr_equal(&from,&uc->proxy,False)) {
                        Message(M_INFO,"udp: received packet that's not "
                                "from the proxy\n");
-                       BUF_FREE(st->rbuf);
+                       BUF_FREE(cc->rbuf);
                        continue;
                    }
-                   memcpy(&from.sin_addr,buf_unprepend(st->rbuf,4),4);
-                   buf_unprepend(st->rbuf,2);
-                   memcpy(&from.sin_port,buf_unprepend(st->rbuf,2),2);
+                   /* proxy protocol supports ipv4 transport only */
+                   from.sa.sa_family=AF_INET;
+                   BUF_GET_BYTES(unprepend,cc->rbuf,&from.sin.sin_addr,4);
+                   buf_unprepend(cc->rbuf,2);
+                   BUF_GET_BYTES(unprepend,cc->rbuf,&from.sin.sin_port,2);
                }
                struct comm_addr ca;
-               FILLZERO(ca);
-               ca.comm=&st->ops;
-               ca.sin=from;
-               done=False;
-               for (n=st->notify; n; n=n->next) {
-                   if (n->fn(n->state, st->rbuf, &ca)) {
-                       done=True;
-                       break;
-                   }
-               }
-               if (!done) {
+               ca.comm=&cc->ops;
+               ca.ia=from;
+               ca.ix=i;
+               done=comm_notify(cc, cc->rbuf, &ca);
+               if (done) {
+                   udp_sock_experienced(0,uc,socks,us,0,
+                                        from.sa.sa_family,0,0);
+               } else {
                    uint32_t msgtype;
-                   if (st->rbuf->size>12 /* prevents traffic amplification */
-                       && ((msgtype=get_uint32(st->rbuf->start+8))
+                   if (cc->rbuf->size>12 /* prevents traffic amplification */
+                       && ((msgtype=get_uint32(cc->rbuf->start+8))
                            != LABEL_NAK)) {
                        uint32_t source,dest;
                        /* Manufacture and send NAK packet */
-                       source=get_uint32(st->rbuf->start); /* Us */
-                       dest=get_uint32(st->rbuf->start+4); /* Them */
-                       send_nak(&ca,source,dest,msgtype,st->rbuf,"unwanted");
+                       source=get_uint32(cc->rbuf->start); /* Us */
+                       dest=get_uint32(cc->rbuf->start+4); /* Them */
+                       send_nak(&ca,source,dest,msgtype,cc->rbuf,
+                                priomsg_getmessage(&cc->why_unwanted,
+                                                   "unwanted"));
                    }
-                   BUF_FREE(st->rbuf);
+                   BUF_FREE(cc->rbuf);
                }
-               BUF_ASSERT_FREE(st->rbuf);
-           } else {
-               BUF_FREE(st->rbuf);
+               BUF_ASSERT_FREE(cc->rbuf);
+           } else { /* rv<=0 */
+               if (errno!=EINTR && !iswouldblock(errno))
+                   udp_sock_experienced(0,uc,socks,us, 0,0, rv,errno);
+               BUF_FREE(cc->rbuf);
            }
        } while (rv>=0);
     }
 }
 
-static void request_notify(void *commst, void *nst, comm_notify_fn *fn)
-{
-    struct udp *st=commst;
-    struct notify_list *n;
-    
-    n=safe_malloc(sizeof(*n),"request_notify");
-    n->fn=fn;
-    n->state=nst;
-    n->next=st->notify;
-    st->notify=n;
-}
-
-static void release_notify(void *commst, void *nst, comm_notify_fn *fn)
-{
-    struct udp *st=commst;
-    struct notify_list *n, **p, *t;
-
-    /* XXX untested */
-    p=&st->notify;
-    for (n=st->notify; n; )
-    {
-       if (n->state==nst && n->fn==fn) {
-           t=n;
-           *p=n->next;
-           n=n->next;
-           free(t);
-       } else {
-           p=&n->next;
-           n=n->next;
-       }
-    }
-}
-
 static bool_t udp_sendmsg(void *commst, struct buffer_if *buf,
-                         const struct comm_addr *dest)
+                         const struct comm_addr *dest,
+                         struct comm_clientinfo *clientinfo)
 {
     struct udp *st=commst;
+    struct udpcommon *uc=&st->uc;
+    struct udpsocks *socks=&st->socks;
     uint8_t *sa;
 
-    if (st->use_proxy) {
+    if (uc->use_proxy) {
+       struct udpsock *us=&socks->socks[0];
        sa=buf_prepend(buf,8);
-       memcpy(sa,&dest->sin.sin_addr,4);
+       if (dest->ia.sa.sa_family != AF_INET) {
+           Message(M_INFO,
+               "udp: proxy means dropping outgoing non-IPv4 packet to %s\n",
+                   iaddr_to_string(&dest->ia));
+           return False;
+       }
+       memcpy(sa,&dest->ia.sin.sin_addr,4);
        memset(sa+4,0,4);
-       memcpy(sa+6,&dest->sin.sin_port,2);
-       sendto(st->fd,sa,buf->size+8,0,(struct sockaddr *)&st->proxy,
-              sizeof(st->proxy));
+       memcpy(sa+6,&dest->ia.sin.sin_port,2);
+       int r=sendto(us->fd,sa,buf->size+8,0,&uc->proxy.sa,
+              iaddr_socklen(&uc->proxy));
+       udp_sock_experienced(0,uc,socks,us, &dest->ia,0, r,errno);
        buf_unprepend(buf,8);
     } else {
-       sendto(st->fd, buf->start, buf->size, 0,
-              (struct sockaddr *)&dest->sin, sizeof(dest->sin));
+       int i,r;
+       bool_t allunsupported=True;
+       int af=dest->ia.sa.sa_family;
+       for (i=0; i<socks->n_socks; i++) {
+           struct udpsock *us=&socks->socks[i];
+           if (us->addr.sa.sa_family != af)
+               /* no point even trying */
+               continue;
+           r=sendto(us->fd, buf->start, buf->size, 0,
+                    &dest->ia.sa, iaddr_socklen(&dest->ia));
+           udp_sock_experienced(0,uc,socks,us, &dest->ia,af, r,errno);
+           if (r>=0) return True;
+           if (!(errno==EAFNOSUPPORT || errno==ENETUNREACH))
+               /* who knows what that error means? */
+               allunsupported=False;
+       }
+       return !allunsupported; /* see doc for comm_sendmsg_fn in secnet.h */
     }
 
     return True;
 }
 
-static void udp_phase_hook(void *sst, uint32_t new_phase)
+void udp_destroy_socket(struct udpcommon *uc, struct udpsock *us)
 {
-    struct udp *st=sst;
-    struct sockaddr_in addr;
-
-    st->fd=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
-    if (st->fd<0) {
-       fatal_perror("udp (%s:%d): socket",st->loc.file,st->loc.line);
-    }
-    if (fcntl(st->fd, F_SETFL, fcntl(st->fd, F_GETFL)|O_NONBLOCK)==-1) {
-       fatal_perror("udp (%s:%d): fcntl(set O_NONBLOCK)",
-                    st->loc.file,st->loc.line);
+    if (us->fd>=0) {
+       close(us->fd);
+       us->fd=-1;
     }
-    if (fcntl(st->fd, F_SETFD, FD_CLOEXEC)==-1) {
-       fatal_perror("udp (%s:%d): fcntl(set FD_CLOEXEC)",
-                    st->loc.file,st->loc.line);
+}
+
+#define FAIL_LG 0, cc->cl.description, &cc->loc, failmsgclass
+#define FAIL(...) do{                                          \
+        lg_perror(FAIL_LG,errno,__VA_ARGS__);  \
+       goto failed;                                            \
+    }while(0)
+
+static bool_t record_socket_gotaddr(struct udpcommon *uc, struct udpsock *us,
+                                   int failmsgclass)
+{
+    struct commcommon *cc=&uc->cc;
+    socklen_t salen=sizeof(us->addr);
+    int r=getsockname(us->fd,&us->addr.sa,&salen);
+    if (r) FAIL("getsockname()");
+    if ((size_t)salen>sizeof(us->addr)) /* cast squashes clang warning */
+      { errno=0; FAIL("getsockname() length"); }
+    return True;
+
+ failed:
+    return False;
+}
+
+bool_t udp_import_socket(struct udpcommon *uc, struct udpsock *us,
+                        int failmsgclass, int fd)
+{
+    FILLZERO(us->experienced);
+    us->fd=fd;
+    return record_socket_gotaddr(uc,us,failmsgclass);
+}
+
+bool_t udp_make_socket(struct udpcommon *uc, struct udpsock *us,
+                      int failmsgclass)
+{
+    const union iaddr *addr=&us->addr;
+    struct commcommon *cc=&uc->cc;
+    us->fd=-1;
+
+    FILLZERO(us->experienced);
+    us->fd=socket(addr->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP);
+    if (us->fd<0) FAIL("socket");
+    setnonblock(us->fd);
+    setcloexec(us->fd);
+#ifdef CONFIG_IPV6
+    if (addr->sa.sa_family==AF_INET6) {
+       int r;
+       int optval=1;
+       socklen_t optlen=sizeof(optval);
+       r=setsockopt(us->fd,IPPROTO_IPV6,IPV6_V6ONLY,&optval,optlen);
+       if (r) FAIL("setsockopt(,IPV6_V6ONLY,&1,)");
     }
+#endif
 
-    FILLZERO(addr);
-    addr.sin_family=AF_INET;
-    addr.sin_addr.s_addr=htonl(st->addr);
-    addr.sin_port=htons(st->port);
-    if (st->authbind) {
+    if (uc->authbind) {
        pid_t c;
        int status;
+       char desc[200];
+       snprintf(desc,sizeof(desc),"authbind for %s: %s",
+                iaddr_to_string(addr), uc->authbind);
 
        /* XXX this fork() and waitpid() business needs to be hidden
           in some system-specific library functions. */
        c=fork();
-       if (c==-1) {
-           fatal_perror("udp_phase_hook: fork() for authbind");
-       }
+       if (c==-1)
+           FAIL("fork() for authbind");
        if (c==0) {
-           char *argv[4], addrstr[9], portstr[5];
-           sprintf(addrstr,"%08lX",(long)addr.sin_addr.s_addr);
-           sprintf(portstr,"%04X",addr.sin_port);
-           argv[0]=st->authbind;
+           char *argv[5], addrstr[33], portstr[5];
+           const char *addrfam;
+           int port;
+           afterfork();
+           switch (addr->sa.sa_family) {
+           case AF_INET:
+               sprintf(addrstr,"%08lX",(long)addr->sin.sin_addr.s_addr);
+               port=addr->sin.sin_port;
+               addrfam=NULL;
+               break;
+#ifdef CONFIG_IPV6
+           case AF_INET6: {
+               int i;
+               for (i=0; i<16; i++)
+                   sprintf(addrstr+i*2,"%02X",addr->sin6.sin6_addr.s6_addr[i]);
+               port=addr->sin6.sin6_port;
+               addrfam="6";
+               break;
+           }
+#endif /*CONFIG_IPV6*/
+           default:
+               fatal("udp (%s:%d): unsupported address family for authbind",
+                     cc->loc.file,cc->loc.line);
+           }
+           sprintf(portstr,"%04X",port);
+           argv[0]=uc->authbind;
            argv[1]=addrstr;
            argv[2]=portstr;
-           argv[3]=NULL;
-           dup2(st->fd,0);
-           execvp(st->authbind,argv);
+           argv[3]=(char*)addrfam;
+           argv[4]=NULL;
+           dup2(us->fd,0);
+           execvp(uc->authbind,argv);
            _exit(255);
        }
        while (waitpid(c,&status,0)==-1) {
            if (errno==EINTR) continue;
-           fatal_perror("udp (%s:%d): authbind",st->loc.file,st->loc.line);
-       }
-       if (WIFSIGNALED(status)) {
-           fatal("udp (%s:%d): authbind died on signal %d",st->loc.file,
-                 st->loc.line, WTERMSIG(status));
+           FAIL("waitpid for authbind");
        }
-       if (WIFEXITED(status) && WEXITSTATUS(status)!=0) {
-           fatal("udp (%s:%d): authbind died with status %d",st->loc.file,
-                 st->loc.line, WEXITSTATUS(status));
+       if (status) {
+           if (WIFEXITED(status) && WEXITSTATUS(status)<127) {
+               int es=WEXITSTATUS(status);
+               lg_perror(FAIL_LG,es,
+                         "%s exited with error exit status %d;"
+                         " indicates error",desc,es);
+           } else {
+               lg_exitstatus(FAIL_LG,status,desc);
+           }
+           goto failed;
        }
     } else {
-       if (bind(st->fd, (struct sockaddr *)&addr, sizeof(addr))!=0) {
-           fatal_perror("udp (%s:%d): bind",st->loc.file,st->loc.line);
-       }
+       if (bind(us->fd, &addr->sa, iaddr_socklen(addr))!=0)
+           FAIL("bind (%s)",iaddr_to_string(addr));
     }
 
-    register_for_poll(st,udp_beforepoll,udp_afterpoll,1,"udp");
+    bool_t ok=record_socket_gotaddr(uc,us,failmsgclass);
+    if (!ok) goto failed;
+
+    return True;
+
+failed:
+    udp_destroy_socket(uc,us);
+    return False;
+}
+
+#undef FAIL
+
+void udp_socks_register(struct udpcommon *uc, struct udpsocks *socks,
+                       const char *desc)
+{
+    socks->uc=uc;
+    socks->desc=desc;
+    socks->interest=
+       register_for_poll(socks,udp_socks_beforepoll,udp_socks_afterpoll,"udp");
+}
+
+void udp_socks_deregister(struct udpcommon *uc, struct udpsocks *socks)
+{
+    socks->uc=uc;
+    deregister_for_poll(socks->interest);
+}
+
+void udp_socks_childpersist(struct udpcommon *uc, struct udpsocks *socks)
+{
+    int i;
+    for (i=0; i<socks->n_socks; i++)
+       udp_destroy_socket(uc,&socks->socks[i]);
+}
+
+static void udp_childpersist_hook(void *sst, uint32_t new_phase)
+{
+    struct udp *st=sst;
+    udp_socks_childpersist(&st->uc,&st->socks);
+}
+
+static void udp_phase_hook(void *sst, uint32_t new_phase)
+{
+    struct udp *st=sst;
+    struct udpsocks *socks=&st->socks;
+    struct udpcommon *uc=&st->uc;
+    int i;
+    bool_t anydone=0;
+
+    for (i=0; i<socks->n_socks; i++) {
+       bool_t required=st->addr_configured
+           || (!anydone && i==socks->n_socks-1);
+       anydone += udp_make_socket(uc,&socks->socks[i],
+                                  required ? M_FATAL : M_WARNING);
+    }
+
+    udp_socks_register(uc,socks, uc->use_proxy ? "proxy" : "socket");
+
+    add_hook(PHASE_CHILDPERSIST,udp_childpersist_hook,st);
 }
 
 static list_t *udp_apply(closure_t *self, struct cloc loc, dict_t *context,
                         list_t *args)
 {
+    static unsigned counter;
+
     struct udp *st;
-    item_t *i,*j;
-    dict_t *d;
+    list_t *caddrl;
     list_t *l;
     uint32_t a;
-
-    st=safe_malloc(sizeof(*st),"udp_apply(st)");
-    st->loc=loc;
-    st->cl.description="udp";
-    st->cl.type=CL_COMM;
-    st->cl.apply=NULL;
-    st->cl.interface=&st->ops;
-    st->ops.st=st;
-    st->ops.request_notify=request_notify;
-    st->ops.release_notify=release_notify;
-    st->ops.sendmsg=udp_sendmsg;
-    st->ops.addr_to_string=addr_to_string;
-    st->port=0;
-    st->use_proxy=False;
-
-    i=list_elem(args,0);
-    if (!i || i->type!=t_dict) {
-       cfgfatal(st->loc,"udp","first argument must be a dictionary\n");
+    int i;
+
+    COMM_APPLY(st,&st->uc.cc,udp_,"udp",loc);
+    COMM_APPLY_STANDARD(st,&st->uc.cc,"udp",args);
+    UDP_APPLY_STANDARD(st,&st->uc,"udp");
+
+    struct udpcommon *uc=&st->uc;
+    struct udpsocks *socks=&st->socks;
+    struct commcommon *cc=&uc->cc;
+
+    st->counter=counter++;
+
+    union iaddr defaultaddrs[] = {
+#ifdef CONFIG_IPV6
+       { .sin6 = { .sin6_family=AF_INET6,
+                   .sin6_port=htons(uc->port),
+                   .sin6_addr=IN6ADDR_ANY_INIT } },
+#endif
+       { .sin = { .sin_family=AF_INET,
+                  .sin_port=htons(uc->port),
+                  .sin_addr= { .s_addr=INADDR_ANY } } }
+    };
+
+    caddrl=dict_lookup(d,"address");
+    st->addr_configured=!!caddrl;
+    socks->n_socks=st->addr_configured ? list_length(caddrl)
+       : (int)ARRAY_SIZE(defaultaddrs);
+    if (socks->n_socks<=0 || socks->n_socks>UDP_MAX_SOCKETS)
+       cfgfatal(cc->loc,"udp","`address' must be 1..%d addresses",
+                UDP_MAX_SOCKETS);
+
+    for (i=0; i<socks->n_socks; i++) {
+       struct udpsock *us=&socks->socks[i];
+       if (!st->addr_configured) {
+           us->addr=defaultaddrs[i];
+       } else {
+           string_item_to_iaddr(list_elem(caddrl,i),uc->port,&us->addr,"udp");
+       }
+       us->fd=-1;
     }
-    d=i->data.dict;
 
-    j=dict_find_item(d,"address",False,"udp",st->loc);
-    st->addr=j?string_item_to_ipaddr(j, "udp"):INADDR_ANY;
-    st->port=dict_read_number(d,"port",True,"udp",st->loc,0);
-    st->rbuf=find_cl_if(d,"buffer",CL_BUFFER,True,"udp",st->loc);
-    st->authbind=dict_read_string(d,"authbind",False,"udp",st->loc);
     l=dict_lookup(d,"proxy");
     if (l) {
-       st->use_proxy=True;
-       FILLZERO(st->proxy);
-       st->proxy.sin_family=AF_INET;
-       i=list_elem(l,0);
-       if (!i || i->type!=t_string) {
-           cfgfatal(st->loc,"udp","proxy must supply ""addr"",port\n");
+       uc->use_proxy=True;
+       uc->proxy.sa.sa_family=AF_INET;
+       item=list_elem(l,0);
+       if (!item || item->type!=t_string) {
+           cfgfatal(cc->loc,"udp","proxy must supply ""addr"",port\n");
        }
-       a=string_item_to_ipaddr(i,"proxy");
-       st->proxy.sin_addr.s_addr=htonl(a);
-       i=list_elem(l,1);
-       if (!i || i->type!=t_number) {
-           cfgfatal(st->loc,"udp","proxy must supply ""addr"",port\n");
+       a=string_item_to_ipaddr(item,"proxy");
+       uc->proxy.sin.sin_addr.s_addr=htonl(a);
+       item=list_elem(l,1);
+       if (!item || item->type!=t_number) {
+           cfgfatal(cc->loc,"udp","proxy must supply ""addr"",port\n");
        }
-       st->proxy.sin_port=htons(i->data.number);
+       uc->proxy.sin.sin_port=htons(item->data.number);
     }
 
-    update_max_start_pad(&comm_max_start_pad, st->use_proxy ? 8 : 0);
+    update_max_start_pad(&comm_max_start_pad, uc->use_proxy ? 8 : 0);
 
     add_hook(PHASE_GETRESOURCES,udp_phase_hook,st);
 
-    return new_closure(&st->cl);
+    return new_closure(&cc->cl);
 }
 
 void udp_module(dict_t *dict)
index 1de6ca47a37c1cf400a965b517527ed879b3cb86..f462c0335cade66b7ec4d565c7812b1f977d33a9 100644 (file)
@@ -1,6 +1,24 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
+<!--
+ This file is part of secnet.
+ See README for full list of copyright holders.
+
+ secnet is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+ secnet is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ version 3 along with secnet; if not, see
+ https://www.gnu.org/licenses/gpl.html.
+  -->
 <dict>
        <key>EnvironmentVariables</key>
        <dict>
index 00c6fc6bca568f6d7c4fd78d8c542279c08bc850..2c48d0e33d77306675f179d7ef5d18ae820b970a 100644 (file)
@@ -1,3 +1,22 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #ifndef unaligned_h
 #define unaligned_h
 
diff --git a/util.c b/util.c
index 8c23485c059581cbb2aef2c1d8c40628dbaeecaa..3e8d56eaebbc50855ed2cde7026b5bf5be7c0767 100644 (file)
--- a/util.c
+++ b/util.c
@@ -6,26 +6,22 @@
  * - MPI convenience functions
  */
 /*
- *  This file is
- *    Copyright (C) 1995--2001 Stephen Early <steve@greenend.org.uk>
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
  *
- *  It is part of secnet, which is
- *    Copyright (C) 1995--2001 Stephen Early <steve@greenend.org.uk>
- *    Copyright (C) 1998 Ross Anderson, Eli Biham, Lars Knudsen
- *  
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2, or (at your option)
- *  any later version.
- *  
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *  
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software Foundation,
- *  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
  */
 
 #include "secnet.h"
 #include <limits.h>
 #include <assert.h>
 #include <sys/wait.h>
+#include <adns.h>
 #include "util.h"
 #include "unaligned.h"
 #include "magic.h"
+#include "ipaddr.h"
 
 #define MIN_BUFFER_SIZE 64
 #define DEFAULT_BUFFER_SIZE 4096
@@ -51,10 +49,10 @@ uint32_t current_phase=0;
 struct phase_hook {
     hook_fn *fn;
     void *state;
-    struct phase_hook *next;
+    LIST_ENTRY(phase_hook) entry;
 };
 
-static struct phase_hook *hooks[NR_PHASES]={NULL,};
+static LIST_HEAD(, phase_hook) hooks[NR_PHASES];
 
 char *safe_strdup(const char *s, const char *message)
 {
@@ -69,44 +67,49 @@ char *safe_strdup(const char *s, const char *message)
 void *safe_malloc(size_t size, const char *message)
 {
     void *r;
+    if (!size)
+       return 0;
     r=malloc(size);
     if (!r) {
        fatal_perror("%s",message);
     }
     return r;
 }
-void *safe_malloc_ary(size_t size, size_t count, const char *message) {
+void *safe_realloc_ary(void *p, size_t size, size_t count,
+                      const char *message) {
     if (count >= INT_MAX/size) {
        fatal("array allocation overflow: %s", message);
     }
-    return safe_malloc(size*count, message);
+    assert(size && count);
+    p = realloc(p, size*count);
+    if (!p)
+       fatal_perror("%s", message);
+    return p;
 }
 
-/* Convert a buffer into its MP_INT representation */
-void read_mpbin(MP_INT *a, uint8_t *bin, int binsize)
+void *safe_malloc_ary(size_t size, size_t count, const char *message) {
+    if (!size || !count)
+       return 0;
+    return safe_realloc_ary(0,size,count,message);
+}
+
+void hex_encode(const uint8_t *bin, int binsize, char *buff)
 {
-    char *buff;
     int i;
 
-    buff=safe_malloc(binsize*2 + 1,"read_mpbin");
-
     for (i=0; i<binsize; i++) {
        buff[i*2]=hexdigits[(bin[i] & 0xf0) >> 4];
        buff[i*2+1]=hexdigits[(bin[i] & 0xf)];
     }
     buff[binsize*2]=0;
-
-    mpz_set_str(a, buff, 16);
-    free(buff);
 }
 
-/* Convert a MP_INT into a hex string */
-char *write_mpstring(MP_INT *a)
+string_t hex_encode_alloc(const uint8_t *bin, int binsize)
 {
     char *buff;
 
-    buff=safe_malloc(mpz_sizeinbase(a,16)+2,"write_mpstring");
-    mpz_get_str(buff, 16, a);
+    buff=safe_malloc(hex_encode_size(binsize),"hex_encode");
+    hex_encode(bin,binsize,buff);
     return buff;
 }
 
@@ -139,27 +142,63 @@ static uint8_t hexval(uint8_t c)
     return -1;
 }
 
-/* Convert a MP_INT into a buffer; return length; truncate if necessary */
-int32_t write_mpbin(MP_INT *a, uint8_t *buffer, int32_t buflen)
+bool_t hex_decode(uint8_t *buffer, int32_t buflen, int32_t *outlen,
+                 cstring_t hb, bool_t allow_odd_nibble)
 {
-    char *hb;
-    int i,j,l;
-    
-    if (buflen==0) return 0;
-    hb=write_mpstring(a);
-    
-    l=strlen(hb);
-    i=0; j=0;
+    int i = 0, j = 0, l = strlen(hb), hi, lo;
+    bool_t ok = False;
+
+    if (!l || !buflen) { ok = !l; goto done; }
     if (l&1) {
        /* The number starts with a half-byte */
-       buffer[i++]=hexval(hb[j++]);
+       if (!allow_odd_nibble) goto done;
+       lo = hexval(hb[j++]); if (lo < 0) goto done;
+       buffer[i++] = lo;
     }
-    for (; hb[j] && i<buflen; i++) {
-       buffer[i]=(hexval(hb[j])<<4)|hexval(hb[j+1]);
-       j+=2;
+    for (; hb[j] && i < buflen; i++) {
+       hi = hexval(hb[j++]);
+       lo = hexval(hb[j++]);
+       if (hi < 0 || lo < 0) goto done;
+       buffer[i] = (hi << 4) | lo;
     }
-    free(hb);
-    return i;
+    ok = !hb[j];
+done:
+    *outlen = i;
+    return ok;
+}
+
+void read_mpbin(MP_INT *a, uint8_t *bin, int binsize)
+{
+    char *buff = hex_encode_alloc(bin, binsize);
+    mpz_set_str(a, buff, 16);
+    free(buff);
+}
+
+char *write_mpstring(MP_INT *a)
+{
+    char *buff;
+
+    buff=safe_malloc(mpz_sizeinbase(a,16)+2,"write_mpstring");
+    mpz_get_str(buff, 16, a);
+    return buff;
+}
+
+#define DEFINE_SETFDFLAG(fn,FL,FLAG)                                   \
+void fn(int fd) {                                                      \
+    int r=fcntl(fd, F_GET##FL);                                                \
+    if (r<0) fatal_perror("fcntl(,F_GET" #FL ") failed");              \
+    r=fcntl(fd, F_SET##FL, r|FLAG);                                    \
+    if (r<0) fatal_perror("fcntl(,F_SET" #FL ",|" #FLAG ") failed");   \
+}
+
+DEFINE_SETFDFLAG(setcloexec,FD,FD_CLOEXEC);
+DEFINE_SETFDFLAG(setnonblock,FL,O_NONBLOCK);
+
+void pipe_cloexec(int fd[2]) {
+    int r=pipe(fd);
+    if (r) fatal_perror("pipe");
+    setcloexec(fd[0]);
+    setcloexec(fd[1]);
 }
 
 static const char *phases[NR_PHASES]={
@@ -171,31 +210,46 @@ static const char *phases[NR_PHASES]={
     "PHASE_GETRESOURCES",
     "PHASE_DROPPRIV",
     "PHASE_RUN",
-    "PHASE_SHUTDOWN"
+    "PHASE_SHUTDOWN",
+    "PHASE_CHILDPERSIST"
 };
 
 void enter_phase(uint32_t new_phase)
 {
     struct phase_hook *i;
 
-    if (hooks[new_phase])
+    if (!LIST_EMPTY(&hooks[new_phase]))
        Message(M_DEBUG_PHASE,"Running hooks for %s...\n", phases[new_phase]);
     current_phase=new_phase;
 
-    for (i=hooks[new_phase]; i; i=i->next)
+    LIST_FOREACH(i, &hooks[new_phase], entry)
        i->fn(i->state, new_phase);
     Message(M_DEBUG_PHASE,"Now in %s\n",phases[new_phase]);
 }
 
+void phase_hooks_init(void)
+{
+    int i;
+    for (i=0; i<NR_PHASES; i++)
+       LIST_INIT(&hooks[i]);
+}
+
+void clear_phase_hooks(uint32_t phase)
+{
+    struct phase_hook *h, *htmp;
+    LIST_FOREACH_SAFE(h, &hooks[phase], entry, htmp)
+       free(h);
+    LIST_INIT(&hooks[phase]);
+}
+
 bool_t add_hook(uint32_t phase, hook_fn *fn, void *state)
 {
     struct phase_hook *h;
 
-    h=safe_malloc(sizeof(*h),"add_hook");
+    NEW(h);
     h->fn=fn;
     h->state=state;
-    h->next=hooks[phase];
-    hooks[phase]=h;
+    LIST_INSERT_HEAD(&hooks[phase],h,entry);
     return True;
 }
 
@@ -247,14 +301,22 @@ void buffer_assert_used(struct buffer_if *buffer, cstring_t file,
 
 void buffer_init(struct buffer_if *buffer, int32_t max_start_pad)
 {
-    assert(max_start_pad<=buffer->len);
+    assert(max_start_pad<=buffer->alloclen);
     buffer->start=buffer->base+max_start_pad;
     buffer->size=0;
 }
 
+void buffer_destroy(struct buffer_if *buf)
+{
+    BUF_ASSERT_FREE(buf);
+    free(buf->base);
+    buf->start=buf->base=0;
+    buf->size=buf->alloclen=0;
+}
+
 void *buf_append(struct buffer_if *buf, int32_t amount) {
     void *p;
-    assert(buf->size <= buf->len - amount);
+    assert(amount <= buf_remaining_space(buf));
     p=buf->start + buf->size;
     buf->size+=amount;
     return p;
@@ -280,8 +342,6 @@ void *buf_unprepend(struct buffer_if *buf, int32_t amount) {
     return p;
 }
 
-/* Append a two-byte length and the string to the buffer. Length is in
-   network byte order. */
 void buf_append_string(struct buffer_if *buf, cstring_t s)
 {
     size_t len;
@@ -289,18 +349,89 @@ void buf_append_string(struct buffer_if *buf, cstring_t s)
     len=strlen(s);
     /* fixme: if string is longer than 65535, result is a corrupted packet */
     buf_append_uint16(buf,len);
-    memcpy(buf_append(buf,len),s,len);
+    BUF_ADD_BYTES(append,buf,s,len);
+}
+
+void truncmsg_add_string(struct buffer_if *buf, cstring_t s)
+{
+    int32_t l = MIN((int32_t)strlen(s), buf_remaining_space(buf));
+    BUF_ADD_BYTES(append, buf, s, l);
+}
+void truncmsg_add_packet_string(struct buffer_if *buf, int32_t l,
+                               const uint8_t *s)
+{
+    char c;
+    while (l-- > 0) {
+       c = *s++;
+       if (c >= ' ' && c <= 126 && c != '\\' && c != '"' && c != '\'') {
+           if (!buf_remaining_space(buf)) break;
+           buf->start[buf->size++] = c;
+           continue;
+       }
+       char quoted[5];
+       quoted[0] = '\\';
+       quoted[2] = 0;
+       switch (c) {
+       case '\n': quoted[1] = 'n'; break;
+       case '\r': quoted[1] = 'r'; break;
+       case '\t': quoted[1] = 't'; break;
+       case '\\': case '"': case '\'': quoted[1] = c; break;
+       default: sprintf(quoted, "\\x%02x", (unsigned)c);
+       }
+       truncmsg_add_string(buf, quoted);
+    }
+}
+const char *truncmsg_terminate(const struct buffer_if *buf)
+{
+    if (buf_remaining_space(buf)) {
+       buf->start[buf->size] = 0;
+    } else {
+       assert(buf->size >= 4);
+       strcpy(buf->start + buf->size - 4, "...");
+    }
+    return buf->start;
+}
+
+void priomsg_new(struct priomsg *pm, int32_t maxlen)
+{
+    buffer_new(&pm->m, maxlen);
+    pm->prio = INT_MIN;
+}
+void priomsg_reset(struct priomsg *pm)
+{
+    buffer_init(&pm->m, 0);
+    pm->prio = INT_MIN;
+}
+bool_t priomsg_update_p(struct priomsg *pm, int prio)
+{
+    if (!pm) return False;
+    if (prio <= pm->prio) return False;
+    buffer_init(&pm->m, 0);
+    pm->prio = prio;
+    return True;
+}
+const char *priomsg_getmessage(const struct priomsg *pm, const char *defmsg)
+{
+    if (pm->prio >= INT_MIN)
+       return truncmsg_terminate(&pm->m);
+    else
+       return defmsg;
+}
+
+bool_t priomsg_update_fixed(struct priomsg *pm, int prio, const char *m) {
+    if (!priomsg_update_p(pm, prio)) return False;
+    truncmsg_add_string(&pm->m, m);
+    return True;
 }
 
 void buffer_new(struct buffer_if *buf, int32_t len)
 {
     buf->free=True;
     buf->owner=NULL;
-    buf->flags=0;
     buf->loc.file=NULL;
     buf->loc.line=0;
     buf->size=0;
-    buf->len=len;
+    buf->alloclen=len;
     buf->start=NULL;
     buf->base=safe_malloc(len,"buffer_new");
 }
@@ -309,10 +440,9 @@ void buffer_readonly_view(struct buffer_if *buf, const void *data, int32_t len)
 {
     buf->free=False;
     buf->owner="READONLY";
-    buf->flags=0;
     buf->loc.file=NULL;
     buf->loc.line=0;
-    buf->size=buf->len=len;
+    buf->size=buf->alloclen=len;
     buf->base=buf->start=(uint8_t*)data;
 }
 
@@ -323,10 +453,10 @@ void buffer_readonly_clone(struct buffer_if *out, const struct buffer_if *in)
 
 void buffer_copy(struct buffer_if *dst, const struct buffer_if *src)
 {
-    if (dst->len < src->len) {
-       dst->base=realloc(dst->base,src->len);
+    if (dst->alloclen < src->alloclen) {
+       dst->base=realloc(dst->base,src->alloclen);
        if (!dst->base) fatal_perror("buffer_copy");
-       dst->len = src->len;
+       dst->alloclen = src->alloclen;
     }
     dst->start = dst->base + (src->start - src->base);
     dst->size = src->size;
@@ -342,7 +472,7 @@ static list_t *buffer_apply(closure_t *self, struct cloc loc, dict_t *context,
     bool_t lockdown=False;
     uint32_t len=DEFAULT_BUFFER_SIZE;
     
-    st=safe_malloc(sizeof(*st),"buffer_apply");
+    NEW(st);
     st->cl.description="buffer";
     st->cl.type=CL_BUFFER;
     st->cl.apply=NULL;
@@ -392,11 +522,12 @@ void send_nak(const struct comm_addr *dest, uint32_t our_index,
     buf_append_uint32(buf,our_index);
     buf_append_uint32(buf,LABEL_NAK);
     if (logwhy)
-       Message(M_INFO,"%s: %08"PRIx32"<-%08"PRIx32": %08"PRIx32":"
-               " %s; sending NAK\n",
-               dest->comm->addr_to_string(dest->comm->st,dest),
-               our_index, their_index, msgtype, logwhy);
-    dest->comm->sendmsg(dest->comm->st, buf, dest);
+       Message(M_INFO,"%s: sending NAK for"
+               " %08"PRIx32" %08"PRIx32"<-%08"PRIx32":"
+               " %s\n",
+               comm_addr_to_string(dest),
+               msgtype, our_index, their_index, logwhy);
+    dest->comm->sendmsg(dest->comm->st, buf, dest, 0);
 }
 
 int consttime_memeq(const void *s1in, const void *s2in, size_t n)
@@ -415,6 +546,14 @@ int consttime_memeq(const void *s1in, const void *s2in, size_t n)
     return accumulator;
 }
 
+void hash_hash(const struct hash_if *hashi, const void *msg, int32_t len,
+              uint8_t *digest) {
+    uint8_t hst[hashi->slen];
+    hashi->init(hst);
+    hashi->update(hst,msg,len);
+    hashi->final(hst,digest);
+}
+
 void util_module(dict_t *dict)
 {
     add_closure(dict,"sysbuffer",buffer_apply);
@@ -435,3 +574,234 @@ int32_t calculate_max_start_pad(void)
        transform_max_start_pad +
        comm_max_start_pad;
 }
+
+void vslilog_part(struct log_if *lf, int priority, const char *message, va_list ap)
+{
+    char *buff=lf->buff;
+    size_t bp;
+    char *nlp;
+
+    bp=strlen(buff);
+    assert(bp < LOG_MESSAGE_BUFLEN);
+    vsnprintf(buff+bp,LOG_MESSAGE_BUFLEN-bp,message,ap);
+    buff[LOG_MESSAGE_BUFLEN-1] = '\n';
+    buff[LOG_MESSAGE_BUFLEN] = '\0';
+    /* Each line is sent separately */
+    while ((nlp=strchr(buff,'\n'))) {
+       *nlp=0;
+       slilog(lf,priority,"%s",buff);
+       memmove(buff,nlp+1,strlen(nlp+1)+1);
+    }
+}
+
+extern void slilog_part(struct log_if *lf, int priority, const char *message, ...)
+{
+    va_list ap;
+    va_start(ap,message);
+    vslilog_part(lf,priority,message,ap);
+    va_end(ap);
+}
+
+void string_item_to_iaddr(const item_t *item, uint16_t port, union iaddr *ia,
+                         const char *desc)
+{
+#ifndef CONFIG_IPV6
+
+    ia->sin.sin_family=AF_INET;
+    ia->sin.sin_addr.s_addr=htonl(string_item_to_ipaddr(item,desc));
+    ia->sin.sin_port=htons(port);
+
+#else /* CONFIG_IPV6 => we have adns_text2addr */
+
+    if (item->type!=t_string)
+       cfgfatal(item->loc,desc,"expecting a string IP (v4 or v6) address\n");
+    socklen_t salen=sizeof(*ia);
+    int r=adns_text2addr(item->data.string, port,
+                        adns_qf_addrlit_ipv4_quadonly,
+                        &ia->sa, &salen);
+    assert(r!=ENOSPC);
+    if (r) cfgfatal(item->loc,desc,"invalid IP (v4 or v6) address: %s\n",
+                   strerror(r));
+
+#endif /* CONFIG_IPV6 */
+}
+
+#define IADDR_NBUFS 8
+
+const char *iaddr_to_string(const union iaddr *ia)
+{
+#ifndef CONFIG_IPV6
+
+    SBUF_DEFINE(IADDR_NBUFS, 100);
+
+    assert(ia->sa.sa_family == AF_INET);
+
+    snprintf(SBUF, sizeof(SBUF), "[%s]:%d",
+            inet_ntoa(ia->sin.sin_addr),
+            ntohs(ia->sin.sin_port));
+
+#else /* CONFIG_IPV6 => we have adns_addr2text */
+
+    SBUF_DEFINE(IADDR_NBUFS, 1+ADNS_ADDR2TEXT_BUFLEN+20);
+
+    int port;
+
+    char *addrbuf = SBUF;
+    *addrbuf++ = '[';
+    int addrbuflen = ADNS_ADDR2TEXT_BUFLEN;
+
+    int r = adns_addr2text(&ia->sa, 0, addrbuf, &addrbuflen, &port);
+    if (r) {
+       const char fmt[]= "bad addr, error: %.*s";
+       sprintf(addrbuf, fmt,
+               (int)(ADNS_ADDR2TEXT_BUFLEN - sizeof(fmt)) /* underestimate */,
+               strerror(r));
+    }
+
+    char *portbuf = addrbuf;
+    int addrl = strlen(addrbuf);
+    portbuf += addrl;
+
+    snprintf(portbuf, sizeof(SBUF)-addrl, "]:%d", port);
+
+#endif /* CONFIG_IPV6 */
+
+    return SBUF;
+}
+
+bool_t iaddr_equal(const union iaddr *ia, const union iaddr *ib,
+                  bool_t ignoreport)
+{
+    if (ia->sa.sa_family != ib->sa.sa_family)
+       return 0;
+    switch (ia->sa.sa_family) {
+    case AF_INET:
+       return ia->sin.sin_addr.s_addr == ib->sin.sin_addr.s_addr
+           && (ignoreport ||
+              ia->sin.sin_port        == ib->sin.sin_port);
+#ifdef CONFIG_IPV6
+    case AF_INET6:
+       return !memcmp(&ia->sin6.sin6_addr, &ib->sin6.sin6_addr, 16)
+          &&  ia->sin6.sin6_scope_id  == ib->sin6.sin6_scope_id
+           && (ignoreport ||
+              ia->sin6.sin6_port      == ib->sin6.sin6_port)
+           /* we ignore the flowinfo field */;
+#endif /* CONFIG_IPV6 */
+    default:
+       abort();
+    }
+}
+
+int iaddr_socklen(const union iaddr *ia)
+{
+    switch (ia->sa.sa_family) {
+    case AF_INET:  return sizeof(ia->sin);
+#ifdef CONFIG_IPV6
+    case AF_INET6: return sizeof(ia->sin6);
+#endif /* CONFIG_IPV6 */
+    default:       abort();
+    }
+}
+
+const char *pollbadbit(int revents)
+{
+#define BADBIT(b) \
+    if ((revents & b)) return #b
+    BADBIT(POLLERR);
+    BADBIT(POLLHUP);
+    /* POLLNVAL is handled by the event loop - see afterpoll_fn comment */
+#undef BADBIT
+    return 0;
+}
+
+void pathprefix_template_init(struct pathprefix_template *out,
+                             const char *prefix, int maxsuffix)
+{
+    size_t l=strlen(prefix);
+    NEW_ARY(out->buffer,l+maxsuffix+1);
+    strcpy(out->buffer,prefix);
+    out->write_here=out->buffer+l;
+}
+
+enum async_linebuf_result
+async_linebuf_read(struct pollfd *pfd, struct buffer_if *buf,
+                  const char **emsg_out)
+{
+    int revents=pfd->revents;
+
+#define BAD(m) do{ *emsg_out=(m); return async_linebuf_broken; }while(0)
+
+    const char *badbit=pollbadbit(revents);
+    if (badbit) BAD(badbit);
+
+    if (!(revents & POLLIN))
+       return async_linebuf_nothing;
+
+    /*
+     * Data structure: A line which has been returned to the user is
+     * stored in buf at base before start.  But we retain the usual
+     * buffer meaning of size.  So:
+     *
+     *   | returned :    | input read,   |    unused    |
+     *   |  to user : \0 |  awaiting     |     buffer   |
+     *   |          :    |  processing   |      space   |
+     *   |          :    |               |              |
+     *   ^base           ^start          ^start+size    ^base+alloclen
+     */
+
+    BUF_ASSERT_USED(buf);
+
+    /* firstly, eat any previous */
+    if (buf->start != buf->base) {
+       memmove(buf->base,buf->start,buf->size);
+       buf->start=buf->base;
+    }
+
+    uint8_t *searched=buf->base;
+
+    /*
+     * During the workings here we do not use start.  We set start
+     * when we return some actual data.  So we have this:
+     *
+     *   | searched     | read, might   |  unused      |
+     *   |  for \n      |  contain \n   |   buffer     |
+     *   |  none found  |  but not \0   |    space     |
+     *   |              |               |              |
+     *   ^base          ^searched       ^base+size     ^base+alloclen
+     *  [^start]                        ^dataend
+     *
+     */
+    for (;;) {
+       uint8_t *dataend=buf->base+buf->size;
+       char *newline=memchr(searched,'\n',dataend-searched);
+       if (newline) {
+           *newline=0;
+           buf->start=newline+1;
+           buf->size=dataend-buf->start;
+           return async_linebuf_ok;
+       }
+       searched=dataend;
+       ssize_t space=(buf->base+buf->alloclen)-dataend;
+       if (!space) BAD("input line too long");
+       ssize_t r=read(pfd->fd,searched,space);
+       if (r==0) {
+           *searched=0;
+           *emsg_out=buf->size?"no newline at eof":0;
+           buf->start=searched+1;
+           buf->size=0;
+           return async_linebuf_eof;
+       }
+       if (r<0) {
+           if (errno==EINTR)
+               continue;
+           if (iswouldblock(errno))
+               return async_linebuf_nothing;
+           BAD(strerror(errno));
+       }
+       assert(r<=space);
+       if (memchr(searched,0,r)) BAD("nul in input data");
+       buf->size+=r;
+    }
+
+#undef BAD
+}
diff --git a/util.h b/util.h
index 816c05621571262924460c46ed3358819ecd051e..f28a00da7c538823e22957cbf52f06cf121a811b 100644 (file)
--- a/util.h
+++ b/util.h
@@ -1,3 +1,22 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 #ifndef util_h
 #define util_h
 
@@ -23,12 +42,78 @@ extern void buffer_assert_used(struct buffer_if *buffer, cstring_t file,
                               int line);
 extern void buffer_new(struct buffer_if *buffer, int32_t len);
 extern void buffer_init(struct buffer_if *buffer, int32_t max_start_pad);
+extern void buffer_destroy(struct buffer_if *buffer);
 extern void buffer_copy(struct buffer_if *dst, const struct buffer_if *src);
 extern void *buf_append(struct buffer_if *buf, int32_t amount);
 extern void *buf_prepend(struct buffer_if *buf, int32_t amount);
 extern void *buf_unappend(struct buffer_if *buf, int32_t amount);
 extern void *buf_unprepend(struct buffer_if *buf, int32_t amount);
 
+/* These construct a message in a buffer, truncating if necessary.
+ * _string is only safe for trusted input and *not* UTF-8 (sorry).
+ * _packet_string is safe for any input, including untrusted.
+ * _terminate arranges for the buffer to be null-terminated (and
+ * maybe for a trailing `...' to indicate truncation), and returns
+ * a pointer to the null-terminated string. */
+void truncmsg_add_string(struct buffer_if *buf, cstring_t s);
+void truncmsg_add_packet_string(struct buffer_if*, int32_t, const uint8_t*);
+const char *truncmsg_terminate(const struct buffer_if *buf);
+
+
+struct priomsg {
+    /* U: uninitialised
+     * F: initialised but free (no memory allocated), no leak if discarded
+     * Z: contains no message yet
+     * M: contains some message; you may call truncmsg_add_*
+     */
+    int prio;
+    struct buffer_if m;
+};
+
+void priomsg_new(struct priomsg *pm, int32_t maxlen);          /* UF -> Z */
+void priomsg_destroy(struct priomsg *pm, int32_t maxlen);     /* FZM -> F */
+void priomsg_reset(struct priomsg *pm);                       /* FZM -> Z */
+bool_t priomsg_update_p(struct priomsg *pm, int prio);         /* ZM -> M */
+  /* returns true iff message of priority prio ought to be added,
+   * caller should then call truncmsg_add_*.
+   * pm may be NULL, in which case it just returns false */
+const char *priomsg_getmessage(const struct priomsg *pm, const char *defmsg);
+  /* return value is null-terminated, valid until next call
+   * or until defmsg is no longer valid                                ZM */
+
+bool_t priomsg_update_fixed(struct priomsg *pm, int prio, const char *m);
+  /* convenience combination of _update_p and truncmsg_add_string */
+
+/*
+ * void BUF_ADD_BYTES(append,    struct buffer_if*, const void*, int32_t size);
+ * void BUF_ADD_BYTES(prepend,   struct buffer_if*, const void*, int32_t size);
+ * void BUF_GET_BYTES(unappend,  struct buffer_if*,       void*, int32_t size);
+ * void BUF_GET_BYTES(unprepend, struct buffer_if*,       void*, int32_t size);
+ *     // all of these evaluate size twice
+ *
+ * void BUF_ADD_OBJ(append,    struct_buffer_if*, const OBJECT& something);
+ * void BUF_ADD_OBJ(prepend,   struct_buffer_if*, const OBJECT& something);
+ * void BUF_GET_OBJ(unappend,  struct_buffer_if*,       OBJECT& something);
+ * void BUF_GET_OBJ(unprepend, struct_buffer_if*,       OBJECT& something);
+ */
+#define BUF_ADD_BYTES(appendprepend, bufp, datap, size)                        \
+    (buf_un##appendprepend /* ensures we have correct direction */,    \
+     memcpy(buf_##appendprepend((bufp),(size)),(datap),(size)))
+#define BUF_ADD_OBJ(appendprepend, bufp, obj) \
+    BUF_ADD_BYTES(appendprepend,(bufp),&(obj),sizeof((obj)))
+#define BUF_GET_BYTES(unappendunprepend, bufp, datap, size)            \
+    (BUF_GET__DOESNOTEXIST__buf_un##unappendunprepend,                 \
+     memcpy((datap),buf_##unappendunprepend((bufp),(size)),(size)))
+#define BUF_GET_OBJ(unappendunprepend, bufp, obj) \
+    BUF_ADD_BYTES(unappendunprepend,&(obj),(bufp),sizeof((obj)))
+#define BUF_GET__DOESNOTEXIST__buf_ununappend  0
+#define BUF_GET__DOESNOTEXIST__buf_ununprepend 0
+
+static inline int32_t buf_remaining_space(const struct buffer_if *buf)
+{
+    return (buf->base + buf->alloclen) - (buf->start + buf->size);
+}
+
 extern void buffer_readonly_view(struct buffer_if *n, const void*, int32_t len);
 extern void buffer_readonly_clone(struct buffer_if *n, const struct buffer_if*);
   /* Caller must only use unappend, unprepend et al. on n.
@@ -36,12 +121,32 @@ extern void buffer_readonly_clone(struct buffer_if *n, const struct buffer_if*);
    * it must NOT be freed. */
 
 extern void buf_append_string(struct buffer_if *buf, cstring_t s);
+  /* Append a two-byte length and the string to the buffer. Length is in
+   * network byte order. */
+
+static inline int hex_encode_size(int binsize) { return binsize*2 + 1; }
+extern void hex_encode(const uint8_t *bin, int binsize, char *buf);
+  /* Convert a byte array to hex into a supplied buffer. */
+extern string_t hex_encode_alloc(const uint8_t *bin, int binsize);
+  /* Returns the result in a freshly allocated string. */
+
+extern bool_t hex_decode(uint8_t *buffer, int32_t buflen, int32_t *outlen,
+                        cstring_t hb, bool_t allow_odd_nibble);
+  /* Convert a hex string to binary, storing the result in buffer.  If
+   * allow_odd_nibble then it is acceptable if the input is an odd number of
+   * digits, and an additional leading zero digit is assumed; otherwise, this
+   * is not acceptable and the conversion fails.
+   *
+   * The input is processed left to right until it is consumed, the buffer is
+   * full, or an error is encountered in the input.  The length of output
+   * produced is stored in *outlen.  Returns true if the entire input was
+   * processed without error; otherwise false. */
 
 extern void read_mpbin(MP_INT *a, uint8_t *bin, int binsize);
+  /* Convert a buffer into its MP_INT representation */
 
 extern char *write_mpstring(MP_INT *a);
-
-extern int32_t write_mpbin(MP_INT *a, uint8_t *buffer, int32_t buflen);
+  /* Convert a MP_INT into a hex string */
 
 extern struct log_if *init_log(list_t *loglist);
 
@@ -51,4 +156,131 @@ extern void send_nak(const struct comm_addr *dest, uint32_t our_index,
 
 extern int consttime_memeq(const void *s1, const void *s2, size_t n);
 
+void hash_hash(const struct hash_if *hashi, const void *msg, int32_t len,
+              uint8_t *digest /* hi->hlen bytes */);
+
+const char *iaddr_to_string(const union iaddr *ia);
+int iaddr_socklen(const union iaddr *ia);
+
+void string_item_to_iaddr(const item_t *item, uint16_t port, union iaddr *ia,
+                         const char *desc);
+
+
+/*----- pathprefix_template -----*/
+
+struct pathprefix_template {
+    char *buffer;
+    char *write_here;
+};
+
+void pathprefix_template_init(struct pathprefix_template *out,
+                             const char *prefix, int maxsuffix);
+static inline void pathprefix_template_setsuffix
+   (struct pathprefix_template *upd, const char *suffix)
+   { strcpy(upd->write_here,suffix); }
+
+
+/*
+ * SBUF_DEFINE(int nbufs, size_t size);
+ *   // Generates a number of definitions and statements organising
+ *   // nbufs rotating char[size] buffers such that subsequent code
+ *   // may refer to:
+ * char *const SBUF;
+ */
+#define SBUF_DEFINE(nbufs, size)                       \
+    static int static_bufs__bufnum;                    \
+    static char static_bufs__bufs[(nbufs)][(size)];    \
+    static_bufs__bufnum++;                             \
+    static_bufs__bufnum %= (nbufs);                    \
+    static_bufs__bufs[static_bufs__bufnum]
+#define SBUF (static_bufs__bufs[static_bufs__bufnum])
+
+/*----- line-buffered asynch input -----*/
+
+enum async_linebuf_result {
+    async_linebuf_nothing,
+    async_linebuf_ok,
+    async_linebuf_eof,
+    async_linebuf_broken,
+};
+
+const char *pollbadbit(int revents); /* returns 0, or bad bit description */
+
+enum async_linebuf_result
+async_linebuf_read(struct pollfd *pfd, struct buffer_if *buf,
+                  const char **emsg_out);
+   /* Implements reading whole lines, asynchronously.  Use like
+    * this:
+    *   - set up the fd, which should be readable, O_NONBLOCK
+    *   - set up and initialise buffer, which should be big enough
+    *     for one line plus its trailing newline, and be empty
+    *     with start==base
+    *   - in your beforepoll_fn, be interested in POLLIN
+    *   - in your afterpoll_fn, repeatedly call this function
+    *     until it doesn't return `nothing'
+    *   - after you're done, simply close fd and free or reset buf
+    * State on return from async_linebuf_read depends on return value:
+    *
+    *   async_linebuf_nothing:
+    *
+    *      No complete lines available right now.  You should return
+    *      from afterpoll.  buf should be left untouched until the
+    *      next call to async_linebuf_read.
+    *
+    *   async_linebuf_ok:
+    *
+    *      buf->base contains a input line as a nul-terminated string
+    *      (\n replaced by \0); *emsg_out==0.  You must call
+    *      async_linebuf_read again before returning from afterpoll.
+    *
+    *   async_linebuf_eof:
+    *
+    *      EOF on stream.  buf->base contains any partial
+    *      (non-newline-terminated) line; *emsg_out!=0 iff there was
+    *      such a partial line.  You can call async_linebuf_read again
+    *      if you like but it will probably just return eof again.
+    *
+    *   broken:
+    *
+    *      Fatal problem (might be overly long lines, nuls in input
+    *      data, bad bits in pfd->revents, errors from read, etc.)
+    *
+    *      *emsg_out is the error message describing the problem;
+    *      this message might be stored in buf, might be from
+    *      strerror, or might be a constant.
+    *
+    *      You must not call async_linebuf_read again.  buf contents
+    *      is undefined: it is only safe to reset or free.
+    *
+    * While using this function, do not look at buf->start or ->size
+    * or anything after the first '\0' in buf.
+    *
+    * If you decide to stop reading with async_linebuf_read that's
+    * fine and you can reset or free buf, but you risk missing some
+    * read-but-not-reported data.
+    */
+
+/*----- some handy macros -----*/
+
+#define CL_GET_STR_ARG(ix,vn,what)                                     \
+    item_t *vn##_i=list_elem(args,ix);                                 \
+    if (!vn##_i) cfgfatal(loc,"make-public","need " what);             \
+    if (vn##_i->type!=t_string) cfgfatal(vn##_i->loc,"make-public",    \
+                                   what "must be string");             \
+    const char *vn=vn##_i->data.string
+
+#define MINMAX(ae,be,op) ({                    \
+       typeof((ae)) a=(ae);                    \
+       typeof((be)) b=(be);                    \
+       a op b ? a : b;                         \
+    })
+#define MAX(a,b) MINMAX((a),(b),>)
+#define MIN(a,b) MINMAX((a),(b),<)
+
+#define MAX_RAW(a,b) ((a)>(b)?(a):(b))
+#define MIN_RAW(a,b) ((a)<(b)?(a):(b))
+
+static inline bool_t iswouldblock(int e)
+    { return e==EWOULDBLOCK || e==EAGAIN; }
+
 #endif /* util_h */